test: Add torcontrol coverage for PoW defense enablement
What changed, and why it matters
This commit only adds new automated tests for Bitcoin Core's Tor control feature. It checks that when creating a hidden service, the software requests a modern anti-DoS feature (proof-of-work defenses) and gracefully falls back if the Tor daemon does not support it. No production code is changed, so this cannot introduce a security vulnerability or fix one directly.
No security action required. This is a test-only change. Reviewers may optionally confirm the new test accurately reflects intended Tor hidden-service behavior.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff extends test/functional/feature_torcontrol.py with a new test case, test_pow_fallback. The test simulates a Tor control server that rejects ADD_ONION commands containing PoWDefensesEnabled=1 with a 512 error, and verifies that Bitcoin Core retries ADD_ONION without that option. It also strengthens an existing assertion to require PoWDefensesEnabled=1 in the initial ADD_ONION command. The change is purely to the functional test suite; no C++/Python implementation code is modified.
Changed components
test/functional/feature_torcontrol.pyInspect captured patch +42 / −0
diff --git a/test/functional/feature_torcontrol.py b/test/functional/feature_torcontrol.py
index dfc649a6..084f3dfe 100755
--- a/test/functional/feature_torcontrol.py
+++ b/test/functional/feature_torcontrol.py
@@ -127,6 +127,7 @@ class TorControlTest(BitcoinTestFramework):
assert_equal(mock_tor.received_commands[1], "AUTHENTICATE")
assert_equal(mock_tor.received_commands[2], "GETINFO net/listeners/socks")
assert mock_tor.received_commands[3].startswith("ADD_ONION ")
+ assert "PoWDefensesEnabled=1" in mock_tor.received_commands[3]
# Clean up
mock_tor.stop()
@@ -169,9 +170,50 @@ class TorControlTest(BitcoinTestFramework):
# Clean up
mock_tor.stop()
+ def test_pow_fallback(self):
+ self.log.info("Test that ADD_ONION retries without PoW on 512 error")
+
+ tor_port = p2p_port(self.num_nodes + 3)
+
+ class NoPowServer(MockTorControlServer):
+ def _get_response(self, command):
+ if command.startswith("ADD_ONION"):
+ if "PoWDefensesEnabled=1" in command:
+ return "512 Unrecognized option\r\n"
+ else:
+ return (
+ "250-ServiceID=testserviceid1234567890123456789012345678901234567890123456\r\n"
+ "250 OK\r\n"
+ )
+ return super()._get_response(command)
+
+ mock_tor = NoPowServer(tor_port)
+ mock_tor.start()
+
+ self.restart_node(0, extra_args=[
+ f"-torcontrol=127.0.0.1:{tor_port}",
+ "-listenonion=1",
+ "-debug=tor",
+ ])
+
+ # Expect: PROTOCOLINFO, AUTHENTICATE, GETINFO, ADD_ONION (with PoW), ADD_ONION (without PoW)
+ self.wait_until(lambda: len(mock_tor.received_commands) >= 5, timeout=10)
+
+ # First ADD_ONION should have PoW enabled
+ assert mock_tor.received_commands[3].startswith("ADD_ONION ")
+ assert "PoWDefensesEnabled=1" in mock_tor.received_commands[3]
+
+ # Retry should be ADD_ONION without PoW
+ assert mock_tor.received_commands[4].startswith("ADD_ONION ")
+ assert "PoWDefensesEnabled=1" not in mock_tor.received_commands[4]
+
+ # Clean up
+ mock_tor.stop()
+
def run_test(self):
self.test_basic()
self.test_partial_data()
+ self.test_pow_fallback()
if __name__ == '__main__':
TorControlTest(__file__).main()
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.