Skip to content

Commit

Permalink
feat: add transaction oversized on create message (#1117)
Browse files Browse the repository at this point in the history
Signed-off-by: Svetoslav Borislavov <[email protected]>
  • Loading branch information
SvetBorislavov authored Nov 5, 2024
1 parent 7d40aa0 commit 10eb3fc
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 16 deletions.
40 changes: 24 additions & 16 deletions back-end/apps/api/src/transactions/transactions.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import {
TransactionId,
Timestamp,
Client,
FileUpdateTransaction,
} from '@hashgraph/sdk';

import { NOTIFICATIONS_SERVICE, MirrorNodeService, ErrorCodes } from '@app/common';
Expand All @@ -31,6 +30,7 @@ import {
notifyWaitingForSignatures,
notifySyncIndicators,
MirrorNetworkGRPC,
isTransactionBodyOverMaxSize,
} from '@app/common/utils';
import {
Transaction,
Expand Down Expand Up @@ -383,6 +383,7 @@ describe('TransactionsService', () => {
});
jest.spyOn(PublicKey.prototype, 'verify').mockReturnValueOnce(true);
jest.mocked(isExpired).mockReturnValueOnce(false);
jest.mocked(isTransactionBodyOverMaxSize).mockReturnValueOnce(false);
transactionsRepo.count.mockResolvedValueOnce(0);
jest.spyOn(MirrorNetworkGRPC, 'fromBaseURL').mockReturnValueOnce(MirrorNetworkGRPC.TESTNET);
jest.mocked(getClientFromNetwork).mockResolvedValueOnce(client);
Expand Down Expand Up @@ -440,8 +441,8 @@ describe('TransactionsService', () => {
await expect(service.createTransaction(dto, user as User)).rejects.toThrow(ErrorCodes.SNMP);
});

it.skip('should throw on transaction create if unsupported type', async () => {
const sdkTransaction = new FileUpdateTransaction();
it('should throw on transaction create if expired', async () => {
const sdkTransaction = new AccountCreateTransaction();

const dto: CreateTransactionDto = {
name: 'Transaction 1',
Expand All @@ -456,14 +457,15 @@ describe('TransactionsService', () => {
usr.keys = userKeys;
});
jest.spyOn(PublicKey.prototype, 'verify').mockReturnValueOnce(true);
jest.mocked(isExpired).mockReturnValueOnce(true);

await expect(service.createTransaction(dto, user as User)).rejects.toThrow(
'File Update/Append transactions are not currently supported',
);
await expect(service.createTransaction(dto, user as User)).rejects.toThrow(ErrorCodes.TE);
});

it('should throw on transaction create if expired', async () => {
const sdkTransaction = new AccountCreateTransaction();
it('should throw on transaction create if save fails', async () => {
const sdkTransaction = new AccountCreateTransaction().setTransactionId(
new TransactionId(AccountId.fromString('0.0.1'), Timestamp.fromDate(new Date())),
);

const dto: CreateTransactionDto = {
name: 'Transaction 1',
Expand All @@ -474,16 +476,25 @@ describe('TransactionsService', () => {
mirrorNetwork: 'testnet',
};

const client = Client.forTestnet();

jest.mocked(attachKeys).mockImplementationOnce(async (usr: User) => {
usr.keys = userKeys;
});
jest.spyOn(PublicKey.prototype, 'verify').mockReturnValueOnce(true);
jest.mocked(isExpired).mockReturnValueOnce(true);
jest.mocked(isExpired).mockReturnValueOnce(false);
jest.mocked(isTransactionBodyOverMaxSize).mockReturnValueOnce(false);
transactionsRepo.count.mockResolvedValueOnce(0);
jest.spyOn(MirrorNetworkGRPC, 'fromBaseURL').mockReturnValueOnce(MirrorNetworkGRPC.TESTNET);
jest.mocked(getClientFromNetwork).mockResolvedValueOnce(client);
transactionsRepo.save.mockRejectedValueOnce(new Error('Failed to save'));

await expect(service.createTransaction(dto, user as User)).rejects.toThrow(ErrorCodes.TE);
await expect(service.createTransaction(dto, user as User)).rejects.toThrow(ErrorCodes.FST);

client.close();
});

it('should throw on transaction create if save fails', async () => {
it('should throw on transaction create if transaction over max size', async () => {
const sdkTransaction = new AccountCreateTransaction().setTransactionId(
new TransactionId(AccountId.fromString('0.0.1'), Timestamp.fromDate(new Date())),
);
Expand All @@ -504,12 +515,9 @@ describe('TransactionsService', () => {
});
jest.spyOn(PublicKey.prototype, 'verify').mockReturnValueOnce(true);
jest.mocked(isExpired).mockReturnValueOnce(false);
transactionsRepo.count.mockResolvedValueOnce(0);
jest.spyOn(MirrorNetworkGRPC, 'fromBaseURL').mockReturnValueOnce(MirrorNetworkGRPC.TESTNET);
jest.mocked(getClientFromNetwork).mockResolvedValueOnce(client);
transactionsRepo.save.mockRejectedValueOnce(new Error('Failed to save'));
jest.mocked(isTransactionBodyOverMaxSize).mockReturnValueOnce(true);

await expect(service.createTransaction(dto, user as User)).rejects.toThrow(ErrorCodes.FST);
await expect(service.createTransaction(dto, user as User)).rejects.toThrow(ErrorCodes.TOS);

client.close();
});
Expand Down
6 changes: 6 additions & 0 deletions back-end/apps/api/src/transactions/transactions.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
notifyWaitingForSignatures,
notifySyncIndicators,
ErrorCodes,
isTransactionBodyOverMaxSize,
} from '@app/common';

import { CreateTransactionDto } from './dto';
Expand Down Expand Up @@ -326,6 +327,11 @@ export class TransactionsService {
const sdkTransaction = SDKTransaction.fromBytes(dto.transactionBytes);
if (isExpired(sdkTransaction)) throw new BadRequestException(ErrorCodes.TE);

/* Check if the transaction body is over the max size */
if (isTransactionBodyOverMaxSize(sdkTransaction)) {
throw new BadRequestException(ErrorCodes.TOS);
}

/* Check if the transaction already exists */
const countExisting = await this.repo.count({
where: [
Expand Down
46 changes: 46 additions & 0 deletions back-end/apps/api/test/spec/transaction.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
AccountCreateTransaction,
AccountUpdateTransaction,
Hbar,
KeyList,
PrivateKey,
TransferTransaction,
} from '@hashgraph/sdk';

Expand Down Expand Up @@ -293,6 +295,50 @@ describe('Transactions (e2e)', () => {
expect(countAfter).toEqual(countBefore);
});

it(
'(POST) should not create a transaction that is oversized',
async () => {
const countBefore = await repo.count();

const keylist = new KeyList();
for (let i = 0; i < 180; i++) {
keylist.push(PrivateKey.generate().publicKey);
}
const transaction = new AccountCreateTransaction()
.setTransactionId(createTransactionId(localnet1003.accountId, new Date()))
.setKey(keylist);

const buffer = Buffer.from(transaction.toBytes()).toString('hex');

const userKey = await getUserKey(user.id, localnet1003.publicKeyRaw);

if (userKey === null) throw new Error('User key not found');

const dto = {
name: 'Oversized Account Create Transaction',
description: 'This is a oversized account create transaction',
transactionBytes: buffer,
creatorKeyId: userKey.id,
signature: Buffer.from(localnet1003.privateKey.sign(transaction.toBytes())).toString(
'hex',
),
mirrorNetwork: localnet1003.mirrorNetwork,
};

const { status, body } = await endpoint.post(dto, null, userAuthToken);
const countAfter = await repo.count();

expect(status).toEqual(400);
expect(body).toMatchObject(
expect.objectContaining({
code: ErrorCodes.TOS,
}),
);
expect(countAfter).toEqual(countBefore);
},
30 * 1_000,
);

it('(GET) should get all transactions for user', async () => {
const { status, body } = await endpoint.get(null, userAuthToken, 'page=1&size=99');

Expand Down
2 changes: 2 additions & 0 deletions back-end/libs/common/src/constants/errorCodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export enum ErrorCodes {
KNF = 'KNF',
NNF = 'NNF',
IB = 'IB',
TOS = 'TOS',
}

export const ErrorMessages: { [key in ErrorCodes]: string } = {
Expand Down Expand Up @@ -65,4 +66,5 @@ export const ErrorMessages: { [key in ErrorCodes]: string } = {
[ErrorCodes.KNF]: 'Key not found',
[ErrorCodes.NNF]: 'Notification not found',
[ErrorCodes.IB]: 'Invalid body',
[ErrorCodes.TOS]: 'Transaction is over the size limit and cannot be executed',
};
5 changes: 5 additions & 0 deletions back-end/libs/common/src/utils/sdk/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,3 +253,8 @@ export async function isTransactionOverMaxSize(transaction: SDKTransaction) {
const request = await transaction._makeRequestAsync();
return proto.Transaction.encode(request).finish().length > MAX_TRANSACTION_BYTE_SIZE;
}

export function isTransactionBodyOverMaxSize(transaction: SDKTransaction) {
const bodyBytes = getTransactionBodyBytes(transaction);
return bodyBytes.length > MAX_TRANSACTION_BYTE_SIZE;
}
2 changes: 2 additions & 0 deletions front-end/src/main/shared/constants/errorCodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export enum ErrorCodes {
KNF = 'KNF',
NNF = 'NNF',
IB = 'IB',
TOS = 'TOS',
}

export const ErrorMessages: { [key in ErrorCodes]: string } = {
Expand Down Expand Up @@ -65,4 +66,5 @@ export const ErrorMessages: { [key in ErrorCodes]: string } = {
[ErrorCodes.KNF]: 'Key not found',
[ErrorCodes.NNF]: 'Notification not found',
[ErrorCodes.IB]: 'Invalid body',
[ErrorCodes.TOS]: 'Transaction is over the size limit and cannot be executed',
};

0 comments on commit 10eb3fc

Please sign in to comment.