Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FABN-1530: Timestamp added to full and private block transaction events #490

Merged
merged 9 commits into from
Sep 2, 2021
2 changes: 2 additions & 0 deletions fabric-network/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@
* @memberof module:fabric-network
* @property {string} transactionId The ID of the transaction this event represents.
* @property {string} status The status of this transaction.
* @property {Date} timestamp The transaction timestamp. Note that
* timestamp does not exist for <strong>filtered</strong> event.
* @property {boolean} isValid Whether this transaction was successfully committed to the ledger. <code>true</code> if
* the transaction was commited; otherwise <code>false</code>. The status will provide a more specific reason why an
* invalid transaction was not committed.
Expand Down
1 change: 1 addition & 0 deletions fabric-network/src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface TransactionEvent {
readonly transactionData: fabproto6.protos.ITransaction | fabproto6.protos.IFilteredTransaction;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
readonly privateData?: any;
readonly timestamp?: Date;
getBlockEvent(): BlockEvent;
getContractEvents(): ContractEvent[];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export function newFullTransactionEvent(blockEvent: BlockEvent, txEnvelopeIndex:

const envelope: any = block.data.data[txEnvelopeIndex];
const transactionId = envelope.payload.header.channel_header.tx_id;
const timestamp = new Date(envelope.payload.header.channel_header.timestamp);
const code = transactionStatusCodes[txEnvelopeIndex];
const status = TransactionStatus.getStatusForCode(code);

Expand All @@ -49,6 +50,7 @@ export function newFullTransactionEvent(blockEvent: BlockEvent, txEnvelopeIndex:
status,
transactionData: envelope.payload.data,
isValid: status === TransactionStatus.VALID_STATUS,
timestamp,
getBlockEvent: () => blockEvent,
getContractEvents: cachedResult(() => newFullContractEvents(transactionEvent))
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ function newPrivateTransactionEvent(blockEvent: BlockEvent, index: number, priva
status: fullTransactionEvent.status,
transactionData: fullTransactionEvent.transactionData,
isValid: fullTransactionEvent.isValid,
timestamp:fullTransactionEvent.timestamp,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
privateData,
getBlockEvent: () => blockEvent,
Expand Down
87 changes: 82 additions & 5 deletions fabric-network/test/impl/event/blocklistener.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
* SPDX-License-Identifier: Apache-2.0
*/

/* eslint-disable @typescript-eslint/no-unsafe-member-access */

import sinon = require('sinon');
import chai = require('chai');
const expect = chai.expect;
Expand All @@ -21,6 +19,7 @@ import Long = require('long');

import {Gateway} from '../../../src/gateway';
import {StubCheckpointer} from './stubcheckpointer';
import * as fabproto6 from 'fabric-protos';

interface StubBlockListener extends BlockListener {
completePromise: Promise<BlockEvent[]>;
Expand Down Expand Up @@ -49,19 +48,20 @@ describe('block listener', () => {
channel.newEventService.returns(eventService);

const endorser = sinon.createStubInstance(Endorser);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-member-access
(endorser as any).name = 'endorser';
channel.getEndorsers.returns([endorser]);

const client = sinon.createStubInstance(Client);
const eventer = sinon.createStubInstance(Eventer);
client.newEventer.returns(eventer);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-member-access
(channel as any).client = client;

network = new NetworkImpl(gateway as unknown as Gateway, channel);

// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-explicit-any
// eslint-disable-next-line max-len
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-member-access
eventServiceManager = (network as any).eventServiceManager;

listener = testUtils.newAsyncListener<BlockEvent>();
Expand Down Expand Up @@ -110,6 +110,46 @@ describe('block listener', () => {
});
}

function addTransaction(event:EventInfo, timestamp?:Date): EventInfo {
event.block.data = new fabproto6.common.BlockData();
event.block.data.data.push(newEnvelope(new fabproto6.protos.Transaction(), 'txn1', timestamp));
event.block.metadata = new fabproto6.common.BlockMetadata();
event.block.metadata.metadata = [];
event.block.metadata.metadata[fabproto6.common.BlockMetadataIndex.TRANSACTIONS_FILTER] = new Uint8Array(10);
return event;
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function addFilteredTransaction(event: EventInfo, filteredTransaction: any): void {
event.filteredBlock.filtered_transactions.push(filteredTransaction);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function newFilteredTransaction(): any {
const filteredTransaction = new fabproto6.protos.FilteredTransaction();
filteredTransaction.tx_validation_code = fabproto6.protos.TxValidationCode.VALID;
filteredTransaction.transaction_actions = {};
return filteredTransaction;
}


// eslint-disable-next-line @typescript-eslint/no-explicit-any
function newEnvelope(transaction: any, transactionId?: string, timestamp?: Date): any {
const channelHeader = {
type:fabproto6.common.HeaderType.ENDORSER_TRANSACTION,
tx_id:transactionId,
timestamp:timestamp?.toISOString()
};
const payload = new fabproto6.common.Payload();
payload.header = new fabproto6.common.Header();
payload.header.channel_header = channelHeader as unknown as Buffer;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
payload.data = transaction;
const envelope = {
payload : payload
};
return envelope;
}

describe('common behavior', () => {
it('add listener returns the listener', async () => {
const result = await network.addBlockListener(listener, listenerOptions);
Expand Down Expand Up @@ -345,11 +385,13 @@ describe('block listener', () => {
it('listener changing event data does not affect other listeners', async () => {
const fake1 = sinon.fake(async (e) => {
await listener(e);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
e.blockNumber = Long.ONE;
});
const listener2 = testUtils.newAsyncListener<BlockEvent>();
const fake2 = sinon.fake(async (e) => {
await listener2(e);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
e.blockNumber = Long.fromNumber(2);
});
const event = newFilteredBlockEventInfo(0);
Expand Down Expand Up @@ -463,6 +505,10 @@ describe('block listener', () => {
});

describe('event types', () => {

const timestamp = new Date();


it('listener can specify filtered blocks', async () => {
const stub = sinon.stub(eventServiceManager, 'startEventService');

Expand Down Expand Up @@ -508,6 +554,37 @@ describe('block listener', () => {
const [actual] = await listener.completePromise;
expect(actual.blockNumber).to.equal(event.blockNumber);
});
it('Timestamp matches in full block transactionevent', async () => {
const event = newFullBlockEventInfo(1);
addTransaction(event, timestamp);
listenerOptions = {
type: 'full'
};
await network.addBlockListener(listener, listenerOptions);
eventService.sendEvent(event);
const actual = await listener.completePromise;
expect(actual[0].getTransactionEvents()[0].timestamp.getTime()).equal(timestamp.getTime());
});

it('Timestamp matches in private block transactionevent', async () => {
const event = newPrivateBlockEventInfo(1);
addTransaction(event, timestamp);
listenerOptions = {
type: 'private'
};
await network.addBlockListener(listener, listenerOptions);
eventService.sendEvent(event);
const actual = await listener.completePromise;
expect(actual[0].getTransactionEvents()[0].timestamp.getTime()).equal(timestamp.getTime());
});
it('Timestamp does not exist in filtered block transactionevent', async () => {
const event = newFilteredBlockEventInfo(1);
addFilteredTransaction(event, newFilteredTransaction());
await network.addBlockListener(listener, listenerOptions);
eventService.sendEvent(event);
const actual = await listener.completePromise;
expect(actual[0].getTransactionEvents()[0].timestamp).equal(undefined);
});
});

describe('checkpoint', () => {
Expand Down