diff --git a/ironfish/src/wallet/__fixtures__/wallet.test.ts.fixture b/ironfish/src/wallet/__fixtures__/wallet.test.ts.fixture index ce480f7ba5..8e5eb1410f 100644 --- a/ironfish/src/wallet/__fixtures__/wallet.test.ts.fixture +++ b/ironfish/src/wallet/__fixtures__/wallet.test.ts.fixture @@ -8103,5 +8103,62 @@ "sequence": 1 } } + ], + "Wallet createTransaction should throw error if transaction exceeds maximum size": [ + { + "value": { + "encrypted": false, + "version": 4, + "id": "9750689e-2fb8-4682-bb44-398e7f1d5876", + "name": "a", + "spendingKey": "bff9625704dd16050f3f47e86e7b927c182b50d0d6b592db980969a2e8da07dd", + "viewKey": "5cea66f29bcb20ce16502736e8df819966089dba2d7a04c7587873bae5b0bd056822dbb2a8b1c546de5484474323efa8f99fe2ade280a496f624e2dacc97a4b0", + "incomingViewKey": "f4ab45f0354c5bc34ae5ed59516a5b43989a760038b13a77f7fa7b806a248c01", + "outgoingViewKey": "59086f1ba18db5bdd080ef1687586b18fdb12874fbefa6382e9ac093020f436e", + "publicAddress": "3a9ccabccf1c5c43faf585c5868c52b789d4cc637f35625b00a172b323544114", + "createdAt": { + "sequence": 1, + "hash": { + "type": "Buffer", + "data": "base64:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + } + }, + "scanningEnabled": true, + "proofAuthorizingKey": "3aeda7001389e5892759d9cd44cd90d6a4aba0542d90754afc55cbb1dbab5e08" + }, + "head": { + "hash": { + "type": "Buffer", + "data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY=" + }, + "sequence": 1 + } + }, + { + "header": { + "sequence": 2, + "previousBlockHash": "4791D7AE9F97DF100EF1558E84772D6A09B43762388283F75C6F20A32A88AA86", + "noteCommitment": { + "type": "Buffer", + "data": "base64:87WziYftOAPyFtS5rVkAQ0sW+vZQ8dTfsg9Jc6hq8HA=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:dGv/oHWnDcNihtSRMGnTPqyddl55UH8vVHAsi88hTco=" + }, + "target": "9282972777491357380673661573939192202192629606981189395159182914949423", + "randomness": "0", + "timestamp": 1733261439593, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 4, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAQqOshS/dbjwndXBZHbs5y919S0KrvROZ3qnprQS1BgmhcAFGABDAsZ7BzweJK8KgplR92F3aPnx2yfCCVgKk5lLq06WbEKvoIt0mR6E1bFSVA7AHzgfQhn0kD14hiuJbH550P2fGq+WYcxOALA0VTINO2U4w/mLw+sjXL+eDbpQAzn0qMBKYTCphVupLlndGtove+1fBkTmiiwuGKHgV5q2iOMuv1UuPZ3oZKnRXAPiZ105dArrbQwZx6SHYLP+pjBlu1VNJQtUOeEMUevrS8L+/ry/OPmCy5mxMawi/+o7b8su2lJt1kichaSYbCxjm+9lu3rbUu4zF5KYYh+Jdg6WgO10jOBlixnMmLb5AY0CkyU5nQDPqBXWZRa2dh8MdEsGrxS0Jw8n/rInT8eQxknrD8FlJMDFk4mB2o+w/gEdsvhGvVqM010FeFO6V98HBcVHSxanTHhA6tmDuk6SwFnYlo+6EwehvBw+9PdO7B+Rts8SZoNmn40vwwJJKt2VrGTY1G6TQQVkTNfSX6dCCZy+zs7DWVXucnyE0X7/eGAHT1hVyBb/VFhf9O2XKgoSFJuO8Yh4Xp5AVw/v8mco4TcH3zqqmFNKfcWDO88uBuj/lc6z4nBjbuUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwfDbJIcH+Rojdcjg3IiS2/oi/vYhkMGu0No4f1x1pFUf2uxM92ra4uXm4JgRdXzDoHQxsgICkr9TkwAtW7mwJBA==" + } + ] + } ] } \ No newline at end of file diff --git a/ironfish/src/wallet/errors.ts b/ironfish/src/wallet/errors.ts index 6cb3a5a9a2..c92a267da0 100644 --- a/ironfish/src/wallet/errors.ts +++ b/ironfish/src/wallet/errors.ts @@ -34,6 +34,10 @@ export class MaxMemoLengthError extends Error { } } +export class MaxTransactionSizeError extends Error { + name = this.constructor.name +} + export class DuplicateAccountNameError extends Error { name = this.constructor.name diff --git a/ironfish/src/wallet/wallet.test.ts b/ironfish/src/wallet/wallet.test.ts index 3398ce23d0..3b76d0e33a 100644 --- a/ironfish/src/wallet/wallet.test.ts +++ b/ironfish/src/wallet/wallet.test.ts @@ -6,7 +6,7 @@ import { BufferMap, BufferSet } from 'buffer-map' import { v4 as uuid } from 'uuid' import { Assert } from '../assert' import { Blockchain } from '../blockchain' -import { VerificationResultReason } from '../consensus' +import { VerificationResultReason, Verifier } from '../consensus' import { RawTransaction } from '../primitives' import { TransactionVersion } from '../primitives/transaction' import { @@ -28,6 +28,7 @@ import { DuplicateAccountNameError, DuplicateSpendingKeyError, MaxMemoLengthError, + MaxTransactionSizeError, } from './errors' import { toAccountImport } from './exporter' import { AssetStatus, Wallet } from './wallet' @@ -1459,6 +1460,33 @@ describe('Wallet', () => { // no spends needed expect(rawTransaction.spends.length).toBe(0) }) + + it('should throw error if transaction exceeds maximum size', async () => { + const { node } = nodeTest + + const account = await useAccountFixture(node.wallet, 'a') + + const block1 = await useMinerBlockFixture(node.chain, undefined, account, node.wallet) + await expect(node.chain).toAddBlock(block1) + await node.wallet.scan() + + // Mock verifier to only allow transactions of size 0 + jest.spyOn(Verifier, 'getMaxTransactionBytes').mockImplementation((_) => 0) + + const promise = nodeTest.wallet.createTransaction({ + account: account, + fee: 0n, + outputs: [ + { + publicAddress: account.publicAddress, + amount: 1n, + memo: Buffer.alloc(0), + assetId: Asset.nativeId(), + }, + ], + }) + await expect(promise).rejects.toThrow(MaxTransactionSizeError) + }) }) describe('getTransactionStatus', () => { diff --git a/ironfish/src/wallet/wallet.ts b/ironfish/src/wallet/wallet.ts index d8cad39384..8d8f21452f 100644 --- a/ironfish/src/wallet/wallet.ts +++ b/ironfish/src/wallet/wallet.ts @@ -51,6 +51,7 @@ import { DuplicateMultisigSecretNameError, DuplicateSpendingKeyError, MaxMemoLengthError, + MaxTransactionSizeError, NotEnoughFundsError, } from './errors' import { isMultisigSignerImport } from './exporter' @@ -934,6 +935,15 @@ export class Wallet { }) } + const maxTransactionSize = Verifier.getMaxTransactionBytes( + this.consensus.parameters.maxBlockSizeBytes, + ) + if (raw.postedSize() > maxTransactionSize) { + throw new MaxTransactionSizeError( + `Proposed transaction is larger than maximum transaction size of ${maxTransactionSize} bytes`, + ) + } + return raw } finally { unlock()