Skip to content

rpc: RPC handlers return codes.Unknown for predictable validation/lookup errors #2086

@darioAnongba

Description

@darioAnongba

Problem

Most tapd RPC handlers return plain fmt.Errorf / wrapped errors rather than status.Error(codes.X, ...). gRPC then surfaces these as codes.Unknown, leaving clients with no programmatic way to distinguish bad input from a missing resource from an internal failure — the error string is the only signal.

Caught while wiring typed errors through tap-sdk's REST transport (lightninglabs/tap-sdk#80). REST and gRPC now agree on the code, but that agreement is trivially Unknown == Unknown for every case we tried:

RPC Input Actual Expected
DecodeAddr "not-a-tap-addr" Unknown: unable to decode addr: address: invalid bech32m string InvalidArgument
DecodeProof [0x00, 0x01] Unknown: invalid raw proof, could not identify decoding format InvalidArgument
ExportProof zero asset_id + zero script_key Unknown: invalid script key: ... x coordinate 0x00..00 is not on the secp256k1 curve InvalidArgument (bad key) or NotFound (missing proof)

Why it matters

SDK callers (Go, tap-sdk REST, any grpc-gateway client) want to branch on the error type — retry on Unavailable, surface InvalidArgument to the user, treat NotFound as a non-error, etc. Today they have to string-match, which is fragile and locale-sensitive.

Proposal

Audit the RPC surface and replace plain fmt.Errorf returns in handler paths with status.Errorf(codes.X, ...). Obvious mappings:

  • Argument parsing/decoding (bech32, proof bytes, keys, hex/base64) → codes.InvalidArgument
  • Explicit "not in DB" lookups → codes.NotFound
  • Permission/macaroon → codes.PermissionDenied
  • Shutdown / context canceled → codes.Canceled / codes.Unavailable
  • Genuine internals (DB transaction failure, etc.) can keep the default Unknown/Internal

A stricter pattern is to introduce a small helper that wraps known sentinel errors into status codes at the RPC boundary, keeping handler internals free of grpc imports.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    🆕 New

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions