Skip to content

Release v0.15.0 - xchain-encoder - #8

Open
jdogresorg wants to merge 16 commits into
masterfrom
release/v0.15.0
Open

Release v0.15.0 - xchain-encoder#8
jdogresorg wants to merge 16 commits into
masterfrom
release/v0.15.0

Conversation

@jdogresorg

Copy link
Copy Markdown
Contributor

Cuts xchain-encoder for the v0.15.0 release train. 16 commits from master.

Added

  • An exactInputs createtransaction option spends the caller's UTXO list verbatim.
  • The operator maintenance window is surfaced through /status without silencing the 503.

Fixed

  • Every named input is reserved at build time so chained sends cannot respend a UTXO.

Changed

  • The vendored coin registry is resynced from the hub.

Levelling develop with master, the ceremony step that closes a train. Commits
that reach master without coming back leave the branch all work lands on missing
what shipped, so the next diff off develop reads as though it never happened.
…s cannot respend a UTXO

UTXO reservation was not synchronous with the build, so a chained send could be composed against an outpoint an earlier send had already claimed and broadcast the same transaction twice. Reservation no longer depends on the tracker fetch, and exact-input mode claims or refuses the named set outright rather than silently skipping a reserved outpoint.
One wave of the review round on the xchain-platform board.
Every change was re-derived from the code rather than applied from the
finding recommended option, and each carries a control that reproduces
the original failure.

Review findings: 6456 6514 6532 6544
… emits

The double served a UTXO record the tracker never produces (a Number value with
no sync sibling), so the encoder's real guards were exercised against a shape
production cannot deliver.

Review round 7 finding #6854. Also carries review round 6's encoder work.
Review-round fixes.

Every payload-integrity guard sat behind the validate-all entry point, which
only the JSON-RPC surface runs, while the public transaction builder forwarded
straight through to the wire conversions unguarded. This file already re-applies
four other guards for exactly that reason, so payload integrity was the
oversight rather than the policy.

Latin-1 validation now runs pre-compression and push-decodability
post-compression, which is where each has to sit. The decodability guard sees
Buffer inputs too: canonicalization is a property of the COMPILED push, so a
one-byte Buffer in the minimal-op range loses everything the same way its string
spelling does, and scoping it to strings left the hole open on precisely the
direct-library surface the fix is about.

Suite: 772 passing, 0 failing.
The test harness authored segwit inputs on every chain, including one whose
consensus rules have no segwit. There the output is anyone-can-spend and a
witnessUtxo input signs nothing the network enforces, so the harness was
exercising a shape that cannot be valid and the encoder accepted it.

Input selection now fails closed on a witness-program scriptPubKey when the
network registry marks the chain as having no segwit, beside the existing taproot
input guard and before any outpoint is reserved.

The guard could not land without the harness first, which is why this is one
change: with the fixtures unmigrated it reddens 95 tests across 8 files. The
shared factory is now network-aware, so the fixture matches the chain. Suites
pinned to a no-segwit chain only because it carried a convenient dust threshold
move to a segwit-capable one; suites whose subject genuinely IS a chain without
segwit keep their chain and take legacy inputs.

Adds a control for the guard itself, and one test covering a raw-pubkey address
branch the migration would otherwise have dropped.
…cannot pin the socket

_post cleared its abort timer when the response HEADERS arrived and never released
res.body, so a collector that answers 200 and then stalls held the socket open with
nothing bounding it. Measured against a stalling collector, the socket was still
open at three seconds and the batch was counted as shipped.

That is worse than a leak, because the timeout was configured and did nothing: at a
400ms ship timeout the timer cleared at 22ms, so the stall outlived its only bound
and the shipper reported success.

The body is now cancelled inside the same then, within the abort timer's window,
rather than after finally has cleared it. The stream is cancelled and never read,
and a cancel on an already-errored body is swallowed.

Every existing test injected a transport and bypassed the real fetch path, which is
why this survived. The new case drives _post itself.

Vendored copy, written by the hub sync script and never hand-edited. Parity is
gated in the hub.
cors() was mounted below the API-key gate. A browser preflight is an OPTIONS
carrying no x-api-key, because that header is not CORS-safelisted, which is
exactly what forces the preflight in the first place. So on any deploy with a key
configured the preflight was answered 401 with no Access-Control-Allow headers
before cors() ran, and the browser never sent the real request.

The gate's 401 and the limiter's 429s were also emitted bare, so a browser client
saw an opaque network error rather than the shed status those gates exist to
communicate.

cors() now mounts above the gate, which is the order the hub already uses.

With CORS disabled the middleware is a pure pass-through for every method, so the
delta is bounded to keyed deploys that configure an origin. Control: 4 passing /
2 failing before, 6 passing / 0 failing after, and four of the six assertions pass
identically both ways to prove auth is not weakened.
… bypassing

createEnvelopeCancelTransaction is a second public transaction-producing path,
exposed over JSON-RPC, and it built a spend of a caller-named commit outpoint
without reading or writing the reservation map. It sits outside _buildTransaction
so it never reached the duplicate-build guard either, and the comment there
claimed every producing path passed through it.

The race is real rather than theoretical: a create_tx whose caller-supplied set
contains the commit output selected and spent that outpoint while an unsigned
cancel of the same output was outstanding.

Casing matters here too. This method accepted a mixed-case txid while create_tx
lowercases its keys, so an uppercase txid produced a key that could never
collide. The cancel now keys on the lowercased outpoint.

A repeat of the same cancel still succeeds and returns byte-identical bytes, so
the retry exemption is preserved; a foreign claim refuses with an operational
error naming the outpoint.

Control: 8 cases, 0 passing / 8 failing before, 8 passing / 0 failing after. The
load-bearing one reproduces the race itself rather than asserting internal state.
…tore

The guard's rationale named two in-process stores and recentBuilds is a third it
did not count, so the comment understated what a second instance would split.

Counting it in the rationale, with the store named alongside the other two.
@jdogresorg jdogresorg changed the title Release v0.15.0 Release v0.15.0 - xchain-encoder Sep 7, 2026
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