diff --git a/fabric-network/index.js b/fabric-network/index.js
index 4f320a307b..0d058ff571 100644
--- a/fabric-network/index.js
+++ b/fabric-network/index.js
@@ -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.
*/
@@ -305,11 +306,29 @@
*/
+/**
+ * The type for BlockEvent objects with a type of full
.
+ * @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.
@@ -334,6 +353,7 @@
/**
+ * The type for TransactionEvent objects with a type of filtered
.
* @interface FilteredTransactionEvent
* @implements {module:fabric-network.TransactionEvent}
* @memberof module:fabric-network
@@ -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 full
.
+ * @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.
*/
/**
@@ -375,21 +422,39 @@
/**
- * Event representing a contract event emitted by a smart contract.
+ * The type for ContractEvent objects with a type of filtered
.
* @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 full
.
+ * @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
diff --git a/fabric-network/src/contract.js b/fabric-network/src/contract.js
index add6b4d8d1..c2fb52303b 100644
--- a/fabric-network/src/contract.js
+++ b/fabric-network/src/contract.js
@@ -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} 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
* }
diff --git a/fabric-network/src/events.ts b/fabric-network/src/events.ts
index 0f9bdf5f93..07da0f6ea8 100644
--- a/fabric-network/src/events.ts
+++ b/fabric-network/src/events.ts
@@ -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;
}
diff --git a/fabric-network/src/impl/event/contractlistenersession.ts b/fabric-network/src/impl/event/contractlistenersession.ts
index 5ca036324f..493319ca3c 100644
--- a/fabric-network/src/impl/event/contractlistenersession.ts
+++ b/fabric-network/src/impl/event/contractlistenersession.ts
@@ -34,20 +34,14 @@ export class ContractListenerSession implements ListenerSession {
}
private async onBlockEvent(blockEvent: BlockEvent): Promise {
- 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 {
- 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 {
+ private async onTransactionEvent(transactionEvent: TransactionEvent): Promise {
for (const contractEvent of transactionEvent.getContractEvents()) {
if (this.isMatch(contractEvent)) {
await this.notifyListener(contractEvent);
diff --git a/fabric-network/src/impl/event/filteredeventfactory.ts b/fabric-network/src/impl/event/filteredeventfactory.ts
index 5d611c4eb4..2f31d2f242 100644
--- a/fabric-network/src/impl/event/filteredeventfactory.ts
+++ b/fabric-network/src/impl/event/filteredeventfactory.ts
@@ -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);
diff --git a/fabric-network/test/impl/event/contractlistener.ts b/fabric-network/test/impl/event/contractlistener.ts
index 0f8312d77e..5258f5636c 100644
--- a/fabric-network/test/impl/event/contractlistener.ts
+++ b/fabric-network/test/impl/event/contractlistener.ts
@@ -31,8 +31,9 @@ describe('contract event listener', () => {
let listener: StubContractListener;
let spyListener: sinon.SinonSpy<[ContractEvent], Promise>;
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');
@@ -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;
}
@@ -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);
+ });
});