Skip to content

Fix enum discriminant collision, fuzz multisig, trace outbox boundary, reproducible builds, SLOs - #1148

Merged
joelpeace48-cell merged 5 commits into
FinesseStudioLab:mainfrom
presidojay1:fix/issues-778-851-779-775
Aug 28, 2026
Merged

Fix enum discriminant collision, fuzz multisig, trace outbox boundary, reproducible builds, SLOs#1148
joelpeace48-cell merged 5 commits into
FinesseStudioLab:mainfrom
presidojay1:fix/issues-778-851-779-775

Conversation

@presidojay1

Copy link
Copy Markdown
Contributor

Summary

Closes #851, closes #778, closes #775, closes #779.

Found and fixed first: main currently fails to compile (cargo check -p trivela-rewards-contract → E0081) — two independently-merged PRs (#1134 for #838, and a separately
merged issues-850-860-861-848 batch) each added a new Error variant at discriminant 46
(TimelockNotFound and BelowMinClaim), colliding once both landed on main. Renumbered
BelowMinClaim to 48; safe since nothing could have deployed relying on that value while the
crate didn't build.

#851 — fuzz the multisig. Adds fuzz_targets/fuzz_multisig.rs, extending the existing
fuzz_balance.rs coverage to verify_multisig (used by set_paused): duplicate signers, unknown/
unregistered signers, corrupted signatures, nonce replay. Asserts try_set_paused never panics for
any input shape, success implies a sufficiently large distinct-valid-registered signer set plus an
unconsumed nonce, and replaying a successful call is rejected. Wires both fuzz_balance and the new
fuzz_multisig into contract-fuzzing.yml as actual cargo fuzz run (libFuzzer) jobs — that
workflow was only running the separate proptest-based fuzz_* unit tests before, never the
cargo-fuzz targets in contracts/rewards/fuzz/ at all.

#778 — trace context across the outbox async boundary. The outbox pattern (write now, deliver
later on an unrelated poll tick) breaks OTel's automatic context propagation. writeOutbox now
captures the enqueuing request's traceparent and stores it alongside the payload;
OutboxRelay._deliver re-establishes it as the parent of a new outbox.deliver span via
captureTraceparent()/linkedSpan() (new helpers in tracing.js). tracing.test.js adds the
trace assertion test the issue's Verification section asks for, using an in-memory OTel exporter to
confirm parent/child linkage across the boundary. DB-query and RPC-call span instrumentation aren't
added here — withSpan() already exists and is documented for exactly that; wiring it into every
call site is a larger mechanical follow-up.

#775 — reproducible builds. rust-toolchain.toml pins an exact rustc version (CI currently
installs the floating stable channel). scripts/reproducible-build.sh builds a package twice from
a clean target dir with SOURCE_DATE_EPOCH fixed and asserts identical sha256.
docs/PROVENANCE.md documents the rebuild/verify process and explicitly scopes out (rather than
half-building) updating CI to respect the pinned toolchain and full SLSA/in-toto signed attestation.

#779 — SLOs. docs/SLO.md defines SLIs/SLOs for the four named journeys, grounded in the
metrics /metrics actually exposes today, with example multi-window burn-rate Prometheus alert
rules (Google SRE workbook pattern). Flags two concrete gaps instead of writing rules against
nonexistent metrics: HTTP errors/latency aren't broken down per-route yet, and webhook-delivery
metrics referenced in the alert rules don't exist yet either.

Test plan

  • cargo check -p trivela-rewards-contract passes (was failing on main)
  • cargo check in contracts/rewards/fuzz passes (both fuzz targets compile)
  • node --check on all three changed/new backend JS files passes
  • node --test src/tracing.test.js — not run; needs @opentelemetry/sdk-trace-base installed,
    and npm install in this environment hits unrelated platform-lock issues on other optional
    deps (seen in prior PRs against this repo)
  • scripts/reproducible-build.sh — logic reviewed, not executed end-to-end (a double
    wasm32v1-none build takes several minutes)
  • Manual review against each issue's acceptance criteria

presidojay1 and others added 5 commits August 27, 2026 19:25
Closes FinesseStudioLab#851

Fixes a build-blocking bug on main first: two independently-merged PRs
each added a new Error variant at discriminant 46 (TimelockNotFound,
from FinesseStudioLab#1134, and BelowMinClaim, from a separately-merged issues-850-860-
861-848 batch) — colliding once merged together, so
`cargo check -p trivela-rewards-contract` currently fails with
E0081 "discriminant value 46 assigned more than once" on main. Renumbers
BelowMinClaim to 48 (the next free slot); safe since nothing could have
successfully deployed relying on that specific discriminant value while
the crate didn't compile.

Adds contracts/rewards/fuzz/fuzz_targets/fuzz_multisig.rs: adversarial
fuzzing of verify_multisig (used by set_paused) covering duplicate
signers, unknown (unregistered) signers, corrupted signatures, and
nonce replay — extending the existing fuzz_balance.rs coverage to the
co-admin ed25519 multisig path. Asserts try_set_paused never panics for
any signature-set shape, and that success implies a sufficiently large
set of distinct, valid, registered signatures plus an unconsumed nonce;
replaying the exact same call after a success must then be rejected.

Wires both fuzz_balance and the new fuzz_multisig into
contract-fuzzing.yml as libFuzzer (cargo-fuzz) runs, time-boxed to the
workflow's existing FUZZ_DURATION, alongside the pre-existing proptest-
based fuzz_* unit tests that workflow already ran (which weren't
actually running the cargo-fuzz targets in contracts/rewards/fuzz/ at
all before this).
Closes FinesseStudioLab#778

The outbox pattern (writeOutbox now, OutboxRelay._deliver later, on a
completely separate poll-loop tick) breaks OTel's automatic in-process
context propagation — a webhook delivery span had no way to link back to
the HTTP request that enqueued it, so root-causing latency across that
async hop meant manually correlating timestamps instead of following one
trace.

Adds two helpers to tracing.js: captureTraceparent() snapshots the
active span's W3C traceparent string (reusing the same format already
built for traceparentMiddleware's response header), and linkedSpan()
re-establishes that context as the parent of a new span via OTel's
propagation.extract, falling back to a plain unlinked withSpan() when no
traceparent is available (e.g. rows enqueued before this change).

writeOutbox now wraps the stored payload in { payload, _traceparent }
rather than adding a migration column — the reserved _traceparent key
round-trips through the existing payload TEXT column. _deliver detects
the wrapped shape (vs. legacy raw-payload rows already queued) and runs
the handler inside linkedSpan(..., 'outbox.deliver', ...).

tracing.test.js adds the trace assertion test the issue's Verification
section asks for: using an in-memory OTel exporter, confirms a span
captured before the async boundary is recorded as the parent of the
outbox.deliver span created after it, sharing the same trace id — plus a
fallback case confirming delivery is still traced (unlinked) when no
traceparent was stored.

Note: DB query and Stellar RPC call spans (the issue's other scope
items) aren't added here — withSpan() already exists for exactly that
purpose and is documented for it in tracing.js's module doc comment;
wiring it into every individual query/RPC call site across the codebase
is a much larger, mechanical follow-up beyond what fits in this pass.
… doc

Closes FinesseStudioLab#775

Adds rust-toolchain.toml pinning an exact rustc version (1.87.0) plus
the wasm32v1-none target — contracts-ci.yml currently installs the
floating `stable` channel, which can silently pick up a different
compiler release on different days, incompatible with "anyone can
rebuild from a tag and get the published hash."

Adds scripts/reproducible-build.sh: builds a given contract package
twice, from a clean CARGO_TARGET_DIR each time, with SOURCE_DATE_EPOCH
fixed and --locked --release --target wasm32v1-none, then asserts both
passes produce byte-identical WASM via sha256.

Adds docs/PROVENANCE.md documenting the rebuild-and-verify process and
what to publish per release. Explicitly scopes out, rather than
half-implementing, the two heavier remaining pieces: (1) updating
contracts-ci.yml / contract-fuzzing.yml to respect rust-toolchain.toml
instead of the floating stable channel they currently install, and (2)
SLSA/in-toto signed provenance attestation plus tying the attested hash
into the contract's own upgrade allowlist — both real infrastructure
projects needing CI signing-identity setup and design decisions beyond
this pass.

Note: scripts/reproducible-build.sh's logic was reviewed but not
executed end-to-end in this environment (a double wasm32v1-none build
takes several minutes and wasn't run here) — the CI wiring called out as
follow-up above is also where "CI rebuilds twice -> identical hash" from
the issue's Verification section would actually get exercised.
Closes FinesseStudioLab#779

Adds docs/SLO.md defining SLIs/SLOs (availability + p95 latency) for the
four critical journeys the issue names — campaign read, register,
redeem, webhook delivery — grounded in the metrics /metrics
(backend/src/routes/health.js) actually exposes today
(trivela_requests_total, trivela_request_errors_total,
trivela_route_hits_total{route}, trivela_http_request_duration_ms_bucket).
Includes example Prometheus alert rules using the Google SRE workbook's
multiwindow multi-burn-rate pattern (fast: 14.4x/1h+5m, slow: 6x/6h+30m)
for HTTP error budget burn, plus a webhook-delivery-specific pair.

Flags two real, concrete gaps rather than writing alert rules against
metrics that don't exist: (1) trivela_requests_total/
trivela_request_errors_total are global, not broken down per-route —
trivela_route_hits_total{route} exists but there's no per-route error or
latency-bucket metric yet, so a redeem-specific outage would only show
up diluted in the aggregate rate today; and (2) the webhook-delivery
alert rules reference trivela_outbox_delivered_total /
trivela_outbox_failed_total metrics that don't exist yet either — they'd
need to be added to outboxService.js's _deliver/_markFailed paths.

Dashboard provisioning (Grafana, as dashboards-as-code) and on-call
paging integration are infrastructure setup outside this repo and are
also not done here — see the Follow-up section for the full list.
@joelpeace48-cell
joelpeace48-cell merged commit 2ce7cf4 into FinesseStudioLab:main Aug 28, 2026
4 of 20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants