feat: CCH multi-asset swap#1257
Open
doitian wants to merge 17 commits intonervosnetwork:developfrom
Open
Conversation
75583f0 to
f55b11f
Compare
added 6 commits
May 5, 2026 15:46
This document specifies cross-chain hub behavior for atomic swaps between Bitcoin on Lightning and arbitrary Fiber assets (native CKB or UDTs).
Step 1 of the cch-multi-asset-swap spec: drop the BTC-centric
`wrapped_btc_type_script[_args]` config fields in favor of a generic
`fiber_asset_allowlist` plus a `fixed_rate_assets` table. This is the
config-layer foundation for accepting swaps against arbitrary Fiber
assets (native CKB or any allowlisted UDT) instead of a single
hard-coded wrapped-BTC UDT.
- Introduce `CchAsset` (`Option<Script>`, where `None` = native CKB) and
`FixedRateAsset { fiber_asset, sats_per_smallest_unit }` in
`cch::config`, re-exported from `cch::mod`.
- `validate_standalone` now requires a non-empty allowlist; UDT type
scripts are declared inline so no separate JSON-string parsing step
is needed.
- Update the deployer yaml to declare the SimpleUDT entry with a zero
`code_hash` placeholder; `tests/deploy/udt-init` recursively patches
every cch `code_hash` placeholder to the real deployed
SIMPLE_UDT data hash, so all CCH-enabled nodes (in-process and
standalone) get a uniformly populated config.
Call sites in `cch/actor.rs`, `fiber-types::CchOrder`, the json-types
mirror, and the cch tests still reference the removed fields and will
be updated in Step 2 along with the order-type refactor; the tree
intentionally does not compile between this commit and Step 2.
Rename and extend the persisted CchOrder so a single record can describe a swap between BTC and any allowlisted Fiber asset (native CKB or any UDT), not just wrapped BTC. Field changes: wrapped_btc_type_script: Script -> fiber_type_script: Option<Script> amount_sats: u128 -> btc_amount_msat: u128 fee_sats: u128 -> btc_fee_msat: u128 (new) -> fiber_amount_smallest_unit: u128 The BTC leg is now denominated in millisatoshi (1 sat = 1000 msat) so fee/exchange-rate math is rounding-free; the Fiber leg uses the smallest unit of the target asset (shannon for native CKB, the UDT's smallest denomination otherwise). `fiber_type_script = None` denotes native CKB. Bincode migration mig_20260421_cch_multi_asset rewrites every existing order at prefix 232: legacy single-asset records were 1:1 sat-to-UDT smallest-unit, so fiber_amount_smallest_unit is set to the old amount_sats and BTC amounts are scaled by 1000. The fiber-lib tree intentionally does not compile after this commit; call sites and tests are updated in the next commit.
Mechanically rewrites the CCH actor and JSON-types to use the new CchOrder fields (btc_amount_msat, btc_fee_msat, fiber_amount_smallest_unit, fiber_type_script) introduced in the previous commit. Behaviour is unchanged: per-request asset selection, allowlist enforcement, and fixed-rate computation are deferred to a follow-up; the actor uses a placeholder asset resolver that returns the first allowlist entry. - actor.rs: send_btc/receive_btc fee math now in msat throughout - json-types: CchOrderResponse mirrors the new on-disk shape - tests: update CchOrder/CchConfig literals to new fields; harness injects a default allowlist entry; rewrite the two fee-assertion tests to use msat-based expectations - regenerate store schema
Step 3a of the multi-asset CCH refactor. Replaces the placeholder single-asset resolver with real per-request asset selection: - SendBTC / SendBTCParams gain a fiber_type_script: Option<Script> parameter (None = native CKB). The hub validates it against the configured fiber_asset_allowlist before creating the order, and the proxy Fiber invoice omits the UdtScript attribute for native CKB. - ReceiveBTC reads the asset directly from the submitted Fiber invoice (UdtScript attribute, or None for native CKB) and validates it against the same allowlist. - Replace CchError::WrappedBTCTypescriptMismatch with FiberAssetNotAllowlisted; update the existing rejection test and add coverage for (a) send_btc rejecting a non-allowlisted UDT and (b) send_btc accepting native CKB end-to-end when allowlisted. Per-asset fixed-rate computation is still TODO (Step 3b); fee math remains in msat with the current 1 sat \u2194 1 UDT smallest unit shortcut.
Replace the legacy hard-coded 1:1 sat-to-smallest-unit conversion with a lookup against the configured `fixed_rate_assets` table, so the CCH can quote orders for any allowlisted Fiber asset whose fixed rate is published in config. Send/receive amounts are derived from the configured `smallest_units_per_sat` (renamed from `sats_per_smallest_unit` to match its actual semantics): on send_btc the Fiber-leg amount is `btc_amount_msat * rate / 1000`, on receive_btc the BTC-leg is `fiber * 1000 / rate` plus the BTC-denominated hub fee. Allowlisted assets without a fixed-rate entry are rejected with the new `NoFixedRateForAsset` error in lieu of the not-yet-implemented proposal-driven path.
2bdf38b to
8f188f6
Compare
added 6 commits
May 5, 2026 17:50
Rename the fixed-rate field in the deployer test config from the obsolete 'sats_per_smallest_unit' to the canonical 'smallest_units_per_sat' accepted by the CCH config loader, and update the surrounding comment to match.
Adds the operator-facing swap acceptor that gates send_btc /
receive_btc swaps whose Fiber asset is allowlisted but not in the
configured fixed_rate_assets, replacing the prior outright rejection.
Components:
* SwapAcceptorActor (cch/acceptor.rs): a top-level ractor actor that
tracks pending proposals plus subscriber sinks, mints proposal_ids,
enforces the per-proposal timeout, and resolves the first valid
operator response (later responses for the same proposal_id are
rejected).
* SwapProposal / SwapProposalResponse (fiber-types/cch.rs): the
notification payload pushed to operators and the structured reply
they submit.
* RPC (rpc/cch.rs):
- subscribe_swap_proposals: server->client subscription that
streams SwapProposal notifications.
- submit_swap_proposal_response: companion client->server method
the operator calls to resolve a proposal, correlated by
proposal_id. Split out because jsonrpsee's SubscriptionSink is
unidirectional, so inline replies on the subscription channel
would require a bespoke WebSocket route outside the standard
JSON-RPC stack.
* CchActor.send_btc / receive_btc: when no fixed rate is configured
for the Fiber asset, build a SwapProposal, await the acceptor's
decision, and only mint the counterparty invoice on accept. Reject
and timeout surface as SwapProposalRejected / SwapProposalTimeout
errors from the original RPC call rather than a separate
pending_acceptor order state.
* Config: swap_proposal_timeout_seconds (default applied at load).
* biscuit.rs: auth rules for the two new RPC endpoints.
Tests cover the proposal-timeout path through send_btc.
Update the multi-asset swap spec to reflect the now-implemented proposal/acceptor path. Drops the 'deferred' status banner and the section-5 'Status: deferred' callout, and rewrites section 5 to describe the shipped wire protocol: * server->client subscribe_swap_proposals notification stream, plus a companion client->server submit_swap_proposal_response method, carried over the same WebSocket and correlated by a server-minted proposal_id. * synchronous send_btc / receive_btc semantics: the original RPC blocks until the proposal is accepted (returning the order with the counterparty invoice), explicitly rejected, or times out. No pending_acceptor order state and no client polling for the counterparty invoice. * swap_proposal_timeout_seconds config and the SwapProposalTimeout / SwapProposalRejected error surfaces. * updated SwapProposal field list to match fiber-types. Adds a Design rationale subsection explaining why the protocol is split into a notification stream plus a companion method instead of inline replies on the subscription channel: jsonrpsee's SubscriptionSink is server->client only and JSON-RPC 2.0 has no client-originated subscription frames, so inline replies would require a bespoke axum / tokio-tungstenite route with its own framing, correlation, error envelope, and biscuit-auth integration. A companion JSON-RPC method preserves all of that for free while remaining functionally equivalent (same socket, proposal_id-keyed correlation, multiplexable proposals). Brings sections 1, 2.3, 6, and the mermaid diagram in line with the synchronous return + companion-method shape.
…e example Cross-review against the implementation surfaced two documentation gaps in the multi-asset swap spec. * Section 5.6 implied the SwapProposal's btc_amount_msat for SendBTC was the raw Bolt11 amount, but the actor sends the total payout (Bolt11 amount + configured fee). Clarify the field semantics and note that the raw Bolt11 amount can be recovered as btc_amount_msat - fee_on_btc_side_msat. * Section 6.2 left ReceiveBTC proposal-path operators to infer how to combine the configured fee with their chosen rate. Add a worked formula and numeric example matching the fast-path computation, so operators have a reference implementation for the BTC-leg amount they submit.
The OpenRPC generator requires JsonSchema on every RPC parameter, but `fiber_types::SwapProposalResponse` and `fiber_types::SwapProposal` do not (and should not) implement it. Mirror them in fiber-json-types as `SubmitSwapProposalResponseParams` and `SwapProposal`, add the matching conversions, and switch the CchRpc trait to the JSON variants so OpenRPC spec generation and the RPC doc check both succeed. Also regenerate crates/fiber-lib/src/rpc/README.md to include the new swap-proposal entries.
After the fiber-asset allowlist enforcement landed, an absent `fiber_type_script` is interpreted as native CKB which the test config's allowlist (wrapped-BTC UDT only) rejects. Pass the wrapped-BTC UDT script in the send_btc cross-chain-hub e2e bodies to keep the suites green.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR moves CCH from a single wrapped-BTC assumption to a multi-asset swap model for native CKB and allowlisted UDTs. Although the PR description says “spec only,” the diff already includes the first implementation slice across CCH runtime logic, RPC/API types, persistence, and test/deploy tooling.
Changes:
- Generalizes CCH config, domain types, and actor logic around
fiber_type_script, asset allowlists, fixed-rate assets, and operator-approved proposal flows. - Adds operator-facing swap proposal RPC/subscription endpoints, auth rules, JSON conversions, and CCH tests for the new flow.
- Introduces persisted
CchOrdershape changes, a migration/schema update, and supporting deploy/Bruno/doc updates.
Reviewed changes
Copilot reviewed 25 out of 26 changed files in this pull request and generated 15 comments.
Show a summary per file
| File | Description |
|---|---|
tests/nodes/deployer/config.yml |
Replaces single-asset CCH test config with allowlist/fixed-rate asset config. |
tests/deploy/udt-init/src/main.rs |
Patches deployed UDT code hashes into generated CCH node configs. |
tests/bruno/e2e/cross-chain-hub/02-create-send-btc-order.bru |
Adds fiber_type_script to integrated CCH Bruno send_btc request. |
tests/bruno/e2e/cross-chain-hub-separate/02-create-send-btc-order.bru |
Adds fiber_type_script to standalone CCH Bruno send_btc request. |
migrate/src/migrations/mod.rs |
Registers the new CCH multi-asset migration module. |
migrate/src/migrations/mig_20260421_cch_multi_asset.rs |
Migrates stored CCH orders to the new multi-asset layout. |
migrate/Cargo.toml |
Adds migration-only serde/CKB dependencies for the new order shape. |
migrate/Cargo.lock |
Locks the added migration dependencies. |
docs/specs/cch-multi-asset-swap.md |
Adds the multi-asset CCH spec and swap-acceptor protocol. |
crates/fiber-types/src/lib.rs |
Re-exports new CCH proposal-related types. |
crates/fiber-types/src/cch.rs |
Redefines CCH order/proposal domain types for multi-asset swaps. |
crates/fiber-lib/src/store/.schema.json |
Updates persisted schema fingerprints for CCH type changes. |
crates/fiber-lib/src/rpc/README.md |
Regenerates RPC docs for the new CCH APIs. |
crates/fiber-lib/src/rpc/cch.rs |
Adds swap proposal RPC/subscription methods and CCH RPC timeout changes. |
crates/fiber-lib/src/rpc/biscuit.rs |
Adds biscuit auth rules for operator acceptor RPCs. |
crates/fiber-lib/src/cch/tests/state_machine_tests.rs |
Updates state-machine tests for the new CCH order fields. |
crates/fiber-lib/src/cch/tests/scheduler_tests.rs |
Updates scheduler tests for the new CCH order layout. |
crates/fiber-lib/src/cch/tests/dispatcher_tests.rs |
Updates dispatcher tests for the new CCH order layout. |
crates/fiber-lib/src/cch/tests/actor_tests.rs |
Adds actor coverage for fixed-rate and proposal-path CCH behavior. |
crates/fiber-lib/src/cch/mod.rs |
Exports new CCH acceptor/config types. |
crates/fiber-lib/src/cch/error.rs |
Adds proposal-flow-specific CCH errors. |
crates/fiber-lib/src/cch/config.rs |
Replaces single-asset config with allowlist, fixed-rate, and proposal-timeout config. |
crates/fiber-lib/src/cch/actor.rs |
Implements multi-asset send/receive flows and operator proposal handling. |
crates/fiber-lib/src/cch/acceptor.rs |
Implements proposal broadcast, response handling, and timeout logic. |
crates/fiber-json-types/src/convert.rs |
Adds JSON conversion support for new CCH/proposal types. |
crates/fiber-json-types/src/cch.rs |
Defines new JSON-RPC params/responses for multi-asset CCH. |
added 5 commits
May 6, 2026 17:19
Reserve `payment_hash` against concurrent in-flight `send_btc` and `receive_btc` work via an `Arc<Mutex<HashSet>>` guard so the heavy detached task cannot race the store-level check-then-put and silently overwrite an existing order. Hoist all `receive_btc` Fiber-invoice validation (hash algorithm, final-TLC delta, expiry) before the proposal flow so the operator is never asked to sign off on an invoice the hub will subsequently reject. Recompute `duration_since_epoch` after the proposal wait when deriving the outgoing invoice expiry, since the operator-side wait can exceed the remaining lifetime of the original timestamp. Round the SendBTC fast path up via ceiling division so the hub never under-collects the Fiber leg for sub-satoshi remainders. Derive `btc_fee_msat` for the proposal-path `receive_btc` flow from the operator-supplied gross total instead of leaving it at zero. Reject `smallest_units_per_sat = 0` at startup via a new `CchConfig::validate()` invoked from `fiber-bin`, so a misconfigured fixed-rate entry cannot mint zero-amount invoices on `send_btc` or panic on `receive_btc`. Expose `CchMessage::GetSwapProposalTimeoutSeconds` and a test-only `TestGetAcceptor` so the RPC layer and unit tests can discover the configured deadline / live acceptor without relying on hardcoded values. Document on `SwapAcceptorMessage::SubmitResponse` that any authenticated operator may resolve any pending proposal — the acceptor does not bind responses to the original subscription session.
…line Pre-existing `send_btc` / `receive_btc` RPC handlers used a hard-coded 10-minute mailbox-call timeout, so an operator who configures `swap_proposal_timeout_seconds` above that value sees a client-visible RPC timeout while the detached task keeps waiting (and may still create the order later, leaving the client unable to learn its id). Query the configured value via `CchMessage::GetSwapProposalTimeoutSeconds` when wiring the RPC module and pass it to the new `CchRpcServerImpl::with_proposal_timeout` builder, with a small fixed margin to absorb scheduling/IO jitter. Document on the trait method that responses are not bound to the originating subscription session.
The CCH multi-asset migration writes a shadow `NewCchOrder` whose `fiber_type_script` is a `ckb_jsonrpc_types::Script`. The migrate crate previously pinned `ckb-jsonrpc-types = "0.202"`, which serializes the inner `ScriptHashType` differently from the v1.x type used by the running `fiber-lib`. After running the migration the upgraded record would therefore be unreadable by the node. Pin to the same major version as the live code, keep the v0.202 dependency aliased as `ckb-jsonrpc-types-legacy` for converting the v0.7.0 snapshot value, and bridge the two via a JSON round-trip in `convert_script`. Add a roundtrip test module in `tests/migration_tests.rs` that constructs a fully-populated `OldCchOrder`, bincode-encodes it, runs `migrate_cch_order`, and re-decodes / inspects the new shape. A real `Bolt11Invoice` is built programmatically via `lightning-invoice` + `bitcoin` (added as `[dev-dependencies]`) to avoid hand-crafted bech32 strings going stale.
Replace the brute-force counter loop in `test_send_btc_proposal_path_accept` with a query through the new `TestGetAcceptor` / `TestPendingProposalIds` plumbing, so the test discovers the actor-generated `proposal_id` instead of guessing it. Add `test_receive_btc_proposal_path_reject` covering the operator-rejects branch of ReceiveBTC and a follow-up sanity check that submitting an unknown `proposal_id` returns `SwapProposalUnknown`. Update `create_test_fiber_invoice_with_amount` to include `HashAlgorithm::Sha256` and to pick a final-TLC delta that always satisfies the ReceiveBTC validation, so the `test_receive_btc_amount_*` tests reach the amount-overflow branch they were written to exercise.
Match the spec to the implementation: rounding `btc_msat * smallest_units_per_sat / 1000` up ensures the hub never under-collects the Fiber leg for sub-satoshi remainders.
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.
Summary
This PR tracks CCH multi-asset swap work: a protocol specification plus the implementation that follows it.
Current branch state: the spec only (
docs/specs/cch-multi-asset-swap.md). Implementation commits will be added here before merge.Specification (done on branch)
subscribe_swap_proposals/respond_swap_proposal) with binary accept/reject only, configurable timeout, and recommended explicit RPC errors on reject/timeout.docs/specs/payment-invoice.mdanddocs/specs/cch-expiry-dependency.md.Implementation (planned)
To be added in follow-up commits on this branch, aligned with the spec—for example: