Skip to content

Commit

Permalink
FABN-1524: Checkpoint scenario tests
Browse files Browse the repository at this point in the history
Signed-off-by: Mark S. Lewis <[email protected]>
  • Loading branch information
bestbeforetoday committed Mar 28, 2020
1 parent 070a06a commit 83e6202
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 43 deletions.
4 changes: 2 additions & 2 deletions fabric-network/src/impl/filecheckpointer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export class FileCheckpointer implements Checkpointer {
if (data) {
const json = data.toString(encoding);
const state = JSON.parse(json);
this.loadState(state);
this.setState(state);
}
}

Expand All @@ -65,7 +65,7 @@ export class FileCheckpointer implements Checkpointer {
}
}

private loadState(state: PersistentState): void {
private setState(state: PersistentState): void {
this.blockNumber = state.blockNumber ? Long.fromString(state.blockNumber) : undefined;
this.transactionIds = new Set(state.transactionIds);
}
Expand Down
18 changes: 18 additions & 0 deletions test/ts-scenario/features/events.feature
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,21 @@ Feature: Node SDK Events
When I unregister the listener named privateBlockListener
And I use the gateway named event_gateway to submit a total of 5 transactions with args [privateValuePut] for contract events instantiated on channel eventschannel
Then I receive 0 events from the listener named privateBlockListener

Scenario: Checkpoint block event listening
When I use the gateway named event_gateway to listen for full block events with a new file checkpoint listener named checkpointBlockListener on channel eventschannel
When I use the gateway named event_gateway to submit a transaction with args [createValue] for contract events instantiated on channel eventschannel
Then I receive a minimum 1 events from the listener named checkpointBlockListener
When I unregister the listener named checkpointBlockListener
When I use the gateway named event_gateway to submit a transaction with args [createValue] for contract events instantiated on channel eventschannel
When I use the gateway named event_gateway to listen for full block events with an existing file checkpoint listener named checkpointBlockListener on channel eventschannel
Then I receive a minimum 1 events from the listener named checkpointBlockListener

Scenario: Checkpoint contract event listening
When I use the gateway named event_gateway to listen for full contract events named create with a new checkpoint listener named checkpointContractListener for the smart contract named events on channel eventschannel
And I use the gateway named event_gateway to submit a transaction with args [createValue] for contract events instantiated on channel eventschannel
Then the listener named checkpointContractListener should have contract events with payload containing "createValueTransactionContent"
When I unregister the listener named checkpointContractListener
And I use the gateway named event_gateway to submit a transaction with args [createValue] for contract events instantiated on channel eventschannel
And I use the gateway named event_gateway to listen for full contract events named create with an existing checkpoint listener named checkpointContractListener for the smart contract named events on channel eventschannel
Then the listener named checkpointContractListener should have contract events with payload containing "createValueTransactionContent"
56 changes: 51 additions & 5 deletions test/ts-scenario/steps/event-listeners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Constants } from './constants';
import * as Listeners from './lib/listeners';

import { Given, Then, When } from 'cucumber';
import { EventType } from 'fabric-network';
import { EventType, ListenerOptions } from 'fabric-network';

Given(/^I am listening for (filtered|full) contract events named (.+?) with a listener named (.+?)$/, {timeout: Constants.STEP_SHORT as number }, async (type: EventType, eventName: string, listenerName: string) => {
const isActive: boolean = true;
Expand All @@ -27,20 +27,66 @@ Given(/^I am listening for transaction events with a listener named (.+?)$/, {ti

// Contract events
When(/^I use the gateway named (.+?) to listen for (filtered|full) contract events named (.+?) with a listener named (.+?) for the smart contract named (.+?) on channel (.+?)$/, {timeout: Constants.STEP_SHORT as number}, async (gatewayName: string, eventType: EventType, eventName: string, listenerName: string, ccName: string, channelName: string) => {
return await Listeners.createContractListener(gatewayName, channelName, ccName, eventName, listenerName, eventType);
const options: ListenerOptions = {
type: eventType
};
return await Listeners.createContractListener(gatewayName, channelName, ccName, eventName, listenerName, options);
});

When(/^I use the gateway named (.+?) to replay (filtered|full) contract events named (.+?) from starting block ([0-9]+) with a listener named (.+?) for the smart contract named (.+?) on channel (.+?)$/, {timeout: Constants.STEP_SHORT as number}, async (gatewayName: string, eventType: EventType, eventName: string, startBlock: number, listenerName: string, ccName: string, channelName: string) => {
return await Listeners.createContractListener(gatewayName, channelName, ccName, eventName, listenerName, eventType, startBlock);
const options: ListenerOptions = {
type: eventType,
startBlock
};
return await Listeners.createContractListener(gatewayName, channelName, ccName, eventName, listenerName, options);
});

When(/^I use the gateway named (.+?) to listen for (filtered|full) contract events named (.+?) with a new file checkpoint listener named (.+?) for the smart contract named (.+?) on channel (.+?)$/, {timeout: Constants.STEP_SHORT as number}, async (gatewayName: string, eventType: EventType, eventName: string, listenerName: string, ccName: string, channelName: string) => {
const options: ListenerOptions = {
type: eventType,
checkpointer: await Listeners.newFileCheckpointer()
};
return await Listeners.createContractListener(gatewayName, channelName, ccName, eventName, listenerName, options);
});

When(/^I use the gateway named (.+?) to listen for (filtered|full) contract events named (.+?) with an existing file checkpoint listener named (.+?) for the smart contract named (.+?) on channel (.+?)$/, {timeout: Constants.STEP_SHORT as number}, async (gatewayName: string, eventType: EventType, eventName: string, listenerName: string, ccName: string, channelName: string) => {
const options: ListenerOptions = {
type: eventType,
checkpointer: await Listeners.getFileCheckpointer()
};
return await Listeners.createContractListener(gatewayName, channelName, ccName, eventName, listenerName, options);
});

// Block events
When(/^I use the gateway named (.+?) to listen for (filtered|full|private) block events with a listener named (.+?) on channel (.+?)$/, {timeout: Constants.STEP_SHORT as number}, async (gatewayName: string, eventType: EventType, listenerName: string, channelName: string) => {
return await Listeners.createBlockListener(gatewayName, channelName, listenerName, eventType);
const options: ListenerOptions = {
type: eventType
};
return await Listeners.createBlockListener(gatewayName, channelName, listenerName, options);
});

When(/^I use the gateway named (.+?) to listen for (filtered|full|private) block events between ([0-9]+) and ([0-9]+) with a listener named (.+?) on channel (.+?)$/, {timeout: Constants.STEP_SHORT as number}, async (gatewayName: string, eventType: EventType, startBlock: number, endBlock: number, listenerName: string, channelName: string) => {
return await Listeners.createBlockListener(gatewayName, channelName, listenerName, eventType, startBlock, endBlock);
const options: ListenerOptions = {
type: eventType,
startBlock
};
return await Listeners.createBlockListener(gatewayName, channelName, listenerName, options, endBlock);
});

When(/^I use the gateway named (.+?) to listen for (filtered|full|private) block events with a new file checkpoint listener named (.+?) on channel (.+?)$/, {timeout: Constants.STEP_SHORT as number}, async (gatewayName: string, eventType: EventType, listenerName: string, channelName: string) => {
const options: ListenerOptions = {
type: eventType,
checkpointer: await Listeners.newFileCheckpointer()
};
return await Listeners.createBlockListener(gatewayName, channelName, listenerName, options);
});

When(/^I use the gateway named (.+?) to listen for (filtered|full|private) block events with an existing file checkpoint listener named (.+?) on channel (.+?)$/, {timeout: Constants.STEP_SHORT as number}, async (gatewayName: string, eventType: EventType, listenerName: string, channelName: string) => {
const options: ListenerOptions = {
type: eventType,
checkpointer: await Listeners.getFileCheckpointer()
};
return await Listeners.createBlockListener(gatewayName, channelName, listenerName, options);
});

// Unregister
Expand Down
85 changes: 49 additions & 36 deletions test/ts-scenario/steps/lib/listeners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,58 +4,53 @@

'use strict';

import { BlockEvent, BlockListener, Contract, ContractEvent, ContractListener, EventType, Gateway, ListenerOptions, Network, TransactionEvent } from 'fabric-network';
import { BlockEvent, BlockListener, Checkpointer, Contract, ContractEvent, ContractListener, EventType, Gateway, ListenerOptions, Network, DefaultCheckpointers } from 'fabric-network';
import { Constants } from '../constants';
import * as GatewayHelper from './gateway';
import * as BaseUtils from './utility/baseUtils';
import { StateStore } from './utility/stateStore';
import Long = require('long');
import fs = require('fs');
import path = require('path');
import os = require('os');

const stateStore: StateStore = StateStore.getInstance();
const CHECKPOINT_FILE_KEY = 'checkpointFile';

export async function createContractListener(gatewayName: string, channelName: string, ccName: string, eventName: string, listenerName: string, type: EventType, startBlock?: number): Promise<void> {
export async function createContractListener(gatewayName: string, channelName: string, ccName: string, eventName: string, listenerName: string, listenerOptions: ListenerOptions): Promise<void> {
const gateways: Map<string, any> = stateStore.get(Constants.GATEWAYS);
const gateway: Gateway = gateways.get(gatewayName).gateway;
const contract: Contract = await GatewayHelper.retrieveContractFromGateway(gateway, channelName, ccName);

const listenerObject: any = {
active: true,
eventName,
eventType: type,
listener: {},
payloads: [],
type: Constants.CONTRACT,
};
const payloads: ContractEvent[] = [];

const contractListener: ContractListener = async (event: ContractEvent) => {
const listener: ContractListener = async (event: ContractEvent) => {
BaseUtils.logMsg(`-> Received a contract event for listener [${listenerName}] of eventName ${eventName}`);
if (event.eventName === eventName) {
listenerObject.payloads.push(event);
payloads.push(event);
}
};
const listenerOptions: ListenerOptions = {
startBlock,
type
};
await contract.addContractListener(contractListener, listenerOptions);
await contract.addContractListener(listener, listenerOptions);

// Roll into a listener object to store
listenerObject.listener = contractListener;
listenerObject.remove = () => contract.removeContractListener(contractListener);
const listenerObject: any = {
active: true,
eventName,
eventType: listenerOptions.type,
listener,
payloads,
type: Constants.CONTRACT,
remove: () => contract.removeContractListener(listener)
};
putListenerObject(listenerName, listenerObject);
}

export async function createBlockListener(gatewayName: string, channelName: string, listenerName: string, type: EventType, startBlock?: number, endBlock?: number): Promise<void> {
export async function createBlockListener(gatewayName: string, channelName: string, listenerName: string, listenerOptions: ListenerOptions, endBlock?: number): Promise<void> {
const gateways: Map<string, any> = stateStore.get(Constants.GATEWAYS);
const gateway: Gateway = gateways.get(gatewayName).gateway;
const network: Network = await gateway.getNetwork(channelName);

const listenerObject: any = {
active: true,
eventType: type,
listener: {},
payloads: [],
type: Constants.BLOCK
};
const payloads: BlockEvent[] = [];
const startBlock = listenerOptions.startBlock ? Long.fromValue(listenerOptions.startBlock).toNumber() : undefined;

// Create the listener
const listener: BlockListener = async (blockEvent: BlockEvent) => {
Expand All @@ -67,7 +62,7 @@ export async function createBlockListener(gatewayName: string, channelName: stri
BaseUtils.checkSizeEquality(blockEvent.blockNumber.toNumber(), endBlock + 1, false, true);
}

listenerObject.payloads.push(blockEvent);
payloads.push(blockEvent);
BaseUtils.logMsg('->Received a block event - added blockevent to payloads', listenerName);
const transactionEvents = blockEvent.getTransactionEvents();
for (const transactionEvent of transactionEvents) {
Expand All @@ -80,19 +75,37 @@ export async function createBlockListener(gatewayName: string, channelName: stri
network.removeBlockListener(listener);
}
};
const listenerOptions: ListenerOptions = {
startBlock,
type
};
await network.addBlockListener(listener, listenerOptions);

// Roll into a listener object to store
listenerObject.listener = listener;
listenerObject.remove = () => network.removeBlockListener(listener);
const listenerObject: any = {
active: true,
eventType: listenerOptions.type,
listener,
payloads,
type: Constants.BLOCK,
remove: () => network.removeBlockListener(listener)
};
putListenerObject(listenerName, listenerObject);
BaseUtils.logMsg('->Stored a block event listener:', listenerName);
}

export async function newFileCheckpointer(): Promise<Checkpointer> {
const prefix = os.tmpdir + path.sep;
const tmpDir = await fs.promises.mkdtemp(prefix);
const file = path.join(tmpDir, 'checkpoint.json');
const checkpointer = await DefaultCheckpointers.file(file);
stateStore.set(CHECKPOINT_FILE_KEY, file);
return checkpointer;
}

export async function getFileCheckpointer(): Promise<Checkpointer> {
const file = stateStore.get(CHECKPOINT_FILE_KEY);
if (!file) {
throw new Error('Checkpointer does not exist');
}
return await DefaultCheckpointers.file(file);
}

function getListenerObject(listenerName: string): any {
const listener = getListeners().get(listenerName);
if (!listener) {
Expand Down

0 comments on commit 83e6202

Please sign in to comment.