mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-22 17:45:10 +00:00
Simplify fabric code
Attempt to make fabric code easier to document Signed-off-by: James Taylor <jamest@uk.ibm.com>
This commit is contained in:
parent
862080773e
commit
45683b2a1a
9 changed files with 274 additions and 234 deletions
|
|
@ -3,10 +3,14 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { mock } from 'jest-mock-extended';
|
import { mock } from 'jest-mock-extended';
|
||||||
import { Contract, Network, Transaction, WalletStore } from 'fabric-network';
|
import { Contract, Network, Transaction } from 'fabric-network';
|
||||||
import { mocked } from 'ts-jest/utils';
|
import { mocked } from 'ts-jest/utils';
|
||||||
import * as fabricProtos from 'fabric-protos';
|
import * as fabricProtos from 'fabric-protos';
|
||||||
|
|
||||||
|
const actualFabricNetwork = jest.requireActual('fabric-network');
|
||||||
|
const Wallet = actualFabricNetwork.Wallet;
|
||||||
|
const Wallets = actualFabricNetwork.Wallets;
|
||||||
|
|
||||||
const mockAsset1 = {
|
const mockAsset1 = {
|
||||||
ID: 'asset1',
|
ID: 'asset1',
|
||||||
Color: 'blue',
|
Color: 'blue',
|
||||||
|
|
@ -50,15 +54,8 @@ const {
|
||||||
DefaultEventHandlerStrategies,
|
DefaultEventHandlerStrategies,
|
||||||
DefaultQueryHandlerStrategies,
|
DefaultQueryHandlerStrategies,
|
||||||
Gateway,
|
Gateway,
|
||||||
Wallet,
|
|
||||||
Wallets,
|
|
||||||
}: FabricNetworkModule = jest.createMockFromModule('fabric-network');
|
}: FabricNetworkModule = jest.createMockFromModule('fabric-network');
|
||||||
|
|
||||||
const mockWalletStore = mock<WalletStore>();
|
|
||||||
mocked(Wallets.newInMemoryWallet).mockResolvedValue(
|
|
||||||
new Wallet(mockWalletStore)
|
|
||||||
);
|
|
||||||
|
|
||||||
const mockAssetExistsTransaction = mock<Transaction>();
|
const mockAssetExistsTransaction = mock<Transaction>();
|
||||||
mockAssetExistsTransaction.evaluate
|
mockAssetExistsTransaction.evaluate
|
||||||
.calledWith('asset1')
|
.calledWith('asset1')
|
||||||
|
|
@ -171,24 +168,11 @@ mockNetwork.getContract.calledWith('qscc').mockReturnValue(mockSystemContract);
|
||||||
|
|
||||||
mocked(Gateway.prototype.getNetwork).mockResolvedValue(mockNetwork);
|
mocked(Gateway.prototype.getNetwork).mockResolvedValue(mockNetwork);
|
||||||
|
|
||||||
// TODO remove this and use simpler mocks in fabric spec tests
|
|
||||||
const getMockedNetwork = (getContract = jest.fn()) => {
|
|
||||||
return mocked(Gateway.prototype.getNetwork).mockResolvedValue({
|
|
||||||
getGateway: jest.fn(),
|
|
||||||
getContract,
|
|
||||||
getChannel: jest.fn(),
|
|
||||||
addCommitListener: jest.fn(),
|
|
||||||
removeCommitListener: jest.fn(),
|
|
||||||
addBlockListener: jest.fn(),
|
|
||||||
removeBlockListener: jest.fn(),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
DefaultEventHandlerStrategies,
|
DefaultEventHandlerStrategies,
|
||||||
DefaultQueryHandlerStrategies,
|
DefaultQueryHandlerStrategies,
|
||||||
Contract,
|
Contract,
|
||||||
Gateway,
|
Gateway,
|
||||||
|
Wallet,
|
||||||
Wallets,
|
Wallets,
|
||||||
getMockedNetwork,
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,7 @@ import { Contract } from 'fabric-network';
|
||||||
import { getReasonPhrase, StatusCodes } from 'http-status-codes';
|
import { getReasonPhrase, StatusCodes } from 'http-status-codes';
|
||||||
import { Redis } from 'ioredis';
|
import { Redis } from 'ioredis';
|
||||||
import { AssetExistsError, AssetNotFoundError } from './errors';
|
import { AssetExistsError, AssetNotFoundError } from './errors';
|
||||||
import {
|
import { evatuateTransaction, submitTransaction } from './fabric';
|
||||||
evatuateTransaction,
|
|
||||||
submitTransaction,
|
|
||||||
getContractForOrg,
|
|
||||||
} from './fabric';
|
|
||||||
import { logger } from './logger';
|
import { logger } from './logger';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
@ -38,7 +34,9 @@ assetsRouter.get('/', async (req: Request, res: Response) => {
|
||||||
logger.debug('Get all assets request received');
|
logger.debug('Get all assets request received');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const contract: Contract = getContractForOrg(req).contract;
|
const mspId = req.user as string;
|
||||||
|
const contract = req.app.get(mspId).assetContract as Contract;
|
||||||
|
|
||||||
const data = await evatuateTransaction(contract, 'GetAllAssets');
|
const data = await evatuateTransaction(contract, 'GetAllAssets');
|
||||||
let assets = [];
|
let assets = [];
|
||||||
if (data.length > 0) {
|
if (data.length > 0) {
|
||||||
|
|
@ -77,8 +75,9 @@ assetsRouter.post(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const contract: Contract = getContractForOrg(req).contract;
|
const mspId = req.user as string;
|
||||||
const redis: Redis = req.app.get('redis');
|
const contract = req.app.get(mspId).assetContract as Contract;
|
||||||
|
const redis = req.app.get('redis') as Redis;
|
||||||
const assetId = req.body.id;
|
const assetId = req.body.id;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -128,7 +127,8 @@ assetsRouter.options('/:assetId', async (req: Request, res: Response) => {
|
||||||
logger.debug('Asset options request received for asset ID %s', assetId);
|
logger.debug('Asset options request received for asset ID %s', assetId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const contract: Contract = getContractForOrg(req).contract;
|
const mspId = req.user as string;
|
||||||
|
const contract = req.app.get(mspId).assetContract as Contract;
|
||||||
|
|
||||||
const data = await evatuateTransaction(contract, 'AssetExists', assetId);
|
const data = await evatuateTransaction(contract, 'AssetExists', assetId);
|
||||||
const exists = data.toString() === 'true';
|
const exists = data.toString() === 'true';
|
||||||
|
|
@ -167,7 +167,8 @@ assetsRouter.get('/:assetId', async (req: Request, res: Response) => {
|
||||||
logger.debug('Read asset request received for asset ID %s', assetId);
|
logger.debug('Read asset request received for asset ID %s', assetId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const contract: Contract = getContractForOrg(req).contract;
|
const mspId = req.user as string;
|
||||||
|
const contract = req.app.get(mspId).assetContract as Contract;
|
||||||
|
|
||||||
const data = await evatuateTransaction(contract, 'ReadAsset', assetId);
|
const data = await evatuateTransaction(contract, 'ReadAsset', assetId);
|
||||||
const asset = JSON.parse(data.toString());
|
const asset = JSON.parse(data.toString());
|
||||||
|
|
@ -225,8 +226,9 @@ assetsRouter.put(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const contract: Contract = getContractForOrg(req).contract;
|
const mspId = req.user as string;
|
||||||
const redis: Redis = req.app.get('redis');
|
const contract = req.app.get(mspId).assetContract as Contract;
|
||||||
|
const redis = req.app.get('redis') as Redis;
|
||||||
const assetId = req.params.assetId;
|
const assetId = req.params.assetId;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -294,8 +296,9 @@ assetsRouter.patch(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const contract: Contract = getContractForOrg(req).contract;
|
const mspId = req.user as string;
|
||||||
const redis: Redis = req.app.get('redis');
|
const contract = req.app.get(mspId).assetContract as Contract;
|
||||||
|
const redis = req.app.get('redis') as Redis;
|
||||||
const assetId = req.params.assetId;
|
const assetId = req.params.assetId;
|
||||||
const newOwner = req.body[0].value;
|
const newOwner = req.body[0].value;
|
||||||
|
|
||||||
|
|
@ -339,8 +342,9 @@ assetsRouter.patch(
|
||||||
assetsRouter.delete('/:assetId', async (req: Request, res: Response) => {
|
assetsRouter.delete('/:assetId', async (req: Request, res: Response) => {
|
||||||
logger.debug(req.body, 'Delete asset request received');
|
logger.debug(req.body, 'Delete asset request received');
|
||||||
|
|
||||||
const contract: Contract = getContractForOrg(req).contract;
|
const mspId = req.user as string;
|
||||||
const redis: Redis = req.app.get('redis');
|
const contract = req.app.get(mspId).assetContract as Contract;
|
||||||
|
const redis = req.app.get('redis') as Redis;
|
||||||
const assetId = req.params.assetId;
|
const assetId = req.params.assetId;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -17,16 +17,13 @@ export const fabricAPIKeyStrategy: HeaderAPIKeyStrategy =
|
||||||
false,
|
false,
|
||||||
function (apikey, done) {
|
function (apikey, done) {
|
||||||
logger.debug({ apikey }, 'Checking X-API-Key');
|
logger.debug({ apikey }, 'Checking X-API-Key');
|
||||||
const user: { org: string } = {
|
|
||||||
org: '',
|
|
||||||
};
|
|
||||||
if (apikey === config.org1ApiKey) {
|
if (apikey === config.org1ApiKey) {
|
||||||
user.org = config.identityNameOrg1;
|
const user = config.mspIdOrg1;
|
||||||
logger.debug('Organisation set to Org1');
|
logger.debug('User set to %s', user);
|
||||||
done(null, user);
|
done(null, user);
|
||||||
} else if (apikey === config.org2ApiKey) {
|
} else if (apikey === config.org2ApiKey) {
|
||||||
user.org = config.identityNameOrg2;
|
const user = config.mspIdOrg2;
|
||||||
logger.info('Organisation set to Org2');
|
logger.debug('User set to %s', user);
|
||||||
done(null, user);
|
done(null, user);
|
||||||
} else {
|
} else {
|
||||||
logger.debug({ apikey }, 'No valid X-API-Key');
|
logger.debug({ apikey }, 'No valid X-API-Key');
|
||||||
|
|
@ -44,7 +41,6 @@ export const authenticateApiKey = (
|
||||||
'headerapikey',
|
'headerapikey',
|
||||||
{ session: false },
|
{ session: false },
|
||||||
(err, user, _info) => {
|
(err, user, _info) => {
|
||||||
logger.debug({ user }, 'USERUSERUSER');
|
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
if (!user)
|
if (!user)
|
||||||
return res.status(UNAUTHORIZED).json({
|
return res.status(UNAUTHORIZED).json({
|
||||||
|
|
|
||||||
|
|
@ -50,12 +50,6 @@ export const asLocalhost = env
|
||||||
.example('true')
|
.example('true')
|
||||||
.asBoolStrict();
|
.asBoolStrict();
|
||||||
|
|
||||||
// TODO delete this and use mspIdOrg1
|
|
||||||
export const identityNameOrg1 = 'Org1';
|
|
||||||
|
|
||||||
// TODO delete this and use mspIdOrg2
|
|
||||||
export const identityNameOrg2 = 'Org2';
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The Org1 MSP ID
|
* The Org1 MSP ID
|
||||||
*/
|
*/
|
||||||
|
|
@ -128,7 +122,7 @@ export const connectionProfileOrg1 = env
|
||||||
.example(
|
.example(
|
||||||
'{"name":"test-network-org1","version":"1.0.0","client":{"organization":"Org1" ... }'
|
'{"name":"test-network-org1","version":"1.0.0","client":{"organization":"Org1" ... }'
|
||||||
)
|
)
|
||||||
.asJsonObject();
|
.asJsonObject() as Record<string, unknown>;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Certificate for the Org1 identity
|
* Certificate for the Org1 identity
|
||||||
|
|
@ -157,7 +151,7 @@ export const connectionProfileOrg2 = env
|
||||||
.example(
|
.example(
|
||||||
'{"name":"test-network-org2","version":"1.0.0","client":{"organization":"Org2" ... }'
|
'{"name":"test-network-org2","version":"1.0.0","client":{"organization":"Org2" ... }'
|
||||||
)
|
)
|
||||||
.asJsonObject();
|
.asJsonObject() as Record<string, unknown>;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Certificate for the Org2 identity
|
* Certificate for the Org2 identity
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,32 @@
|
||||||
import { retryTransaction, getGateway } from './fabric';
|
/*
|
||||||
import { getMockedNetwork } from './__mocks__/fabric-network';
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
createGateway,
|
||||||
|
createWallet,
|
||||||
|
getContracts,
|
||||||
|
getNetwork,
|
||||||
|
retryTransaction,
|
||||||
|
} from './fabric';
|
||||||
import * as config from './config';
|
import * as config from './config';
|
||||||
|
|
||||||
import IORedis from './__mocks__/IORedis';
|
import IORedis from './__mocks__/IORedis';
|
||||||
import { Redis } from 'ioredis';
|
import { Redis } from 'ioredis';
|
||||||
import { Contract } from 'fabric-network';
|
import {
|
||||||
|
Contract,
|
||||||
|
Gateway,
|
||||||
|
GatewayOptions,
|
||||||
|
Network,
|
||||||
|
Transaction,
|
||||||
|
Wallet,
|
||||||
|
} from 'fabric-network';
|
||||||
|
|
||||||
|
import { mock } from 'jest-mock-extended';
|
||||||
|
|
||||||
jest.mock('./config');
|
jest.mock('./config');
|
||||||
jest.mock('ioredis');
|
jest.mock('ioredis');
|
||||||
|
|
||||||
const redisOptions = {
|
const redisOptions = {
|
||||||
port: config.redisPort,
|
port: config.redisPort,
|
||||||
host: config.redisHost,
|
host: config.redisHost,
|
||||||
|
|
@ -17,6 +36,71 @@ const redisOptions = {
|
||||||
|
|
||||||
const redis = new IORedis(redisOptions) as unknown as Redis;
|
const redis = new IORedis(redisOptions) as unknown as Redis;
|
||||||
|
|
||||||
|
describe('Fabric', () => {
|
||||||
|
describe('createWallet', () => {
|
||||||
|
it('creates a wallet containing identities for both orgs', async () => {
|
||||||
|
const wallet = await createWallet();
|
||||||
|
|
||||||
|
expect(await wallet.list()).toStrictEqual(['Org1MSP', 'Org2MSP']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createGateway', () => {
|
||||||
|
it('creates a Gateway and connects using the provided arguments', async () => {
|
||||||
|
const connectionProfile = config.connectionProfileOrg1;
|
||||||
|
const identity = config.mspIdOrg1;
|
||||||
|
const mockWallet = mock<Wallet>();
|
||||||
|
|
||||||
|
const gateway = await createGateway(
|
||||||
|
connectionProfile,
|
||||||
|
identity,
|
||||||
|
mockWallet
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(gateway.connect).toBeCalledWith(
|
||||||
|
connectionProfile,
|
||||||
|
expect.objectContaining<GatewayOptions>({
|
||||||
|
wallet: mockWallet,
|
||||||
|
identity,
|
||||||
|
discovery: expect.any(Object),
|
||||||
|
eventHandlerOptions: expect.any(Object),
|
||||||
|
queryHandlerOptions: expect.any(Object),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getNetwork', () => {
|
||||||
|
it('gets a Network instance for the required channel from the Gateway', async () => {
|
||||||
|
const mockGateway = mock<Gateway>();
|
||||||
|
|
||||||
|
await getNetwork(mockGateway);
|
||||||
|
|
||||||
|
expect(mockGateway.getNetwork).toHaveBeenCalledWith(config.channelName);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getContracts', () => {
|
||||||
|
it('gets the asset and qscc contracts from the network', async () => {
|
||||||
|
const mockBasicContract = mock<Contract>();
|
||||||
|
const mockSystemContract = mock<Contract>();
|
||||||
|
const mockNetwork = mock<Network>();
|
||||||
|
mockNetwork.getContract
|
||||||
|
.calledWith(config.chaincodeName)
|
||||||
|
.mockReturnValue(mockBasicContract);
|
||||||
|
mockNetwork.getContract
|
||||||
|
.calledWith('qscc')
|
||||||
|
.mockReturnValue(mockSystemContract);
|
||||||
|
|
||||||
|
const contracts = await getContracts(mockNetwork);
|
||||||
|
|
||||||
|
expect(contracts).toStrictEqual({
|
||||||
|
assetContract: mockBasicContract,
|
||||||
|
qsccContract: mockSystemContract,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Testing retryTransaction', () => {
|
describe('Testing retryTransaction', () => {
|
||||||
const transactionId =
|
const transactionId =
|
||||||
'0ae62c01e4c4b112c3f3954a2f11243da76778e46df9ad2783bcbafc79652b95';
|
'0ae62c01e4c4b112c3f3954a2f11243da76778e46df9ad2783bcbafc79652b95';
|
||||||
|
|
@ -44,51 +128,39 @@ describe('Testing retryTransaction', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
it('Transaction failure, check redis increment func call', async () => {
|
it('Transaction failure, check redis increment func call', async () => {
|
||||||
jest.doMock('fabric-network');
|
const mockTransaction = mock<Transaction>();
|
||||||
const transaction = {
|
mockTransaction.submit.mockRejectedValue('MOCKERROR');
|
||||||
submit: jest.fn().mockRejectedValue({}),
|
const mockContract = mock<Contract>();
|
||||||
};
|
mockContract.deserializeTransaction.mockReturnValue(mockTransaction);
|
||||||
const mockedContact = {
|
|
||||||
deserializeTransaction: jest.fn().mockReturnValue(transaction),
|
|
||||||
};
|
|
||||||
const rejectableGetContract = jest
|
|
||||||
.fn()
|
|
||||||
.mockImplementation(() => mockedContact);
|
|
||||||
|
|
||||||
const network = getMockedNetwork(rejectableGetContract)('');
|
|
||||||
const contract: Contract = (await network).getContract('');
|
|
||||||
savedTransaction.retries = '3';
|
savedTransaction.retries = '3';
|
||||||
await retryTransaction(contract, redis, transactionId, savedTransaction);
|
await retryTransaction(
|
||||||
|
mockContract,
|
||||||
|
redis,
|
||||||
|
transactionId,
|
||||||
|
savedTransaction
|
||||||
|
);
|
||||||
expect(redis.hincrby).toHaveBeenCalledTimes(1);
|
expect(redis.hincrby).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Transaction successful, check redis delete key func call ', () => {
|
describe('Transaction successful, check redis delete key func call ', () => {
|
||||||
it('call redis increment', async () => {
|
it('call redis increment', async () => {
|
||||||
jest.doMock('fabric-network');
|
const mockTransaction = mock<Transaction>();
|
||||||
const transaction = {
|
mockTransaction.submit.mockResolvedValue(Buffer.from('{}'));
|
||||||
submit: jest.fn().mockResolvedValue({}),
|
const mockContract = mock<Contract>();
|
||||||
};
|
mockContract.deserializeTransaction.mockReturnValue(mockTransaction);
|
||||||
const mockedContact = {
|
|
||||||
deserializeTransaction: jest.fn().mockReturnValue(transaction),
|
|
||||||
};
|
|
||||||
const resolvableGetContract = jest
|
|
||||||
.fn()
|
|
||||||
.mockImplementation(() => mockedContact);
|
|
||||||
|
|
||||||
const network = getMockedNetwork(resolvableGetContract)('');
|
|
||||||
const contract: Contract = (await network).getContract('');
|
|
||||||
savedTransaction.retries = '3';
|
savedTransaction.retries = '3';
|
||||||
await retryTransaction(contract, redis, transactionId, savedTransaction);
|
await retryTransaction(
|
||||||
|
mockContract,
|
||||||
|
redis,
|
||||||
|
transactionId,
|
||||||
|
savedTransaction
|
||||||
|
);
|
||||||
|
|
||||||
expect(redis.del).toHaveBeenCalledTimes(1);
|
expect(redis.del).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Test getGateway', () => {
|
|
||||||
it('should throw error for invalid org name', async () => {
|
|
||||||
expect(async () => await getGateway('')).rejects.toThrow(
|
|
||||||
'Invalid org name for gateway'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ import {
|
||||||
BlockListener,
|
BlockListener,
|
||||||
BlockEvent,
|
BlockEvent,
|
||||||
TransactionEvent,
|
TransactionEvent,
|
||||||
|
Wallet,
|
||||||
} from 'fabric-network';
|
} from 'fabric-network';
|
||||||
import { Request } from 'express';
|
|
||||||
import { Redis } from 'ioredis';
|
import { Redis } from 'ioredis';
|
||||||
import * as config from './config';
|
import * as config from './config';
|
||||||
import { logger } from './logger';
|
import { logger } from './logger';
|
||||||
|
|
@ -31,63 +31,60 @@ import {
|
||||||
} from './errors';
|
} from './errors';
|
||||||
import protos from 'fabric-protos';
|
import protos from 'fabric-protos';
|
||||||
|
|
||||||
export const getNetwork = async (gateway: Gateway): Promise<Network> => {
|
/*
|
||||||
const network = await gateway.getNetwork(config.channelName);
|
* Creates an in memory wallet to hold credentials for an Org1 and Org2 user
|
||||||
return network;
|
*
|
||||||
};
|
* In this sample there is a single user for each MSP ID to demonstrate how
|
||||||
|
* a client app might submit transactions for different users
|
||||||
interface FabricConfigType {
|
*
|
||||||
identityName: string;
|
* Alternatively a REST server could use its own identity for all transactions,
|
||||||
mspId: string;
|
* or it could use credentials supplied in the REST requests
|
||||||
connectionProfile: { [key: string]: any };
|
*/
|
||||||
certificate: string;
|
export const createWallet = async (): Promise<Wallet> => {
|
||||||
privateKey: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ORG1_CONFIG = {
|
|
||||||
identityName: config.identityNameOrg1,
|
|
||||||
mspId: config.mspIdOrg1,
|
|
||||||
connectionProfile: config.connectionProfileOrg1,
|
|
||||||
certificate: config.certificateOrg1,
|
|
||||||
privateKey: config.privateKeyOrg1,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ORG2_CONFIG = {
|
|
||||||
identityName: config.identityNameOrg2,
|
|
||||||
mspId: config.mspIdOrg2,
|
|
||||||
connectionProfile: config.connectionProfileOrg2,
|
|
||||||
certificate: config.certificateOrg2,
|
|
||||||
privateKey: config.privateKeyOrg2,
|
|
||||||
};
|
|
||||||
|
|
||||||
const FabricDataMapper: { [key: string]: FabricConfigType } = {
|
|
||||||
[config.identityNameOrg1]: ORG1_CONFIG,
|
|
||||||
[config.identityNameOrg2]: ORG2_CONFIG,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getGateway = async (org: string): Promise<Gateway> => {
|
|
||||||
const fabricConfig = FabricDataMapper[org];
|
|
||||||
if (fabricConfig == undefined) {
|
|
||||||
throw new Error('Invalid org name for gateway');
|
|
||||||
}
|
|
||||||
logger.debug('Configuring fabric gateway for %s', org);
|
|
||||||
const wallet = await Wallets.newInMemoryWallet();
|
const wallet = await Wallets.newInMemoryWallet();
|
||||||
|
|
||||||
const x509Identity = {
|
const org1Identity = {
|
||||||
credentials: {
|
credentials: {
|
||||||
certificate: fabricConfig.certificate,
|
certificate: config.certificateOrg1,
|
||||||
privateKey: fabricConfig.privateKey,
|
privateKey: config.privateKeyOrg1,
|
||||||
},
|
},
|
||||||
mspId: fabricConfig.mspId,
|
mspId: config.mspIdOrg1,
|
||||||
type: 'X.509',
|
type: 'X.509',
|
||||||
};
|
};
|
||||||
|
|
||||||
await wallet.put(fabricConfig.identityName, x509Identity);
|
await wallet.put(config.mspIdOrg1, org1Identity);
|
||||||
|
|
||||||
|
const org2Identity = {
|
||||||
|
credentials: {
|
||||||
|
certificate: config.certificateOrg2,
|
||||||
|
privateKey: config.privateKeyOrg2,
|
||||||
|
},
|
||||||
|
mspId: config.mspIdOrg2,
|
||||||
|
type: 'X.509',
|
||||||
|
};
|
||||||
|
|
||||||
|
await wallet.put(config.mspIdOrg2, org2Identity);
|
||||||
|
|
||||||
|
return wallet;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a Gateway connection
|
||||||
|
*
|
||||||
|
* Gateway instances can and should be reused rather than connecting to submit every transaction
|
||||||
|
*/
|
||||||
|
export const createGateway = async (
|
||||||
|
connectionProfile: Record<string, unknown>,
|
||||||
|
identity: string,
|
||||||
|
wallet: Wallet
|
||||||
|
): Promise<Gateway> => {
|
||||||
|
logger.debug({ connectionProfile, identity }, 'Configuring gateway');
|
||||||
|
|
||||||
const gateway = new Gateway();
|
const gateway = new Gateway();
|
||||||
|
|
||||||
const connectOptions: GatewayOptions = {
|
const options: GatewayOptions = {
|
||||||
wallet,
|
wallet,
|
||||||
identity: fabricConfig.identityName,
|
identity,
|
||||||
discovery: { enabled: true, asLocalhost: config.asLocalhost },
|
discovery: { enabled: true, asLocalhost: config.asLocalhost },
|
||||||
eventHandlerOptions: {
|
eventHandlerOptions: {
|
||||||
commitTimeout: config.commitTimeout,
|
commitTimeout: config.commitTimeout,
|
||||||
|
|
@ -100,16 +97,22 @@ export const getGateway = async (org: string): Promise<Gateway> => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
await gateway.connect(fabricConfig.connectionProfile, connectOptions);
|
await gateway.connect(connectionProfile, options);
|
||||||
|
|
||||||
return gateway;
|
return gateway;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getNetwork = async (gateway: Gateway): Promise<Network> => {
|
||||||
|
const network = await gateway.getNetwork(config.channelName);
|
||||||
|
return network;
|
||||||
|
};
|
||||||
|
|
||||||
export const getContracts = async (
|
export const getContracts = async (
|
||||||
network: Network
|
network: Network
|
||||||
): Promise<{ contract: Contract; qscc: Contract }> => {
|
): Promise<{ assetContract: Contract; qsccContract: Contract }> => {
|
||||||
const contract = network.getContract(config.chaincodeName);
|
const assetContract = network.getContract(config.chaincodeName);
|
||||||
const qscc = network.getContract('qscc');
|
const qsccContract = network.getContract('qscc');
|
||||||
return { contract, qscc };
|
return { assetContract, qsccContract };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const startRetryLoop = (contract: Contract, redis: Redis): void => {
|
export const startRetryLoop = (contract: Contract, redis: Redis): void => {
|
||||||
|
|
@ -334,25 +337,15 @@ export const blockEventHandler = (redis: Redis): BlockListener => {
|
||||||
return blockListner;
|
return blockListner;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getChainInfo = async (qscc: Contract): Promise<boolean> => {
|
export const getBlockHeight = async (
|
||||||
try {
|
qscc: Contract
|
||||||
|
): Promise<number | Long.Long> => {
|
||||||
const data = await qscc.evaluateTransaction(
|
const data = await qscc.evaluateTransaction(
|
||||||
'GetChainInfo',
|
'GetChainInfo',
|
||||||
config.channelName
|
config.channelName
|
||||||
);
|
);
|
||||||
const info = protos.common.BlockchainInfo.decode(data);
|
const info = protos.common.BlockchainInfo.decode(data);
|
||||||
const blockHeight = info.height.toString();
|
const blockHeight = info.height;
|
||||||
logger.info('Current block height: %s', blockHeight);
|
logger.debug('Current block height: %d', blockHeight);
|
||||||
return true;
|
return blockHeight;
|
||||||
} catch (e) {
|
|
||||||
logger.error(e, 'Unable to get blockchain info');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getContractForOrg = (
|
|
||||||
req: Request
|
|
||||||
): { contract: Contract; qscc: Contract } => {
|
|
||||||
const user: { org: string } = req.user as { org: string };
|
|
||||||
return req.app.get('fabric')[user.org as string].contracts;
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,11 @@ import { createServer } from './server';
|
||||||
async function main() {
|
async function main() {
|
||||||
const app = await createServer();
|
const app = await createServer();
|
||||||
|
|
||||||
const contract: Contract =
|
// TODO block listener and retry logic currently only handles a single org!!!
|
||||||
app.get('fabric')[config.identityNameOrg1].contracts.contract;
|
// TODO should these be initialised here?
|
||||||
const redis: Redis = app.get('redis');
|
const contract = app.get(config.mspIdOrg1).assetContract as Contract;
|
||||||
const network: Network = app.get('fabric')[config.identityNameOrg1].network;
|
const redis = app.get('redis') as Redis;
|
||||||
|
const network = app.get('networkOrg1') as Network;
|
||||||
|
|
||||||
await network.addBlockListener(blockEventHandler(redis));
|
await network.addBlockListener(blockEventHandler(redis));
|
||||||
startRetryLoop(contract, redis);
|
startRetryLoop(contract, redis);
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,10 @@ import { assetsRouter } from './assets.router';
|
||||||
import { transactionsRouter } from './transactions.router';
|
import { transactionsRouter } from './transactions.router';
|
||||||
import {
|
import {
|
||||||
getContracts,
|
getContracts,
|
||||||
getGateway,
|
|
||||||
getNetwork,
|
getNetwork,
|
||||||
getChainInfo,
|
getBlockHeight,
|
||||||
getContractForOrg,
|
createGateway,
|
||||||
|
createWallet,
|
||||||
} from './fabric';
|
} from './fabric';
|
||||||
import { redis } from './redis';
|
import { redis } from './redis';
|
||||||
import { Contract } from 'fabric-network';
|
import { Contract } from 'fabric-network';
|
||||||
|
|
@ -74,29 +74,29 @@ export const createServer = async (): Promise<Application> => {
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
app.use(helmet());
|
app.use(helmet());
|
||||||
}
|
}
|
||||||
//
|
|
||||||
const gatewayOrg1 = await getGateway(config.identityNameOrg1);
|
const wallet = await createWallet();
|
||||||
const gatewayOrg2 = await getGateway(config.identityNameOrg2);
|
|
||||||
|
const gatewayOrg1 = await createGateway(
|
||||||
|
config.connectionProfileOrg1,
|
||||||
|
config.mspIdOrg1,
|
||||||
|
wallet
|
||||||
|
);
|
||||||
const networkOrg1 = await getNetwork(gatewayOrg1);
|
const networkOrg1 = await getNetwork(gatewayOrg1);
|
||||||
const networkOrg2 = await getNetwork(gatewayOrg2);
|
|
||||||
|
|
||||||
const contractsOrg1 = await getContracts(networkOrg1);
|
const contractsOrg1 = await getContracts(networkOrg1);
|
||||||
|
app.set(config.mspIdOrg1, contractsOrg1);
|
||||||
|
|
||||||
|
// TODO used for block listener, which needs fixing!
|
||||||
|
app.set('networkOrg1', networkOrg1);
|
||||||
|
|
||||||
|
const gatewayOrg2 = await createGateway(
|
||||||
|
config.connectionProfileOrg2,
|
||||||
|
config.mspIdOrg2,
|
||||||
|
wallet
|
||||||
|
);
|
||||||
|
const networkOrg2 = await getNetwork(gatewayOrg2);
|
||||||
const contractsOrg2 = await getContracts(networkOrg2);
|
const contractsOrg2 = await getContracts(networkOrg2);
|
||||||
|
app.set(config.mspIdOrg2, contractsOrg2);
|
||||||
const fabric = {
|
|
||||||
[config.identityNameOrg1]: {
|
|
||||||
gateway: gatewayOrg1,
|
|
||||||
contracts: contractsOrg1,
|
|
||||||
network: networkOrg1,
|
|
||||||
},
|
|
||||||
[config.identityNameOrg2]: {
|
|
||||||
gateway: gatewayOrg2,
|
|
||||||
contracts: contractsOrg2,
|
|
||||||
network: networkOrg2,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
app.set('fabric', fabric);
|
|
||||||
|
|
||||||
app.set('redis', redis);
|
app.set('redis', redis);
|
||||||
|
|
||||||
|
|
@ -107,32 +107,27 @@ export const createServer = async (): Promise<Application> => {
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
app.get('/live', async (_req, res) => {
|
app.get('/live', async (req, res) => {
|
||||||
_req.user = { org: config.identityNameOrg1 };
|
logger.debug(req.body, 'Liveness request received');
|
||||||
const qsccOrg1: Contract = getContractForOrg(_req).qscc;
|
|
||||||
const Org1Liveness = await getChainInfo(qsccOrg1);
|
const qsccOrg1 = req.app.get(config.mspIdOrg1).qsccContract as Contract;
|
||||||
logger.debug('Org1 liveness %s', Org1Liveness);
|
const qsccOrg2 = req.app.get(config.mspIdOrg2).qsccContract as Contract;
|
||||||
_req.user = { org: config.identityNameOrg2 };
|
|
||||||
const qsccOrg2: Contract = getContractForOrg(_req).qscc;
|
try {
|
||||||
const Org2Liveness = await getChainInfo(qsccOrg2);
|
await Promise.all([getBlockHeight(qsccOrg1), getBlockHeight(qsccOrg2)]);
|
||||||
logger.debug('Org2 liveness %s', Org2Liveness);
|
} catch (err) {
|
||||||
|
logger.error(err, 'Error processing liveness request');
|
||||||
|
|
||||||
if (Org1Liveness && Org2Liveness) {
|
|
||||||
res.status(OK).json({
|
|
||||||
status: getReasonPhrase(OK),
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.status(SERVICE_UNAVAILABLE).json({
|
res.status(SERVICE_UNAVAILABLE).json({
|
||||||
status: getReasonPhrase(SERVICE_UNAVAILABLE),
|
status: getReasonPhrase(SERVICE_UNAVAILABLE),
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// TODO delete me
|
res.status(OK).json({
|
||||||
app.get('/error', (_req, _res) => {
|
status: getReasonPhrase(OK),
|
||||||
throw new Error('Example error');
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use('/api/assets', authenticateApiKey, assetsRouter);
|
app.use('/api/assets', authenticateApiKey, assetsRouter);
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { Contract } from 'fabric-network';
|
||||||
import { protos } from 'fabric-protos';
|
import { protos } from 'fabric-protos';
|
||||||
import { getReasonPhrase, StatusCodes } from 'http-status-codes';
|
import { getReasonPhrase, StatusCodes } from 'http-status-codes';
|
||||||
import { Redis } from 'ioredis';
|
import { Redis } from 'ioredis';
|
||||||
import { evatuateTransaction, getContractForOrg } from './fabric';
|
import { evatuateTransaction } from './fabric';
|
||||||
import { logger } from './logger';
|
import { logger } from './logger';
|
||||||
import * as config from './config';
|
import * as config from './config';
|
||||||
import { TransactionNotFoundError } from './errors';
|
import { TransactionNotFoundError } from './errors';
|
||||||
|
|
@ -28,8 +28,9 @@ transactionsRouter.get(
|
||||||
let progress: Progress = 'DONE';
|
let progress: Progress = 'DONE';
|
||||||
let validationCode = '';
|
let validationCode = '';
|
||||||
|
|
||||||
const qscc: Contract = getContractForOrg(req).qscc;
|
const mspId = req.user as string;
|
||||||
const redis: Redis = req.app.get('redis');
|
const qscc = req.app.get(mspId).qsccContract as Contract;
|
||||||
|
const redis = req.app.get('redis') as Redis;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const savedTransaction = await (redis as Redis).hgetall(
|
const savedTransaction = await (redis as Redis).hgetall(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue