ValidationState::Error() only sets the reject reason if the state is still valid. When it is called on a state that is already Invalid or Error, the mode changes but the earlier reason, result and debug message are kept. The result is a mix of the old and new error. Code review in #35570 found two places where this happens. This makes the in-and-out use of the state parameter harder to follow and to remove later.
This PR makes both cases explicit:
ConnectBlock()→CheckBlock()with a mutated block (BLOCK_MUTATED, e.g. a merkle root mismatch). This is treated as a sign of corruption and reported throughFatalError(), but the resulting Error state keptBLOCK_MUTATED/bad-txnmrklroot. Now the Invalid state is logged and then replaced, so the Error state carries the corruption message.ActivateBestChainStep()→DisconnectTip()→FlushStateToDisk()failure.FlushStateToDisk()has already reported a fatal error, andActivateBestChainStep()then calledFatalError()a second time with a less specific message. Now the existing error is logged and returned unchanged, and no second fatal error is reported.
Behavior changes:
- Case 1: the returned state has the reject reason "Corrupt block found indicating potential hardware failure.", no result and no debug message. The original reason is logged.
- Case 2: the duplicate fatal-error notification is no longer sent. The returned reject reason does not change.
The first commit adds a unit test for case 1 that captures the current behavior, and the second commit updates its expectations. Case 2 has no test, because a flush failure cannot be triggered without fault injection. The path where DisconnectTip() fails while the state is still valid is covered by feature_abortnode.py and is unchanged.