diff --git a/config.yml b/config.yml new file mode 100644 index 00000000..31f880e9 --- /dev/null +++ b/config.yml @@ -0,0 +1,3 @@ +networks: + mainnet: https://rpc.cheqd.net:443 + testnet: https://api.testnet.cheqd.network:443 \ No newline at end of file diff --git a/main.go b/main.go index 6c767b9a..6b6b6443 100644 --- a/main.go +++ b/main.go @@ -2,19 +2,22 @@ package main import ( "net/http" + "os" "github.com/cheqd/cheqd-did-resolver/services" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" + "gopkg.in/yaml.v2" //"net/url" "strings" ) -func main() { - //setup - requestService := services.NewRequestService() +type Config struct { + Networks map[string]string `yaml:"networks"` +} +func main() { // Echo instance e := echo.New() @@ -22,6 +25,20 @@ func main() { e.Use(middleware.Logger()) e.Use(middleware.Recover()) + //setup + e.StdLogger.Println("get config") + config, err := getConfig("config.yml") + e.StdLogger.Println(config) + if err != nil { + e.Logger.Fatal(err) + } + ledgerService := services.NewLedgerService() + for network, url := range config.Networks { + e.StdLogger.Println(network) + ledgerService.RegisterLedger(network, url) + } + requestService := services.NewRequestService(ledgerService) + // Routes e.GET("/identifier/:did", func(c echo.Context) error { did := c.Param("did") @@ -32,11 +49,13 @@ func main() { //} accept := strings.Split(c.Request().Header.Get("accept"), ";")[0] resolutionOption := map[string]string{"Accept": accept} - responseBody := requestService.ProcessDIDRequest(did, resolutionOption) - if err != nil { - + e.StdLogger.Println("get did") + responseBody, err := requestService.ProcessDIDRequest(did, resolutionOption) + status := http.StatusOK + if err != "" { + status = http.StatusBadRequest } - return c.String(http.StatusOK, responseBody) + return c.String(status, responseBody) //opt := resolver.ResolutionOption{Accept: accept} //rr := resolver.ResolveRepresentation(conn, did, opt) // @@ -57,7 +76,19 @@ func main() { e.Logger.Fatal(e.Start(":1313")) } -// Handler -func hello(c echo.Context) error { - return c.String(http.StatusOK, "Hello, World!") +func getConfig(configFileName string) (Config, error) { + f, err := os.Open(configFileName) + if err != nil { + return Config{}, err + } + defer f.Close() + + var cfg Config + decoder := yaml.NewDecoder(f) + err = decoder.Decode(&cfg) + if err != nil { + return Config{}, err + } + + return cfg, nil } diff --git a/services/diddoc_service_test.go b/services/diddoc_service_test.go new file mode 100644 index 00000000..ab593719 --- /dev/null +++ b/services/diddoc_service_test.go @@ -0,0 +1,35 @@ +package services + +import ( + "fmt" + "testing" + + cheqd "github.com/cheqd/cheqd-node/x/cheqd/types" + "github.com/stretchr/testify/require" + // jsonpb Marshaller is deprecated, but is needed because there's only one way to proto + // marshal in combination with our proto generator version + //nolint +) + +func TestMarshallDID(t *testing.T) { + didDocService := DIDDocService{} + verificationMethod1 := cheqd.VerificationMethod{ + Id: "did:cheqd:mainnet:N22KY2Dyvmuu2PyyqSFKue#verkey", + Type: "Ed25519VerificationKey2020", + Controller: "did:cheqd:mainnet:N22KY2Dyvmuu2PyyqSFKue", + PublicKeyMultibase: "zAKJP3f7BD6W4iWEQ9jwndVTCBq8ua2Utt8EEjJ6Vxsf", + } + didDoc := cheqd.Did{ + Context: []string{"test"}, + Id: "did:cheqd:mainnet:N22KY2Dyvmuu2PyyqSFKue", + VerificationMethod: []*cheqd.VerificationMethod{&verificationMethod1}, + } + + expectedDID := "{\"@context\":[\"test\"],\"id\":\"did:cheqd:mainnet:N22KY2Dyvmuu2PyyqSFKue\",\"verificationMethod\":[{\"controller\":\"did:cheqd:mainnet:N22KY2Dyvmuu2PyyqSFKue\",\"id\":\"did:cheqd:mainnet:N22KY2Dyvmuu2PyyqSFKue#verkey\",\"publicKeyMultibase\":\"zAKJP3f7BD6W4iWEQ9jwndVTCBq8ua2Utt8EEjJ6Vxsf\",\"type\":\"Ed25519VerificationKey2020\"}]}" + + jsonDID, err := didDocService.MarshallDID(didDoc) + + fmt.Println(jsonDID) + require.EqualValues(t, jsonDID, expectedDID) + require.Empty(t, err) +} diff --git a/services/ledger_service.go b/services/ledger_service.go index 15d4eaaa..c26cba83 100644 --- a/services/ledger_service.go +++ b/services/ledger_service.go @@ -1,26 +1,87 @@ package services import ( + "context" "errors" + "flag" + "strings" + "time" cheqd "github.com/cheqd/cheqd-node/x/cheqd/types" + "google.golang.org/grpc" ) type LedgerService struct { ledgers map[string]string // namespace -> url } -func (LedgerService) QueryDIDDoc(did string) (cheqd.Did, cheqd.Metadata, error) { - return cheqd.Did{}, cheqd.Metadata{}, nil +func NewLedgerService() LedgerService { + ls := LedgerService{} + ls.ledgers = make(map[string]string) + return ls +} + +func (ls LedgerService) QueryDIDDoc(did string) (cheqd.Did, cheqd.Metadata, error) { + serverAddr := ls.ledgers[getNamespace(did)] + println(serverAddr) + conn, err := openGRPCConnection(serverAddr) + + if err != nil { + return cheqd.Did{}, cheqd.Metadata{}, err + } + + qc := cheqd.NewQueryClient(conn) + defer conn.Close() + + didDocResponse, err := qc.Did(context.Background(), &cheqd.QueryGetDidRequest{Id: did}) + + println(didDocResponse) + return *didDocResponse.Did, *didDocResponse.Metadata, err } func (ls *LedgerService) RegisterLedger(namespace string, url string) error { - if !(namespace == "") { + println("RegisterLedger") + + if namespace == "" { + println("Namespace cannot be empty") return errors.New("Namespace cannot be empty") } - if !(url == "") { + if url == "" { + println("Ledger node url cannot be empty") return errors.New("Ledger node url cannot be empty") } - ls.ledgers[namespace] = url + ls.ledgers[namespace] = *flag.String("grpc-server-address-"+namespace, url, + "The target grpc server address in the format of host:port") + + println("RegisterLedger end") + return nil } + +func openGRPCConnection(addr string) (conn *grpc.ClientConn, err error) { + opts := []grpc.DialOption{ + grpc.WithInsecure(), + grpc.WithBlock(), + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel = context.WithDeadline(ctx, <-time.After(5*time.Second)) + defer cancel() + // if cancel != nil { + // cancel() + // println("openGRPCConnection: cancel") + // return nil, errors.New("gRPC connection haven't opened") + // } + conn, err = grpc.DialContext(ctx, addr, opts...) + if err != nil { + println("openGRPCConnection: context failed") + println(err.Error()) + return nil, err + } + println("openGRPCConnection: opened") + return conn, nil +} + +func getNamespace(did string) string { + return strings.SplitN(did, ":", 4)[2] +} diff --git a/services/request_service.go b/services/request_service.go index 0c413b3d..16fa7a60 100644 --- a/services/request_service.go +++ b/services/request_service.go @@ -14,31 +14,33 @@ type RequestService struct { didDocService DIDDocService } -func NewRequestService() RequestService { +func NewRequestService(ledgerService LedgerService) RequestService { return RequestService{ - ledgerService: LedgerService{}, + ledgerService: ledgerService, didDocService: DIDDocService{}, } } -func (rs RequestService) ProcessDIDRequest(did string, params map[string]string) string { +func (rs RequestService) ProcessDIDRequest(did string, params map[string]string) (string, string) { didResolution := rs.resolve(did, types.ResolutionOption{Accept: params["Accept"]}) resolutionMetadata, err1 := json.Marshal(didResolution.ResolutionMetadata) didDoc, err2 := rs.didDocService.MarshallDID(didResolution.Did) metadata, err3 := rs.didDocService.MarshallProto(&didResolution.Metadata) if err1 != nil || err2 != nil || err3 != nil { - resolutionMetadata := types.NewResolutionMetadata(resolutionOptions.Accept, + resolutionMetadataProto := types.NewResolutionMetadata(params["Accept"], types.ResolutionRepresentationNotSupported) - resolutionMetadataJson, _ := json.Marshal(didResolution.ResolutionMetadata) - return createJsonResolution("null", "null", string(resolutionMetadataJson)) + resolutionMetadataJson, _ := json.Marshal(resolutionMetadataProto) + return createJsonResolution("null", "null", string(resolutionMetadataJson)), + resolutionMetadataProto.ResolutionError } - if didResolution.ResolutionMetadata.ResolutionError != nil { - return createJsonResolution("null", "null", string(resolutionMetadata)) + if didResolution.ResolutionMetadata.ResolutionError != "" { + return createJsonResolution("null", "null", string(resolutionMetadata)), + didResolution.ResolutionMetadata.ResolutionError } - return createJsonResolution(didDoc, metadata, string(resolutionMetadata)) + return createJsonResolution(didDoc, metadata, string(resolutionMetadata)), "" } @@ -46,10 +48,9 @@ func (rs RequestService) ProcessDIDRequest(did string, params map[string]string) func (rs RequestService) resolve(did string, resolutionOptions types.ResolutionOption) types.DidResolution { didDoc, metadata, err := rs.ledgerService.QueryDIDDoc(did) didResolutionMetadata := types.NewResolutionMetadata(resolutionOptions.Accept, "") - result := types.DidResolution{didDoc, metadata, didResolutionMetadata} if err != nil { didResolutionMetadata.ResolutionError = types.ResolutionNotFound - return types.DidResolution{nil, nil, didResolutionMetadata} + return types.DidResolution{ResolutionMetadata: didResolutionMetadata} } return types.DidResolution{didDoc, metadata, didResolutionMetadata} } @@ -63,7 +64,7 @@ func (rs RequestService) resolve(did string, resolutionOptions types.ResolutionO // return dereferencingMetadata, contentStream, contentMetadata // } -func createJsonResolution(didDoc string, metadata string, resolutionMetadata string) { +func createJsonResolution(didDoc string, metadata string, resolutionMetadata string) string { return fmt.Sprintf("{\"didDocument\" : %s,\"didDocumentMetadata\" : %s,\"didResolutionMetadata\" : %s}", - didDoc, metadata, resolutionMetadata), "" + didDoc, metadata, resolutionMetadata) }