Reproducing the zcashd ZIP 317 Fee Bug
This note documents the zcashd wallet bug that the gRPC comparison fixture
surfaced while building its Sapling-to-Orchard funding transaction.
The short version is:
- a Sapling-funded account sends to an Orchard-only UA,
z_sendmany is called with AllowRevealedAmounts,
- the caller relies on the default ZIP 317 fee (
None / ZIP_317_FEE),
- and the asynchronous operation later fails with an unpaid Orchard action
error.
In the gRPC comparison test we worked around that by passing
conventional_fee(4) explicitly. This page isolates the same bug in a small
test so it can be reported upstream without the rest of the gRPC fixture.
Repro test
The dedicated repro is:
Run it directly with:
uv run python3 qa/rpc-tests/wallet_zip317_fee_repro.py
Or through the RPC harness with:
uv run ./qa/pull-tester/rpc-tests.py wallet_zip317_fee_repro.py
What the test does
The repro intentionally keeps the setup small:
- fund a unified address on node 0 so the account balance initially sits in
the Sapling pool,
- activate NU5,
- create an Orchard-only UA on node 1,
- call:
z_sendmany(source_ua, [{"address": orchard_only_ua, "amount": 1}], 1, ZIP_317_FEE, "AllowRevealedAmounts")
- observe that the async operation fails,
- retry the same call with
conventional_fee(4) and observe that it succeeds.
Observed buggy behavior
The failing call currently returns an opid, but the operation completes with:
Transaction commit failed:: tx unpaid action limit exceeded: 1 action(s) exceeds limit of 0
That means the wallet accepted the request and picked the default ZIP 317 fee,
but later discovered that the constructed Orchard action was not fully paid for.
Expected behavior
One of these should happen instead:
zcashd should select a sufficient fee automatically for this transaction
shape when None / ZIP_317_FEE is used, or
zcashd should reject the request synchronously before returning an opid if
the selected default fee will be insufficient.
Returning an opid and then failing during transaction construction is the part
that made this especially confusing while bringing grpc_comparison.py live.
Current workaround
Passing conventional_fee(4) makes the same transaction succeed:
z_sendmany(source_ua, recipients, 1, conventional_fee(4), "AllowRevealedAmounts")
That workaround is now used in the gRPC comparison fixture.
Related call sites
The same wallet-side behavior was already forcing explicit fee overrides in
other tests:
Suggested upstream issue outline
Suggested title:
zcashd underestimates ZIP 317 fee for Sapling->Orchard z_sendmany with AllowRevealedAmounts
Suggested points to include:
- minimal repro command:
uv run python3 qa/rpc-tests/wallet_zip317_fee_repro.py
- observed error:
tx unpaid action limit exceeded: 1 action(s) exceeds limit of 0
- explicit
conventional_fee(4) succeeds
- the same workaround was needed while stabilizing
grpc_comparison.py
Reproducing the
zcashdZIP 317 Fee BugThis note documents the
zcashdwallet bug that the gRPC comparison fixturesurfaced while building its Sapling-to-Orchard funding transaction.
The short version is:
z_sendmanyis called withAllowRevealedAmounts,None/ZIP_317_FEE),error.
In the gRPC comparison test we worked around that by passing
conventional_fee(4)explicitly. This page isolates the same bug in a smalltest so it can be reported upstream without the rest of the gRPC fixture.
Repro test
The dedicated repro is:
Run it directly with:
Or through the RPC harness with:
What the test does
The repro intentionally keeps the setup small:
the Sapling pool,
conventional_fee(4)and observe that it succeeds.Observed buggy behavior
The failing call currently returns an opid, but the operation completes with:
That means the wallet accepted the request and picked the default ZIP 317 fee,
but later discovered that the constructed Orchard action was not fully paid for.
Expected behavior
One of these should happen instead:
zcashdshould select a sufficient fee automatically for this transactionshape when
None/ZIP_317_FEEis used, orzcashdshould reject the request synchronously before returning an opid ifthe selected default fee will be insufficient.
Returning an opid and then failing during transaction construction is the part
that made this especially confusing while bringing
grpc_comparison.pylive.Current workaround
Passing
conventional_fee(4)makes the same transaction succeed:That workaround is now used in the gRPC comparison fixture.
Related call sites
The same wallet-side behavior was already forcing explicit fee overrides in
other tests:
Suggested upstream issue outline
Suggested title:
Suggested points to include:
uv run python3 qa/rpc-tests/wallet_zip317_fee_repro.pytx unpaid action limit exceeded: 1 action(s) exceeds limit of 0conventional_fee(4)succeedsgrpc_comparison.py