fix: expire stale pending cooperative exits after configurable threshold - #130
Open
vwinee21 wants to merge 2301 commits into
Open
fix: expire stale pending cooperative exits after configurable threshold#130vwinee21 wants to merge 2301 commits into
vwinee21 wants to merge 2301 commits into
Conversation
GitOrigin-RevId: d15831b7011bd07b2fbd045f7f1993dac953cb7c
GitOrigin-RevId: 79c6619c2bb73e8b97c5b17ea8c45e8175ff5999
GitOrigin-RevId: 5271e6a3d2d56b84a6597b67b174c1fbafbf8522
…pc v1.6.0 and fix test GitOrigin-RevId: 01f86b8fd6be2d43d85bb2166392c002f64cf2dd
GitOrigin-RevId: e8005ade085e83c627dc3ee47cd59936dc257b0c
GitOrigin-RevId: 300d0f1a54ce9b64e8aa0f5f4522a724a348d578
## Summary Add Apache-2.0 license field to spark-token-primitives Cargo.toml. --------- Co-authored-by: Lightspark Eng <engineering@lightspark.com> GitOrigin-RevId: 609745441e48c8f1908437a7c304955478919329
## Summary Internal. GitOrigin-RevId: f73777b9c118669f43106fdd6816a7fc796ae889
GitOrigin-RevId: 7c47b61b874129e28f4310844c7adb6f17077c31
…nsfer test GitOrigin-RevId: 055a31f6b7157e19ec4048564a464ce3ad9b49aa
GitOrigin-RevId: 0fca47504f7f0d59cfeb982361364785562c9f1a
GitOrigin-RevId: a1bd7bd05a5a74b06cbf91ab1ffe452684239d29
…ng (#6216) ## Summary Make `spark-token-primitives` publishable to crates.io by solving the proto file dependency problem without duplicating files in git. The crate's `build.rs` references `../../protos` which works in the monorepo but breaks for published crate consumers who won't have that directory. The fix: - `build.rs` checks for a local `protos/` directory first (present in published tarballs), falling back to `../../protos` (monorepo layout) - `Cargo.toml` adds `package.include` listing the four required `.proto` files — Cargo's include list overrides `.gitignore`, so the files get bundled into the tarball without being committed to git - `spark-token-primitives/.gitignore` ignores `protos/` so the pre-publish copy is never accidentally committed - `publish.sh` copies the required protos, runs `cargo publish`, and cleans up on exit ## Test Plan Verified with `cargo package --list --allow-dirty` that the four proto files appear in the tarball. Verified monorepo build still works via `cargo build -p spark-token-primitives`. --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Lightspark Eng <engineering@lightspark.com> GitOrigin-RevId: e1fcf1289ee7be10bffe9521bf9963b9125b07e8
## Summary Introduced a combined gossip message type that carries both the preimage and the optional key tweak settlement data in a single message. This will allow changing preimage swaps to apply both atomically. GitOrigin-RevId: a19389d6a9977fcef4e74e1909cfac99deb004f9
…#6220) ## Summary Add the standard crates.io publishing metadata fields to the `spark-token-primitives` `Cargo.toml` so the crate is correctly indexed and discoverable. Without these fields, `cargo publish` succeeds but crates.io shows the crate with no description, no author, and no searchable keywords. ## Test Plan N/A — metadata-only change; no functional code modified. --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Lightspark Eng <engineering@lightspark.com> GitOrigin-RevId: 6ce92f9a11580633b3e992e5a4af98b10f2a6963
## Summary Running renew leaf tests with both 2PC knobs off and on. ## Test Plan This is the test GitOrigin-RevId: 96a755e2b73e78be3f53bcce67dac7446ce09b31
## Summary Fixed timelock calculation in `getNextTransactionSequence` to ensure proper alignment with `TIME_LOCK_INTERVAL`. The function now normalizes the current timelock by removing any remainder when divided by the interval, preventing potential issues with misaligned timelock values that could affect transaction sequencing. ## Test Plan N/A GitOrigin-RevId: 49e0fa5935fb197d10cad347bddfdc0a0a04ebed
## Summary This refines the Vite example's target and network selection UI. - always show the Network row so the current network is visible even when LOCAL is selected - force LOCAL to use REGTEST and disable other network buttons while LOCAL is selected, instead of hiding the network selector entirely - reuse the existing segmented-control styling for the Target and Network rows - add a real disabled visual state for buttons so locked networks no longer look selectable - remove the debug clientEnv line from the top of the page ## Test Plan - `yarn workspace @buildonspark/spark-vite-app format` - `yarn workspace @buildonspark/spark-vite-app build` GitOrigin-RevId: 785531c96c64aea81408988287fa12123a3f3113
## Summary Adds a dedicated `Deposit` section to the Vite example app and a local-only `Fund Locally` flow for regtest development. The new flow gets a fresh single-use deposit address, funds it through local bitcoind JSON-RPC, mines a block, then claims the deposit back into the wallet. This also adds a same-origin `/bitcoin-rpc` Vite proxy so browser clients can use the local bitcoind RPC without exposing credentials to frontend code. ## Test Plan - `yarn workspace @buildonspark/spark-vite-app format` - `yarn workspace @buildonspark/spark-vite-app build` GitOrigin-RevId: 376c4d6ebfbeaa8240e95fa6ba720ad2cc26e8a1
## Summary Add RisingWaveDatabasePath in main as a placeholder GitOrigin-RevId: b27ec882028c8cbf4163f68f91bcf99aeda99d60
## Summary - Simplify the Spark Vite example so environment selection happens in the app UI instead of through multiple start commands. GitOrigin-RevId: 27ff01ff2cf6b992a8541b5e1eeaebeed16506eb
## Summary `GetUtxosForIdentity` and `GetUtxosForAddress` both implement their `exclude_claimed` filter as a `NOT IN` subquery against `utxo_swaps.utxo`, which is a nullable edge. A single non-cancelled row with `NULL utxo` makes the subquery's result contain `NULL`; in SQL three-valued logic, `x NOT IN (…, NULL)` evaluates to `NULL` — not `TRUE` — for every row, so both handlers return an empty UTXO list for every identity/address on the affected cluster. All downstream flows that rely on these RPCs (client-side deposit sync, auto-claim, static-deposit detection) silently break. Fix: add `IS NOT NULL` to the subquery predicate in both handlers so `NULL` rows are excluded from the `NOT IN` comparison. Observed on dev regtest: a single stale \`utxo_swaps\` row with status != \`CANCELLED\` and null \`utxo\` was enough to make every observatory coop-exit flow time out waiting for its confirmed deposit to appear, even though the UTXO row existed and was correctly linked to a static deposit address owned by the querying identity. ## Test Plan Added two regression subtests, one per handler: - \`TestGetUtxosForIdentity/exclude_claimed_ignores_non-cancelled_swaps_with_null_utxo_edge\` - \`TestGetUtxosFromAddress/exclude_claimed_ignores_non-cancelled_swaps_with_null_utxo_edge\` Each creates a non-cancelled \`UtxoSwap\` with no \`utxo\` edge and asserts the corresponding handler still returns the expected UTXOs. Both fail without the fix (return 0 UTXOs) and pass with it. Full \`./so/handler/...\` suite passes locally (2908 tests, 46 skipped, 0 failed); golangci-lint clean. GitOrigin-RevId: b165dd76ad6f606ce309896c6f59097b85ae8ced
## Summary Add logs for 2PC engine ## Test Plan N/A GitOrigin-RevId: b381be87c9fa53d163eee019c0ad50e92c445fe4
## Summary isTxBroadcast now resolves the electrs URL internally from the Network enum and uses BitcoinFaucet (bitcoind RPC) for LOCAL, matching the pattern already used in spark-wallet.ts. This removes the electrsUrl and networkProto parameters from constructUnilateralExitFeeBumpPackages, replacing them with a single Network parameter. This eliminates the regtest-mempool service dependency for CI integration tests. Contributes to SP-2392. ## Test Plan CI. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> GitOrigin-RevId: 056eabfd49f561d9baca8bedf32548e42f2d3e6e
## Summary Fix `spark-sdk`'s Node.js ESM WASM setup so randomness-dependent bindings work on Node 18 without relying on CommonJS fallbacks or ambient global mutation. The generated WASM wrappers were previously split between `globalThis.crypto` access and legacy `module.require` fallback behavior. That made the Node 18 ESM path fragile and pushed the package toward Node-specific ESM workarounds that do not fit the shared browser/node output graph. This change routes both the Spark FROST and token-primitives WASM wrappers through the SDK's `getCrypto()` abstraction, keeps the Node entrypoint deterministic with `nodeCrypto.webcrypto`, regenerates the patched bindings from source, and fixes the FROST binding regen script so the browser artifacts are patched from the correct directory. ## Test Plan - Regenerated Spark FROST bindings with `CC_wasm32_unknown_unknown=/opt/homebrew/opt/llvm/bin/clang AR_wasm32_unknown_unknown=/opt/homebrew/opt/llvm/bin/llvm-ar mise exec node@20 -- bash ./build-bindings.sh` from `signer/spark-frost-uniffi` - Regenerated token-primitives bindings with `mise exec node@20 -- bash ./build-bindings.sh` from `signer/spark-token-primitives-uniffi` - Ran `mise exec node@20 -- yarn workspace @buildonspark/spark-sdk build` - Ran `mise exec node@20 -- yarn workspace @buildonspark/spark-sdk test-cmd src/tests/secret-sharing.test.ts src/tests/transaction-construction.test.ts` - Ran Node 18 and Node 20 ESM smoke tests against the built `spark-wallet.node-*.js` chunk, covering `SparkFrostNodeJS.splitSecretWithProofs(...)` and `SparkTokenPrimitivesNodeJS.prepareTokenInvoice(...)` - Rebuilt the example app and exercised `Test WASM Signing` and `Generate New` in `sdks/js/examples/spark-vite-app` via Playwright --------- Co-authored-by: Lightspark Eng <engineering@lightspark.com> GitOrigin-RevId: 1db42b589bcd868cebcfee89211e09578889da16
## Summary Add `LICENSE` (Apache-2.0) to the `spark-token-primitives` crate directory and include it in the published tarball via the `include` list in `Cargo.toml`. The crate's `Cargo.toml` already declares `license = "Apache-2.0"`, but without a `LICENSE` file in the `include` list, `cargo package` does not bundle the actual license text. crates.io and downstream consumers expect the license file to be present in the tarball alongside the declaration. ## Test Plan `cargo package --manifest-path signer/spark-token-primitives/Cargo.toml --list` confirms `LICENSE` is included in the packaged files. GitOrigin-RevId: f995e966352b9a74e1f4923b39ebf15c58027790
## Summary Removed knob `spark.so.enable_strict_finalize_signature` and hardcoded the strict path. The knob has been fully on in prod since 2025-12-02. GitOrigin-RevId: 52453a473a9fa112cd830dfc1a954c7cf5f0aebf
## Summary Adding a Maintainers file ## Test Plan N/A GitOrigin-RevId: d0832460580438a3d7e6651ba715aa4ab6e11b64
## Summary Run both path for deposit tests ## Test Plan CI GitOrigin-RevId: 1c3e86d2077e64d265f5cc4448ff8e2b7f546d2f
## Summary `spark.proto`, `spark_token.proto`, and `multisig.proto` all import `validate/validate.proto`, but `publish.sh` was only copying the four top-level proto files. This caused `cargo publish --dry-run` to fail with: ``` protoc failed: validate/validate.proto: File not found. ``` **Changes:** - `publish.sh`: create `protos/validate/` and copy `validate/validate.proto` alongside the other vendored protos - `Cargo.toml`: add `protos/validate/validate.proto` to the `include` list so it's bundled in the crate tarball ## Test Plan `./publish.sh --dry-run` now completes successfully: ``` Packaged 21 files, 231.7KiB (44.4KiB compressed) Verifying spark-token-primitives v0.1.0 ... Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.68s warning: aborting upload due to dry run ``` --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Lightspark Eng <engineering@lightspark.com> GitOrigin-RevId: c6699b7b4acd24d18d690534da95fd13e2d4c9f5
…ves (#6471)
## Summary
Wires the `@buildonspark/spark-sdk` React Native install paths to the `spark-token-primitives` UniFFI bindings. Stacked on top of #6458 (which adds the Rust-side build scripts).
**This PR cannot merge until the generated artifacts are committed alongside it.** The Swift/Kotlin sources here import UniFFI-generated types (`SelectedTokenOutput`, `ReceiverTokenOutput`, `TransferBuildRequest`, …) and the podspec vendors `ios/spark_token_primitivesFFI.xcframework`. None of those exist yet — they're produced by `signer/spark-token-primitives-uniffi/build-rn-bindings.sh` (added in #6458). Whoever runs that script needs to commit the generated bindings + native binaries onto this branch before merge.
What's in this PR:
- `ios/SparkTokenPrimitivesModule.{swift,m,h}` — RCT bridge unpacking JS `[String: Any]` into UniFFI-generated structs and calling the 5 functions. Pattern lifted from `SparkFrostModule.swift` (same `arrayToData` / `[Any] as? [Int]` marshalling).
- `android/src/main/java/com/sparktokenprimitives/SparkTokenPrimitivesModule.kt` + `SparkTokenPrimitivesPackage.kt` — Kotlin `@ReactModule` mirror of the iOS bridge.
- `src/token-primitives-bindings/token-primitives-bindings.react-native.ts` — TS layer extending `SparkTokenPrimitivesBase`, marshalling `Uint8Array ↔ number[]` across the RN bridge.
- `src/index.react-native.ts` — adds the missing `setSparkTokenPrimitivesOnce()` call. The RN entry never registered the token-primitives binding, so any prior RN consumer would have hit `getSparkTokenPrimitives()` errors. Node and browser entries already register it.
- `spark-sdk.podspec` — vendors both `spark_frostFFI.xcframework` and `spark_token_primitivesFFI.xcframework`.
- `react-native.config.js` — declares both `SparkFrostPackage` and `SparkTokenPrimitivesPackage` for Android autolinking. RN autolinking only auto-discovers a single ReactPackage per library, so multi-package libraries need an explicit config.
- `package.json` — adds `react-native.config.js` to the `files` array so it ships in the published tarball.
## Test Plan
- [x] `yarn tsdown` builds clean against the new TS binding; `dist/native/index.react-native.js` contains `NativeModules.SparkTokenPrimitivesModule` and the 5 method calls.
- [x] `yarn types` produces the same set of pre-existing TS errors as `main` (logger / sdk-logger issues in unrelated files); no new errors from this PR.
- [x] Prettier-formatted TS.
- [ ] **BLOCKING:** run #6458's `build-rn-bindings.sh` and commit generated artifacts to this branch — without them, `pod install` and Android `gradle build` will fail.
- [ ] iOS simulator smoke test: build a minimal RN app, call `hash_partial_token_transaction` with a known input, assert byte-equal with the Node WASM result.
- [ ] Android emulator smoke test: same parity check.
GitOrigin-RevId: 5f31208d28f51bde14256efd7bf4e000be95db11
… method (#6544) ## Summary Adds `SparkWallet.cleanup()` as the preferred public cleanup method while keeping `cleanupConnections()` as a deprecated compatibility alias. This gives SDK users a clearer public API name without breaking existing callers. ## Test Plan - `yarn workspace @buildonspark/spark-sdk test-cmd src/tests/wrapPublicMethod.test.ts` - `yarn workspace @buildonspark/spark-sdk types` - `yarn changeset status --since=origin/main` - `yarn workspace @buildonspark/spark-sdk build` - `yarn workspace @buildonspark/issuer-sdk build` - `yarn workspace @buildonspark/spark-mcp types` - `yarn workspace @buildonspark/cli types` - `yarn workspace @buildonspark/interactive-cli types` - `yarn workspace artillery-engine-spark types` - `yarn workspace @buildonspark/nodejs-scripts types` - `yarn workspace @buildonspark/nestjs-app build` - `git diff --check` GitOrigin-RevId: 0d2e3993ee4be55918f9c48423ab9a934edd755f
## Summary `QueryAllTransfers` now detects the sender-only + outgoing-in-flight-status-subset filter shape — used by SDK callers like `queryPrimarySwapTransfers`, `queryPendingOutgoingTransfers`, and `getOwnedBalance` sender path — and dispatches to a specialized SQL builder that drives `idx_transfers_outgoing_in_flight_sender_pubkey_time` via column-based leading equality + top-N pushdown. Behind `KnobReadMIMODataModelOutgoingInFlight` (default off, 100 in Tiltfile). Other filter shapes (receiver, SR1, mixed/wider statuses) fall through to legacy `queryTransfers` unchanged. ## Test Plan **Prod plan (`spark-rds.sh EXPLAIN` against SSP mainnet, canonical TS1 shape):** planner picks `idx_transfers_outgoing_in_flight_sender_pubkey_time` cleanly — `Index Cond: sender_identity_pubkey`, top-N pushdown (LIMIT cost 297 vs full-scan cost 8196), no `Sort` above LIMIT. **Dbseed perf scoreboard (warm):** | Shape | Pubkey | Plan | Warm | Rows | |---|---|---|---:|---:| | TS1 full 4-state + types | so0_extreme (~25M edges) | `transfer_status` + Sort * | 109ms | 100 | | TS1 full 4-state + types | so0_medpending (~5M edges) | `transfer_status` + Sort * | 102ms | 40 | | Single-status subset | so0_extreme | `transfer_status` + Sort * | 12ms | 53 | \* The dbseed wipeout is a pre-existing stats artifact unrelated to this PR — dbseed's `full` profile overstresses the global 4-state set (36K rows vs ~753 in prod) which tips the planner's cost model. Same plan was observed before this PR for the equivalent legacy SQL. Prod plan picks correctly. **Equivalence (`TestQueryAllTransfers_Equivalence_OutgoingInFlight`):** 5 cases pass against ephemeral Postgres — full 4-state, single status, 2-state subset, status-outside-partial fall-through, receiver-participant fall-through. Asserts ordered transfer ID identity + per-transfer `Status` / `Type` / `Network` / leaf-id set equivalence between legacy `queryTransfers` and `queryOutgoingInFlight`. --- Resolves SP-3055 created with claude session 1db49ec5-f137-487b-a181-b0977a560703 --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> GitOrigin-RevId: de34dabc893bf127fcd5fa3286329561bfda5fbe
## Summary Pin the `frost-secp256k1-tr` and `frost-core` dependencies to the `frost-core/v3.0.0-ls` tag on `lightsparkdev/frost`, replacing the prior `nested-signing` branch reference. The branch was merged into `main` upstream and this tag captures the v3.0.0 state for reproducible builds. Follow-up to #6425. Affects: - `signer/Cargo.toml` + `signer/Cargo.lock` - `sdks/js/packages/spark-frost-bare-addon/Cargo.toml` + `Cargo.lock` The bare-addon `Cargo.lock` also picks up a stale `ecies` git pin getting reconciled with the registry version that `signer/Cargo.toml` already specified (leftover from #6421). Bare prebuilds will be regenerated by GHA. ## Test Plan - \`cargo check --tests\` clean for \`spark-frost\` - \`cargo check\` clean for \`spark-frost-bare-addon\` - CI --------- Co-authored-by: Lightspark Eng <engineering@lightspark.com> GitOrigin-RevId: 3513067824805dc9a1cc882af637272ed4b5552f
…ransaction handler (#6468) ## Summary Fix error handling bug in token transaction handler where keyshare errors could be silently ignored. --- 🤖 *custodial-hashrate* | [Dashboard](https://zeus.dev.dev.sparkinfra.net/#/instance?id=custodial-hashrate) | [Voice](https://zeus.dev.dev.sparkinfra.net/#/command-center?focus=custodial-hashrate) | [Feedback](https://zeus.dev.dev.sparkinfra.net/feedback) GitOrigin-RevId: c11ea8be46ed2d056cc3a8027bb70350101b57aa
## Summary `QueryNodes` already caps owner-based pagination, but the `node_ids` path accepted an unbounded list. A client could submit a very large `node_ids` array and force UUID parsing, a large SQL `IN` predicate, response map allocation, and optional parent-chain loading. This adds a pre-parse cap of 1000 node IDs for direct `QueryNodes` lookups and rejects oversized requests before UUID parsing or DB query construction. ## Test Plan - `go test ./so/handler -run 'TestQueryNodesRejectsTooManyNodeIDsBeforeParsing$' -count=1` - `go test ./so/handler -run 'TestQueryNodes' -count=1` GitOrigin-RevId: 4bcaa7d001ff1b9e9e68a9b5ed8459f170f4389a
## Summary Make ClaimTransferTweakKeys idempotent for receiver-side states. When concurrent calls race on the same transfer, the second call now returns success instead of a confusing error if the transfer has already advanced past the tweak-keys phase. GitOrigin-RevId: 95af39f0801f2120841dbe747abbedb04c62b89d
## Summary Reject malformed `payment_hash` lengths at the `QueryUserSignedRefunds` handler boundary. The handler now validates that the payment hash is exactly 32 bytes and returns `InvalidArgument` for any other length. - Add handler-level validation for `payment_hash` length in `QueryUserSignedRefunds` - Add unit test covering malformed hash lengths (0, 1, 31, 33, 64 bytes) - Add integration test in `so/security_test` for the raw API input path - Exclude `so/security_test` from unit-test runs (requires a live stack, like `so/grpc_test`) ## Test Plan - `go test ./so/handler -run 'TestQueryUserSignedRefunds_RejectsMalformedPaymentHashLength$' -count=1` - Integration test in `so/security_test` validates live server rejects all non-32-byte hashes with `InvalidArgument` --------- Co-authored-by: Claude <noreply@anthropic.com> GitOrigin-RevId: 8b5aa12a34f5756c65a91baaca89d5d337d15241
…6473) ## Summary #6451 pre-loaded `TransferLeaves` with nested `Leaf -> Tree / SigningKeyshare / Parent` on the two MIMO list builders, collapsing the per-transfer N+1 in `MarshalProto`. The prod impact was sizable enough to justify extending the same pattern to the remaining transfer list endpoints — `GetStuckLightningPayments`, `QueryNodeTransferHistory`, and `InternalTransferHandler.GetTransfers` — which still fan out ~5N serial edge queries per page. `GetStuckLightningPayments` already had a partial pre-load (`Leaf -> Tree` only); this extends it so `MarshalProto` takes the fully pre-loaded path instead of falling back to per-leaf lazy loads. The other two add the chain from scratch. ## Test Plan Behavior unchanged: pre-loads only affect query-fanout shape, and `MarshalProto` (post-#6451) already handles both pre-loaded and lazy-load inputs. --- Related to SP-2917 created with claude session f5c869ad-9ca1-4950-8a26-4f779bb4ece6 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> GitOrigin-RevId: 71ca76583a35962424d5d9fda1092a1b12aefb28
## Summary Allow `NODE_EXTRA_CA_CERTS` through the Spark JS SDK Turbo strict environment so callers can trust a custom local CA when running nested Turbo tasks. This is defensive hardening for workflows like webdev's SSP dev-cli hermetic test, where the GitHub Actions step sets `NODE_EXTRA_CA_CERTS` but `yarn turbo run test:integration:ssp` otherwise filters it before the Jest/Node process opens Spark authn connections. `NODE_EXTRA_CA_CERTS` is included in both `globalEnv` and `globalPassThroughEnv`, matching the existing local-ingress pattern: it is visible to strict-mode Turbo tasks and participates in task hashing when TLS trust input changes. ## Test Plan - `git diff --check -- sdks/js/turbo.json` - `yarn turbo run test:integration:ssp --dry=json > /tmp/spark-turbo-node-extra-ca-dry-v2.json` - Verified the dry run still uses strict env mode and includes `NODE_EXTRA_CA_CERTS` in both the specified env list and global pass-through env allowlist. GitOrigin-RevId: 1b35fd8e83daa4ec61695cc7943427abadb62f5c
## Summary Updates the Next.js example app to the latest Next 15 backport release and keeps `eslint-config-next` on the matching version. This pulls in the patched React Flight protocol implementation without moving the example to Next 16. ## Test Plan - `yarn workspace @buildonspark/spark-sdk build` - `yarn workspace spark-nextjs-app build` GitOrigin-RevId: 94f405b245db19128336a6c7b939ed1d35da0b9c
## Summary `QueryTransfers` caps response page size, but caller-provided `filter.transfer_ids` was unbounded. A large transfer ID filter forces UUID parsing and a large SQL `IN` predicate before the normal page-size cap can help. This caps direct transfer ID filters at 1000 values and rejects oversized requests before UUID parsing and query construction. ## Test Plan - `go test ./so/handler -run 'TestQueryTransfersRejectsTooManyTransferIDsBeforeParsing$' -count=1` - `go test ./so/handler -run 'TestQueryTransfers' -count=1` GitOrigin-RevId: 1b7ca30e87e915570c9eef99ef332d93ef3b53da
## Summary `QueryTokenOutputs` accepted caller-provided network enum values and passed them directly to `btcnetwork.FromProtoNetwork`. When the network was `UNSPECIFIED` or an unknown enum, the conversion error was untyped and surfaced as an internal RPC error. This validates that `network` is specified up front and wraps unknown enum conversion failures as malformed client input. The new unit test covers both `UNSPECIFIED` and an unknown enum value. ## Test Plan - `go test ./so/handler/tokens -run 'TestQueryTokenOutputsRejectsInvalidNetwork|TestQueryTokenOutputsRejectsNilRequest' -count=1`\n- `git diff --check` --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> GitOrigin-RevId: c514ea4d4c3b1dfcb8625d8e3aa5fb6cf6bcc4ec
## Summary Validate token revocation share input references before adding them to the operator share map. The preferred `InputTtxoRef` hash/vout format is used only when `PrevTokenTransactionHash` is exactly 32 bytes. To preserve backwards compatibility with older operators, a malformed or absent `InputTtxoRef` still falls back to the legacy `InputTtxoId` UUID when that field is present. The new validation errors only when there is no usable input reference at all, when the usable legacy UUID is malformed, or when the operator/share/secret fields themselves are malformed. ## Test Plan - `env -u MINIKUBE_IP go test ./so/handler/tokens -run TestBuildInputOperatorShareMap -count=1 -timeout=300s` - `env -u MINIKUBE_IP GOFLAGS='-tags=lightspark' go test ./so/handler/tokens -run TestBuildInputOperatorShareMap -count=1 -timeout=300s` - `git diff --check` GitOrigin-RevId: ea214cd2ac9e49e418edfb774f5ca58a71cd6239
## Summary Updates the React Native example apps from React Native CLI 19.0.0 to the patched 19.1.2 release line. This keeps the examples on the same CLI major while pulling in the patched Metro development server package. ## Test Plan - `yarn workspace @buildonspark/spark-react-native-app lint` - `yarn workspace @buildonspark/issuer-sdk build` - `yarn workspace @buildonspark/spark-expo-react-native-app build` GitOrigin-RevId: 2c592501a5590eac8c3e15f7eb7e429087321c02
…fer (#6583) ## Summary Follow-up to #6473. `GetStuckTransfers` (both legacy and MIMO branches), `QueryStuckTransfer`, and `QueryLightningSwapTransfer` share the same N+1 fanout as the endpoints fixed in #6473. The stuck-transfer handlers additionally walk leaves + per-leaf `SigningKeyshare` explicitly inside `marshalStuckTransfer`, so the preload chain alone isn't enough — `.QueryX().All(ctx)` always issues fresh SQL. Adds the `WithTransferLeaves → WithLeaf → WithTree / WithSigningKeyshare / WithParent` chain to all four query sites and refactors `marshalStuckTransfer` to read from `transfer.Edges.TransferLeaves` / `leaf.Edges.SigningKeyshare` directly. If a future caller forgets the preload, the handler now errors instead of silently degrading to N+1. ## Test Plan - Three new postgres-backed tests assert `SigningKeysharePublicShares` is populated for an attached leaf via the MIMO path, the legacy path, and `QueryStuckTransfer` — previously zero coverage on that field. - `QueryLightningSwapTransfer` goes through `transfer.MarshalProto(ctx)` directly (no `marshalStuckTransfer`-style edge walk), which #6451 already made preload-aware, so the preload addition is behavior-neutral. --- Related to SP-2917 created with claude session 456e80ba-a666-4afd-a57b-5b6e657ac434 --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> GitOrigin-RevId: 5975a192b09dd9061901dc89976325249733ff31
## Summary FinalizeNodeSignaturesV2 accepts client-supplied node IDs and signature bytes. The current creation path returned untyped errors for missing node IDs and invalid signatures, which surfaced to clients as Internal errors. This change classifies malformed node IDs as InvalidArgument, missing nodes as NotFound, missing required direct signatures as InvalidArgument, and bad signature verification failures as FailedPrecondition while preserving existing validation flow. The new tests cover malformed node IDs, nonexistent node IDs, and invalid signature bytes against an existing node. ## Test Plan - `go test ./so/handler -run 'TestFinalizeNodeSignaturesV2RejectsMalformedAndMissingNodeIDs|TestFinalizeNodeSignaturesV2RejectsBadSignature' -count=1`\n- `go test ./so/handler -run 'TestFinalizeSignatureHandler_ErrorCases|TestFinalizeNodeSignaturesV2RejectsMalformedAndMissingNodeIDs|TestFinalizeNodeSignaturesV2RejectsBadSignature' -count=1`\n- `git diff --check` GitOrigin-RevId: b5696eb16163c64b30d96ccf39db5824eeccb903
## Summary The shared deposit UTXO verifier accepts client-provided UTXO identifiers. When callers provide a missing UTXO object or a txid with the wrong length, the helper currently returns plain errors, causing public RPCs that use it to surface Internal instead of InvalidArgument. This change classifies nil UTXO inputs as missing-field InvalidArgument and malformed txid values as malformed-field InvalidArgument in both verifier variants. Existing NotFound behavior for well-formed but unknown UTXOs is preserved. ## Test Plan - `go test ./so/handler -run 'TestVerifiedTargetUtxo/invalid_txid' -count=1`\n- `git diff --check` GitOrigin-RevId: aef58a5ce572e7411d9d22f34fcffd1c3ba80c5a
## Summary InitiateStaticDepositUtxoRefund validates the client-provided refund transaction after resolving the requested static-deposit UTXO. The validator returned plain errors for missing, malformed, and structurally mismatched refund transactions, which surfaced as Internal errors on the public RPC. This change classifies missing refund transaction bytes as InvalidArgument missing field, malformed transaction bytes as InvalidArgument malformed field, and refund transactions that do not spend the requested UTXO as InvalidArgument malformed field. The structural check itself is unchanged. ## Test Plan - `go test ./so/handler -run 'TestValidateStaticDepositRefundTxRejectsClientRawTxInputs' -count=1`\n- `git diff --check` GitOrigin-RevId: 3ac6bd1968c12b7de5ce5005f259d8206b952112
## Summary Return typed gRPC errors from ProvidePreimage validation instead of allowing attacker-controlled malformed inputs and missing preimage requests to surface as Internal errors. Payment hash and preimage length failures now return InvalidArgument, identity key parsing failures return InvalidArgument, preimage/hash mismatch returns FailedPrecondition, and a well-formed but missing preimage request returns NotFound. This preserves the existing wrapper context because Spark gRPC errors are preserved through wrapped error chains. ## Test Plan - `go test ./so/handler -run 'TestValidatePreimage_InvalidPreimage_Errors|TestProvidePreimageRejectsMalformedIdentityPublicKeyWithInvalidArgument' -count=1` - `git diff --check` GitOrigin-RevId: d10e458765b4616a5c0318f0e84854c15c36533f
## Summary QueryHTLC accepted unbounded client-provided transfer_id and payment_hash filter lists. Those values were allocated, parsed, and passed into SQL predicates before any count limit, so a large request could consume handler CPU/memory and build oversized IN predicates. This caps both filters at 500 values and rejects payment hashes that are not exactly 32 bytes before DB access. ## Test Plan - `go test ./so/handler -run 'TestQueryHTLCRejectsFilterResourceExhaustionBeforeDB$' -count=1` - `go test ./so/handler -count=1` --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> GitOrigin-RevId: c20e343b840651b472c62b6dd8f8ee8506bf3396
## Summary `COUNTER_SWAP_V3` validates that the linked primary transfer exists, is still cancellable, has enough time left, matches the counter transfer amount, and has reversed sender/receiver identities. It did not validate that the counter-transfer leaves are on the same Bitcoin network as the primary transfer. That left an SSP-mediated swap able to bind a user primary transfer on one network to an SSP counter transfer on another network. The amount and party checks still pass, but the swap no longer represents an equal exchange of value on the same network. This change rejects `COUNTER_SWAP_V3` creation when the loaded counter-transfer leaves resolve to a different network than the linked primary swap transfer. ## Test Plan - `go test -tags lightspark ./so/handler -run TestCreateTransfer_CounterSwapV3_FailsWithMismatchedNetwork -count=1` - `go test -tags lightspark ./so/handler -run 'TestCreateTransfer_CounterSwapV3_FailsWithMismatched(Network|Amount|Parties)' -count=1` - `go test -tags lightspark ./so/handler -count=1` GitOrigin-RevId: 397b92354c2f65c7b21682781ee9796336123adb
## Summary FinalizeNodeSignatures and FinalizeNodeSignaturesV2 accepted an unbounded client-provided node_signatures list. That list is later parsed into UUID slices/maps, used in SQL predicates, processed one node at a time, and included in error formatting paths, so an oversized request can force unnecessary handler allocation, parsing, and DB work. This caps the request at the existing 1000-leaf processing limit before ownership checks or DB access. ## Test Plan - `go test ./so/handler -run 'TestFinalizeSignatureHandler_FinalizeNodeSignaturesRejectsTooManyNodeSignaturesBeforeDB$' -count=1` - `go test ./so/handler -count=1` - `git diff --check` GitOrigin-RevId: 411709908b0fe647dec6079ad90ff857c1e4e522
## Summary Updates JS tooling dependencies so transitive security fixes are resolved through maintained upstream packages: - Bumps `ts-jest` to resolve `handlebars` 4.7.9. - Bumps Artillery and dedupes AWS/Azure transitive packages so their XML parser path resolves to patched `fast-xml-parser` 5.x versions. - Refreshes protobuf loading/exporter dependencies so `protobufjs` resolves to patched versions. ## Test Plan - `yarn workspace artillery-engine-spark build` - `yarn workspace @buildonspark/spark-mcp test` - `yarn workspace @buildonspark/issuer-sdk test` - `yarn workspace @buildonspark/nodejs-scripts test` - `yarn workspace @buildonspark/spark-sdk test` - `yarn workspace @buildonspark/nestjs-app build` GitOrigin-RevId: 4145fc64582b27e366605a60b3f5badc96def500
## Summary Return typed gRPC errors from transfer query validation paths instead of allowing malformed client filters to surface as Internal errors. Malformed participant identity keys, malformed transfer IDs, invalid transfer statuses, and status filters on pending-only queries now return InvalidArgument. This intentionally does not duplicate the existing transfer query pagination PR; negative limit/offset and nil-filter getter behavior remain separate. ## Test Plan - `go test ./so/handler -run 'TestQueryTransfersRejectsMalformedFiltersWithTypedErrors' -count=1` - `git diff --check` GitOrigin-RevId: c67007a0fdf8b41e4425f28d236c0f833614eb8c
## Summary This PR bumps a few dependency versions with reported issues. GitOrigin-RevId: b39ccde599a3035c7f37e3b8d0caf440b79f111f
## Summary The shared knob `spark.so.watch_chain.coop_exit_key_tweak_required_confirmations` is read by two code paths with different fallback defaults (`watch_chain.go` defaults to `3`, `checkCoopExitTxBroadcasted` defaults to `6`). If the knob is unset and the delay path is enabled, watch-chain advances a coop-exit transfer to `SENDER_KEY_TWEAKED` at 3 confirmations while the finalization guard would still require 6. The MIMO `ClaimTransfer` branch and `InternalTransferHandler.FinalizeTransferReceiver` both skip `checkCoopExitTxBroadcasted` entirely, so the receiver-finalization side never enforces the 6-confirmation policy. This is a non-issue in production today, as all knobs are set to 1. However, this change is a convergence correction and defense against future regression. This change unifies the default by moving `CoopExitConfirmationThreshold` into the `knobs` package and references it from both call sites, then adds `checkCoopExitTxBroadcasted` to the MIMO claim branch and to `FinalizeTransferReceiver` to mirror the coordinator-side guard added in SP-2961. ## Test Plan - Existing `TestCoopExitBasic` / `TestCoopExitSingleCall` still pass — they mine `CoopExitConfirmationThreshold+2` blocks, so the new guard remains satisfied. - Follow-up: focused negative-case integration test for the MIMO claim path at sub-threshold confirmations. --- Resolves SP-3098 created with claude session 456e80ba-a666-4afd-a57b-5b6e657ac434 --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> GitOrigin-RevId: b21d15f89b47ee943a941f40ad999d4a96d48e6b
Replace fragile prefix matching with proper btcutil.DecodeAddress validation which includes checksum verification. Resolves TODO in IsBitcoinAddressForNetwork.
Pending coop exits with ConfirmationHeightIsNil() can accumulate indefinitely if the L1 transaction is never confirmed (e.g. evicted from mempool due to low fee or replaced via RBF). Add expiry logic that runs each block cycle and deletes pending coop exits older than KnobWatchChainCoopExitPendingExpiryDays (default: 14 days). The 14-day default matches Bitcoin Core's DEFAULT_MEMPOOL_EXPIRY so entries are cleaned up around the time Bitcoin Core would drop them. Expiry count is logged at Info level when non-zero for observability. The threshold is runtime-configurable via the knob system without a code deploy.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Pending cooperative exits (
ConfirmationHeightIsNil()) accumulate indefinitely if the L1 transaction is never confirmed — for example, when evicted from the Bitcoin mempool due to low fees or replaced via RBF. This was noted in a TODO comment inwatch_chain.go.Solution
Add expiry logic that runs each block processing cycle. Pending coop exits older than
KnobWatchChainCoopExitPendingExpiryDays(default: 14 days) are deleted.Why 14 days? This matches Bitcoin Core's
DEFAULT_MEMPOOL_EXPIRY— by this point, any unconfirmed transaction would have been dropped from the mempool, so the pending record serves no purpose.Changes
so/knobs/knobs.go— addKnobWatchChainCoopExitPendingExpiryDaysknob +CoopExitPendingExpiryDays = 14default constantso/chain/watch_chain.go— replace TODO with expiry logic; logs count at Info level when non-zeroBehavior
ConfirmationHeightIsNil()entries — confirmed exits are untouched