Skip to content

Ironwood (NU6.3) v1 on master — CocoaPods, no vendored SwiftPM deps - #72

Merged
peachbits merged 4 commits into
masterfrom
zcash-ironwood-v3
Aug 2, 2026
Merged

Ironwood (NU6.3) v1 on master — CocoaPods, no vendored SwiftPM deps#72
peachbits merged 4 commits into
masterfrom
zcash-ironwood-v3

Conversation

@peachbits

Copy link
Copy Markdown
Contributor

CHANGELOG

Does this branch warrant an entry to the CHANGELOG?

  • Yes
  • No

Dependencies

none — this branch sits directly on master.

Description

Ironwood (NU6.3) v1 on master, CocoaPods only

The same four Ironwood commits as the earlier stacked work, rebased onto master and with the SwiftPM vendored-deps machinery removed. No stacked base, no buildVendoredDeps.ts, no libZcashDeps.xcframework — the podspec keeps master's gRPC-Swift ~> 1.8 unchanged.

The motivation: #71 was based on zcash-spm-deps-binary rather than master, so merging it landed Ironwood on that stack branch and never on master. The 0.13.0 publish that followed was cut from master and therefore shipped without any Ironwood code (verified against the published tarball: zero occurrences of proposeOrchardToIronwoodMigration, and zcash-android-sdk:2.5.2). This branch is mergeable to master on its own.

What's here

  1. Pin SDK 2.7.0-rc.4 + build inputs — the release the Zcash team confirmed production-ready for Ironwood, pinned by commit so the checkout is immutable if the tag moves. Android SDK and incubator go 2.5.2 -> 2.7.0-rc.4. update-sources no longer builds the FFI download URL from a version string: it reads the url/checksum pair out of the pinned checkout's own binaryTarget, so a source/binary mismatch is unrepresentable, cross-checked against the reviewed LIBZCASHLC_XCFRAMEWORK_SHA256. Also carries TreeState.ironwoodTree (field 7) for checkpoint generation, and resolves the regtest checkpoint directory through Bundle.main since the SwiftPM-only Bundle.module accessor does not exist in the CocoaPods build.
  2. BalanceEvent: Ironwood pool fields — both platforms; the deprecated summed fields include the pool.
  3. The migration surface, identical on both platformsSynchronizer.proposeOrchardToIronwoodMigration builds the sweep (every Orchard note to the wallet's own address, fee chosen so no Orchard change remains, Sapling and transparent untouched, all-or-nothing). Tools.getIronwoodActivationHeight answers from consensus constants (ZIP 258). No migration state to poll.
  4. Report transactions that settled while nothing was listening — the native stream only carries newly-found or newly-mined transactions and drops events until JS attaches a listener, so a transaction mined while the app was closed stayed at height 0, "pending", forever. subscribe now asks native for the current set once its listeners are attached. Not Ironwood-specific; could land independently.

Verification

  • update-sources regenerated from the rc.4 tag (fb9f6cf4 Prepare release 2.7.0-rc.4); FFI downloaded and checksum-verified; no ios/vendored produced.
  • pod install resolves gRPC-Swift 1.8.0 + SwiftNIO 2.x from CocoaPods.
  • react-native-zcash pod target: BUILD SUCCEEDED (arm64 + x86_64 simulator).
  • Full Edge iOS app: BUILD SUCCEEDED. Symbols confirmed in Edge.debug.dylibproposeOrchardToIronwoodMigration (118), ironwoodActivationHeight (6), emitExistingTransactions (30), gRPC/NIO linked from pods (55,535). No libZcashDeps anywhere in the bundle.
  • eslint and rollup + tsc clean.

This confirms rc.4 compiles against CocoaPods gRPC-Swift 1.8, matching the earlier rc.2 finding that the SDK's declared grpc-swift 1.24.2 dependency is not reflected in the API surface its generated code actually uses.

Known gaps

  • Functional testing was done on the SPM build, not this one. The migration sweep, pool balances and transaction reporting were exercised end-to-end on Android and iOS against the vendored-deps variant. This branch is byte-identical in android/, ios/ and src/, so behaviour should be unchanged, but the runtime flows have not been re-run on a pods build.
  • Missing the checkpoint 3430000 regeneration present on zcash-spm-deps-binary (0bc93ba), which rewrites that checkpoint with Ironwood tree state. Worth cherry-picking before merge.
  • Swap to the final 2.7.0 tag when it ships (pins-only commit).

Points update-sources at the Swift SDK's 2.7.0-rc.4 tag - the release the
Zcash team confirmed production-ready for Ironwood - and bumps the Android
SDK (and the incubator) from 2.5.2 to the same release. The Swift side is
pinned by commit rather than tag name, so the checkout is immutable even if
the tag is ever moved. The Android release ships Kotlin 2.3 metadata, which
the app already provides.

update-sources no longer builds the FFI's download URL from a version
string. It reads the url/checksum pair out of the pinned checkout's own
binaryTarget, which is the one place upstream writes down which FFI build
matches this source - so a source/binary mismatch is unrepresentable.
LIBZCASHLC_XCFRAMEWORK_SHA256 remains as the reviewed copy of that
checksum, and the build stops if the two disagree, so bumping the SDK pin
has to change native code deliberately rather than silently.

Also carries the two other build inputs Ironwood needs. TreeState.ironwoodTree
(field 7) in the bundled lightwalletd proto: update-checkpoints writes the
GetTreeState response verbatim, so a field missing from our proto is never
decoded and silently never written - every checkpoint generated after
activation would have shipped without Ironwood tree state. Pre-activation
output is unchanged, so existing checkpoints need no regeneration. And the
regtest checkpoint directory now resolves through Bundle.main, since the
SwiftPM-only Bundle.module accessor does not exist in the CocoaPods build.
Reports the Ironwood pool alongside transparent/sapling/orchard on both
platforms, and folds it into the deprecated summed fields so funds do not
appear to vanish from them once value starts crossing into Ironwood.
Detection plus the v1 sweep, identical on both platforms.

Synchronizer.proposeOrchardToIronwoodMigration builds the sweep: the SDK
spends every Orchard note to the account's own address with the fee
chosen so no Orchard change remains, leaving Sapling and transparent
funds untouched, and it executes through the ordinary createTransfer
pipeline. Neither SDK reports a payment value on the proposal, so both
platforms quote the crossing amount the same way - the Orchard balance
minus that fee - and fail rather than quote a figure they cannot source.

Tools.getIronwoodActivationHeight is stateless and served from consensus
constants (ZIP 258), which neither SDK exposes, so it answers on both
platforms without an SDK call.

There is deliberately no migration state to poll: the app decides whether
to offer a sweep from the activation height, sync state and Orchard
balance, and broadcasting the sweep spends those notes, which withdraws
the offer on its own.
The synchronizer's event stream only carries transactions newly found in
scanned blocks or newly mined, and the native module drops every event
until JavaScript attaches a listener. A transaction that settled while
nothing was listening - mined while the app was closed, or during a failed
sync - is therefore neither newly found nor newly mined on the next launch,
so it was never reported again: it stayed at height 0, "pending", forever.

Synchronizer.subscribe now asks native for the current transaction set once
its listeners are attached. Driving it from JavaScript is what makes the
delivery reliable: emitting during initialize would race the app's own
subscribe call and lose, because hasListeners is still false there.

Both platforms are affected. iOS never dumps the existing set outside its
restart path. Android's collector does deliver the full list, but on its
first emission during initialize, which is the same race - one it usually
wins, which is not the same as being correct.

Re-sending known transactions is harmless: the app updates only those whose
height or amount actually changed, and its new-transaction notification path
is not reached for transactions it already has.
@j0ntz

j0ntz commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Approved, make sure we get the latest ironwood checkpoint

@peachbits
peachbits merged commit 69cebf4 into master Aug 2, 2026
4 checks passed
@peachbits
peachbits deleted the zcash-ironwood-v3 branch August 2, 2026 15:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants