mirror of
https://github.com/luxfi/bridge.git
synced 2026-09-24 00:26:46 +00:00
Brings the SDK-v1.0.3 layered cosigner pattern from app/server/src/domain/cosigners.ts
across to Go. Adds:
internal/secrets/ URI-scheme secret resolver (literal:/env:/file:/kms:)
with pluggable KMS providers (init-time Register).
Single source of truth for mpcd bearer tokens,
cosigner PEMs, and any future secret material.
internal/cosigners/ Layered cosigner SDK — per-swap external custodian
attestation on TOP of the native MPC threshold sig.
Wire shape mirrors the TS reference exactly (snake-
case, `kind` discriminator). Families:
- fireblocks.go Real REST integration; RAW
transaction approval flow,
per-tenant JWT (RSA), polls
/v1/transactions/{id} to
terminal status.
- utila.go Connect-RPC scaffold; mirrors
FireblocksRESTFamily shape so
the future port is one
contained method swap.
- stub_family.go Catch-all for unknown kinds.
Security invariant baked in: SECRET MATERIAL NEVER CROSSES THE WIRE. The
SDK sends only public identifiers (org_id, client_id, api_key, vault ids).
ValidateIntents rejects SECRET_FIELD_NAMES (api_secret, private_key, jwt,
token, …) as a defensive layer. Secrets are fetched at dispatch time via
secrets.Resolver, which is also where KMS plugs in.
Tests: cosigners (2.0s) + secrets (0.3s) all green. Self-contained — no
out-of-tree imports beyond stdlib and within-repo packages.
Extracted from origin/whispers/bridgev2 as the first of a series of focused
slices integrating that branch onto main without dragging in the noise
(export.txt transcript dump, bridge binary diff). Per "x.x.x+1" rule the
next bridge release tag is v1.2.0 — this is the first additive feature
that warrants the minor bump (new internal packages, no consumer break).
161 lines
6.2 KiB
Go
161 lines
6.2 KiB
Go
package cosigners
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestUtilaConnectRPCFamily_RunUtila_StubFailureToday documents the
|
|
// current contract: until the real Utila Connect-RPC client lands,
|
|
// every UtilaIntent fails with a clear "not yet implemented" reason.
|
|
// The signing driver's policy ("all listed must approve") then moves
|
|
// the swap to refund_pending so the user's deposit is returned.
|
|
//
|
|
// When the real implementation lands, REPLACE THIS TEST with a real
|
|
// flow assertion — but keep the "fail without secret" / "fail on
|
|
// transport error" coverage that already exists for FireblocksRESTFamily.
|
|
func TestUtilaConnectRPCFamily_RunUtila_StubFailureToday(t *testing.T) {
|
|
got := UtilaConnectRPCFamily{}.RunUtila(context.Background(),
|
|
&UtilaIntent{
|
|
OrgID: "tenant-x",
|
|
ClientID: "lux-bridge",
|
|
VaultID: "v-42",
|
|
},
|
|
"unused-secret",
|
|
DispatchOptions{SwapID: "swap_uscaffold", TxHash: "0xabcd"},
|
|
)
|
|
if got.Status != StatusFailed {
|
|
t.Errorf("expected StatusFailed (real impl pending), got %s reason=%q", got.Status, got.Reason)
|
|
}
|
|
for _, want := range []string{"not yet ported", "internal/cosigners/utila.go", "swap_uscaffold", "tenant-x", "lux-bridge", "v-42"} {
|
|
if !strings.Contains(got.Reason, want) {
|
|
t.Errorf("reason should mention %q for grepability, got %q", want, got.Reason)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUtilaConnectRPCFamily_RunUtila_DefaultVaultPlaceholder(t *testing.T) {
|
|
// VaultID omitted — reason should say "(tenant default)" so a
|
|
// reader knows the intent didn't specify a vault.
|
|
got := UtilaConnectRPCFamily{}.RunUtila(context.Background(),
|
|
&UtilaIntent{OrgID: "o", ClientID: "c"},
|
|
"unused-secret",
|
|
DispatchOptions{SwapID: "swap_dvault"},
|
|
)
|
|
if !strings.Contains(got.Reason, "(tenant default)") {
|
|
t.Errorf("reason should label the default-vault case, got %q", got.Reason)
|
|
}
|
|
}
|
|
|
|
func TestUtilaConnectRPCFamily_RunFireblocks_DelegatesToStubByDefault(t *testing.T) {
|
|
got := UtilaConnectRPCFamily{}.RunFireblocks(context.Background(),
|
|
&FireblocksIntent{APIKey: "k"},
|
|
"unused",
|
|
DispatchOptions{SwapID: "swap_uf"},
|
|
)
|
|
if got.Status != StatusFailed {
|
|
t.Errorf("default Fireblocks delegate = stub, want StatusFailed, got %s", got.Status)
|
|
}
|
|
if !strings.Contains(got.Reason, "not yet implemented") {
|
|
t.Errorf("expected stub's reason, got %q", got.Reason)
|
|
}
|
|
}
|
|
|
|
func TestUtilaConnectRPCFamily_RunFireblocks_CustomDelegate(t *testing.T) {
|
|
delegate := approvingFireblocksDelegate{}
|
|
got := UtilaConnectRPCFamily{FireblocksDelegate: delegate}.RunFireblocks(
|
|
context.Background(),
|
|
&FireblocksIntent{APIKey: "k"},
|
|
"unused",
|
|
DispatchOptions{SwapID: "swap_uf2"},
|
|
)
|
|
if got.Status != StatusApproved {
|
|
t.Errorf("custom delegate should approve, got %s reason=%q", got.Status, got.Reason)
|
|
}
|
|
}
|
|
|
|
// approvingFireblocksDelegate is a test-only family that approves
|
|
// Fireblocks calls and panics on Utila (asserts routing).
|
|
type approvingFireblocksDelegate struct{}
|
|
|
|
func (approvingFireblocksDelegate) RunUtila(_ context.Context, _ *UtilaIntent, _ string, _ DispatchOptions) Result {
|
|
panic("approvingFireblocksDelegate.RunUtila should not be called")
|
|
}
|
|
func (approvingFireblocksDelegate) RunFireblocks(_ context.Context, intent *FireblocksIntent, _ string, _ DispatchOptions) Result {
|
|
return Result{
|
|
Intent: Intent{Kind: KindFireblocks, Fireblocks: intent},
|
|
Status: StatusApproved,
|
|
Signature: "fb-test-sig",
|
|
ExternalID: "fb-test-ext",
|
|
}
|
|
}
|
|
|
|
// ───────────────────────────────────────────────────────────────────────
|
|
// CompositeFamilyDispatcher — verifies routing in both directions
|
|
// ───────────────────────────────────────────────────────────────────────
|
|
|
|
func TestCompositeFamilyDispatcher_BothNilFallsBackToStub(t *testing.T) {
|
|
c := CompositeFamilyDispatcher{}
|
|
|
|
utilaGot := c.RunUtila(context.Background(),
|
|
&UtilaIntent{OrgID: "o", ClientID: "c"}, "x",
|
|
DispatchOptions{SwapID: "swap_cu"})
|
|
if utilaGot.Status != StatusFailed {
|
|
t.Errorf("zero Utila → stub failure expected, got %s", utilaGot.Status)
|
|
}
|
|
|
|
fbGot := c.RunFireblocks(context.Background(),
|
|
&FireblocksIntent{APIKey: "k"}, "x",
|
|
DispatchOptions{SwapID: "swap_cf"})
|
|
if fbGot.Status != StatusFailed {
|
|
t.Errorf("zero Fireblocks → stub failure expected, got %s", fbGot.Status)
|
|
}
|
|
}
|
|
|
|
func TestCompositeFamilyDispatcher_RoutesPerFamily(t *testing.T) {
|
|
c := CompositeFamilyDispatcher{
|
|
UtilaFamily: approvingUtilaDelegate{}, // from fireblocks_test.go — approves Utila, panics on Fireblocks
|
|
FireblocksFamily: approvingFireblocksDelegate{}, // approves Fireblocks, panics on Utila
|
|
}
|
|
|
|
uGot := c.RunUtila(context.Background(),
|
|
&UtilaIntent{OrgID: "o", ClientID: "c"}, "x",
|
|
DispatchOptions{SwapID: "swap_cu2"})
|
|
if uGot.Status != StatusApproved {
|
|
t.Errorf("Utila family should approve, got %s reason=%q", uGot.Status, uGot.Reason)
|
|
}
|
|
|
|
fGot := c.RunFireblocks(context.Background(),
|
|
&FireblocksIntent{APIKey: "k"}, "x",
|
|
DispatchOptions{SwapID: "swap_cf2"})
|
|
if fGot.Status != StatusApproved {
|
|
t.Errorf("Fireblocks family should approve, got %s reason=%q", fGot.Status, fGot.Reason)
|
|
}
|
|
}
|
|
|
|
// TestCompositeFamilyDispatcher_MixedRealAndNil shows the wiring that
|
|
// today's --enable-fireblocks-cosigner path produces under the hood:
|
|
// real Fireblocks + nil Utila ⇒ Utila intents fail loudly, Fireblocks
|
|
// intents go to the real runner.
|
|
func TestCompositeFamilyDispatcher_MixedRealAndNil(t *testing.T) {
|
|
c := CompositeFamilyDispatcher{
|
|
// UtilaFamily intentionally nil — falls back to stub
|
|
FireblocksFamily: approvingFireblocksDelegate{},
|
|
}
|
|
|
|
uGot := c.RunUtila(context.Background(),
|
|
&UtilaIntent{OrgID: "o", ClientID: "c"}, "x",
|
|
DispatchOptions{SwapID: "swap_mix_u"})
|
|
if uGot.Status != StatusFailed {
|
|
t.Errorf("nil Utila → stub failure expected, got %s", uGot.Status)
|
|
}
|
|
|
|
fGot := c.RunFireblocks(context.Background(),
|
|
&FireblocksIntent{APIKey: "k"}, "x",
|
|
DispatchOptions{SwapID: "swap_mix_f"})
|
|
if fGot.Status != StatusApproved {
|
|
t.Errorf("real Fireblocks should approve, got %s", fGot.Status)
|
|
}
|
|
}
|