Skip to content

eth/eventhandler: classify remote-signer share decryption errors as malformed - #2997

Open
iurii-ssv wants to merge 3 commits into
stagefrom
fix/remote-signer-share-decryption-classification
Open

eth/eventhandler: classify remote-signer share decryption errors as malformed#2997
iurii-ssv wants to merge 3 commits into
stagefrom
fix/remote-signer-share-decryption-classification

Conversation

@iurii-ssv

@iurii-ssv iurii-ssv commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Summary

A validator share that the signer cannot decrypt should be treated as a malformed registry event (logged and skipped), exactly as the local key manager already does. In remote-signing mode it wasn't: ssvsigner declared ShareDecryptionError as an interface alias of error, so the value the client returned on an HTTP 422 kept its underlying *errors.errorString dynamic type, and the event handler's errors.As(err, &ekm.ShareDecryptionError{}) (a struct type) never matched. A share that failed to decrypt was therefore handled as a non-malformed error instead of a skippable malformed event, inconsistently with local signing.

Change

  • Consolidate the two identically-named ShareDecryptionError types into a single concrete struct in the ssvsigner package, used by the client, both key managers, and the event handler, so decryption failures are classified consistently across local and remote signing modes.
  • Reserve HTTP 422 from the add-validator endpoint for shares that cannot be decrypted/validated: a random-password-generation failure now returns 500 (a transient, retryable error) rather than 422, so it is not misclassified as a malformed share.

Tests

  • Replace the ShareDecryptionError errors.As assertions that passed for any error (the type was an interface) with ones that distinguish a decryption error from other failures.
  • Add coverage for the remote client → AddShare path.

Compatibility

The classification fix is node-side and works with any ssv-signer version (it relies only on the existing HTTP 422 contract). The server-side 422 tightening is independent and degrades gracefully, so nodes and ssv-signer can still be upgraded independently.

…alformed

A validator share that the signer cannot decrypt should be treated as a
malformed registry event (logged and skipped), exactly as the local key
manager already handles it. In remote-signing mode it was not:

ssvsigner declared ShareDecryptionError as an interface alias of error
(type ShareDecryptionError error), so the value the client returned on an
HTTP 422 kept its underlying *errors.errorString dynamic type. The event
handler classifies decryption failures with
errors.As(err, &ekm.ShareDecryptionError{}) against a struct type, which never
matched the remote error, so a share that failed to decrypt was handled as a
non-malformed (fatal) error instead of a skippable malformed event.

Consolidate the two identically-named ShareDecryptionError types into a single
concrete struct in the ssvsigner package, used by the client, both key managers
and the event handler, so decryption failures are classified consistently in
local and remote signing modes.

Tests: replace the ShareDecryptionError errors.As assertions that passed for
any error (the type was an interface) with ones that distinguish a decryption
error from other failures, and add coverage for the remote client -> AddShare
path.
@iurii-ssv
iurii-ssv requested review from a team as code owners August 24, 2026 09:43
@greptile-apps

greptile-apps Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR consolidates share-decryption failures into a concrete error type so local and remote key managers classify malformed validator events consistently.

  • Maps signer HTTP 422 responses to the concrete ShareDecryptionError.
  • Reserves HTTP 422 for malformed shares and returns HTTP 500 for password generation, internal keystore, and Web3Signer failures.
  • Adds event-handler, client, and remote-key-manager coverage for the classification contract.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
ssvsigner/server.go Narrows HTTP 422 to malformed-share failures and maps internal keystore and downstream Web3Signer failures to retryable HTTP 500 responses.
ssvsigner/client.go Converts add-validator HTTP 422 responses into the concrete share-decryption error while leaving other statuses on the ordinary error path.
ssvsigner/types.go Replaces the error-interface alias with a concrete wrapping error that supports precise errors.As classification.
eth/eventhandler/handlers.go Recognizes the shared concrete decryption error and converts it into a skippable malformed-event result.
ssvsigner/ekm/local_key_manager.go Uses the shared signer-package error type for local share decryption and validation failures.
ssvsigner/ekm/remote_key_manager.go Preserves the concrete decryption error through wrapping so the event handler can classify remote-signer failures correctly.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
    E[ValidatorAdded event] --> N[SSV node]
    N --> S[ssv-signer add-validator endpoint]
    S --> V{Share decrypts and validates?}
    V -->|No| U[HTTP 422]
    U --> D[ShareDecryptionError]
    D --> M[Malformed event: log and skip]
    V -->|Yes| W[Import into Web3Signer]
    W --> I{Internal or downstream failure?}
    I -->|Yes| F[HTTP 500]
    F --> R[Retryable event-handler error]
    I -->|No| O[Share registered]
Loading

Reviews (3): Last reviewed commit: "ssvsigner: tighten 422 usage and error h..." | Re-trigger Greptile

Comment thread ssvsigner/client.go Outdated
@codecov

codecov Bot commented Aug 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 47.36842% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.3%. Comparing base (f8dd2c6) to head (cf9c513).

Files with missing lines Patch % Lines
ssvsigner/server.go 28.5% 4 Missing and 1 partial ⚠️
ssvsigner/ekm/local_key_manager.go 0.0% 3 Missing ⚠️
ssvsigner/types.go 66.6% 1 Missing and 1 partial ⚠️

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The add-validator endpoint returned 422 both for shares that cannot be
decrypted/validated and for a random-password-generation failure. The node
maps 422 to a malformed registry event (logged and skipped), so an internal
password-generation error would drop a valid ValidatorAdded event instead of
being retried.

Return 500 for the password-generation failure (an internal, transient error)
and keep 422 exclusively for shares that cannot be decrypted or validated.
Document the 422 contract on both the server and the client.
@iurii-ssv

Copy link
Copy Markdown
Contributor Author

@greptile pls re-review

Comment thread ssvsigner/client.go
Server:
- Reserve HTTP 422 for malformed shares only. Keystore generation and marshalling
  failures in keystoreJSONFromEncryptedShare happen after the share is validated, so
  they are internal (retryable) errors: tag them with errInternalKeystore and reply 500,
  matching the earlier password-generation fix.
- handleWeb3SignerErr always replies 500 and no longer forwards Web3Signer's upstream
  status. The previous errors.As never matched (Web3Signer returns a value-typed
  HTTPResponseError, the check targeted a pointer), so this is behaviour-preserving; the
  comment now documents that a forwarded upstream 422 must not reach the node, which would
  misclassify it as a malformed share.

Client:
- Keep the transport error alongside the 422 body so a bodyless 422 still carries context.

Docs:
- Update the stale RemoteKeyManager.AddShare comment: a malformed share is now skipped
  (not crash-and-retried).

Tests:
- Replace the hand-rolled classification test with one that drives the real
  handleValidatorAdded path: a ValidatorAdded event with a valid signature but an
  undecryptable share must be classified as a MalformedEventError.
- Enforce the slashing-protection mock expectations in the AddShare error subtests.
- Use require.ErrorAs/NotErrorAs and add a 400-status row so the "non-422 must not
  classify" assertion exercises the 4xx boundary.
@iurii-ssv

Copy link
Copy Markdown
Contributor Author

@greptile pls re-review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant