Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: CORD Blockchain Integration for Identity, Schema, and Credential Anchoring in Sunbird RC #354

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
85cba7e
feat: anchor schema and verifiable credentials (VC) to Cord
ashwin275 Oct 3, 2024
2f1e663
fix: import AnchorCordUtilsServices service
ashwin275 Oct 7, 2024
80d2cec
refactor:remove commented codes
ashwin275 Oct 7, 2024
560601d
feat:Put Anchor to cord Default : false
ashwin275 Oct 7, 2024
6a28159
refact:anchor schema
ashwin275 Oct 9, 2024
ef24ec5
add column blockchainStatus:credential schema
ashwin275 Oct 9, 2024
ca99d4a
add block chains status field: credential
ashwin275 Oct 10, 2024
cb25e43
update blockchainstatus
ashwin275 Oct 10, 2024
ea6b285
feat:anchor did to cord block chain
ashwin275 Oct 23, 2024
dc1cae0
Add necessary environment variables to identity Docker Compose for CO…
ashwin275 Nov 8, 2024
654471c
test: add AnchorCordService to test file-didservice
ashwin275 Nov 18, 2024
b18c629
test: add BlockchainStatus field in to specs.ts
ashwin275 Nov 18, 2024
1c4cacf
test:add AnchorCordUtilsServices in credentials service specs files
ashwin275 Nov 18, 2024
aa5b102
Identity-service:Interface implemented
ashwin275 Dec 13, 2024
391e05e
Schema-service:Interface Implemented
ashwin275 Dec 13, 2024
a66c279
Credential-service:Interface Implemented
ashwin275 Dec 13, 2024
7776e56
remove cord.utils.service
ashwin275 Dec 16, 2024
a562302
Identiy Service : added test cases
ashwin275 Dec 16, 2024
e25f62c
credential schema - Test case added
ashwin275 Dec 16, 2024
6166586
Credential-service:add test case for anchor credential to cord
ashwin275 Dec 18, 2024
bd47823
feat/anchor to cord : make default as false
ashwin275 Jan 22, 2025
59a0d63
Feat/anchor to cord : import necessary modules , providers to spec files
ashwin275 Jan 22, 2025
91cd9d9
feat/anchor_to_cord : Credential Schema : import necessary module to …
ashwin275 Jan 22, 2025
def9ab8
feat/anchor_to_cord : Credential Service : import necessary module t…
ashwin275 Jan 22, 2025
2601eb7
feat/anchor_to_cord : add block chain type in env
ashwin275 Jan 23, 2025
d555fde
feat(anchor-to-cord): set default null for blockchain status instead …
ashwin275 Jan 23, 2025
08d46f1
feat(anchor-to-cord): Credentials-Services -set default null for blo…
ashwin275 Jan 23, 2025
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
22 changes: 22 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,25 @@ SCHEMA_BASE_URL=http://credential-schema:3333
CREDENTIAL_SERVICE_BASE_URL=https://example.com/credentials
JWKS_URI=
ENABLE_AUTH=false



# Flag to enable/disable anchoring to blockchain
ANCHOR_TO_BLOCKCHAIN = false
BLOCKCHAIN_TYPE = CORD


# Anchor to cord block chain
# Base URL for Issuer Agent
# This is the service responsible for issuing credentials on Cord BlockChain
# Example: https://<ISSUER_AGENT_IP>/api/v1
ISSUER_AGENT_BASE_URL=https://<ISSUER_AGENT_IP>/api/v1

# Base URL for Verification Middleware
# This service is responsible for verifying credentials on Cord BlockChain
# Example: https://<VERIFICATION_MIDDLEWARE_IP>/api/v1/verify
VERIFICATION_MIDDLEWARE_BASE_URL=https://<VERIFICATION_MIDDLEWARE_IP>/api/v1/verify

# Additional Resources:
# - For more details on Issuer Agent, visit: https://github.com/dhiway/issuer-agent
# - For more details on Verification Middleware, visit: https://github.com/dhiway/verification-middleware
10 changes: 10 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,9 @@ services:
- JWKS_URI=${JWKS_URI}
- ENABLE_AUTH=${ENABLE_AUTH}
- WEB_DID_BASE_URL=${WEB_DID_BASE_URL}
- ANCHOR_TO_BLOCKCHAIN=${ANCHOR_TO_BLOCKCHAIN}
- BLOCKCHAIN_TYPE=${BLOCKCHAIN_TYPE}
- ISSUER_AGENT_BASE_URL=${ISSUER_AGENT_BASE_URL}
healthcheck:
test:
[ "CMD-SHELL", "curl -f http://localhost:3332/health || exit 1" ]
Expand All @@ -263,6 +266,9 @@ services:
- IDENTITY_BASE_URL=${IDENTITY_BASE_URL}
- JWKS_URI=${JWKS_URI}
- ENABLE_AUTH=${ENABLE_AUTH}
- ANCHOR_TO_BLOCKCHAIN=${ANCHOR_TO_BLOCKCHAIN}
- BLOCKCHAIN_TYPE=${BLOCKCHAIN_TYPE}
- ISSUER_AGENT_BASE_URL=${ISSUER_AGENT_BASE_URL}
healthcheck:
test:
[ "CMD-SHELL", "curl -f http://localhost:3333/health || exit 1" ]
Expand All @@ -288,6 +294,10 @@ services:
- JWKS_URI=${JWKS_URI}
- ENABLE_AUTH=${ENABLE_AUTH}
- QR_TYPE=${QR_TYPE}
- ANCHOR_TO_BLOCKCHAIN=${ANCHOR_TO_BLOCKCHAIN}
- BLOCKCHAIN_TYPE=${BLOCKCHAIN_TYPE}
- ISSUER_AGENT_BASE_URL=${ISSUER_AGENT_BASE_URL}
- VERIFICATION_MIDDLEWARE_BASE_URL=${VERIFICATION_MIDDLEWARE_BASE_URL}
healthcheck:
test:
[ "CMD-SHELL", "curl -f http://localhost:3000/health || exit 1" ]
Expand Down
13 changes: 13 additions & 0 deletions services/credential-schema/.env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,16 @@ IDENTITY_BASE_URL= # URL of the identity service to facilitate DID creation
# Service VARS
PORT=3000
SCHEMA_BASE_URL=


# Flag to enable/disable anchoring to blockchain
ANCHOR_TO_BLOCKCHAIN = false
BLOCKCHAIN_TYPE = CORD

# Base URL for Issuer Agent
# This is the service responsible for issuing credentials on CORD BLOCKCHAIN
# Example: https://<ISSUER_AGENT_IP>/api/v1
ISSUER_AGENT_BASE_URL=https://<ISSUER_AGENT_IP>/api/v1

# Additional Resources:
# - For more details on Issuer Agent, visit: https://github.com/dhiway/issuer-agent
3 changes: 3 additions & 0 deletions services/credential-schema/docker-compose-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ services:
DATABASE_URL: postgres://postgres:postgres@db-test:5432/postgres
IDENTITY_BASE_URL: "http://identity-service:3332"
ENABLE_AUTH: "false"
ANCHOR_TO_BLOCKCHAIN: "${ANCHOR_TO_BLOCKCHAIN}"
BLOCKCHAIN_TYPE: "${BLOCKCHAIN_TYPE}"
ISSUER_AGENT_BASE_URL: "${ISSUER_AGENT_BASE_URL}"
networks:
test:
rcw-test:
Expand Down
3 changes: 3 additions & 0 deletions services/credential-schema/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ services:
DATABASE_URL: postgres://postgres:postgres@db:5432/postgres
IDENTITY_BASE_URL: "http://identity-service:3332"
ENABLE_AUTH: "false"
ANCHOR_TO_BLOCKCHAIN: "${ANCHOR_TO_BLOCKCHAIN}"
BLOCKCHAIN_TYPE: "${BLOCKCHAIN_TYPE}"
ISSUER_AGENT_BASE_URL: "${ISSUER_AGENT_BASE_URL}"
networks:
rcw-test:
default:
Expand Down
4 changes: 2 additions & 2 deletions services/credential-schema/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"@nestjs/platform-fastify": "^9.2.1",
"@nestjs/swagger": "^6.1.4",
"@nestjs/terminus": "^10.0.1",
"@prisma/client": "4.8.1",
"@prisma/client": "6.2.1",
"ajv": "^8.11.2",
"axios": "^1.4.0",
"cache-manager": "^5.1.4",
Expand All @@ -46,7 +46,7 @@
"passport": "^0.6.0",
"passport-http": "^0.3.0",
"passport-jwt": "^4.0.1",
"prisma": "4.8.1",
"prisma": "6.2.1",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- CreateEnum
CREATE TYPE "BlockchainStatus" AS ENUM ('PENDING', 'ANCHORED', 'FAILED');

-- AlterTable
ALTER TABLE "VerifiableCredentialSchema" ADD COLUMN "blockchainStatus" "BlockchainStatus";
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
# It should be added in your version-control system (e.g., Git)
provider = "postgresql"
7 changes: 7 additions & 0 deletions services/credential-schema/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ enum SchemaStatus {
REVOKED
}

enum BlockchainStatus {
PENDING
ANCHORED
FAILED
}

model VerifiableCredentialSchema {
id String
type String
Expand All @@ -35,6 +41,7 @@ model VerifiableCredentialSchema {
tags String[]
status SchemaStatus @default(DRAFT)
deprecatedId String?
blockchainStatus BlockchainStatus?

@@id([id, version])
@@index([type], type: Hash)
Expand Down
4 changes: 4 additions & 0 deletions services/credential-schema/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { PrismaHealthIndicator } from './utils/prisma.health';
import { PrismaClient } from '@prisma/client';
import { APP_GUARD } from '@nestjs/core';
import { AuthGuard } from './auth/auth.guard';
import { AnchorCordService } from './schema/implementations/anchor-cord.service';
import { BlockchainAnchorFactory } from './schema/factories/blockchain-anchor.factory';

@Module({
imports: [
Expand All @@ -32,6 +34,8 @@ import { AuthGuard } from './auth/auth.guard';
UtilsService,
PrismaHealthIndicator,
PrismaClient,
AnchorCordService,
BlockchainAnchorFactory,
{
provide: APP_GUARD,
useClass: AuthGuard,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { ValidateTemplateService } from './validate-template.service';
import { SchemaService } from '../schema/schema.service';
import { UtilsService } from '../utils/utils.service';
import { PrismaClient } from '@prisma/client';

import { BlockchainAnchorFactory } from '../schema/factories/blockchain-anchor.factory';
import { AnchorCordService } from '../schema/implementations/anchor-cord.service';
describe('RenderingTemplatesController', () => {
let controller: RenderingTemplatesController;

Expand All @@ -20,6 +21,8 @@ describe('RenderingTemplatesController', () => {
ValidateTemplateService,
SchemaService,
UtilsService,
BlockchainAnchorFactory,
AnchorCordService
],
}).compile();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { SchemaService } from '../schema/schema.service';
import { HttpModule } from '@nestjs/axios';
import { UtilsService } from '../utils/utils.service';
import { PrismaClient } from '@prisma/client';
import { BlockchainAnchorFactory } from 'src/schema/factories/blockchain-anchor.factory';
import { AnchorCordService } from 'src/schema/implementations/anchor-cord.service';

@Module({
imports: [HttpModule],
Expand All @@ -15,6 +17,8 @@ import { PrismaClient } from '@prisma/client';
ValidateTemplateService,
SchemaService,
UtilsService,
BlockchainAnchorFactory,
AnchorCordService,
],
controllers: [RenderingTemplatesController],
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ import {
generateTestDIDBody,
} from '../schema/schema.fixtures';

import { BlockchainAnchorFactory } from '../schema/factories/blockchain-anchor.factory';
import { AnchorCordService } from '../schema/implementations/anchor-cord.service';
describe('RenderingTemplatesService', () => {
let service: RenderingTemplatesService;
let schemaService: SchemaService;
let utilsService: UtilsService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [HttpModule],
Expand All @@ -25,6 +27,8 @@ describe('RenderingTemplatesService', () => {
ValidateTemplateService,
SchemaService,
UtilsService,
BlockchainAnchorFactory,
AnchorCordService
],
}).compile();

Expand Down
114 changes: 114 additions & 0 deletions services/credential-schema/src/schema/anchor-cord-service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { Test, TestingModule } from '@nestjs/testing';
import { SchemaService } from './schema.service';
import { BlockchainAnchorFactory } from './factories/blockchain-anchor.factory';
import { PrismaClient } from '@prisma/client';
import { CreateCredentialDTO } from './dto/create-credentials.dto';
import { UtilsService } from '../utils/utils.service';
import { generateCredentialSchemaTestBody } from './schema.fixtures';
import { BadRequestException, InternalServerErrorException } from '@nestjs/common';

describe('SchemaService - createCredentialSchema', () => {
let service: SchemaService;
let blockchainFactory: BlockchainAnchorFactory;
let utilsService: UtilsService;

const mockBlockchainService = {
anchorSchema: jest.fn(),
};

const mockPrismaService = {
verifiableCredentialSchema: {
create: jest.fn(),
findMany: jest.fn(),
},
};

const mockUtilsService = {
generateDID: jest.fn(),
};

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
SchemaService,
{
provide: BlockchainAnchorFactory,
useValue: {
getAnchorService: jest.fn(() => mockBlockchainService),
},
},
{
provide: PrismaClient,
useValue: mockPrismaService,
},
{
provide: UtilsService,
useValue: mockUtilsService,
},
],
}).compile();

service = module.get<SchemaService>(SchemaService);
blockchainFactory = module.get<BlockchainAnchorFactory>(BlockchainAnchorFactory);
utilsService = module.get<UtilsService>(UtilsService);
});

afterEach(() => {
jest.clearAllMocks(); // Clear mocks after each test
});

afterAll(async () => {
await mockPrismaService.verifiableCredentialSchema.create.mockClear();
});

it('should verify ANCHOR_TO_CORD is true', () => {
const anchorToCord = process.env.ANCHOR_TO_CORD;
const isTrue = anchorToCord?.toLowerCase().trim() === 'true';
expect(isTrue).toBe(true);
});

it('should verify ISSUER_AGENT_BASE_URL is a valid URL', () => {
const isValidUrl = (url: string) => {
try {
new URL(url);
return true;
} catch {
return false;
}
};
expect(isValidUrl(process.env.ISSUER_AGENT_BASE_URL)).toBe(true);
});

it('should successfully anchor a credential schema to the blockchain', async () => {
const mockRequestBody = generateCredentialSchemaTestBody();
const mockResponse = { schemaId: 'schema-id-blockchain' };

mockBlockchainService.anchorSchema.mockResolvedValueOnce(mockResponse);
mockPrismaService.verifiableCredentialSchema.create.mockResolvedValueOnce({
...mockRequestBody.schema,
blockchainStatus: 'ANCHORED',
});

const result = await service.createCredentialSchema(mockRequestBody);

expect(blockchainFactory.getAnchorService).toHaveBeenCalledWith('cord');
expect(mockBlockchainService.anchorSchema).toHaveBeenCalledWith(mockRequestBody.schema);
expect(result).toBeDefined();
expect(result.schema.id).toEqual(mockResponse.schemaId);
expect(result.blockchainStatus).toEqual('ANCHORED');
});

it('should throw an error if blockchain anchoring fails', async () => {
const mockRequestBody = generateCredentialSchemaTestBody();

mockBlockchainService.anchorSchema.mockRejectedValueOnce(
new InternalServerErrorException('Blockchain anchoring failed')
);
await expect(service.createCredentialSchema(mockRequestBody)).rejects.toThrow(
InternalServerErrorException
);
});



});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Prisma, VerifiableCredentialSchema } from '@prisma/client';
import { Prisma, VerifiableCredentialSchema ,BlockchainStatus} from '@prisma/client';

// represents the schema stored in Prisma
export class VCItem implements VerifiableCredentialSchema {
Expand Down Expand Up @@ -34,4 +34,6 @@ export class VCItem implements VerifiableCredentialSchema {
createdBy: string;
updatedBy: string;
deprecatedId: string;
@ApiProperty({ enum: BlockchainStatus, description: 'Blockchain status' })
blockchainStatus: BlockchainStatus | null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Injectable, BadRequestException } from '@nestjs/common';
import { AnchorCordService } from '../implementations/anchor-cord.service';
import { BlockchainAnchor } from '../interfaces/blockchain_anchor.interface';

/**
* Factory class to dynamically resolve the appropriate BlockchainAnchor service.
* It uses the specified method to determine which implementation to return.
*/
@Injectable()
export class BlockchainAnchorFactory {
/**
* Constructor for the BlockchainAnchorFactory.
* @param cordService - An instance of AnchorCordService, which handles CORD-specific anchoring logic.
*/
constructor(private readonly cordService: AnchorCordService) {}

/**
* Resolves the appropriate BlockchainAnchor service based on the provided method.
* @param method - The blockchain method (e.g., 'cord').
* @returns The service instance corresponding to the specified method or null if no method is provided.
* @throws
*/
getAnchorService(method?: string): BlockchainAnchor | null {
// If no method is specified, return null to indicate no anchoring is required
if (!method) {
return null;
}

// Determine the appropriate service implementation based on the method
switch (method) {
case 'cord':
// Return the CORD-specific implementation
return this.cordService;
default:
throw new BadRequestException(`Unsupported blockchain method: ${method}`);
}
}
}
Loading