Skip to content

Add modern Zcash transaction support and Zebra RPC integration - #393

Open
DeckerSU wants to merge 5 commits into
spesmilo:masterfrom
DeckerSU:zcash
Open

DeckerSU wants to merge 5 commits into
spesmilo:masterfrom
DeckerSU:zcash

Conversation

@DeckerSU

Copy link
Copy Markdown

Summary

This change updates ElectrumX's Zcash support from the legacy Sapling-only
deserializer to the transaction formats used by the modern Zcash chain,
including NU5, NU6, NU6.1, NU6.2, and NU6.3 (Ironwood / ZIP-258). It also
adds a Zebra-specific daemon adapter and makes the Zcash coin use it.

Zcash transaction deserialization

  • Added parsing of Zcash v5 and v6 transaction encodings.
  • Added ZIP-244-style transaction ID construction, including the transparent,
    Sapling, Orchard, and v6 Ironwood digest components.
  • Preserved ElectrumX's transparent transaction projection: inputs, outputs,
    scripts, locktime, and transaction ID are available to the indexer while
    shielded data is consumed only as required to find transaction boundaries
    and calculate the transaction ID.
  • Added shielded value-balance accounting to the mempool fee calculation, so
    fees for Zcash transactions with shielded bundles are calculated correctly.
  • Added strict bounds and canonical CompactSize validation for the new
    Zcash-specific reader.
  • Added fixtures and unit coverage from mainnet transactions around NU5 and
    NU6.3 activation boundaries, as well as v5/v6 transparent input/output and
    transaction-ID checks.

ZIP-233 boundary

The v6 parser implements the currently deployed NU6.3 layout. ZIP-233 is
not currently enabled by Zebra. When that future consensus change is
activated, an eight-byte zip233Amount is inserted in the v6 header and must
be included in the header digest and deducted from the fee adjustment. The
code documents this explicitly so that a future activation cannot silently
produce an incorrect parse or transaction ID.

Zebra RPC compatibility

ZcashZebraDaemon isolates Zebra-specific JSON-RPC behaviour from domain
logic:

  • Sends JSON-RPC 2.0 requests and always includes params, including an
    empty parameter list.
  • Normalizes Zebra's JSON-RPC 2.0 response envelope to the response shape
    expected by existing ElectrumX daemon code.
  • Uses numeric getblock verbosity (0 for raw blocks, 1 for decoded
    blocks), which Zebra requires.
  • Preserves batch response ordering by JSON-RPC request ID.
  • Returns Electrum's standard -1 sentinel for fee estimates because Zebra
    does not expose an equivalent fee-estimation RPC.

The adapter is intentionally Zebra-oriented. zcashd has a more legacy RPC
interface and different operational assumptions; deployments that use zcashd
should use a separately verified zcashd adapter/configuration rather than
assuming every Zebra workaround is required there. In particular, the
current upstream zcashd codebase does not support the NU6.3 mainnet upgrade,
so it cannot synchronize the current mainnet regardless of RPC envelope
compatibility.

Bounded block fetching

Zebra rejects oversized JSON-RPC batch responses. Raw blocks are
hex-encoded in RPC responses, making response payloads roughly twice the raw
block byte size. The Zcash max_fetch_blocks() policy now uses explicit
height ranges and conservative batch limits derived from full-mainnet block
size measurements, targeting a 35 MB response budget below Zebra's 50 MiB
limit. The former fixed batch size of 10 remains in a comment for reference.

Electrum protocol version: 1.5.2

The normal ElectrumX session class supports protocol versions through
1.7.0. ZcashElectrumX deliberately advertises a maximum of 1.5.2 when it
is backed directly by Zebra. Zebra does not provide the mempool fee and
recent-transaction information needed to implement the 1.6+ Electrum methods
correctly. Advertising 1.5.2 prevents clients from negotiating methods that
the server cannot faithfully support; it is a Zcash/Zebra-specific limit, not
a global ElectrumX limit.

Tests

  • Extended deserializer unit tests for v5 and v6 formats, including malformed
    encodings and known mainnet transaction IDs.
  • Added mainnet block and transaction fixtures at upgrade boundaries.
  • Added daemon adapter tests for Zebra request formatting, response
    normalization, verbosity handling, batch ordering, and fetch limits.

@DeckerSU

Copy link
Copy Markdown
Author

Tested this PR against the latest Zebra version. ElectrumX successfully parsed and synchronized the full
blockchain up to the current tip:

  INFO:DB:flush took 0.0s.  Height 3,481,501 txs: 18,253,118 (+16)

The repository test suite also passes. In addition, I ran integration checks outside this repository
against a live Zebra instance:

  • Selected the latest 10 blocks containing transparent outputs, extracted their transparent addresses, and
    compared balances obtained via Zebra RPC with balances returned by ElectrumX. The results matched.

  • Selected transactions immediately before, at, and after multiple Network Upgrade activation heights.

  • Compared their raw transaction hex returned by Zebra and by ElectrumX.

  • Extracted transparent addresses from those boundary transactions and verified that the relevant
    transaction IDs are returned by blockchain.scripthash.get_history for the corresponding addresses.

  • Performed additional consistency checks intended to confirm that the ElectrumX database reflects the
    chain state as seen by Zebra.

This is not a guarantee that the implementation contains no other bugs. The transaction deserialization
logic was also independently reviewed by several AI models against Zebra’s implementation; no deviations
were identified. Everything appears to be working correctly, but additional independent review and testing
of this PR would still be very welcome.

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.

1 participant