Surface consensus-branch mismatch diagnostics in the Sync Error sheet - #2372
Draft
pacu wants to merge 2 commits into
Draft
Surface consensus-branch mismatch diagnostics in the Sync Error sheet#2372pacu wants to merge 2 commits into
pacu wants to merge 2 commits into
Conversation
When a lightwalletd server publishes a consensus branch ID the app does not expect, sync can never progress. The SDK's message was already well-written and already hex, but none of it reached anyone: the sheet rendered a generic "check your connection" message and the support email carried only a raw Java stack trace. Worse, these failures never reached that sheet at all. NavigateToErrorUseCase.isSyncError() matched only ResponseException 5xx, 3200 transport errors and UninitializedTorClientException, so a CompactBlockProcessorException fell through to the generic error bottom sheet, which shows a truncated stack trace and offers no way to change server. Route the server-compatibility family to the Sync Error sheet instead, where Switch server already exists. No button is added. Read the mismatched values off the SDK exception's new properties rather than parsing them out of its message, whose wording is not a contract. The sheet shows a localized explanation, the selected server, both sides of the disagreement and the error type; support reports lead with the same facts in English, matching the rest of the report. Generic and transient sync errors are unchanged and gain no detail, since retrying is the remedy there. Branch IDs are unordered constants, so neither the copy nor the code claims which side is stale. In the case that prompted this the app was behind, not the server. Requires the SDK-side change promoting those constructor params to vals. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The tests launched their state collector on backgroundScope without a dispatcher, so it never ran before the assertion read state.value. The assertions saw the StateFlow's initial value, which is built with a null endpoint, and the two tests covering the server line failed on a list that was correct except for the missing "Server" row. Use UnconfinedTestDispatcher(testScheduler), matching SwapSlippageVMTest. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
pacu
requested review from
nesence-m,
noop-sk and
zcash-harry
and removed request for
nesence-m and
noop-sk
July 25, 2026 22:01
pacu
marked this pull request as ready for review
July 25, 2026 22:01
pacu
marked this pull request as draft
July 25, 2026 22:04
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Android counterpart of zodl-ios#1948 / zodl-ios#1947.
The scenario
A lightwalletd server publishes a consensus branch ID the app doesn't expect — in practice a server on NU6.3/Ironwood (
0x37a5165b) while the SDK expects NU6.2 (0x5437f330). Sync can never progress.Branch IDs are unordered constants, so the error itself cannot say which side is stale. In the observed case the app was behind, not the server, so neither the copy nor the code blames the server.
The gap this fixes
Android's gap was the inverse of iOS's. The SDK's message was already fine — already hex, already naming both remedies — but the user was shown none of it.
Two problems, and the second was the bigger one:
SyncErrorViewrendered a genericsync_error_message("check your connection, restart the app") plus four buttons. That copy is actively wrong here: restarting cannot help.NavigateToErrorUseCase.isSyncError()matched onlyResponseException5xx, code 3200 transport errors, andUninitializedTorClientException.MismatchedConsensusBranchis aCompactBlockProcessorException, so it fell through to the generic error bottom sheet — a truncated stack trace with no Switch server button, i.e. no route to the one remedy that works.Support also received only
getStackTrace(null), with the useful sentence buried in a Java trace.What changed
MismatchedConsensusBranch,MismatchedNetwork,MismatchedSaplingActivationHeight) now routes to the Sync Error sheet, where Switch server already exists. No button was added.AppInfo/EnvironmentInfoin the existing report, since support staff read it.ServerCompatibilityErroris the single source of truth shared by the routing gate, the sheet and the email.Values are read off the SDK exception's properties, not parsed out of its message — that wording is not a contract.
Design
ZashiSimpleListItem(the existing label/value row, already used for fact lists in Swap and Review Transaction) inside the existing sheet structure. It wraps values in aSelectionContainer, so users can copy a branch ID straight into a support message. OnlyZashiColors/ZashiTypography/Spacer(dp)tokens; no new components, no hardcoded colors. Strings invalues/andvalues-es/. A second@PreviewScreenspreview covers the diagnostics variant.Not built on
Synchronizer.validateConsensusBranch()(broken, SDK#1405) andext/ConsensusBranchId.kt(hardcoded enum stopping at CANOPY —fromHex("37a5165b")returns null and both sides read as "unknown").Verification
Run against the SDK change composite-built in (
-PSDK_INCLUDED_BUILD_PATH), on JDK 21../gradlew :ui-lib:testZcashmainnetStoreDebugUnitTest— passing, exit code 0../gradlew ktlint detektAll— passing, exit code 0.18 new tests cover: reading the mismatch off each of the three exception types, finding it through a wrapped cause chain, ignoring unrelated errors, the routing decision for each type (and that unrelated failures are still left alone), the rendered fact list including the server line, omission of the server line when no endpoint is selected, omission of a side the SDK could not name, and that Switch server still routes to
ChooseServerArgswith no button added../gradlew :ui-lib:pixel2TargetZcashmainnetStoreDebugAndroidTest— passing, exit code 0.SyncErrorViewTestrendersSyncErrorContentdirectly on a Gradle managed device (Pixel 2, API 36, Google APIs ARM64) and asserts the generic sheet keeps its default message with no detail rows, the explanation replaces that message for an incompatible server, the server and both branch IDs render, and Switch server is present with no extra button.BEFORE/AFTER screenshots are attached in a comment below.
⛔ Still blocked on the SDK
Depends on zcash/zcash-android-wallet-sdk#2053, which promotes those constructor params to
vals. That PR targetsmaint/v2.5.xand merges forward; since this app tracksZCASH_SDK_VERSION=2.6.5-SNAPSHOT, it will pick the change up with no version bump.The passing run above used a local composite build of that SDK branch. CI here will fail until #2053 merges forward and a snapshot publishes — hence still draft.
UI changes
Before
After
🤖 Generated with Claude Code