Skip to content
This repository has been archived by the owner on Apr 13, 2022. It is now read-only.

Commit

Permalink
feat(core-chaincode,core-controller): remove the use of fabric-client…
Browse files Browse the repository at this point in the history
… in controllers

Closes #37, #34

* update the chaincode-invokable relation so we can pass external objects
* add the chaincodetx ref with the types
* update unit tests for invokable changes

Signed-off-by: Diego Barahona <[email protected]>
  • Loading branch information
walmon committed Feb 24, 2019
1 parent 578fd51 commit e5db472
Show file tree
Hide file tree
Showing 10 changed files with 1,576 additions and 1,474 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ dist
docs
.convector-dev-env
chaincode

.history
7 changes: 7 additions & 0 deletions @worldsibu/convector-core-chaincode/src/chaincode-tx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ClientIdentity } from 'fabric-shim';
import { StubHelper } from '@theledger/fabric-chaincode-utils';

export abstract class ChaincodeTx {
stub: StubHelper;
identity: ClientIdentity;
}
34 changes: 31 additions & 3 deletions @worldsibu/convector-core-chaincode/src/chaincode.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/** @module convector-core-chaincode */

import { Stub } from 'fabric-shim';
import { Stub, ClientIdentity } from 'fabric-shim';
import { getInvokables } from '@worldsibu/convector-core-controller';
import { BaseStorage } from '@worldsibu/convector-core-storage';
import { StubStorage } from '@worldsibu/convector-storage-stub';
Expand All @@ -13,6 +13,10 @@ import {

import { Config } from './config';

function isFunction(functionToCheck) {
return functionToCheck && {}.toString.call(functionToCheck) === '[object Function]';
}

/**
* The Chaincode class is used as a wrapper of controllers in a blockchain.
*
Expand Down Expand Up @@ -78,8 +82,32 @@ export class Chaincode extends CC {
}

const controllers = await config.getControllers();

controllers.forEach(C => Object.assign(this, getInvokables(C)));
const identity = new ClientIdentity(stub.getStub());
const fingerprint = identity.getX509Certificate().fingerPrint;

controllers.forEach(C => {
const invokables = getInvokables(C);

const injectedInvokables = Object.keys(invokables)
.reduce((result, fnName) => ({
...result,
[fnName]: isFunction(invokables[fnName]) ?
(stubHelper: StubHelper, _args: string[]) =>
invokables[fnName].call(this, stubHelper, _args, {
sender: {
value: fingerprint
},
tx: {
value: {
identity,
stub
}
}
}) :
invokables[fnName]
}), {});
return Object.assign(this, injectedInvokables);
});

this.initialized = true;
}
Expand Down
1 change: 1 addition & 0 deletions @worldsibu/convector-core-chaincode/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@

export * from './config';
export * from './chaincode';
export * from './chaincode-tx';
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
/** @module convector-core-controller */

/** Base controller class. All controllers must inherit it */
export abstract class ConvectorController {
export abstract class ConvectorController<T=any> {
/**
* Sender's public certificate fingerprint.
* This can be used to identify the user invoking the controller functions
*/
protected sender: string;

/**
* Transaction details. Depends on the platform running, when using core-chaincode
* this will be a @worldsibu/convector-core-chaincode#ChaincodeTx
*/
protected tx: T;
}
13 changes: 6 additions & 7 deletions @worldsibu/convector-core-controller/src/invokable.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
/** @module convector-core-controller */

import { Schema } from 'yup';
import { ClientIdentity } from 'fabric-shim';
import { StubHelper, ChaincodeError } from '@theledger/fabric-chaincode-utils';
import {
ControllerNamespaceMissingError,
ControllerInstantiationError,
Expand Down Expand Up @@ -55,8 +53,10 @@ export function Invokable() {
// The use of `function` here is necessary to keep the context of `this`
descriptor.value = async function internalFn(
this: any,
stubHelper: StubHelper,
args: string[]
// This used to be the stub param, deprecated now
_: any,
args: string[],
extras: any = {}
) {
const schemas: [Schema<any>, any, { new(...args: any[]): any }][] =
Reflect.getOwnMetadata(paramMetadataKey, target, key);
Expand Down Expand Up @@ -91,14 +91,13 @@ export function Invokable() {
}, Promise.resolve([]));
}

const identity = new ClientIdentity(stubHelper.getStub());
const namespace = Reflect.getMetadata(controllerMetadataKey, target.constructor);
const ctx = Object.create(this[namespace], { sender: { value: identity.getX509Certificate().fingerPrint } });
const ctx = Object.create(this[namespace], extras);

try {
return await fn.call(ctx, ...args);
} catch (e) {
const error = new ChaincodeError(e.message);
const error = new Error(e.message);
error.stack = e.stack;
throw error;
}
Expand Down
118 changes: 76 additions & 42 deletions @worldsibu/convector-core-controller/tests/invokable.decorator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,65 +2,89 @@

import * as yup from 'yup';
import { expect } from 'chai';
import { Validate } from '@worldsibu/convector-core-model';
import { ClientIdentity } from 'fabric-shim';
import { ChaincodeMockStub } from '@theledger/fabric-mock-stub';
import { Chaincode, StubHelper } from '@theledger/fabric-chaincode-utils';
import 'mocha';
import 'reflect-metadata';

import { Validate } from '@worldsibu/convector-core-model';

import { Param } from '../src/param.decorator';
import { Controller } from '../src/controller.decorator';
import { Invokable, getInvokables } from '../src/invokable.decorator';

describe('Invokable Decorator', () => {
class TestCC extends Chaincode {
constructor(ctr: any) {
super();
class TestCC extends Chaincode {
constructor(ctr: any) {
super();
Object.assign(this, getInvokables(ctr));
}
}

class TestModel {
@Validate(yup.string())
public name: string;

Object.assign(this, getInvokables(ctr));
}
constructor(content) {
this.name = content.name;
}
}

class TestModel {
@Validate(yup.string())
public name: string;
@Controller('test')
class Test {
@Invokable()
public async sender() {
return this.sender;
}

constructor(content) {
this.name = content.name;
}
@Invokable()
public async plain(
@Param(yup.string())
name: string
) {
return name;
}

@Controller('test')
class Test {
@Invokable()
public async plain(
@Param(yup.string())
name: string
) {
return name;
}

@Invokable()
public async complex(
@Param(TestModel)
model: TestModel
) {
return model;
}

@Invokable()
public async update(
@Param(TestModel, { update: true })
model: TestModel
) {
return model;
}
@Invokable()
public async complex(
@Param(TestModel)
model: TestModel
) {
return model;
}

@Invokable()
public async update(
@Param(TestModel, { update: true })
model: TestModel
) {
return model;
}
}

describe('Invokable Decorator', () => {
let test: Test;
let testCC: TestCC;
let stub: ChaincodeMockStub;

function getExtras() {
const s = new StubHelper(stub);
const identity = new ClientIdentity(stub);
const fingerprint = identity.getX509Certificate().fingerPrint;

return {
sender: {
value: fingerprint
},
tx: {
value: {
identity,
stub: s
}
}
};
}

beforeEach(() => {
testCC = new TestCC(Test);
stub = new ChaincodeMockStub('test-cc', testCC);
Expand All @@ -73,21 +97,31 @@ describe('Invokable Decorator', () => {
expect(invokables.test).to.exist;
});

it('should translate a chaincode call into a controller call', async () => {
it('should initialize and return the `this.sender`', async () => {
const result = await test.sender
.call(testCC, new StubHelper(stub), [], getExtras());
console.log('should initialize and return the `this.sender`');
console.log(result);
expect(result).to.not.eq('');
});

const result = await test.plain.call(testCC, new StubHelper(stub), ['test']);
it('should translate a chaincode call into a controller call', async () => {
const result = await test.plain
.call(testCC, new StubHelper(stub), ['test'], getExtras());
expect(result).to.eq('test');
});

it('should instantiate models from args', async () => {
const result = await test.complex.call(testCC, new StubHelper(stub), [JSON.stringify({ name: 'test' })]);
const result = await test.complex
.call(testCC, new StubHelper(stub), [JSON.stringify({ name: 'test' })], getExtras());

expect(result).to.be.instanceof(TestModel);
expect(result.name).to.eq('test');
});

it('should succeed accepting an incomplete model as a param if it is for update the content', async () => {
const result = await test.update.call(testCC, new StubHelper(stub), [JSON.stringify({})]);
const result = await test.update
.call(testCC, new StubHelper(stub), [JSON.stringify({})], getExtras());

expect(result).to.be.instanceof(TestModel);
});
Expand Down
13 changes: 12 additions & 1 deletion examples/token/src/token.controller.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/** @module @worldsibu/convector-examples-token */

import * as yup from 'yup';
import { ChaincodeTx } from '@worldsibu/convector-core-chaincode';
import {
Controller,
ConvectorController,
Expand All @@ -11,7 +12,7 @@ import {
import { Token, CompanyToken } from './token.model';

@Controller('token')
export class TokenController extends ConvectorController {
export class TokenController extends ConvectorController<ChaincodeTx> {
@Invokable()
public async init(
@Param(Token)
Expand Down Expand Up @@ -81,4 +82,14 @@ export class TokenController extends ConvectorController {

await token.save();
}

@Invokable()
public async getIdentityInfo() {
const mspId = this.tx.identity.getMSPID();
const cert = this.tx.identity.getX509Certificate();
const subject = cert.subject.commonName;
const issuer = cert.issuer.commonName;

return `Cert Fingerprint ${this.sender} for ${subject} (${mspId}) issued by ${issuer}`;
}
}
22 changes: 18 additions & 4 deletions examples/token/tests/token.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ describe('Token', () => {
const token = await tokenCtrl.get(tokenId);
expect(token.balances[certificate]).to.eq(500000);
});

it('should be able to access the identity in the controller', async () => {
const info = await tokenCtrl.getIdentityInfo();

console.log(info);

expect(info).to.exist;
});
});

describe('Extendable Model', () => {
Expand Down Expand Up @@ -95,7 +103,11 @@ describe('Extendable Model', () => {
name: 'Token',
symbol: 'TKN',
totalSupply: totalSupply,
balances: { [certificate]: totalSupply }
balances: { [certificate]: totalSupply },
complex: {
name: 'Test',
value: 5
}
}));

const token = await adapter.getById<CompanyToken>(tokenId);
Expand All @@ -114,16 +126,18 @@ describe('Extendable Model', () => {
});

it('should fail to create a new model if any sub model is missing', async () => {
await tokenCtrl.init(new Token({
id: tokenId + 1,
const id = uuid();

await tokenCtrl.init(new CompanyToken({
id,
name: 'Token',
symbol: 'TKN',
totalSupply: totalSupply,
balances: { [certificate]: totalSupply }
}));

console.log('Expected error in unit-test');
const token = await adapter.getById<Token<any>>(tokenId + 1);
const token = await adapter.getById<CompanyToken>(id);

expect(token).to.not.exist;
});
Expand Down
Loading

0 comments on commit e5db472

Please sign in to comment.