Security release/candidate 1 - #4109
Conversation
|
Important Review skippedToo many files! This PR contains 402 files, which is 302 over the limit of 100. To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch. Upgrade to a paid plan to raise the limit. Usage-priced reviews support at most 300 files. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (402)
You can disable this status message by setting the ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
ee628a3 to
cbf31e0
Compare
|
The PR description says head is Worth double-checking |
dc42a85 to
971f603
Compare
thanks Leo! Fix applied |
…ee RPCs, monitoring wiring (vet round 2) Addresses the round-2 vet report (findings 1, 3, 6 + three net-new). P0 — production discovery/identity is now authoritative, not optional (finding 1): - Completeness now REQUIRES service-discovery reconciliation and on-chain identity verification (CollectorConfig.RequireServiceDiscovery / RequireIdentityVerification, set true by the command). A missing feed blocks readiness instead of degrading to trusting inventory; the command warns that readiness cannot be certified. (isComplete, main.go) - Service discovery is keyed by network ID (per-instance) not operator-only, so multiple instances of one operator resolve to distinct discovered targets (ServiceDiscovery.byNetworkID, MetricsURLForInstance, InventoryInstance.NetworkID, applyDiscoveredTargets). - The metrics adapter validates the responding node's self-attested identity (diagnostics chain_address + network_id) instead of copying InstanceID/ OperatorAddress from inventory; a mismatch is rejected (reportadapter.go). P0 — reporter revision is durable across collector restart (net-new 1): - ReporterRevision now derives from the attestation timestamp (UnixNano) rather than a process-local counter that reset on restart and sat below the persisted high-water mark. Added TestCollector_ReportsAcceptedImmediatelyAfterRestart and TestMetricsReportSource_ReporterRevisionSurvivesRestart. P1 — remaining evidence gaps (finding 3): - StakingProvider must be a canonical, non-zero address; expected image digest must be sha256:<64 hex> (test fixture corrected to a full digest); cross-instance staking-provider contradictions are fail-closed (last claim no longer wins). P1 — no collector lock across network calls (net-new 2): - Identity verification runs BEFORE the central-state lock (verify first, apply under lock) so a degraded RPC never blocks readers; the RPC timeout derives from the passed-in context (CollectContext(ctx), IdentityVerifier now takes ctx, ethCall uses context.WithTimeout(ctx, ...)). P1 — monitoring deployment wiring (finding 6): - A non-loopback API bind without an allowlist is refused at startup. - cutover-readiness.json added to the Grafana ConfigMap; instance-reason text panel replaced with a real table sourced from the readiness API via a new Infinity datasource (+ plugin install). - Added a cutover-roster Deployment/Service/PVC and a Prometheus scrape job, plus an Alertmanager workload + routing tree matching the alerts' route_to label. (Image digest, allowlist CIDR, inventory ConfigMap/Secret, and receiver integrations are REPLACE_ placeholders — ops follow-up, documented in README.adoc.) P1 — 30-day resolved purge (net-new 3): resolution of the semantic conflict. - The fail-closed reopening of a vanished resolved operator (finding 2, RESOLVED) and the naive 30-day purge of a departed resolved operator are in genuine, irreconcilable tension for the "resolved operator departs" population: a departed operator MUST reopen offline_unknown and be retained indefinitely, so it can never age out via the resolved path, and an actively-resolved operator is continuously re-confirmed (ResolvedAt refreshed) and must not be dropped. purge therefore remains a bounded-store backstop for a resolved record no longer being re-confirmed. Restored independent coverage as a white-box test of the purge mechanism (TestCollector_PurgeResolvedAfter30Days): a resolved record older than the retention window is purged with its instances, while a fresh resolved record and any blocking record are retained. P1/P2 — harness: README/compose/script examples now use an immutable @sha256: digest instead of the mutable keep-client:candidate tag the harness itself rejects. P2 — draft PR body: corrected the inaccurate "wire-compatible / no session-ID/KDF change" claim to scope it to THIS diff only and explicitly note PR threshold-network#4109's Part A cryptographic changes are wire-breaking and not described here. Live PR/issue untouched (out of band). Local CI oracle (format, vet, staticcheck SA*, golangci-lint, gosec, go test ./..., race tier-2 subset, integration, race over new packages): PASS. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ee RPCs, monitoring wiring (vet round 2) Addresses the round-2 vet report (findings 1, 3, 6 + three net-new). P0 — production discovery/identity is now authoritative, not optional (finding 1): - Completeness now REQUIRES service-discovery reconciliation and on-chain identity verification (CollectorConfig.RequireServiceDiscovery / RequireIdentityVerification, set true by the command). A missing feed blocks readiness instead of degrading to trusting inventory; the command warns that readiness cannot be certified. (isComplete, main.go) - Service discovery is keyed by network ID (per-instance) not operator-only, so multiple instances of one operator resolve to distinct discovered targets (ServiceDiscovery.byNetworkID, MetricsURLForInstance, InventoryInstance.NetworkID, applyDiscoveredTargets). - The metrics adapter validates the responding node's self-attested identity (diagnostics chain_address + network_id) instead of copying InstanceID/ OperatorAddress from inventory; a mismatch is rejected (reportadapter.go). P0 — reporter revision is durable across collector restart (net-new 1): - ReporterRevision now derives from the attestation timestamp (UnixNano) rather than a process-local counter that reset on restart and sat below the persisted high-water mark. Added TestCollector_ReportsAcceptedImmediatelyAfterRestart and TestMetricsReportSource_ReporterRevisionSurvivesRestart. P1 — remaining evidence gaps (finding 3): - StakingProvider must be a canonical, non-zero address; expected image digest must be sha256:<64 hex> (test fixture corrected to a full digest); cross-instance staking-provider contradictions are fail-closed (last claim no longer wins). P1 — no collector lock across network calls (net-new 2): - Identity verification runs BEFORE the central-state lock (verify first, apply under lock) so a degraded RPC never blocks readers; the RPC timeout derives from the passed-in context (CollectContext(ctx), IdentityVerifier now takes ctx, ethCall uses context.WithTimeout(ctx, ...)). P1 — monitoring deployment wiring (finding 6): - A non-loopback API bind without an allowlist is refused at startup. - cutover-readiness.json added to the Grafana ConfigMap; instance-reason text panel replaced with a real table sourced from the readiness API via a new Infinity datasource (+ plugin install). - Added a cutover-roster Deployment/Service/PVC and a Prometheus scrape job, plus an Alertmanager workload + routing tree matching the alerts' route_to label. (Image digest, allowlist CIDR, inventory ConfigMap/Secret, and receiver integrations are REPLACE_ placeholders — ops follow-up, documented in README.adoc.) P1 — 30-day resolved purge (net-new 3): resolution of the semantic conflict. - The fail-closed reopening of a vanished resolved operator (finding 2, RESOLVED) and the naive 30-day purge of a departed resolved operator are in genuine, irreconcilable tension for the "resolved operator departs" population: a departed operator MUST reopen offline_unknown and be retained indefinitely, so it can never age out via the resolved path, and an actively-resolved operator is continuously re-confirmed (ResolvedAt refreshed) and must not be dropped. purge therefore remains a bounded-store backstop for a resolved record no longer being re-confirmed. Restored independent coverage as a white-box test of the purge mechanism (TestCollector_PurgeResolvedAfter30Days): a resolved record older than the retention window is purged with its instances, while a fresh resolved record and any blocking record are retained. P1/P2 — harness: README/compose/script examples now use an immutable @sha256: digest instead of the mutable keep-client:candidate tag the harness itself rejects. P2 — draft PR body: corrected the inaccurate "wire-compatible / no session-ID/KDF change" claim to scope it to THIS diff only and explicitly note PR threshold-network#4109's Part A cryptographic changes are wire-breaking and not described here. Live PR/issue untouched (out of band). Local CI oracle (format, vet, staticcheck SA*, golangci-lint, gosec, go test ./..., race tier-2 subset, integration, race over new packages): PASS.
54cdc4a to
33808cb
Compare
|
Two 1. The
|
| advisory | summary | fixed in |
|---|---|---|
GO-2022-1098 |
DoS in message decoding | 0.23.2 |
GHSA-2chg-86hq-7w38 |
mishandles witness size checking | 0.23.2 |
GO-2024-2818 |
consensus failures | 0.24.0 |
GHSA-3jgf-r68h-xfqm |
consensus failures | 0.24.0 |
GO-2024-3189 |
consensus failure | 0.24.2-beta.rc1 |
GHSA-27vh-h6mc-q6g8 |
incorrect re-implementation of FindAndDelete |
0.24.2-beta.rc1 |
The first two are already fixed at the version this repo requires — the replace reintroduces them.
Unlike the x/crypto case, this code is in the build: go list -deps ./... returns 16 btcsuite/btcd packages including blockchain, blockchain/indexers, btcec, btcjson and btcutil. Consensus-failure advisories in a Bitcoin bridge seem worth either fixing here or recording why the downgrade is deliberate — right now the replace carries no comment, unlike the keep-common and protobuf ones above it.
All five workflows that build and test the contracts pin Node 18.15.0,
which reached end of life on 2025-04-30 and receives no further security
patches. `engines.node` in both solidity manifests and the two contract
Dockerfiles say the same.
Target is Node 22 (Jod), EOL 2027-04-30. Node 24 has more than twice the
remaining support and was tried first, but Hardhat's undici call is
incompatible with Node 24 until ~2.20, and @defi-wonderland/smock breaks on
hardhat >= 2.20 -- the two windows do not overlap. Node 22 is the ceiling
in both repos until smock is replaced, and smock is archived upstream
(wonderland-archive/smock).
No dependency changes. Verified against this branch, solidity/random-beacon:
Node v18.20.8 168 passing, 12 failing
Node v22.23.1 168 passing, 12 failing
The 12 are identical and pre-existing on both -- every one is
"No deployment found for: T", a missing external deployment artifact in a
local checkout, unrelated to the runtime. hardhat 2.10.0 and
@defi-wonderland/smock 2.0.7, the packages most likely to break on a newer
runtime, are clean.
infrastructure/ is deliberately untouched. Its Dockerfiles are not built
by CI -- `client.yml` lists `infrastructure/**` under `paths-ignore`, and
the only build contexts in the workflows are `.`, `./solidity/ecdsa` and
`./solidity/random-beacon`. The node:11 init container there wants a
decision about whether it is still deployed, not a bump to a file nothing
builds.
Stacked on #4109 -- carries that branch's commits and should merge after
it. #4109 changes no Node-runtime surface, so there is no overlap.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The block counters deliver exactly one notification per registered waiter with a blocking send on an unbuffered channel. A canceled execution that simply walked away from its waiter — the interruptible start-block and delay waits, or the machine select abandoning a state's end-block waiter — left that sender goroutine parked forever once the height was eventually reached. Ownership of an abandoned waiter now passes to a drain goroutine that performs the single receive, so the eventual sender terminates and the drain exits with it. The new tests drive a manually advanced counter that reproduces the production waiter contract and prove the sender completes after a cancellation, both for the bare wait helper and for the machine's abandoned end-block waiter.
Every tBTC ceremony choke point now runs under a permit from the shared release gate, so process quiescence, clock failure, and the active-mode accounting finally see tBTC work: - Each locally controlled DKG member acquires a permit anchored at the DKG started event block immediately before its goroutine; the retry loop, announcer classification, and execution context all derive from the permit, and a gate cancellation is no longer counted as an ordinary DKG failure. - Each wallet action acquires one permit before handler and dispatcher setup, anchored at the proposal-processing start block; the action owns the permit for its whole execution, and its signing and Bitcoin broadcast run on the permit context instead of unowned background contexts. - The coordination procedure runs under a tracking permit anchored at the window's coordination block; its wire format is shared by both releases, so the procedure itself runs in either mode. - The last-moment completion fences guard signer activation, DKG result submission, and every Bitcoin broadcast attempt; the penalty fence guards the heartbeat's consecutive-failure accounting and the terminal inactivity claim submission, so quiescence or a legacy-after-cutover result suppresses new penalty state instead of punishing the grace. - A refused activation preserves the generated share without activating it: durably in the active namespace when the wallet is registered on chain, otherwise in the new protected tbtc-quarantine storage namespace no release's active-wallet scan reads, with audit metadata recording mode, cutover arithmetic, and a seed hash but never raw seeds or shares. - registerSigner is split into durable save and cache activation so the non-activating save path exists. The pinned tss-lib revision has no reviewed per-party legacy mode, so a tECDSA ceremony cannot reproduce the legacy proof transcript. A legacy-mode permit for DKG, signing, heartbeat, or any wallet action is therefore refused outright — emitting the hardened transcript under a legacy permit would produce wire traffic compatible with neither release. The signing executor additionally hard-refuses any non-security-v2 mode as defense in depth. The tbtc block-height wait helper also drains its abandoned waiter on context cancellation, matching the protocol state machine fix.
…antine Any readable file could previously satisfy an external rollback-evidence input, so four placeholder text files made the offline audit declare the rollback barrier ready. Every evidence record now has a mandatory JSON schema — decoded strictly, rejecting unknown fields — with a common envelope binding it to this exact audited snapshot by aggregate checksum, and each record's mandated contents are enforced: - The Ethereum reconciliation must cover every persisted tbtc wallet and beacon group and show each registered with an approved DKG settlement. - The Bitcoin reconciliation must attest a complete pending set with a known terminal state per transaction. - The quiescence report must list a known ceremony, mode, and terminal outcome per active permit, and a claimed quarantined DKG output must be matched by preserved quarantine state in the snapshot. - The prior-reader record must show the tested prior release compatible with every schema this release writes, including loading and signing a wallet created after the cutover block. A missing, undecodable, unbound, uncovered, or contradicted record is a rollback blocker; placeholder bytes can no longer authorize the barrier. The audit also interprets the new tbtc-quarantine namespace with the production loader decode, pairing metadata and membership halves and cross-validating schema, release identity, cutover arithmetic, storage location, and active-namespace overlap — and the tbtc active-namespace checks now verify the member file name, the signing-group index bounds, and per-wallet index uniqueness. The command build artifacts join keep-client in .gitignore so a local go build cannot leave stray binaries in the tree.
The container rehearsal job pointed its keystore directory at an empty workspace path with no way to populate it, so supplying the dispatch digests and chain inputs alone could never pass preflight. The job now decodes a base64 tar.gz from the REHEARSAL_KEYSTORE_BUNDLE_B64 repository secret into that directory — one config.toml plus rehearsal-only key material per node — before preflight runs, and reports BLOCKED with the exact missing input when the secret is not provisioned. The scaffold README documents how to produce the bundle and that production operator keys must never enter it. The container stages themselves remain truthfully blocked until the real fleet inputs exist.
Coordination normally concludes during the window's active phase, so the window end block that anchors every wallet action lies ahead of the chain when the result is processed. The participation gate refuses future anchors, which silently blocked all five wallet actions on the ordinary pre-end-block path. The result processor now waits until the chain reaches the anchor, then acquires the permit against that same block; the coordination-layer context bounds the wait so shutdown drops the action. The routing tests install a real gate over a fast local chain and prove handler invocation through the dispatcher rejected-actions counter instead of passing vacuously on an untouched busy sentinel. Boundary tests pin both sides of the cutover block: an anchor at the cutover dispatches for every action type, an anchor one block below refuses without touching the dispatcher.
All five workflows that build and test the contracts pin Node 18.15.0,
which reached end of life on 2025-04-30 and receives no further security
patches. `engines.node` in both solidity manifests and the two contract
Dockerfiles say the same.
Target is Node 22 (Jod), EOL 2027-04-30. Node 24 has more than twice the
remaining support and was tried first, but Hardhat's undici call is
incompatible with Node 24 until ~2.20, and @defi-wonderland/smock breaks on
hardhat >= 2.20 -- the two windows do not overlap. Node 22 is the ceiling
in both repos until smock is replaced, and smock is archived upstream
(wonderland-archive/smock).
No dependency changes. Verified against this branch, solidity/random-beacon:
Node v18.20.8 168 passing, 12 failing
Node v22.23.1 168 passing, 12 failing
The 12 are identical and pre-existing on both -- every one is
"No deployment found for: T", a missing external deployment artifact in a
local checkout, unrelated to the runtime. hardhat 2.10.0 and
@defi-wonderland/smock 2.0.7, the packages most likely to break on a newer
runtime, are clean.
infrastructure/ is deliberately untouched. Its Dockerfiles are not built
by CI -- `client.yml` lists `infrastructure/**` under `paths-ignore`, and
the only build contexts in the workflows are `.`, `./solidity/ecdsa` and
`./solidity/random-beacon`. The node:11 init container there wants a
decision about whether it is still deployed, not a bump to a file nothing
builds.
Stacked on #4109 -- carries that branch's commits and should merge after
it. #4109 changes no Node-runtime surface, so there is no overlap.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
All five workflows that build and test the contracts pin Node 18.15.0,
which reached end of life on 2025-04-30 and receives no further security
patches. `engines.node` in both solidity manifests and the two contract
Dockerfiles say the same.
Target is Node 22 (Jod), EOL 2027-04-30. Node 24 has more than twice the
remaining support and was tried first, but Hardhat's undici call is
incompatible with Node 24 until ~2.20, and @defi-wonderland/smock breaks on
hardhat >= 2.20 -- the two windows do not overlap. Node 22 is the ceiling
in both repos until smock is replaced, and smock is archived upstream
(wonderland-archive/smock).
No dependency changes. Verified against this branch, solidity/random-beacon:
Node v18.20.8 168 passing, 12 failing
Node v22.23.1 168 passing, 12 failing
The 12 are identical and pre-existing on both -- every one is
"No deployment found for: T", a missing external deployment artifact in a
local checkout, unrelated to the runtime. hardhat 2.10.0 and
@defi-wonderland/smock 2.0.7, the packages most likely to break on a newer
runtime, are clean.
infrastructure/ is deliberately untouched. Its Dockerfiles are not built
by CI -- `client.yml` lists `infrastructure/**` under `paths-ignore`, and
the only build contexts in the workflows are `.`, `./solidity/ecdsa` and
`./solidity/random-beacon`. The node:11 init container there wants a
decision about whether it is still deployed, not a bump to a file nothing
builds.
Stacked on #4109 -- carries that branch's commits and should merge after
it. #4109 changes no Node-runtime surface, so there is no overlap.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ate aborts A DKG member used to persist and cache-activate its generated signer before the result publication, so a clock failure or forced quiescence between the two fences could leave an active signer for a result that never reached the chain. Publication now concludes first; the activation fence runs afterwards, and every non-activation outcome — a refused submission fence, a canceled permit, or a publication window that closed without an observed submitted result — preserves the share through the interrupted-signer path instead of activating or dropping it. A result submitted by another member still activates the signer, and a failed registration preserves the share as well. Gate-caused cancellations also no longer contaminate ordinary failure telemetry: the signing executor, the wallet-action dispatcher, the deposit sweep and redemption actions, and the coordination executor check the participation-gate cause before counting ordinary failures or timeouts, the transaction executor surfaces the gate sentinel instead of a broadcast timeout, and the sign/broadcast error chains wrap their causes so every layer classifies the outcome the same way.
… audit Schema-valid evidence generated against the wrong operational target used to pass the offline audit: chain, network, and prior-artifact identities were only checked nonempty, chain reconciliation covered active state alone, and a quarantined quiescence claim was vouched for by any quarantined output of the same ceremony class. The audit now takes the expected Ethereum chain ID, Bitcoin network, exact prior version and revision, and an evidence freshness bound as inputs — each missing one is a rollback blocker — validates every evidence record against them, reconciles active and quarantined outputs one-to-one in both directions, flags registered wallets or groups whose share is preserved only in quarantine, and matches each quarantined quiescence claim by ceremony, protocol mode, and canonical anchor.
registerSigner durably saved the signer before calculating the wallet ID, so a calculation failure left an active-namespace record while the ceremony completion path preserved a second copy in quarantine. Every fallible step now precedes the durable save and cache activation follows it unconditionally, so a failed registration provably leaves no active record and the quarantine copy stays the only one.
…ting A gate-canceled coordination procedure was recorded in the window metrics as an ordinary failed coordination, so clock failures and forced quiescence inflated the per-window failed-wallet counts operators read as protocol health. The outcome recording is now a single helper that skips gate aborts entirely, with a positive control proving an ordinary failure still reaches the window's failure view.
The offline state audit could match a decoded signer record to chain reconciliation evidence only by its storage key, so evidence carrying a fabricated wallet ID was indistinguishable from evidence for the real wallet. The audit record now carries the ECDSA wallet ID and the Bitcoin public key hash derived locally from the decoded public key, byte-identical to the chain's own derivation, with no chain access required.
|
Checking in on this security release candidate — it conflicts with |
Bring PR threshold-network#4109 current with threshold-network/keep-core main. Keep cutover participation permits and fold in main's stuck-transaction monitor plus the btcd/btcec third_party replace. Drop main's node.go file splits that duplicated the candidate's monolithic node (handlers stay permit-aware). Co-authored-by: Cursor <cursoragent@cursor.com>
6713089 to
2d858b7
Compare
…ee RPCs, monitoring wiring (vet round 2) Addresses the round-2 vet report (findings 1, 3, 6 + three net-new). P0 — production discovery/identity is now authoritative, not optional (finding 1): - Completeness now REQUIRES service-discovery reconciliation and on-chain identity verification (CollectorConfig.RequireServiceDiscovery / RequireIdentityVerification, set true by the command). A missing feed blocks readiness instead of degrading to trusting inventory; the command warns that readiness cannot be certified. (isComplete, main.go) - Service discovery is keyed by network ID (per-instance) not operator-only, so multiple instances of one operator resolve to distinct discovered targets (ServiceDiscovery.byNetworkID, MetricsURLForInstance, InventoryInstance.NetworkID, applyDiscoveredTargets). - The metrics adapter validates the responding node's self-attested identity (diagnostics chain_address + network_id) instead of copying InstanceID/ OperatorAddress from inventory; a mismatch is rejected (reportadapter.go). P0 — reporter revision is durable across collector restart (net-new 1): - ReporterRevision now derives from the attestation timestamp (UnixNano) rather than a process-local counter that reset on restart and sat below the persisted high-water mark. Added TestCollector_ReportsAcceptedImmediatelyAfterRestart and TestMetricsReportSource_ReporterRevisionSurvivesRestart. P1 — remaining evidence gaps (finding 3): - StakingProvider must be a canonical, non-zero address; expected image digest must be sha256:<64 hex> (test fixture corrected to a full digest); cross-instance staking-provider contradictions are fail-closed (last claim no longer wins). P1 — no collector lock across network calls (net-new 2): - Identity verification runs BEFORE the central-state lock (verify first, apply under lock) so a degraded RPC never blocks readers; the RPC timeout derives from the passed-in context (CollectContext(ctx), IdentityVerifier now takes ctx, ethCall uses context.WithTimeout(ctx, ...)). P1 — monitoring deployment wiring (finding 6): - A non-loopback API bind without an allowlist is refused at startup. - cutover-readiness.json added to the Grafana ConfigMap; instance-reason text panel replaced with a real table sourced from the readiness API via a new Infinity datasource (+ plugin install). - Added a cutover-roster Deployment/Service/PVC and a Prometheus scrape job, plus an Alertmanager workload + routing tree matching the alerts' route_to label. (Image digest, allowlist CIDR, inventory ConfigMap/Secret, and receiver integrations are REPLACE_ placeholders — ops follow-up, documented in README.adoc.) P1 — 30-day resolved purge (net-new 3): resolution of the semantic conflict. - The fail-closed reopening of a vanished resolved operator (finding 2, RESOLVED) and the naive 30-day purge of a departed resolved operator are in genuine, irreconcilable tension for the "resolved operator departs" population: a departed operator MUST reopen offline_unknown and be retained indefinitely, so it can never age out via the resolved path, and an actively-resolved operator is continuously re-confirmed (ResolvedAt refreshed) and must not be dropped. purge therefore remains a bounded-store backstop for a resolved record no longer being re-confirmed. Restored independent coverage as a white-box test of the purge mechanism (TestCollector_PurgeResolvedAfter30Days): a resolved record older than the retention window is purged with its instances, while a fresh resolved record and any blocking record are retained. P1/P2 — harness: README/compose/script examples now use an immutable @sha256: digest instead of the mutable keep-client:candidate tag the harness itself rejects. P2 — draft PR body: corrected the inaccurate "wire-compatible / no session-ID/KDF change" claim to scope it to THIS diff only and explicitly note PR threshold-network#4109's Part A cryptographic changes are wire-breaking and not described here. Live PR/issue untouched (out of band). Local CI oracle (format, vet, staticcheck SA*, golangci-lint, gosec, go test ./..., race tier-2 subset, integration, race over new packages): PASS.
Bring PR threshold-network#4109 current with threshold-network/keep-core main. Keep cutover participation permits and fold in main's stuck-transaction monitor plus the btcd/btcec third_party replace. Drop main's node.go file splits that duplicated the candidate's monolithic node (handlers stay permit-aware).
Bring PR threshold-network#4109 current with threshold-network/keep-core main. Keep cutover participation permits and fold in main's stuck-transaction monitor plus the btcd/btcec third_party replace. Drop main's node.go file splits that duplicated the candidate's monolithic node (handlers stay permit-aware).
2d858b7 to
da9de1b
Compare
@piotr-roslaniec Thanks for the ping. I’ve synced security-release/candidate-1 with current main (merge commit on the PR tip) so the conflict set should be cleared for re-review. I’m aiming to keep this as a single PR and finish remaining review/CI follow-ups. I’d prefer not to split unless review becomes blocked on size/scope. The cutover/participation changes are tightly coupled; splitting would likely create awkward intermediate states |
Update GetTransactionConfirmations stub signature after main merge so cmd tests compile under lint/golangci/CI.
Treat IsDKGResultValid errors (e.g. high-s OpenZeppelin recover reverts) as invalid results and enter the challenge loop, matching on-chain challengeDkgResult's validation-reverted path.
…ionLayer The merge that ported this branch's monolithic node.go from main's split node files (da9de1b) carried over the transaction monitor's construction, metrics wiring, and action wiring, but dropped the five lines that actually started its polling goroutine. The monitor has been registering broadcast transactions since that merge without ever checking them: no stuck-warning, no 24h eviction, and the stuck/unmonitored counters read a healthy zero whether the loop is alive or not. Restore the single nil-guarded launch at the top of runCoordinationLayer, bound to its own context so it starts asynchronously and stops with the node. Add a private check-interval seam so tests don't wait out the production five-minute tick, and three liveness/progress metrics (running, check cycles, tracked count) as positive evidence an operator can gate on, since the existing counters can't distinguish an inert loop from a healthy one.
…oordinationLayer" This reverts commit 591b879.
…ionLayer The merge that ported this branch's monolithic node.go from main's split node files (da9de1b) carried over the transaction monitor's construction, metrics wiring, and action wiring, but dropped the five lines that actually started its polling goroutine. The monitor has been registering broadcast transactions since that merge without ever checking them: no stuck-warning, no 24h eviction, and the stuck/unmonitored counters read a healthy zero whether the loop is alive or not. Restore the single nil-guarded launch at the top of runCoordinationLayer, bound to its own context so it starts asynchronously and stops with the node. Add a private check-interval seam so tests don't wait out the production five-minute tick, and three liveness/progress metrics (running, check cycles, tracked count) as positive evidence an operator can gate on, since the existing counters can't distinguish an inert loop from a healthy one.
|
Transparency note for @lionakhnazarov: I accidentally pushed 591b879 directly to security-release/candidate-1 while setting up to open a PR for the transaction-monitor lifecycle-launch fix (a local clone had push.default=upstream, which redirected the push to the tracked ref instead of creating a new branch). I reverted it immediately with a forward commit (d2a95ee) that restores the branch tree to its exact prior state without rewriting shared history, then reopened the same verified patch properly at #15 from my fork. Sorry for the disruption. |
…launch fix(tbtc): restore transaction-monitor lifecycle launch
Implement chain.AuthoritativeClock on baseChain using a direct RPC head read instead of the subscription-backed block counter cache.
Expose the Ethereum base chain's direct RPC clock from Connect so production startup no longer derives authoritative height from the block-counter cache.
Replace removed blockCounterAuthoritativeClock references in quiesce lifecycle tests with a local test helper matching the Task 2 pattern.
Use the direct chain clock for roster height decisions so cached block heights cannot claim readiness during RPC outages.
Wrap roster CurrentHeight calls with authoritativeClockTimeout so construction, sweeps, and ObserveLegacy match Gate clock discipline.
…k-minimal fix(participation): authoritative chain clock for gate and cutover roster (threshold-network#4245)
Summary
Integration candidate for the coordinated security release. This description
tracks the current PR head rather than the earlier refresh checkpoint.
d7bf8c0753f3aac574f8d20d93c75268f350b389main@a7ac8989b662d51d8a94fa28f1ac226058d5d6ccgithub.com/threshold-network/tss-lib@v0.0.0-20260729021955-d847ce003019(d847ce003019)v1.7.1-tlabs.1v1.17.31.25.7, toolchaingo1.25.10The candidate includes the chain-clocked participation gate, immutable
per-ceremony legacy/security-v2 strategy selection, cutover diagnostics, and
release-manifest checks. It is not a mainnet-ready artifact:
participation.MainnetCutoverBlockis deliberately0, and mainnet startupfails closed until a reviewed release commit supplies the real cutover block.
Cutover contract
before the reviewed cutover block
C; the permitted residual is zero PRIORprocesses and zero PRIOR-eligible seats.
Work anchored below
Cremains byte-compatible legacy work until terminal;work anchored at or after
Cuses security-v2.delivery time, peer claims, and dependency version are not selectors.
Cunder its canonical commit fence.That exception does not authorize a PRIOR process, an unsealed legacy
session, a post-
Clegacy penalty commit, or a downgrade retry.fail closed rather than negotiate or fall back.
Breaking changes
clientInfo.port = 9601compatibility defaultconnected_bootstrap_countrenamed toconnected_wellknown_peers_count--network.bootstrap=truedeprecatedSee
CHANGELOG.mdandSECURITY-BREAKING-CHANGES.mdfor the detailedoperator-facing contract.
Validation state
checks completed successfully in the original PR run.
libFuzzer's
TracePC::ClearInlineCountersbefore the Go callback executed,this time on a different target and the empty seed. The runner logged
language: c++for native-Go binaries; the build/runner contract is beingcorrected before another rerun: run 32481030115.
go test -timeout 15m ./...on the exact current head plusaudit-only landing changes passed on 2026-08-27.
they were produced by pre-current commits and are not evidence for the final
source/build/policy identity.
Current release blockers
Cand release commitidentity; no signatures or approvals are inferred from this PR
immutable source and output digests
promotable OCI digest
fresh green ClusterFuzzLite rerun
The external scenario runner and DigitalOcean evidence production remain in
tlabs-xyz/pr4109-do-infra; they are not vendored into this product/release DAG.Review focus
for sealed
< CR1 work and reject legacy input for>= Cworkfail closed
and evidence roots remain bound end to end
Related
d847ce003019