Use case
Defensive pre-broadcast verification for transaction chains that touch multiple broadcasters within one session. When a parent transaction is broadcast through one broadcaster and a spend-the-parent child is then submitted through a different broadcaster, the second broadcaster can arrive before the first has propagated the parent across its mempool. The second broadcaster stamps the child SEEN_IN_ORPHAN_MEMPOOL — sticky for 5+ blocks; child eventually drops; downstream state has to be reconciled after the fact.
The architectural fix is single-broadcaster injection (see ISSUE-02). This issue proposes a defensive opt-in probe that catches the same class reactively when the architectural fix isn't available: before broadcasting, the SDK ARC-pings each input's parent txid and bails if any parent isn't visible yet.
How this surfaced
Surfaced during cross-broadcaster transaction chains on mainnet — parent broadcast through one broadcaster, child broadcast through another, parent visibility hadn't propagated to the second broadcaster's mempool yet, child stamped SEEN_IN_ORPHAN_MEMPOOL and held for 5+ blocks before eventually dropping. The downstream impact was a recurring reconcile-after-the-fact tax on every chained broadcast where the deployment topology happened to route the two through different ARC instances.
Current behavior
packages/runar-rs/src/sdk/wallet.rs::broadcast POSTs directly to ARC without any pre-flight chain-state check. If the broadcaster doesn't recognize a parent's txid, it accepts the child into its orphan mempool and waits for the parent — but the parent may never arrive if the parent broadcaster is a separate ARC deployment.
There is no CallOptions flag today that gates broadcast on parent visibility. A consumer wanting this behaviour has to write its own probe outside the SDK and short-circuit before calling into RunarContract::call(...).
Proposed shape (design question)
Add verify_inputs_on_chain: bool to CallOptions, default false. When true, before broadcasting:
- For each input on the call transaction, look up the parent txid (already known — every input has
source_txid).
- Probe ARC's
/v1/tx/{parent_txid} status endpoint for each.
- If any parent returns
SEEN_IN_ORPHAN_MEMPOOL, 404, or otherwise unhealthy → bail with Err(OrphanInputs { parents: Vec<String> }) or similar; do not submit the child.
- If all parents are visible → broadcast normally.
pub struct CallOptions {
pub satoshis: Option<i64>,
pub locktime: Option<u32>,
// ...
pub verify_inputs_on_chain: bool, // ← new, defaults to false
}
Trade-offs:
- Latency: one extra round-trip per input. Significant for many-input transactions; trivial for the common 1-2-input case. Opt-in default-off so consumers who know their topology is single-broadcaster skip the tax.
- Coupling to ARC's status surface: the probe depends on ARC's status response shape. If the consumer's deployment uses a non-ARC broadcaster, the probe can't be issued. Suggest gating the probe on whether the configured broadcaster implements a
status_probe capability (a future trait method) and silently no-op when it doesn't.
- Symptom vs cause: this is symptom-treatment. The architectural fix is shared broadcaster injection (ISSUE-02) — once both wallet-toolbox and runar broadcast through the same instance, parent visibility is guaranteed at the broadcaster layer and the probe becomes unnecessary. Suggest landing both: this issue as the defensive measure for consumers that can't unify broadcaster configuration yet; ISSUE-02 as the structural follow-up.
An optional patch (runar-input-chain-truth-verification.patch) sketches one possible implementation, but the design call should land first.
Alternatives considered
| Shape |
Strengths |
Weaknesses |
CallOptions::verify_inputs_on_chain (proposed) |
Opt-in, default off. Catches cross-broadcaster orphan class reactively. |
Per-broadcast latency cost. Symptom-treatment, not root cause. |
| Always-on probe |
No per-call config; consumers can't forget to enable. |
Latency tax on every broadcast even in single-broadcaster topologies. |
| Broadcaster injection (ISSUE-02) |
Root-cause fix — single broadcaster means single mempool, no orphan class. |
Larger surface change. May warrant trait-signature break. |
| Document the deployment matching requirement |
Zero code. |
Punts on the consumer side; consumers in multi-tenant environments can't always match. |
Backwards compatibility
Purely additive. Existing callers that don't set verify_inputs_on_chain see byte-identical behaviour. Default-off ensures no latency regression for current consumers. The new error variant (OrphanInputs or equivalent) is reachable only when the flag is set.
Related
Use case
Defensive pre-broadcast verification for transaction chains that touch multiple broadcasters within one session. When a parent transaction is broadcast through one broadcaster and a spend-the-parent child is then submitted through a different broadcaster, the second broadcaster can arrive before the first has propagated the parent across its mempool. The second broadcaster stamps the child
SEEN_IN_ORPHAN_MEMPOOL— sticky for 5+ blocks; child eventually drops; downstream state has to be reconciled after the fact.The architectural fix is single-broadcaster injection (see ISSUE-02). This issue proposes a defensive opt-in probe that catches the same class reactively when the architectural fix isn't available: before broadcasting, the SDK ARC-pings each input's parent txid and bails if any parent isn't visible yet.
How this surfaced
Surfaced during cross-broadcaster transaction chains on mainnet — parent broadcast through one broadcaster, child broadcast through another, parent visibility hadn't propagated to the second broadcaster's mempool yet, child stamped
SEEN_IN_ORPHAN_MEMPOOLand held for 5+ blocks before eventually dropping. The downstream impact was a recurring reconcile-after-the-fact tax on every chained broadcast where the deployment topology happened to route the two through different ARC instances.Current behavior
packages/runar-rs/src/sdk/wallet.rs::broadcastPOSTs directly to ARC without any pre-flight chain-state check. If the broadcaster doesn't recognize a parent's txid, it accepts the child into its orphan mempool and waits for the parent — but the parent may never arrive if the parent broadcaster is a separate ARC deployment.There is no
CallOptionsflag today that gates broadcast on parent visibility. A consumer wanting this behaviour has to write its own probe outside the SDK and short-circuit before calling intoRunarContract::call(...).Proposed shape (design question)
Add
verify_inputs_on_chain: booltoCallOptions, defaultfalse. Whentrue, before broadcasting:source_txid)./v1/tx/{parent_txid}status endpoint for each.SEEN_IN_ORPHAN_MEMPOOL,404, or otherwise unhealthy → bail withErr(OrphanInputs { parents: Vec<String> })or similar; do not submit the child.Trade-offs:
status_probecapability (a future trait method) and silently no-op when it doesn't.An optional patch (
runar-input-chain-truth-verification.patch) sketches one possible implementation, but the design call should land first.Alternatives considered
CallOptions::verify_inputs_on_chain(proposed)Backwards compatibility
Purely additive. Existing callers that don't set
verify_inputs_on_chainsee byte-identical behaviour. Default-off ensures no latency regression for current consumers. The new error variant (OrphanInputsor equivalent) is reachable only when the flag is set.Related
WalletProvider::with_broadcaster injection: architectural follow-up. Single-broadcaster injection makes this issue's probe largely unnecessary by removing the cross-broadcaster parent-visibility gap at the source. Suggest landing both — this defensively, ISSUE-02 structurally.runar broadcast and get_transaction: with R1 surfacing ARC errors, the probe'sErr(OrphanInputs)shape composes naturally with the now-visible failure path.