# How Errors Are Propagated and Handled in Distributed MPC Sessions

> Learn how mpcium handles errors in distributed MPC sessions. Discover non-blocking failure detection via Go channels and structured error event transformation for robust handling.

- Repository: [Fystack Labs/mpcium](https://github.com/fystack/mpcium)
- Tags: internals
- Published: 2026-03-02

---

**Errors in mpcium's distributed MPC sessions propagate through dedicated Go channels, allowing non-blocking failure detection while centralized event consumers transform low-level faults into structured error events for downstream handling.**

In the **mpcium** open-source framework, every Multi-Party Computation (MPC) session—whether for key generation, signing, or ECDH exchange—runs inside its own Go struct. Rather than returning errors directly via function calls, the system adopts a **channel-centric error propagation model**. This ensures that failures in cryptographic operations, network message handling, or TSS (Threshold Signature Scheme) round processing surface asynchronously without blocking the main execution flow.

## The Channel-Centric Error Propagation Model

Each session struct declares a dedicated error channel at construction time. In [`pkg/mpc/session.go`](https://github.com/fystack/mpcium/blob/main/pkg/mpc/session.go), the base `session` struct defines:

```go
type session struct {
    ...
    ErrCh    chan error      // 【session.go#L61-L62】
}

```

Sessions expose this channel through the `ErrChan()` method, allowing callers to select on it:

```go
func (s *session) ErrChan() <-chan error { // 【session.go#L333】
    return s.ErrCh
}

```

When any internal component encounters a failure, it pushes the error onto `s.ErrCh` rather than returning it. This pattern appears consistently across TSS message handlers, P2P decryption routines, and broadcast verification logic.

## Internal Error Sources and Propagation Points

### TSS Message Handling Failures

During signing or key generation, the `handleTssMessage` method in [`pkg/mpc/session.go`](https://github.com/fystack/mpcium/blob/main/pkg/mpc/session.go) captures cryptographic failures and forwards them to the error channel:

```go
s.ErrCh <- fmt.Errorf("failed to sign message: %w", err) // 【session.go#L124-L130】

```

Similarly, round-specific processing errors in `receiveTssMessage` are wrapped and sent:

```go
s.ErrCh <- errors.Wrap(err, "Broken TSS Share") // 【session.go#L228-L229】

```

### P2P Decryption and Broadcast Verification

When decrypting peer-to-peer messages or verifying broadcast signatures, failures indicate potential tampering. The session pushes these errors immediately:

```go
// P2P decryption failure
s.ErrCh <- fmt.Errorf("failed to decrypt message: %w, tampered message", err) // 【session.go#L188-L190】

// Broadcast signature verification failure
s.ErrCh <- fmt.Errorf("Failed to verify message: %w, tampered message", err) // 【session.go#L212-L213】

```

### ECDH Key Exchange Errors

The `ecdhSession` in [`pkg/mpc/key_exchange_session.go`](https://github.com/fystack/mpcium/blob/main/pkg/mpc/key_exchange_session.go) maintains its own `errCh` channel. Signature verification failures, key derivation errors, or HKDF failures are sent via:

```go
e.errCh <- err // 【key_exchange_session.go#L107-L110】

```

The channel is declared at struct initialization:

```go
errCh    chan error // 【key_exchange_session.go#L74-L77】

```

## Consuming Errors at the Event Consumer Layer

The high-level `eventConsumer` in [`pkg/eventconsumer/event_consumer.go`](https://github.com/fystack/mpcium/blob/main/pkg/eventconsumer/event_consumer.go) centralizes error handling. After creating a session, it launches a goroutine that selects on both the context cancellation and the session's error channel:

```go
go func() {
    defer wg.Done()
    select {
    case <-ctxEcdsa.Done():
        successEvent.ECDSAPubKey = ecdsaSession.GetPubKeyResult()
    case err := <-ecdsaSession.ErrChan(): // 【event_consumer.go#L96-L108】
        logger.Error("ECDSA keygen session error", err)
        ec.handleKeygenSessionError(walletID, err, "ECDSA keygen session error", natMsg)
        errorChan <- err
        doneEcdsa()
    }
}()

```

When an error arrives, the consumer performs three actions:
1. Logs the error via `logger.Error`.
2. Transforms the error into a structured `KeygenResultEvent` using `handleKeygenSessionError`.
3. Forwards the error to a central `errorChan` for system-wide coordination.

### Structured Error Event Construction

The helper method `handleKeygenSessionError` converts raw errors into domain-specific events:

```go
func (ec *eventConsumer) handleKeygenSessionError(walletID string, err error,
    contextMsg string, natMsg *nats.Msg) {
    fullErrMsg := fmt.Sprintf("%s: %v", contextMsg, err)          // 【event_consumer.go#L77-L78】
    errorCode := event.GetErrorCodeFromError(err)                // 【event_consumer.go#L80】
    keygenResult := event.KeygenResultEvent{
        ResultType:  event.ResultTypeError,
        ErrorCode:   string(errorCode),
        WalletID:    walletID,
        ErrorReason: fullErrMsg,
    }                                                          // 【event_consumer.go#L81-L86】
    // marshal & enqueue omitted for brevity
}

```

This ensures that low-level failures (e.g., "failed to decrypt message") become high-level events with error codes, wallet IDs, and full context strings.

## Registry-Level Error Handling for ECDH Sessions

The `registry` in [`pkg/mpc/registry.go`](https://github.com/fystack/mpcium/blob/main/pkg/mpc/registry.go) manages peer connections and watches the ECDH session's error channel separately:

```go
for err := range r.ecdhSession.ErrChan() { // 【registry.go#L410-L413】
    logger.Error("ECDH error", err)
}

```

Unlike key generation or signing sessions, ECDH errors are treated as recoverable connection failures. The registry logs the error and relies on `triggerECDHExchange` to re-establish the secure channel, ensuring the distributed MPC network remains available despite transient cryptographic failures.

## Practical Implementation Example

The following pattern demonstrates how to integrate error handling when initiating a distributed MPC session:

```go
// 1️⃣ Create a signing session (any concrete implementation)
sess, err := node.CreateSigningSession(mpc.SessionTypeEDDSA, walletID, threshold, version, resultQueue)
if err != nil {
    // Creation errors are immediate – handle them directly
    log.Fatalf("session creation failed: %v", err)
}

// 2️⃣ Start listening for inbound messages (broadcast + P2P)
sess.ListenToIncomingMessageAsync()

// 3️⃣ Launch a goroutine that watches the session's error channel
go func() {
    for err := range sess.ErrChan() {
        // Centralised error handling
        logger.Error("signing session error", err)

        // Convert to a high‑level event (similar to handleKeygenSessionError)
        // … enqueue a failure event, clean up resources, etc.
    }
}()

// 4️⃣ Kick‑off the protocol
if err := sess.Init(tx); err != nil {
    logger.Error("failed to initialize signing session", err)
    // optional: abort early
}

```

This implementation mirrors the production flow in [`event_consumer.go`](https://github.com/fystack/mpcium/blob/main/event_consumer.go) and guarantees that any failure—whether from TSS processing, network decryption, or signature verification—is captured without blocking the main execution flow.

## Summary

- **Channel-based propagation** is the core mechanism: every MPC session owns an `ErrCh` that receives errors from internal components.
- **Exposed via `ErrChan()`**: Sessions return a read-only channel interface, allowing callers to `select` or `range` over errors without accessing internal state.
- **Centralized consumption**: The `eventConsumer` listens on `ErrChan()`, logs failures, and transforms them into structured `KeygenResultEvent` objects with error codes and wallet IDs.
- **Recovery without blocking**: The `registry` handles ECDH errors as recoverable events, logging them and triggering re-exchange rather than terminating the session.
- **Consistent wrapping**: All errors use `pkg/common/errors` for contextual wrapping, ensuring stack traces and error codes propagate correctly through the distributed system.

## Frequently Asked Questions

### How does mpcium ensure non-blocking error propagation in distributed MPC sessions?

mpcium implements a **channel-centric error model** where each session maintains a dedicated `ErrCh` field. Internal components—such as TSS message handlers in [`pkg/mpc/session.go`](https://github.com/fystack/mpcium/blob/main/pkg/mpc/session.go) or ECDH logic in [`pkg/mpc/key_exchange_session.go`](https://github.com/fystack/mpcium/blob/main/pkg/mpc/key_exchange_session.go)—push errors onto this channel using `s.ErrCh <- err`. The session exposes a read-only view via `ErrChan()`, allowing the `eventConsumer` to use Go's `select` statement to listen for errors concurrently without blocking the main protocol execution.

### What happens when an ECDH key exchange fails in mpcium?

When the `ecdhSession` encounters a signature verification or key derivation error, it sends the error to its internal `errCh` channel. The `registry` in [`pkg/mpc/registry.go`](https://github.com/fystack/mpcium/blob/main/pkg/mpc/registry.go) listens on this channel via `for err := range r.ecdhSession.ErrChan()`. Rather than terminating the system, the registry logs the error and treats it as a recoverable failure. The session can then be re-triggered via `triggerECDHExchange`, allowing the distributed MPC network to re-establish secure channels without service interruption.

### How are low-level TSS errors converted to high-level events?

Low-level failures—such as decryption errors in `receiveP2PTssMessage` or signature verification failures in `receiveBroadcastTssMessage`—are pushed onto the session's `ErrCh`. The `eventConsumer` captures these via `ecdsaSession.ErrChan()` or `eddsaSession.ErrChan()`. It then invokes `handleKeygenSessionError`, which constructs a `KeygenResultEvent` struct containing the `ResultTypeError` constant, a mapped error code from `event.GetErrorCodeFromError(err)`, the wallet ID, and the full error message. This structured event is then enqueued for downstream API or UI consumption.

### Where are error channels defined in the mpcium source code?

Error channels are defined in the core session structs. The base `session` struct in [`pkg/mpc/session.go`](https://github.com/fystack/mpcium/blob/main/pkg/mpc/session.go) declares `ErrCh chan error` at line 61. The ECDH-specific session in [`pkg/mpc/key_exchange_session.go`](https://github.com/fystack/mpcium/blob/main/pkg/mpc/key_exchange_session.go) declares its own `errCh chan error` within the `ecdhSession` struct at lines 74-77. Both channels are initialized during session construction and exposed via public methods (`ErrChan()`) to enable external monitoring.