diff options
| author | mtg0001 <106211915+mtg0001@users.noreply.github.com> | 2026-08-02 10:56:50 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-02 10:56:50 -0300 |
| commit | 89f6d045c8e3dacbfbb369a3ac89c0c33de22b4c (patch) | |
| tree | 3ff7c5ed2c0814a51e5746320f42bfe9c893c56b /src/selftest.cpp | |
| parent | ee340c535dd26c034e18c4d97bf225a56bf91f6f (diff) | |
Keep HD change recoverable and bound message stalls (#109)
Change from an HD wallet now goes to a newly derived phrase-backed key instead of reusing a spent input's key. Reusing an input key silently moved value back to a key the phrase cannot reproduce; if derivation fails the transaction is refused rather than creating unrecoverable change. Legacy wallets with no seed keep the 0.1.0 behaviour. Restore also scans past the pre-generated key-pool window before treating an empty batch as the end.
The message parser no longer consumes a header it cannot complete: it reads from a copy, so the O(n) reinsert and the 100 ms sleep inside the shared message thread are gone, and a peer that sits on an incomplete payload is disconnected after two minutes. The wire cap drops from 256 MB to MAX_SIZE. New net-message self-test covers complete, incomplete, stale and oversized headers.
Verified here: clean rebuild, all self-tests pass, lock order checked (no path takes cs_keyPool before cs_mapWallet), and a node built from this branch synced from genesis to the chain tip on the live network with zero rejections.
Diffstat (limited to 'src/selftest.cpp')
| -rw-r--r-- | src/selftest.cpp | 65 |
1 files changed, 64 insertions, 1 deletions
diff --git a/src/selftest.cpp b/src/selftest.cpp index 88e9863..c04adf4 100644 --- a/src/selftest.cpp +++ b/src/selftest.cpp | |||
| @@ -470,6 +470,67 @@ static int RunWalletHDSelfTest() | |||
| 470 | return nFail == 0 ? 0 : 1; | 470 | return nFail == 0 ? 0 : 1; |
| 471 | } | 471 | } |
| 472 | 472 | ||
| 473 | static int RunNetMessageSelfTest() | ||
| 474 | { | ||
| 475 | fflush(stdout); | ||
| 476 | printf("net-message self-test\n"); | ||
| 477 | |||
| 478 | int nFail = 0; | ||
| 479 | try | ||
| 480 | { | ||
| 481 | CNode complete(INVALID_SOCKET, CAddress("127.0.0.1")); | ||
| 482 | complete.nVersion = VERSION; | ||
| 483 | complete.vRecv << CMessageHeader("ping", 0); | ||
| 484 | nFail += Check(ProcessMessages(&complete), "a complete empty message processes") ? 0 : 1; | ||
| 485 | nFail += Check(complete.vRecv.empty(), "a complete message is consumed") ? 0 : 1; | ||
| 486 | nFail += Check(!complete.fDisconnect, "a valid ping does not disconnect the peer") ? 0 : 1; | ||
| 487 | |||
| 488 | CNode partial(INVALID_SOCKET, CAddress("127.0.0.1")); | ||
| 489 | partial.nVersion = VERSION; | ||
| 490 | partial.vRecv << CMessageHeader("block", 100); | ||
| 491 | unsigned int nPartialBefore = partial.vRecv.size(); | ||
| 492 | nFail += Check(ProcessMessages(&partial), "an incomplete message returns cleanly") ? 0 : 1; | ||
| 493 | nFail += Check(partial.vRecv.size() == nPartialBefore, | ||
| 494 | "an incomplete message keeps one header in the buffer") ? 0 : 1; | ||
| 495 | nFail += Check(partial.nIncompleteMessageStart != 0, | ||
| 496 | "an incomplete message starts a timeout clock") ? 0 : 1; | ||
| 497 | nFail += Check(!partial.fDisconnect, | ||
| 498 | "a fresh incomplete message does not disconnect immediately") ? 0 : 1; | ||
| 499 | |||
| 500 | CNode stale(INVALID_SOCKET, CAddress("127.0.0.1")); | ||
| 501 | stale.nVersion = VERSION; | ||
| 502 | stale.vRecv << CMessageHeader("block", 100); | ||
| 503 | stale.nIncompleteMessageStart = GetTime() - BTF_INCOMPLETE_MESSAGE_TIMEOUT_SECS - 1; | ||
| 504 | stale.nIncompleteMessageSize = 100; | ||
| 505 | stale.strIncompleteMessageCommand = "block"; | ||
| 506 | ProcessMessages(&stale); | ||
| 507 | nFail += Check(stale.fDisconnect, | ||
| 508 | "a stale incomplete message disconnects the peer") ? 0 : 1; | ||
| 509 | |||
| 510 | CNode oversized(INVALID_SOCKET, CAddress("127.0.0.1")); | ||
| 511 | oversized.nVersion = VERSION; | ||
| 512 | oversized.vRecv << CMessageHeader("block", MAX_PROTOCOL_MESSAGE_SIZE + 1); | ||
| 513 | ProcessMessages(&oversized); | ||
| 514 | nFail += Check(oversized.fDisconnect, | ||
| 515 | "an oversized message header disconnects the peer") ? 0 : 1; | ||
| 516 | } | ||
| 517 | catch (const std::exception& e) | ||
| 518 | { | ||
| 519 | printf(" FAIL exception: %s\n", e.what()); | ||
| 520 | nFail++; | ||
| 521 | } | ||
| 522 | catch (...) | ||
| 523 | { | ||
| 524 | printf(" FAIL unknown exception\n"); | ||
| 525 | nFail++; | ||
| 526 | } | ||
| 527 | |||
| 528 | printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED", | ||
| 529 | nFail, nFail == 1 ? "" : "s"); | ||
| 530 | fflush(stdout); | ||
| 531 | return nFail == 0 ? 0 : 1; | ||
| 532 | } | ||
| 533 | |||
| 473 | int RunSelfTest(const std::string& name) | 534 | int RunSelfTest(const std::string& name) |
| 474 | { | 535 | { |
| 475 | AttachTerminal(); | 536 | AttachTerminal(); |
| @@ -478,8 +539,10 @@ int RunSelfTest(const std::string& name) | |||
| 478 | return RunWalletKeyPoolSelfTest(); | 539 | return RunWalletKeyPoolSelfTest(); |
| 479 | if (name == "wallet-hd") | 540 | if (name == "wallet-hd") |
| 480 | return RunWalletHDSelfTest(); | 541 | return RunWalletHDSelfTest(); |
| 542 | if (name == "net-message") | ||
| 543 | return RunNetMessageSelfTest(); | ||
| 481 | 544 | ||
| 482 | printf("Unknown self-test '%s'\n", name.c_str()); | 545 | printf("Unknown self-test '%s'\n", name.c_str()); |
| 483 | printf("Known self-tests: wallet-keypool, wallet-hd\n"); | 546 | printf("Known self-tests: wallet-keypool, wallet-hd, net-message\n"); |
| 484 | return 1; | 547 | return 1; |
| 485 | } | 548 | } |