Skip to content

Commit

Permalink
[FABCN-403] around transaction (#196)
Browse files Browse the repository at this point in the history
* [FABCN-403] Adds aroundTransaction hook

Signed-off-by: iMacTia <giuffrida.mattia@gmail.com>

* [FABC-403] A few more formatting fixes

Signed-off-by: iMacTia <giuffrida.mattia@gmail.com>

* [FABCN-403] Trying to fight with tabs...

Signed-off-by: iMacTia <giuffrida.mattia@gmail.com>

Co-authored-by: Matthew B White <mbwhite@users.noreply.github.com>
  • Loading branch information
iMacTia and mbwhite authored Nov 26, 2020
1 parent 64f59d0 commit bfade8a
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 48 deletions.
10 changes: 10 additions & 0 deletions TUTORIAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,16 @@ For example
// emit events etc...
}
async aroundTransaction(ctx, fn, parameters) {
try {
// don't forget to call super, or your transaction function won't run!
super.aroundTransaction(ctx, fn, parameters)
} catch (error) {
// do something with the error, then rethrow
throw error
}
}
```

### Structure of the Transaction Context
Expand Down
42 changes: 31 additions & 11 deletions apis/fabric-contract-api/lib/contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const Context = require('./context');
/**
* The main Contact class that all code working within a Chaincode Container must be extending.
*
* Overriding of the `beforeTransaction` `afterTransaction` `unknownTransaction` and `createContext` are all optional
* Overriding of the `beforeTransaction`, `afterTransaction`, `aroundTransaction`, `unknownTransaction` and `createContext` are all optional
* Supplying a name within the constructor is also option and will default to ''
*
* @memberof fabric-contract-api
Expand Down Expand Up @@ -56,7 +56,7 @@ class Contract {
* @param {Context} ctx the transactional context
*/
async beforeTransaction(ctx) {
// default implementation is do nothing
// default implementation is do nothing
}

/**
Expand All @@ -73,6 +73,26 @@ class Contract {
// default implementation is do nothing
}

/**
* 'aroundTransaction' wraps the call to the transaction function within your contract, allowing you
* to encapsulate it into a code block. Examples of what you could do overriding this include, but
* are not limited to: catching exceptions, logging, use a thread-store.
*
* When overriding this function, remember to call `super.aroundTransaction(ctx, fn, parameters)`!
* If you don't, the contract won't be able to run any transaction.
*
* If an error is thrown, the whole transaction will be rejected
*
* @param {Context} ctx the transactional context
* @param {Function} fn the contract function to invoke
* @param {any} paramters the parameters for the function to invoke
*/
async aroundTransaction(ctx, fn, parameters) {
// use the spread operator to make this pass the arguments seperately not as an array
// this is the point at which control is handed to the tx function
return this[fn](ctx, ...parameters);
}

/**
* 'unknownTransaction' will be called if the required transaction function requested does not exist
* Override this method to implement your own processing.
Expand All @@ -89,15 +109,15 @@ class Contract {
}

/**
* 'createContext' is called before any after, before, unknown or user defined transaction function. This permits contracts
* to use their own subclass of context to add additinal processing.
*
* After this function returns, the chaincodeStub and client identity objects will be injected.
* No chaincode apis are available for calling directly within this function. Nor should the constructor of the subclasses context assume
* any other setup.
*
* @return {Context} a context implementation that must subclass context
*/
* 'createContext' is called before any after, before, unknown or user defined transaction function. This permits contracts
* to use their own subclass of context to add additinal processing.
*
* After this function returns, the chaincodeStub and client identity objects will be injected.
* No chaincode apis are available for calling directly within this function. Nor should the constructor of the subclasses context assume
* any other setup.
*
* @return {Context} a context implementation that must subclass context
*/
createContext() {
return new Context();
}
Expand Down
37 changes: 20 additions & 17 deletions apis/fabric-contract-api/test/typescript/smartcontract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,19 @@ import { Contract, Context } from 'fabric-contract-api';
import { ChaincodeStub, ClientIdentity } from 'fabric-shim-api';

export class ScenarioContext extends Context{

customFunction():void {
customFunction(): void {

}
}

export default class TestContractOne extends Contract {

constructor() {
super('org.papernet.commercialpaper');
}

beforeTransaction(ctx: ScenarioContext){

beforeTransaction(ctx: ScenarioContext) {
// test that the context super class properties are available
const stubApi: ChaincodeStub = ctx.stub;
const stubApi: ChaincodeStub = ctx.stub;
const clientIdentity: ClientIdentity = ctx.clientIdentity;

// tests that the functions in the subclasses context be called
Expand All @@ -35,12 +32,18 @@ export default class TestContractOne extends Contract {
return Promise.resolve();
}

afterTransaction(ctx: ScenarioContext,result: any){
afterTransaction(ctx: ScenarioContext,result: any) {
// This proves that typescript is enforcing the
// return type of Promise<void>
return Promise.resolve();
}

aroundTransaction(ctx: ScenarioContext, fn: Function, parameters: any) {
// This proves that typescript is enforcing the
// return type of Promise<void>
return super.aroundTransaction(ctx, fn, parameters);
}

unknownTransaction(ctx: ScenarioContext){
// This proves that typescript is enforcing the
// return type of Promise<void>
Expand All @@ -51,23 +54,23 @@ export default class TestContractOne extends Contract {
return new ScenarioContext();
}

async Transaction(ctx: ScenarioContext) {
async Transaction(ctx: ScenarioContext) {
// test that the context super class properties are available
const stubApi: ChaincodeStub = ctx.stub;
const clientIdentity: ClientIdentity = ctx.clientIdentity;
const stubApi: ChaincodeStub = ctx.stub;
const clientIdentity: ClientIdentity = ctx.clientIdentity;

// test that the name returns a string
const ns: string = this.getName();
}
const ns: string = this.getName();
}
}

export class TestContractTwo extends Contract {
constructor() {
super();
constructor() {
super();
}

async Transaction(ctx: Context) {
const stubApi: ChaincodeStub = ctx.stub;
const clientIdentity: ClientIdentity = ctx.clientIdentity;
}
const stubApi: ChaincodeStub = ctx.stub;
const clientIdentity: ClientIdentity = ctx.clientIdentity;
}
}
22 changes: 14 additions & 8 deletions apis/fabric-contract-api/test/unit/contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,17 @@ const Context = require(path.join(pathToRoot, 'fabric-contract-api/lib/context')

let beforeStub;
let afterStub;
let aroundStub;
let unknownStub;
let createContextStub;

/*
* A fake contract class;
*/
class SCAlpha extends Contract {

/** */
constructor() {
super('alpha.beta.delta');

}

async unknownTransaction(ctx) {
Expand All @@ -56,19 +55,20 @@ class SCAlpha extends Contract {
afterStub(ctx, result);
}

async aroundTransaction(ctx, fn, parameters) {
aroundStub(ctx, fn, ...parameters);
}

createContext() {
createContextStub();
}
}

class SCBeta extends Contract {

/** */
constructor() {
super();

}

}

describe('contract.js', () => {
Expand Down Expand Up @@ -127,13 +127,13 @@ describe('contract.js', () => {
expect(sc3.getName()).to.equal('SCBeta');
});

it ('should call the default before/after functions', () => {
it ('should call the default before/after/around functions', () => {
const sc0 = new Contract();


return Promise.all([
sc0.beforeTransaction().should.be.fulfilled,
sc0.afterTransaction().should.be.fulfilled]);
sc0.afterTransaction().should.be.fulfilled,
sc0.aroundTransaction(null, 'afterTransaction', [null]).should.be.fulfilled]);
});

it ('should call the default createContext functions', () => {
Expand Down Expand Up @@ -172,6 +172,7 @@ describe('contract.js', () => {
beforeEach('setup the stubs', () => {
beforeStub = sandbox.stub().resolves();
afterStub = sandbox.stub().resolves();
aroundStub = sandbox.stub().resolves();
unknownStub = sandbox.stub().resolves();
createContextStub = sandbox.stub().returns();
});
Expand All @@ -192,6 +193,11 @@ describe('contract.js', () => {
sinon.assert.calledOnce(afterStub);
sinon.assert.calledWith(afterStub, ctx, 'result');

const params = ['param1', 'param2']
sc.aroundTransaction(ctx, 'function', params);
sinon.assert.calledOnce(aroundStub);
sinon.assert.calledWith(aroundStub, ctx, 'function', 'param1', 'param2');

sc.unknownTransaction(ctx);
sinon.assert.calledOnce(unknownStub);
sinon.assert.calledWith(unknownStub, ctx);
Expand Down
3 changes: 2 additions & 1 deletion apis/fabric-contract-api/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ declare module 'fabric-contract-api' {
static _isContract(): boolean;

beforeTransaction(ctx : Context): Promise<void>;
afterTransaction(ctx : Context,result: any): Promise<void>;
afterTransaction(ctx : Context, result: any): Promise<void>;
aroundTransaction(ctx : Context, fn : Function, parameters: any): Promise<void>;

unknownTransaction(ctx : Context): Promise<void>;

Expand Down
13 changes: 10 additions & 3 deletions docs/_jsdoc/tutorials/deep-dive-contract-interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,28 +78,35 @@ Currently the 'stub' api for handling world state, and the 'Client Identity' is
Each contract has a 'createContext' method that can be overridden by specific implementations to provide specific control to add information to the


### Before, After and Unknown Functions
### Before, After, Around and Unknown Functions

The Contract class defines three functions that can be overridden by specific implementations.

```javascript
async beforeTransaction(ctx) {
// default implementation is do nothing
// default implementation is do nothing
}

async afterTransaction(ctx, result) {
// default implementation is do nothing
}

async aroundTransaction(ctx, fn, parameters) {
// default implementation invokes `fn`
}
```

Before is called immediately before the transaction function, and after immediately afterwards. Note that before does not get the arguments to the function (note this was the subject of debate, opinions welcomed). After gets the result from the transaction function (this is the result returned from transaction function without any processing).
Before is called immediately before the transaction function, and after immediately afterwards. Note that before does not get the arguments to the function (note this was the subject of debate, opinions welcomed). After gets the result from the transaction function (this is the result returned from transaction function without any processing).

Around is the one responsible for invoking the trancaction function, and allows you to wrap all of them into a code block.

If the transaction function throws an Error then the whole transaction fails, likewise if the before or after throws an Error then the transaction fails. (note that if say before throws an error the transaction function is never called, nor the after. Similarly if transaction function throws an Error, after is not called. )

Typical use cases of these functions would be

- logging of the functions called
- checks of the identity of the caller
- wrap all functions into a try/catch

The unknown function is called if the requested function is not known; the default implementation is to throw an error. `You've asked to invoke a function that does not exist: {requested function}`
However you can implement an `unkownTransition` function - this can return a successful or throw an error as you wish.
Expand Down
12 changes: 11 additions & 1 deletion docs/_jsdoc/tutorials/using-contractinterface.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ For example
*
*/
async unknownTransaction(ctx){
throw new Error('a custom error message')
throw new Error('a custom error message')
}
async beforeTransaction(ctx){
Expand All @@ -195,6 +195,16 @@ For example
// emit events etc...
}
async aroundTransaction(ctx, fn, parameters) {
try {
// don't forget to call super, or your transaction function won't run!
super.aroundTransaction(ctx, fn, parameters)
} catch (error) {
// do something with the error, then rethrow
throw error
}
}
```

### Structure of the Transaction Context
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -374,9 +374,8 @@ class ChaincodeFromContract {
// before tx
await contractInstance.beforeTransaction(ctx);

// use the spread operator to make this pass the arguments seperately not as an array
// this is the point at which control is handed to the tx function
const result = await contractInstance[fn](ctx, ...parameters);
// around tx
const result = await contractInstance.aroundTransaction(ctx, fn, parameters);

// after tx fn, assuming that the smart contract hasn't gone wrong
await contractInstance.afterTransaction(ctx, result);
Expand Down
Loading

0 comments on commit bfade8a

Please sign in to comment.