Skip to content
Open
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
6 changes: 2 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:

- name: Install ginkgo
run: |
go install github.com/onsi/ginkgo/v2/ginkgo@latest
go install github.com/onsi/ginkgo/v2/ginkgo

- name: Run Golang unit tests
working-directory: ./tests/unit/
Expand Down Expand Up @@ -53,8 +53,7 @@ jobs:
cache: true

- name: Install ginkgo
working-directory: ./..
run: go install github.com/onsi/ginkgo/v2/ginkgo@latest
run: go install github.com/onsi/ginkgo/v2/ginkgo

- name: Run Ginkgo integration tests
working-directory: ./tests/integration/rest
Expand All @@ -63,7 +62,6 @@ jobs:

- name: Show logs
if: failure()
working-directory: ./docker/localnet
run: docker compose -f tests/docker-compose-testing.yml logs --tail --follow

- name: Upload integration tests result
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,11 @@ To configure the resolver, modify the values under the `environment` section of
5. **`TESTNET_ENDPOINT_FALLBACK`** : Fallback testnet endpoint with the same format as `TESTNET_ENDPOINT`. Used when primary endpoint is unavailable.
6. **`RESOLVER_LISTENER`**`: A string with address and port where the resolver listens for requests from clients.
7. **`LOG_LEVEL`**: `debug`/`warn`/`info`/`error` - to define the application log level.
8. **`GRPC_MAX_RECV_MSG_SIZE`**: Maximum gRPC response size (in bytes) the resolver will accept from a ledger node. Default is `16777216` (16MB). Raise this if a DID's resource collection grows large enough to exceed the default.

#### gRPC Endpoints used by DID Resolver

Our DID Resolver uses the [Cosmos gRPC endpoint](https://docs.cosmos.network/main/core/grpc_rest) from `cheqd-node` to fetch data. Typically, this would be running on port `9090` on a `cheqd-node` instance.
Our DID Resolver uses the [Cosmos gRPC endpoint](https://docs.cosmos.network/sdk/latest/learn/concepts/cli-grpc-rest) from `cheqd-node` to fetch data. Typically, this would be running on port `9090` on a `cheqd-node` instance.

You can either use [public gRPC endpoints for the cheqd network](https://cosmos.directory/cheqd/nodes) (such as the default ones mentioned above), or point it to your own `cheqd-node` instance by enabling gRPC in the `app.toml` configuration file on a node:

Expand Down
4 changes: 4 additions & 0 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,9 @@ services:
# Logging level
LOG_LEVEL: "warn"

# Maximum gRPC response size the resolver will accept from a ledger node, in bytes.
# Raise this if a DID's resource collection grows large enough to exceed the default.
GRPC_MAX_RECV_MSG_SIZE: "16777216" # 16MB

# Interface and port to listen on in the container
RESOLVER_LISTENER: "0.0.0.0:8080"
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func serve() {
endpointManager := services.NewEndpointManager(config)

// Services
ledgerService := services.NewLedgerService(endpointManager)
ledgerService := services.NewLedgerService(endpointManager, config.GRPCMaxRecvMsgSize)
didService := services.NewDIDDocService(types.DID_METHOD, ledgerService)
resourceService := services.NewResourceService(types.DID_METHOD, ledgerService)

Expand Down
2 changes: 1 addition & 1 deletion services/endpoint_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ func (em *EndpointManager) markEndpointUnhealthy(endpointHealth *EndpointHealth)

// performSingleHealthCheck performs a simple, fast health check
func (em *EndpointManager) performSingleHealthCheck(endpoint *types.Endpoint) bool {
conn, err := openGRPCConnectionWithTimeout(endpoint.URL, endpoint.UseTls, em.healthTimeout)
conn, err := openGRPCConnectionWithTimeout(endpoint.URL, endpoint.UseTls, em.healthTimeout, em.config.GRPCMaxRecvMsgSize)
if err != nil {
log.Debug().Err(err).Msgf("Health check failed for endpoint %s: connection failed", endpoint.URL)
return false
Expand Down
35 changes: 26 additions & 9 deletions services/ledger_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import (
"github.com/cheqd/did-resolver/utils"
"github.com/rs/zerolog/log"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/status"
)

const (
Expand All @@ -34,12 +36,14 @@ type LedgerServiceI interface {
type LedgerService struct {
ledgers map[string]types.Network // namespace -> endpoint with configs
endpointManager *EndpointManager
maxRecvMsgSize int
}

func NewLedgerService(endpointManager *EndpointManager) LedgerService {
func NewLedgerService(endpointManager *EndpointManager, maxRecvMsgSize int) LedgerService {
ls := LedgerService{}
ls.ledgers = make(map[string]types.Network)
ls.endpointManager = endpointManager
ls.maxRecvMsgSize = maxRecvMsgSize

return ls
}
Expand Down Expand Up @@ -110,15 +114,15 @@ func (ls LedgerService) QueryDIDDoc(did string, version string) (*didTypes.DidDo
if version == "" {
didDocResponse, grpcErr := client.DidDoc(context.Background(), &didTypes.QueryDidDocRequest{Id: did})
if grpcErr != nil {
return nil, types.NewNotFoundError(did, types.JSON, grpcErr, false)
return nil, mapGrpcError(did, grpcErr, false)
}

return didDocResponse.Value, nil
}

didDocResponse, grpcErr := client.DidDocVersion(context.Background(), &didTypes.QueryDidDocVersionRequest{Id: did, Version: version})
if grpcErr != nil {
return nil, types.NewNotFoundError(did, types.JSON, grpcErr, false)
return nil, mapGrpcError(did, grpcErr, false)
}

return didDocResponse.Value, nil
Expand All @@ -144,7 +148,7 @@ func (ls LedgerService) QueryAllDidDocVersionsMetadata(did string) ([]*didTypes.

didDocResponse, grpcErr := client.AllDidDocVersionsMetadata(context.Background(), &didTypes.QueryAllDidDocVersionsMetadataRequest{Id: did})
if grpcErr != nil {
return nil, types.NewNotFoundError(did, types.JSON, grpcErr, false)
return nil, mapGrpcError(did, grpcErr, false)
}

return didDocResponse.Versions, nil
Expand All @@ -171,7 +175,7 @@ func (ls LedgerService) QueryResource(did string, resourceId string) (*resourceT
resourceResponse, grpcErr := client.Resource(context.Background(), &resourceTypes.QueryResourceRequest{CollectionId: collectionId, Id: resourceId})
if grpcErr != nil {
log.Error().Msgf("Resource not found %s", grpcErr.Error())
return nil, types.NewNotFoundError(did, types.JSON, grpcErr, true)
return nil, mapGrpcError(did, grpcErr, true)
}

return resourceResponse.Resource, nil
Expand All @@ -197,7 +201,7 @@ func (ls LedgerService) QueryCollectionResources(did string) ([]*resourceTypes.M
client := resourceTypes.NewQueryClient(conn)
resourceResponse, grpcErr := client.CollectionResources(context.Background(), &resourceTypes.QueryCollectionResourcesRequest{CollectionId: collectionId})
if grpcErr != nil {
return nil, types.NewNotFoundError(did, types.JSON, grpcErr, false)
return nil, mapGrpcError(did, grpcErr, false)
}

return resourceResponse.Resources, nil
Expand Down Expand Up @@ -226,7 +230,7 @@ func (ls LedgerService) openGRPCConnection(endpoint types.Network) (conn *grpc.C
}

// Use shared utility function to eliminate code duplication
return openGRPCConnectionWithTimeout(endpoint.Endpoints[0].URL, endpoint.Endpoints[0].UseTls, endpoint.Endpoints[0].Timeout)
return openGRPCConnectionWithTimeout(endpoint.Endpoints[0].URL, endpoint.Endpoints[0].UseTls, endpoint.Endpoints[0].Timeout, ls.maxRecvMsgSize)
}

func mustCloseGRPCConnection(conn *grpc.ClientConn) {
Expand Down Expand Up @@ -273,14 +277,16 @@ func (ls LedgerService) getOtherEndpoint(namespace string, currentNetwork *types
}

// openGRPCConnectionWithTimeout creates a gRPC connection with timeout
func openGRPCConnectionWithTimeout(endpoint string, useTls bool, timeout time.Duration) (*grpc.ClientConn, error) {
func openGRPCConnectionWithTimeout(endpoint string, useTls bool, timeout time.Duration, maxRecvMsgSize int) (*grpc.ClientConn, error) {
// Dial options (credentials only). Connection readiness is verified by the subsequent RPC's context timeout.
cred := grpc.WithTransportCredentials(insecure.NewCredentials())
if useTls {
cred = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))
}

conn, err := grpc.NewClient(endpoint, cred)
maxRecv := grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(maxRecvMsgSize))

conn, err := grpc.NewClient(endpoint, cred, maxRecv)
if err != nil {
log.Error().Err(err).Msgf("openGRPCConnection: connection failed")
return nil, err
Expand All @@ -289,3 +295,14 @@ func openGRPCConnectionWithTimeout(endpoint string, useTls bool, timeout time.Du
log.Info().Msg("openGRPCConnection: opened")
return conn, nil
}

// mapGrpcError distinguishes a client-side message-size overflow (ResourceExhausted) —
// which can only mean "the ledger response didn't fit," never "the DID doesn't exist" —
// from every other gRPC error, which keeps existing notFound behaviour unchanged.
func mapGrpcError(did string, grpcErr error, isDereferencing bool) *types.IdentityError {
if grpcStatus, ok := status.FromError(grpcErr); ok && grpcStatus.Code() == codes.ResourceExhausted {
log.Error().Err(grpcErr).Msgf("gRPC response exceeded max message size for DID: %s", did)
return types.NewInternalError(did, types.JSON, grpcErr, isDereferencing)
}
return types.NewNotFoundError(did, types.JSON, grpcErr, isDereferencing)
}
2 changes: 2 additions & 0 deletions types/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,15 @@ type RawConfig struct {
EnableFallbackEndpoints bool `mapstructure:"ENABLE_FALLBACK_ENDPOINTS"`
ResolverListener string `mapstructure:"RESOLVER_LISTENER"`
LogLevel string `mapstructure:"LOG_LEVEL"`
GRPCMaxRecvMsgSize int `mapstructure:"GRPC_MAX_RECV_MSG_SIZE"`
}

type Config struct {
Networks []Network
EnableFallbackEndpoints bool
ResolverListener string
LogLevel string
GRPCMaxRecvMsgSize int
}

func (c *Config) MarshalJson() (string, error) {
Expand Down
7 changes: 7 additions & 0 deletions types/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ func LoadConfig() (Config, error) {
viper.SetDefault("ENABLE_FALLBACK_ENDPOINTS", false)
viper.SetDefault("LOG_LEVEL", "")
viper.SetDefault("RESOLVER_LISTENER", "")
viper.SetDefault("GRPC_MAX_RECV_MSG_SIZE", 16*1024*1024) // 16MB
viper.AutomaticEnv()

rawConf := &RawConfig{}
Expand All @@ -95,6 +96,10 @@ func MustLoadConfig() Config {
}

func NewConfig(rawConfig RawConfig) (Config, error) {
if rawConfig.GRPCMaxRecvMsgSize <= 0 {
return Config{}, fmt.Errorf("GRPC_MAX_RECV_MSG_SIZE must be positive, got %d", rawConfig.GRPCMaxRecvMsgSize)
}

// Parse primary endpoints
mainnetPrimary, err := ParseGRPCEndpoint(rawConfig.MainnetEndpoint)
if err != nil {
Expand Down Expand Up @@ -142,6 +147,7 @@ func NewConfig(rawConfig RawConfig) (Config, error) {
EnableFallbackEndpoints: rawConfig.EnableFallbackEndpoints,
ResolverListener: rawConfig.ResolverListener,
LogLevel: rawConfig.LogLevel,
GRPCMaxRecvMsgSize: rawConfig.GRPCMaxRecvMsgSize,
}, nil
}

Expand Down Expand Up @@ -194,6 +200,7 @@ func NewConfig(rawConfig RawConfig) (Config, error) {
EnableFallbackEndpoints: rawConfig.EnableFallbackEndpoints,
ResolverListener: rawConfig.ResolverListener,
LogLevel: rawConfig.LogLevel,
GRPCMaxRecvMsgSize: rawConfig.GRPCMaxRecvMsgSize,
}, nil
}

Expand Down
Loading