Move error related functions to errors.ts

Signed-off-by: James Taylor <jamest@uk.ibm.com>
This commit is contained in:
James Taylor 2021-09-14 17:07:15 +01:00
parent fd269237d4
commit 0456a9e94c
4 changed files with 211 additions and 111 deletions

View file

@ -0,0 +1,102 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
import {
AssetExistsError,
AssetNotFoundError,
TransactionError,
TransactionNotFoundError,
handleError,
isDuplicateTransactionError,
} from './errors';
describe('Errors', () => {
describe('isDuplicateTransactionError', () => {
it('returns true for an error with duplicate transaction endorsement details', () => {
const mockDuplicateTransactionError = {
errors: [
{
endorsements: [
{
details: 'duplicate transaction found',
},
],
},
],
};
expect(isDuplicateTransactionError(mockDuplicateTransactionError)).toBe(
true
);
});
it('returns false for an error without duplicate transaction endorsement details', () => {
const mockDuplicateTransactionError = {
errors: [
{
endorsements: [
{
details: 'mock endorsement details',
},
],
},
],
};
expect(isDuplicateTransactionError(mockDuplicateTransactionError)).toBe(
false
);
});
});
describe('handleError', () => {
it.each([
'the asset GOCHAINCODE already exists',
'Asset JAVACHAINCODE already exists',
'The asset JSCHAINCODE already exists',
])(
'returns an AssetExistsError for errors with an asset already exists message: %s',
(msg) => {
expect(handleError('txn1', new Error(msg))).toStrictEqual(
new AssetExistsError(msg, 'txn1')
);
}
);
it.each([
'the asset GOCHAINCODE does not exist',
'Asset JAVACHAINCODE does not exist',
'The asset JSCHAINCODE does not exist',
])(
'returns an AssetNotFoundError for errors with an asset does not exist message: %s',
(msg) => {
expect(handleError('txn1', new Error(msg))).toStrictEqual(
new AssetNotFoundError(msg, 'txn1')
);
}
);
it('returns a TransactionNotFoundError for errors with a transaction not found message', () => {
expect(
handleError(
'txn1',
new Error(
'Failed to get transaction with id txn, error Entry not found in index'
)
)
).toStrictEqual(
new TransactionNotFoundError(
'Failed to get transaction with id txn, error Entry not found in index',
'txn1'
)
);
});
it('returns a TransactionError for errors with other messages', () => {
expect(handleError('txn1', new Error('MOCK ERROR'))).toStrictEqual(
new TransactionError('Transaction error', 'txn1')
);
});
});
});

View file

@ -2,6 +2,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { logger } from './logger';
export class TransactionError extends Error {
transactionId: string;
@ -43,3 +45,91 @@ export class AssetNotFoundError extends TransactionError {
this.name = 'AssetNotFoundError';
}
}
/*
* Checks whether an error was caused by a duplicate transaction.
*
* Checking error strings like this is not ideal, unfortunately it appears to
* be the only option. In this case it would be better to check for the
* DUPLICATE_TXID TxValidationCode somehow but that does not seem to be
* possible.
*/
export const isDuplicateTransactionError = (error: {
errors: { endorsements: { details: string }[] }[];
}): boolean => {
try {
const isDuplicateTxn = error?.errors?.some((err) =>
err?.endorsements?.some((endorsement) =>
endorsement?.details?.startsWith('duplicate transaction found')
)
);
return isDuplicateTxn;
} catch (err) {
logger.warn(err, 'Error checking for duplicate transaction');
}
return false;
};
/*
* Handles errors from evaluating and submitting transactions.
*
* As with duplicate transaction errors, checking error strings like this is
* not ideal. Unfortunately the chaincode samples do not use error codes so
* again it's the only option. The error message text is not even the same for
* the Go, Java, and Javascript implementations of the chaincode!
*/
export const handleError = (transactionId: string, err: Error): Error => {
// This regex needs to match the following error messages:
// "the asset %s already exists"
// "The asset ${id} already exists"
// "Asset %s already exists"
const assetAlreadyExistsRegex = /([tT]he )?[aA]sset \w* already exists/g;
const assetAlreadyExistsMatch = err.message.match(assetAlreadyExistsRegex);
logger.debug(
{ message: err.message, result: assetAlreadyExistsMatch },
'Checking for asset already exists message'
);
if (assetAlreadyExistsMatch) {
return new AssetExistsError(assetAlreadyExistsMatch[0], transactionId);
}
// This regex needs to match the following error messages:
// "the asset %s does not exist"
// "The asset ${id} does not exist"
// "Asset %s does not exist"
const assetDoesNotExistRegex = /([tT]he )?[aA]sset \w* does not exist/g;
const assetDoesNotExistMatch = err.message.match(assetDoesNotExistRegex);
logger.debug(
{ message: err.message, result: assetDoesNotExistMatch },
'Checking for asset does not exist message'
);
if (assetDoesNotExistMatch) {
return new AssetNotFoundError(assetDoesNotExistMatch[0], transactionId);
}
// This regex needs to match the following error messages:
// "Failed to get transaction with id %s, error Entry not found in index"
const transactionDoesNotExistRegex =
/Failed to get transaction with id [^,]*, error Entry not found in index/g;
const transactionDoesNotExistMatch = err.message.match(
transactionDoesNotExistRegex
);
logger.debug(
{ message: err.message, result: transactionDoesNotExistMatch },
'Checking for transaction does not exist message'
);
if (transactionDoesNotExistMatch) {
return new TransactionNotFoundError(
transactionDoesNotExistMatch[0],
transactionId
);
}
logger.error(
{ transactionId: transactionId, error: err },
'Unhandled transaction error'
);
return new TransactionError('Transaction error', transactionId);
};

View file

@ -320,35 +320,25 @@ describe('Fabric', () => {
expect(result.toString()).toBe(mockPayload.toString());
});
it.each([
'the asset GOCHAINCODE already exists',
'Asset JAVACHAINCODE already exists',
'The asset JSCHAINCODE already exists',
])(
'throws an AssetExistsError an asset already exists error occurs: %s',
async (msg) => {
mockTransaction.evaluate.mockRejectedValue(new Error(msg));
it('throws an AssetExistsError an asset already exists error occurs', async () => {
mockTransaction.evaluate.mockRejectedValue(
new Error('The asset JSCHAINCODE already exists')
);
await expect(async () => {
await evatuateTransaction(mockContract, 'txn', 'arga', 'argb');
}).rejects.toThrow(AssetExistsError);
}
);
await expect(async () => {
await evatuateTransaction(mockContract, 'txn', 'arga', 'argb');
}).rejects.toThrow(AssetExistsError);
});
it.each([
'the asset GOCHAINCODE does not exist',
'Asset JAVACHAINCODE does not exist',
'The asset JSCHAINCODE does not exist',
])(
'throws an AssetNotFoundError if an asset does not exist error occurs: %s',
async (msg) => {
mockTransaction.evaluate.mockRejectedValue(new Error(msg));
it('throws an AssetNotFoundError if an asset does not exist error occurs', async () => {
mockTransaction.evaluate.mockRejectedValue(
new Error('The asset JSCHAINCODE does not exist')
);
await expect(async () => {
await evatuateTransaction(mockContract, 'txn', 'arga', 'argb');
}).rejects.toThrow(AssetNotFoundError);
}
);
await expect(async () => {
await evatuateTransaction(mockContract, 'txn', 'arga', 'argb');
}).rejects.toThrow(AssetNotFoundError);
});
it('throws a TransactionNotFoundError if a transaction not found error occurs', async () => {
mockTransaction.evaluate.mockRejectedValue(

View file

@ -25,12 +25,7 @@ import {
incrementRetryCount,
TransactionDetails,
} from './redis';
import {
AssetExistsError,
AssetNotFoundError,
TransactionError,
TransactionNotFoundError,
} from './errors';
import { handleError, isDuplicateTransactionError } from './errors';
import protos from 'fabric-protos';
/*
@ -247,63 +242,6 @@ export const submitTransaction = async (
return txnId;
};
// Unfortunately the chaincode samples do not use error codes, and the error
// message text is not the same for each implementation
// TODO move to errors.ts?
const handleError = (transactionId: string, err: Error): Error => {
// This regex needs to match the following error messages:
// "the asset %s already exists"
// "The asset ${id} already exists"
// "Asset %s already exists"
const assetAlreadyExistsRegex = /([tT]he )?[aA]sset \w* already exists/g;
const assetAlreadyExistsMatch = err.message.match(assetAlreadyExistsRegex);
logger.debug(
{ message: err.message, result: assetAlreadyExistsMatch },
'Checking for asset already exists message'
);
if (assetAlreadyExistsMatch) {
return new AssetExistsError(assetAlreadyExistsMatch[0], transactionId);
}
// This regex needs to match the following error messages:
// "the asset %s does not exist"
// "The asset ${id} does not exist"
// "Asset %s does not exist"
const assetDoesNotExistRegex = /([tT]he )?[aA]sset \w* does not exist/g;
const assetDoesNotExistMatch = err.message.match(assetDoesNotExistRegex);
logger.debug(
{ message: err.message, result: assetDoesNotExistMatch },
'Checking for asset does not exist message'
);
if (assetDoesNotExistMatch) {
return new AssetNotFoundError(assetDoesNotExistMatch[0], transactionId);
}
// This regex needs to match the following error messages:
// "Failed to get transaction with id %s, error Entry not found in index"
const transactionDoesNotExistRegex =
/Failed to get transaction with id [^,]*, error Entry not found in index/g;
const transactionDoesNotExistMatch = err.message.match(
transactionDoesNotExistRegex
);
logger.debug(
{ message: err.message, result: transactionDoesNotExistMatch },
'Checking for transaction does not exist message'
);
if (transactionDoesNotExistMatch) {
return new TransactionNotFoundError(
transactionDoesNotExistMatch[0],
transactionId
);
}
logger.error(
{ transactionId: transactionId, error: err },
'Unhandled transaction error'
);
return new TransactionError('Transaction error', transactionId);
};
/*
* Retry a transaction
*
@ -357,26 +295,6 @@ const retryTransaction = async (
}
};
// TODO move to errors.ts?
const isDuplicateTransactionError = (error: {
errors: { endorsements: { details: string }[] }[];
}) => {
// TODO this is horrible! Isn't it possible to check for TxValidationCode DUPLICATE_TXID somehow?
try {
const isDuplicateTxn = error?.errors?.some((err) =>
err?.endorsements?.some((endorsement) =>
endorsement?.details?.startsWith('duplicate transaction found')
)
);
return isDuplicateTxn;
} catch (err) {
logger.warn(err, 'Error checking for duplicate transaction');
}
return false;
};
/*
* Block event listener to handle successful transactions
*
@ -409,9 +327,9 @@ export const blockEventHandler = (redis: Redis): BlockListener => {
/*
* Get the current block height
*
*
* This example of using a system contract is used for the liveness REST
* endpoint
* endpoint
*/
export const getBlockHeight = async (
qscc: Contract