Skip to content

Commit bfade8a

Browse files
iMacTiambwhite
andauthored
[FABCN-403] around transaction (#196)
* [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>
1 parent 64f59d0 commit bfade8a

9 files changed

Lines changed: 116 additions & 48 deletions

File tree

TUTORIAL.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,16 @@ For example
195195
// emit events etc...
196196
}
197197
198+
async aroundTransaction(ctx, fn, parameters) {
199+
try {
200+
// don't forget to call super, or your transaction function won't run!
201+
super.aroundTransaction(ctx, fn, parameters)
202+
} catch (error) {
203+
// do something with the error, then rethrow
204+
throw error
205+
}
206+
}
207+
198208
```
199209

200210
### Structure of the Transaction Context

apis/fabric-contract-api/lib/contract.js

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const Context = require('./context');
1414
/**
1515
* The main Contact class that all code working within a Chaincode Container must be extending.
1616
*
17-
* Overriding of the `beforeTransaction` `afterTransaction` `unknownTransaction` and `createContext` are all optional
17+
* Overriding of the `beforeTransaction`, `afterTransaction`, `aroundTransaction`, `unknownTransaction` and `createContext` are all optional
1818
* Supplying a name within the constructor is also option and will default to ''
1919
*
2020
* @memberof fabric-contract-api
@@ -56,7 +56,7 @@ class Contract {
5656
* @param {Context} ctx the transactional context
5757
*/
5858
async beforeTransaction(ctx) {
59-
// default implementation is do nothing
59+
// default implementation is do nothing
6060
}
6161

6262
/**
@@ -73,6 +73,26 @@ class Contract {
7373
// default implementation is do nothing
7474
}
7575

76+
/**
77+
* 'aroundTransaction' wraps the call to the transaction function within your contract, allowing you
78+
* to encapsulate it into a code block. Examples of what you could do overriding this include, but
79+
* are not limited to: catching exceptions, logging, use a thread-store.
80+
*
81+
* When overriding this function, remember to call `super.aroundTransaction(ctx, fn, parameters)`!
82+
* If you don't, the contract won't be able to run any transaction.
83+
*
84+
* If an error is thrown, the whole transaction will be rejected
85+
*
86+
* @param {Context} ctx the transactional context
87+
* @param {Function} fn the contract function to invoke
88+
* @param {any} paramters the parameters for the function to invoke
89+
*/
90+
async aroundTransaction(ctx, fn, parameters) {
91+
// use the spread operator to make this pass the arguments seperately not as an array
92+
// this is the point at which control is handed to the tx function
93+
return this[fn](ctx, ...parameters);
94+
}
95+
7696
/**
7797
* 'unknownTransaction' will be called if the required transaction function requested does not exist
7898
* Override this method to implement your own processing.
@@ -89,15 +109,15 @@ class Contract {
89109
}
90110

91111
/**
92-
* 'createContext' is called before any after, before, unknown or user defined transaction function. This permits contracts
93-
* to use their own subclass of context to add additinal processing.
94-
*
95-
* After this function returns, the chaincodeStub and client identity objects will be injected.
96-
* No chaincode apis are available for calling directly within this function. Nor should the constructor of the subclasses context assume
97-
* any other setup.
98-
*
99-
* @return {Context} a context implementation that must subclass context
100-
*/
112+
* 'createContext' is called before any after, before, unknown or user defined transaction function. This permits contracts
113+
* to use their own subclass of context to add additinal processing.
114+
*
115+
* After this function returns, the chaincodeStub and client identity objects will be injected.
116+
* No chaincode apis are available for calling directly within this function. Nor should the constructor of the subclasses context assume
117+
* any other setup.
118+
*
119+
* @return {Context} a context implementation that must subclass context
120+
*/
101121
createContext() {
102122
return new Context();
103123
}

apis/fabric-contract-api/test/typescript/smartcontract.ts

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,19 @@ import { Contract, Context } from 'fabric-contract-api';
99
import { ChaincodeStub, ClientIdentity } from 'fabric-shim-api';
1010

1111
export class ScenarioContext extends Context{
12-
13-
customFunction():void {
12+
customFunction(): void {
1413

1514
}
1615
}
1716

1817
export default class TestContractOne extends Contract {
19-
2018
constructor() {
2119
super('org.papernet.commercialpaper');
2220
}
2321

24-
beforeTransaction(ctx: ScenarioContext){
25-
22+
beforeTransaction(ctx: ScenarioContext) {
2623
// test that the context super class properties are available
27-
const stubApi: ChaincodeStub = ctx.stub;
24+
const stubApi: ChaincodeStub = ctx.stub;
2825
const clientIdentity: ClientIdentity = ctx.clientIdentity;
2926

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

38-
afterTransaction(ctx: ScenarioContext,result: any){
35+
afterTransaction(ctx: ScenarioContext,result: any) {
3936
// This proves that typescript is enforcing the
4037
// return type of Promise<void>
4138
return Promise.resolve();
4239
}
4340

41+
aroundTransaction(ctx: ScenarioContext, fn: Function, parameters: any) {
42+
// This proves that typescript is enforcing the
43+
// return type of Promise<void>
44+
return super.aroundTransaction(ctx, fn, parameters);
45+
}
46+
4447
unknownTransaction(ctx: ScenarioContext){
4548
// This proves that typescript is enforcing the
4649
// return type of Promise<void>
@@ -51,23 +54,23 @@ export default class TestContractOne extends Contract {
5154
return new ScenarioContext();
5255
}
5356

54-
async Transaction(ctx: ScenarioContext) {
57+
async Transaction(ctx: ScenarioContext) {
5558
// test that the context super class properties are available
56-
const stubApi: ChaincodeStub = ctx.stub;
57-
const clientIdentity: ClientIdentity = ctx.clientIdentity;
59+
const stubApi: ChaincodeStub = ctx.stub;
60+
const clientIdentity: ClientIdentity = ctx.clientIdentity;
5861

5962
// test that the name returns a string
60-
const ns: string = this.getName();
61-
}
63+
const ns: string = this.getName();
64+
}
6265
}
6366

6467
export class TestContractTwo extends Contract {
65-
constructor() {
66-
super();
68+
constructor() {
69+
super();
6770
}
6871

6972
async Transaction(ctx: Context) {
70-
const stubApi: ChaincodeStub = ctx.stub;
71-
const clientIdentity: ClientIdentity = ctx.clientIdentity;
72-
}
73+
const stubApi: ChaincodeStub = ctx.stub;
74+
const clientIdentity: ClientIdentity = ctx.clientIdentity;
75+
}
7376
}

apis/fabric-contract-api/test/unit/contract.js

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,17 @@ const Context = require(path.join(pathToRoot, 'fabric-contract-api/lib/context')
3030

3131
let beforeStub;
3232
let afterStub;
33+
let aroundStub;
3334
let unknownStub;
3435
let createContextStub;
3536

3637
/*
3738
* A fake contract class;
3839
*/
3940
class SCAlpha extends Contract {
40-
4141
/** */
4242
constructor() {
4343
super('alpha.beta.delta');
44-
4544
}
4645

4746
async unknownTransaction(ctx) {
@@ -56,19 +55,20 @@ class SCAlpha extends Contract {
5655
afterStub(ctx, result);
5756
}
5857

58+
async aroundTransaction(ctx, fn, parameters) {
59+
aroundStub(ctx, fn, ...parameters);
60+
}
61+
5962
createContext() {
6063
createContextStub();
6164
}
6265
}
6366

6467
class SCBeta extends Contract {
65-
6668
/** */
6769
constructor() {
6870
super();
69-
7071
}
71-
7272
}
7373

7474
describe('contract.js', () => {
@@ -127,13 +127,13 @@ describe('contract.js', () => {
127127
expect(sc3.getName()).to.equal('SCBeta');
128128
});
129129

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

133-
134133
return Promise.all([
135134
sc0.beforeTransaction().should.be.fulfilled,
136-
sc0.afterTransaction().should.be.fulfilled]);
135+
sc0.afterTransaction().should.be.fulfilled,
136+
sc0.aroundTransaction(null, 'afterTransaction', [null]).should.be.fulfilled]);
137137
});
138138

139139
it ('should call the default createContext functions', () => {
@@ -172,6 +172,7 @@ describe('contract.js', () => {
172172
beforeEach('setup the stubs', () => {
173173
beforeStub = sandbox.stub().resolves();
174174
afterStub = sandbox.stub().resolves();
175+
aroundStub = sandbox.stub().resolves();
175176
unknownStub = sandbox.stub().resolves();
176177
createContextStub = sandbox.stub().returns();
177178
});
@@ -192,6 +193,11 @@ describe('contract.js', () => {
192193
sinon.assert.calledOnce(afterStub);
193194
sinon.assert.calledWith(afterStub, ctx, 'result');
194195

196+
const params = ['param1', 'param2']
197+
sc.aroundTransaction(ctx, 'function', params);
198+
sinon.assert.calledOnce(aroundStub);
199+
sinon.assert.calledWith(aroundStub, ctx, 'function', 'param1', 'param2');
200+
195201
sc.unknownTransaction(ctx);
196202
sinon.assert.calledOnce(unknownStub);
197203
sinon.assert.calledWith(unknownStub, ctx);

apis/fabric-contract-api/types/index.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ declare module 'fabric-contract-api' {
2424
static _isContract(): boolean;
2525

2626
beforeTransaction(ctx : Context): Promise<void>;
27-
afterTransaction(ctx : Context,result: any): Promise<void>;
27+
afterTransaction(ctx : Context, result: any): Promise<void>;
28+
aroundTransaction(ctx : Context, fn : Function, parameters: any): Promise<void>;
2829

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

docs/_jsdoc/tutorials/deep-dive-contract-interface.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,28 +78,35 @@ Currently the 'stub' api for handling world state, and the 'Client Identity' is
7878
Each contract has a 'createContext' method that can be overridden by specific implementations to provide specific control to add information to the
7979

8080

81-
### Before, After and Unknown Functions
81+
### Before, After, Around and Unknown Functions
8282

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

8585
```javascript
8686
async beforeTransaction(ctx) {
87-
// default implementation is do nothing
87+
// default implementation is do nothing
8888
}
8989

9090
async afterTransaction(ctx, result) {
9191
// default implementation is do nothing
9292
}
93+
94+
async aroundTransaction(ctx, fn, parameters) {
95+
// default implementation invokes `fn`
96+
}
9397
```
9498

95-
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).
99+
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).
100+
101+
Around is the one responsible for invoking the trancaction function, and allows you to wrap all of them into a code block.
96102

97103
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. )
98104

99105
Typical use cases of these functions would be
100106

101107
- logging of the functions called
102108
- checks of the identity of the caller
109+
- wrap all functions into a try/catch
103110

104111
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}`
105112
However you can implement an `unkownTransition` function - this can return a successful or throw an error as you wish.

docs/_jsdoc/tutorials/using-contractinterface.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ For example
183183
*
184184
*/
185185
async unknownTransaction(ctx){
186-
throw new Error('a custom error message')
186+
throw new Error('a custom error message')
187187
}
188188
189189
async beforeTransaction(ctx){
@@ -195,6 +195,16 @@ For example
195195
// emit events etc...
196196
}
197197
198+
async aroundTransaction(ctx, fn, parameters) {
199+
try {
200+
// don't forget to call super, or your transaction function won't run!
201+
super.aroundTransaction(ctx, fn, parameters)
202+
} catch (error) {
203+
// do something with the error, then rethrow
204+
throw error
205+
}
206+
}
207+
198208
```
199209

200210
### Structure of the Transaction Context

libraries/fabric-shim/lib/contract-spi/chaincodefromcontract.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -374,9 +374,8 @@ class ChaincodeFromContract {
374374
// before tx
375375
await contractInstance.beforeTransaction(ctx);
376376

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

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

0 commit comments

Comments
 (0)