Skip to content

Commit

Permalink
FABN-1518: Update JSDoc for event listening (#185)
Browse files Browse the repository at this point in the history
- Update JSDoc to include full block events
- Add optional 'payload' property to ContactEvent to make listener code easier to write
- Process transactions within a block in parallel when notifying contract event listeners

Signed-off-by: Mark S. Lewis <mark_lewis@uk.ibm.com>
  • Loading branch information
bestbeforetoday authored Mar 17, 2020
1 parent 07e6d82 commit 2f3c524
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 19 deletions.
71 changes: 68 additions & 3 deletions fabric-network/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@
* @interface BlockEvent
* @memberof module:fabric-network
* @see module:fabric-network.FilteredBlockEvent
* @see module:fabric-network.FullBlockEvent
* @property {module:fabric-network.EventType} type The type of this block event.
* @property {Long} blockNumber The number of the block this event represents.
*/
Expand Down Expand Up @@ -305,11 +306,29 @@
*/


/**
* The type for BlockEvent objects with a type of <code>full</code>.
* @interface FullBlockEvent
* @implements {module:fabric-network.BlockEvent}
* @memberof module:fabric-network
* @property {'full'} type The type of this block event.
* @property {Block} blockData The raw block event protobuf.
*/

/**
* Get the transactions included in this block.
* @method FilteredBlockEvent#getTransactionEvents
* @memberof module:fabric-network
* @returns {module:fabric-network.FullTransactionEvent[]} Transaction events.
*/


/**
* Event representing a transaction processed within a block.
* @interface TransactionEvent
* @memberof module:fabric-network
* @see module:fabric-network.FilteredTransactionEvent
* @see module:fabric-network.FullTransactionEvent
* @property {module:fabric-network.EventType} type The type of this transaction event.
* @property {string} transactionId The ID of the transaction this event represents.
* @property {string} status The status of this transaction.
Expand All @@ -334,6 +353,7 @@


/**
* The type for TransactionEvent objects with a type of <code>filtered</code>.
* @interface FilteredTransactionEvent
* @implements {module:fabric-network.TransactionEvent}
* @memberof module:fabric-network
Expand All @@ -350,20 +370,47 @@

/**
* Get the contract events emitted by this transaction.
* @method TransactionEvent#getContractEvents
* @method FilteredTransactionEvent#getContractEvents
* @memberof module:fabric-network
* @returns {module:fabric-network.FilteredContractEvent[]} Contract events.
*/


/**
* The type for TransactionEvent objects with a type of <code>full</code>.
* @interface FullTransactionEvent
* @implements {module:fabric-network.TransactionEvent}
* @memberof module:fabric-network
* @property {'full'} type The type of this transaction event.
* @property {any} transactionData The raw transaction event protobuf.
*/

/**
* Get the parent block event for this event.
* @method FullTransactionEvent#getBlockEvent
* @memberof module:fabric-network
* @returns {module:fabric-network.FullBlockEvent} Transaction event protobuf.
*/

/**
* Get the contract events emitted by this transaction.
* @method FullTransactionEvent#getContractEvents
* @memberof module:fabric-network
* @returns {module:fabric-network.FullContractEvent[]} Contract events.
*/


/**
* Event representing a contract event emitted by a smart contract.
* @interface ContractEvent
* @memberof module:fabric-network
* @see module:fabric-network.FilteredContractEvent
* @see module:fabric-network.FullContractEvent
* @property {module:fabric-network.EventType} type The type of this contract event.
* @property {string} chaincodeId The chaincode ID of the smart contract that emitted this event.
* @property {string} eventName The name of the emitted event.
* @property {Buffer} [payload] The data associated with this event by the smart contract. Note that filtered events
* do not include any payload data.
*/

/**
Expand All @@ -375,21 +422,39 @@


/**
* Event representing a contract event emitted by a smart contract.
* The type for ContractEvent objects with a type of <code>filtered</code>.
* @interface FilteredContractEvent
* @implements {module:fabric-network.ContractEvent}
* @memberof module:fabric-network
* @property {'filtered'} type The type of this contract event.
* @property {undefined} payload Filtered events do not incude any payload data.
*/

/**
* Get the parent transaction event of this event.
* @method ContractEvent#getTransactionEvent
* @method FilteredContractEvent#getTransactionEvent
* @memberof module:fabric-network
* @returns {module:fabric-network.FilteredTransactionEvent} A transaction event.
*/


/**
* The type for TransactionEvent objects with a type of <code>full</code>.
* @interface FullContractEvent
* @implements {module:fabric-network.ContractEvent}
* @memberof module:fabric-network
* @property {'full'} type The type of this contract event.
* @property {Buffer} payload The data associated with this event by the smart contract.
*/

/**
* Get the parent transaction event of this event.
* @method FullContractEvent#getTransactionEvent
* @memberof module:fabric-network
* @returns {module:fabric-network.FullTransactionEvent} A transaction event.
*/


/**
* A callback function that will be invoked when a block event is received.
* @callback ContractListener
Expand Down
6 changes: 3 additions & 3 deletions fabric-network/src/contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,14 +128,14 @@ class Contract {
}

/**
* Add a listener to receive all contract events emitted by the smart contract.
* The default is to listen for full contract events from the current block position.
* Add a listener to receive all contract events emitted by the smart contract as part of successfully committed
* transactions. The default is to listen for full contract events from the current block position.
* @param {module:fabric-network.ContractListener} listener A contract listener callback function.
* @param {module:fabric-network.ListenerOptions} [options] Listener options.
* @returns {Promise<module:fabric-network.ContractListener>} The added listener.
* @example
* const listener: ContractListener = async (event) => {
* if (event.eventName === 'newOrder') {
* if (event.eventName === 'newOrder') {
* const details = event.payload.toString('utf8');
* // Run business process to handle orders
* }
Expand Down
2 changes: 2 additions & 0 deletions fabric-network/src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,13 @@ export interface ContractEvent {
readonly type: EventType;
readonly chaincodeId: string;
readonly eventName: string;
readonly payload?: Buffer;
getTransactionEvent(): TransactionEvent;
}

export interface FilteredContractEvent extends ContractEvent {
readonly type: 'filtered';
readonly payload: undefined;
getTransactionEvent(): FilteredTransactionEvent;
}

Expand Down
16 changes: 5 additions & 11 deletions fabric-network/src/impl/event/contractlistenersession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,14 @@ export class ContractListenerSession implements ListenerSession {
}

private async onBlockEvent(blockEvent: BlockEvent): Promise<void> {
for (const transactionEvent of blockEvent.getTransactionEvents()) {
await this.onTransactionEvent(transactionEvent);
}
}
const transactionPromises = blockEvent.getTransactionEvents()
.filter((transactionEvent) => transactionEvent.isValid)
.map((transactionEvent) => this.onTransactionEvent(transactionEvent));

private async onTransactionEvent(transactionEvent: TransactionEvent): Promise<void> {
if (transactionEvent.isValid) {
await this.onValidTransactionEvent(transactionEvent);
} else {
logger.debug('Ignored contract events for invalid transaction:', transactionEvent);
}
await Promise.all(transactionPromises);
}

private async onValidTransactionEvent(transactionEvent: TransactionEvent): Promise<void> {
private async onTransactionEvent(transactionEvent: TransactionEvent): Promise<void> {
for (const contractEvent of transactionEvent.getContractEvents()) {
if (this.isMatch(contractEvent)) {
await this.notifyListener(contractEvent);
Expand Down
1 change: 1 addition & 0 deletions fabric-network/src/impl/event/filteredeventfactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ function newFilteredContractEvent(transactionEvent: FilteredTransactionEvent, ch
type: 'filtered',
chaincodeId: chaincodeEvent.chaincode_id,
eventName: chaincodeEvent.event_name,
payload: undefined,
getTransactionEvent: () => transactionEvent
};
return Object.freeze(contractEvent);
Expand Down
76 changes: 74 additions & 2 deletions fabric-network/test/impl/event/contractlistener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ describe('contract event listener', () => {
let listener: StubContractListener;
let spyListener: sinon.SinonSpy<[ContractEvent], Promise<void>>;
let contract: Contract;
const eventName: string = 'eventName';
const chaincodeId: string = 'bourbons';
const eventName = 'eventName';
const chaincodeId = 'bourbons';
const eventPayload = 'payload';

beforeEach(async () => {
eventService = new StubEventService('stub');
Expand Down Expand Up @@ -149,6 +150,7 @@ describe('contract event listener', () => {
const chaincodeEvent = new protos.protos.ChaincodeEvent();
chaincodeEvent.chaincode_id = ccId;
chaincodeEvent.event_name = eventName;
chaincodeEvent.payload = Buffer.from(eventPayload, 'utf8');
return chaincodeEvent;
}

Expand Down Expand Up @@ -369,4 +371,74 @@ describe('contract event listener', () => {
const contractEvents = await listener.completePromise;
expect(contractEvents[0].getTransactionEvent()).to.include({ isValid: true }); // tslint:disable-line: no-unused-expression
});

it('filtered events do not contain payload', async () => {
const event = newFilteredEvent(1);
addFilteredTransaction(event, newFilteredTransaction());

const options: ListenerOptions = {
type: 'filtered'
};
await contract.addContractListener(listener, options);
eventService.sendEvent(event);
const contractEvents = await listener.completePromise;

expect(contractEvents[0].payload).to.be.undefined; // tslint:disable-line: no-unused-expression
});

it('full events contain payload', async () => {
const event = newEvent(1);
addTransaction(event, newTransaction());

const options: ListenerOptions = {
type: 'full'
};
await contract.addContractListener(listener, options);
eventService.sendEvent(event);
const contractEvents = await listener.completePromise;

expect(contractEvents[0].payload?.toString()).to.equal(eventPayload);
});

it('can navigate event heirarchy for filtered events', async () => {
const event = newFilteredEvent(1);
addFilteredTransaction(event, newFilteredTransaction());

const options: ListenerOptions = {
type: 'filtered'
};
await contract.addContractListener(listener, options);
eventService.sendEvent(event);
const contractEvents = await listener.completePromise;

const contractEvent = contractEvents[0];
const transactionEvent = contractEvent.getTransactionEvent();
expect(transactionEvent).to.exist; // tslint:disable-line: no-unused-expression
expect(transactionEvent.getContractEvents()).to.contain(contractEvent);

const blockEvent = transactionEvent.getBlockEvent();
expect(blockEvent).to.exist; // tslint:disable-line: no-unused-expression
expect(blockEvent.getTransactionEvents()).to.contain(transactionEvent);
});

it('can navigate event heirarchy for full events', async () => {
const event = newEvent(1);
addTransaction(event, newTransaction());

const options: ListenerOptions = {
type: 'full'
};
await contract.addContractListener(listener, options);
eventService.sendEvent(event);
const contractEvents = await listener.completePromise;

const contractEvent = contractEvents[0];
const transactionEvent = contractEvent.getTransactionEvent();
expect(transactionEvent).to.exist; // tslint:disable-line: no-unused-expression
expect(transactionEvent.getContractEvents()).to.contain(contractEvent);

const blockEvent = transactionEvent.getBlockEvent();
expect(blockEvent).to.exist; // tslint:disable-line: no-unused-expression
expect(blockEvent.getTransactionEvents()).to.contain(transactionEvent);
});
});

0 comments on commit 2f3c524

Please sign in to comment.