Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .github/workflows/client.yml
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,55 @@ jobs:
install-go: false
checks: "-SA1019"

# Compiles and unit-tests the interactive FROST + ROAST retry coordinator path,
# which lives behind the `frost_roast_retry` (and `frost_native`) build tags. The
# default untagged build the other jobs run compiles only the `!frost_roast_retry`
# no-op stubs, so without this job the entire ROAST retry state machine (liveness +
# evidence/blame) and the ~30 `frost_native` unit tests never compile or run in CI.
#
# This is the mock-FFI path: it exercises the Go coordinator flow with cgo DISABLED,
# so it needs neither libfrost_tbtc (the Rust signer lib) nor Docker. The real-crypto
# cgo variant of these tags is gated separately by frost-cgo-integration.yml.
client-frost-roast-retry:
needs: client-detect-changes
if: |
github.event_name == 'push'
|| needs.client-detect-changes.outputs.path-filter == 'true'
runs-on: ubuntu-latest
env:
CGO_ENABLED: "0"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: "go.mod"

- name: Build the ROAST retry coordinator path (mock FFI, no cgo)
run: |
# `frost_roast_retry` alone compiles the coordinator registry / evidence /
# blame flow against mock producers; adding `frost_native` also compiles the
# native-engine <-> retry integration files (mock FFI engine, still no lib).
go build -tags "frost_roast_retry" ./...
go build -tags "frost_native frost_roast_retry" ./...

- name: Unit-test the tagged path (mock FFI, no cgo)
run: |
# These three tag sets cover every non-cgo tagged test file in pkg/frost and
# pkg/tbtc:
# frost_native -> frost_native (and && !frost_roast_retry)
# frost_roast_retry -> frost_roast_retry (and && !frost_native)
# frost_native frost_roast_retry -> the intersection integration tests
# The `frost_native frost_tbtc_signer cgo` real-crypto tests are NOT run here;
# they are covered by frost-cgo-integration.yml.
for tags in \
"frost_native" \
"frost_roast_retry" \
"frost_native frost_roast_retry"; do
echo "::group::go test -tags \"$tags\""
go test -tags "$tags" -count=1 -timeout 25m ./pkg/frost/... ./pkg/tbtc/...
echo "::endgroup::"
done

client-integration-test:
needs: [client-detect-changes, electrum-integration-detect-changes, client-build-test-publish]
if: |
Expand Down
37 changes: 31 additions & 6 deletions .github/workflows/frost-cgo-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ name: FROST cgo integration
# against it with skips FORBIDDEN (KEEP_CORE_FROST_REQUIRE_CGO=true), so a stale/missing/
# unloadable lib fails the job instead of quietly dropping coverage.
#
# The tag set also includes `frost_roast_retry`, so the ROAST retry coordinator flow is
# compiled and tested against real crypto here (the mock-FFI variant runs in client.yml),
# and the job smoke-builds the full activation artifact via `make build-frost`. The
# non-cgo tagged build/test coverage lives in the client.yml `client-frost-roast-retry`
# job; together they cover the tag matrix that the default untagged CI never touches.
#
# The Go interactive code and the signer crate currently live on separate branches, so
# this checks the pinned mirror commit out into a side path and builds from there. After
# the branches merge, replace the cross-branch checkout with an in-tree cargo build and
Expand All @@ -22,6 +28,7 @@ on:
- "pkg/net/**"
- "go.mod"
- "go.sum"
- "Makefile"
- "ci/frost-signer-pin.env"
- ".github/workflows/frost-cgo-integration.yml"
workflow_dispatch: {}
Expand Down Expand Up @@ -104,10 +111,28 @@ jobs:
# references its symbols only via dlsym (no direct references), so the loader
# makes them resolvable at runtime; rpath lets the test binary find the .so.
export CGO_LDFLAGS="-L${FROST_LIB_DIR} -Wl,--no-as-needed -lfrost_tbtc -Wl,-rpath,${FROST_LIB_DIR}"
# The real-crypto suite (exercises the engine + the ABI preflight against the
# linked lib) plus the ABI fail-closed test (proves a present-but-incompatible
# lib refuses to fall back to legacy signing; uses the seam, so it does not need
# an actually-incompatible lib). This gate is the only CI that builds these tags.
go test -tags "frost_native frost_tbtc_signer" -count=1 \
-run 'TestRealCgoInteractiveSigning|TestBuildTaggedLegacyCompatibleNativeExecutionFFISigningPrimitive_Sign_TBTCSignerPath_ABIIncompatible_DoesNotFallback' \
# Run the WHOLE tagged pkg/frost/signing suite against the linked lib, not a
# narrow -run subset. `frost_roast_retry` is added to the tag set so the ROAST
# retry coordinator flow (liveness + evidence/blame) is compiled and tested
# against real crypto here too, not only in the mock-FFI job (client.yml).
# With KEEP_CORE_FROST_REQUIRE_CGO=true a lib-unavailable would-be skip becomes
# a hard failure, so a stale/missing symbol fails the gate instead of silently
# dropping coverage. This gate is the only CI that builds the cgo tag set.
go test -tags "frost_native frost_tbtc_signer frost_roast_retry" -count=1 \
-timeout 20m \
./pkg/frost/signing/

- name: Smoke-build the FROST activation artifact (make build-frost)
env:
CGO_ENABLED: "1"
run: |
set -euo pipefail
# Proves the release/activation binary - the keep-client built with
# `frost_native frost_tbtc_signer frost_roast_retry` and linked against
# libfrost_tbtc - actually compiles and links. The default `make build` the
# release/Docker path runs ships the `!frost_roast_retry` no-op stubs; this is
# the target that produces the ROAST-retry-enabled artifact once the readiness
# manifest flips. Reuses the same lib built above (frost_lib_dir=FROST_LIB_DIR).
make build-frost frost_lib_dir="${FROST_LIB_DIR}"
test -x ./keep-client || { echo "keep-client activation binary was not produced"; exit 1; }
./keep-client --version
35 changes: 34 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,39 @@ build:
$(info Building Go code)
$(call go_build_cmd,.,$(app_name))

# FROST activation build.
#
# Produces the keep-client binary with the interactive FROST + ROAST retry
# coordinator path compiled in (build tags `frost_native frost_tbtc_signer
# frost_roast_retry`). The default `build` target above compiles the
# `!frost_roast_retry` no-op stubs, so the released image ships with the ROAST
# retry coordinator disabled; this target is the artifact that actually enables
# it once the readiness manifest flips (see
# docs/development/frost-roast-retry-rollout.adoc).
#
# Unlike `build` this is a cgo build that links libfrost_tbtc (the Rust signer
# lib built from the tbtc-signer crate). Point `frost_lib_dir` at the directory
# holding the built `libfrost_tbtc.{so,dylib}`. The CGO_LDFLAGS mirror the
# frost-cgo-integration CI workflow: `--no-as-needed` keeps the DT_NEEDED entry
# even though the cgo bridge resolves the frost_tbtc_* symbols via dlsym (no
# direct references), and the rpath lets the binary find the lib at runtime.
# These are GNU-ld flags; the target is meant for the Linux CI/release toolchain.
frost_build_tags := frost_native frost_tbtc_signer frost_roast_retry

ifndef frost_lib_dir
override frost_lib_dir = $(CURDIR)/_frost-target/debug
endif

build-frost:
$(info Building Go code with the FROST activation tags [$(frost_build_tags)])
CGO_ENABLED=1 \
CGO_LDFLAGS="-L$(frost_lib_dir) -Wl,--no-as-needed -lfrost_tbtc -Wl,-rpath,$(frost_lib_dir)" \
go build \
-tags "$(frost_build_tags)" \
-ldflags "-X github.com/keep-network/keep-core/build.Version=$(version) -X github.com/keep-network/keep-core/build.Revision=$(revision) -checklinkname=0" \
-o $(app_name) \
.

platforms := linux/amd64 \
darwin/amd64

Expand All @@ -150,4 +183,4 @@ cmd-help: build
@echo '$$ $(app_name) start --help' > docs/resources/client-start-help
./$(app_name) start --help >> docs/resources/client-start-help

.PHONY: all development sepolia download_artifacts generate gen_proto build cmd-help release build_multi
.PHONY: all development sepolia download_artifacts generate gen_proto build build-frost cmd-help release build_multi
30 changes: 26 additions & 4 deletions docs/development/frost-roast-retry-rollout.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,32 @@ RFC-21 design and is enforced in

== Production rollout sequencing

. *Build the binary with the tag.* Internal builds and CI
pipelines already exercise the tag via
`go test -tags 'frost_roast_retry' ./pkg/frost/... ./pkg/tbtc/...`.
Production binaries adopt the tag once the readiness manifest at
. *Build the binary with the tag.* CI exercises the tagged path on
every pull request that touches Go, so the ROAST retry flow cannot
silently rot:
+
--
* The `client-frost-roast-retry` job in `.github/workflows/client.yml`
compiles the coordinator flow with cgo disabled
(`go build -tags "frost_roast_retry" ./...` and
`go build -tags "frost_native frost_roast_retry" ./...`) and runs the
tagged unit tests under the three non-cgo tag sets that cover the
matrix — `frost_native`, `frost_roast_retry`, and
`frost_native frost_roast_retry` — via
`go test -tags "<set>" ./pkg/frost/... ./pkg/tbtc/...` against the
mock FFI (no `libfrost_tbtc` required).
* `.github/workflows/frost-cgo-integration.yml` adds `frost_roast_retry`
to its real-crypto cgo tag set, runs the whole tagged
`./pkg/frost/signing/` suite against the linked `libfrost_tbtc` with
skips forbidden, and smoke-builds the activation artifact via
`make build-frost`.
--
+
Production binaries are produced with `make build-frost` (tags
`frost_native frost_tbtc_signer frost_roast_retry`, cgo-linked against
`libfrost_tbtc`); the default `make build` still ships the
`!frost_roast_retry` no-op stubs. Production adopts the tagged build
once the readiness manifest at
`docs/development/frost-readiness-manifest.adoc` flips to
`present`. (The manifest was originally planned for the tBTC
monorepo's `docs/operations/` directory; the monorepo signer is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,29 @@ func TestRegisterBuildTaggedTBTCSignerEngine(t *testing.T) {
)
}

// The fail-closed contract asserted below - every engine operation returns
// ErrNativeCryptographyUnavailable - only holds when libfrost_tbtc is NOT linked:
// the cgo bridge is compiled (this file's build tag) but the frost_tbtc_* symbols
// are not resolvable via dlsym. Under the frost-cgo-integration gate the lib IS
// linked, so native crypto is available and StartSignRound instead reaches the real
// signer (and its provenance gate); asserting an unavailable error there would be a
// false failure. Probe the linked lib with the same check the ABI preflight uses -
// it keeps ErrNativeCryptographyUnavailable in the chain iff the lib is absent - and
// skip the fail-closed assertions with a reason when the lib is present. The
// registration wiring above still runs under both builds, and the linked-lib crypto
// path is covered by TestBuildTaggedTBTCSignerInteractiveFROSTBridge_WithLinkedSigner
// and the TestRealCgoInteractiveSigning* suite.
if abiErr := assertTBTCSignerABICompatible(); !errors.Is(
abiErr, ErrNativeCryptographyUnavailable,
) {
t.Skipf(
"libfrost_tbtc linked (native crypto available; ABI probe: %v); the "+
"fail-closed-unavailable path this test asserts is not exercisable with "+
"the lib present",
abiErr,
)
}

_, err = engine.StartSignRound(
"session-1",
1,
Expand Down
Loading