From 53b3fe2b8a6b56940ba8e3f05b0593f008aee72e Mon Sep 17 00:00:00 2001 From: Joe Date: Wed, 26 Apr 2023 12:27:04 -0700 Subject: [PATCH] ifl-744 add test/updates for calculating raw transaction size --- .../src/structs/note_encrypted.rs | 6 --- .../rawTransaction.test.ts.fixture | 39 ++++++++++++++ ironfish/src/primitives/noteEncrypted.ts | 1 + .../src/primitives/rawTransaction.test.ts | 4 ++ ironfish/src/primitives/rawTransaction.ts | 53 ++++++++++++++++--- ironfish/src/primitives/spend.ts | 2 +- ironfish/src/primitives/transaction.ts | 6 +-- .../rpc/routes/wallet/createTransaction.ts | 4 +- ironfish/src/wallet/wallet.ts | 4 +- 9 files changed, 100 insertions(+), 19 deletions(-) diff --git a/ironfish-rust-nodejs/src/structs/note_encrypted.rs b/ironfish-rust-nodejs/src/structs/note_encrypted.rs index 37060973a8..5866aa4fb1 100644 --- a/ironfish-rust-nodejs/src/structs/note_encrypted.rs +++ b/ironfish-rust-nodejs/src/structs/note_encrypted.rs @@ -28,12 +28,6 @@ pub const ENCRYPTED_NOTE_PLAINTEXT_LENGTH: u32 = ENCRYPTED_NOTE_SIZE as u32 + MA #[napi] pub const ENCRYPTED_NOTE_LENGTH: u32 = NOTE_ENCRYPTION_KEY_LENGTH + ENCRYPTED_NOTE_PLAINTEXT_LENGTH + 96; -// 32 value commitment -//+ 32 note commitment -//+ 32 ephemeral public key -//+ 120 encrypted note -//+ 80 note encryption keys -//= 296 bytes #[napi(js_name = "NoteEncrypted")] pub struct NativeNoteEncrypted { diff --git a/ironfish/src/primitives/__fixtures__/rawTransaction.test.ts.fixture b/ironfish/src/primitives/__fixtures__/rawTransaction.test.ts.fixture index e06b515b79..c985f718bf 100644 --- a/ironfish/src/primitives/__fixtures__/rawTransaction.test.ts.fixture +++ b/ironfish/src/primitives/__fixtures__/rawTransaction.test.ts.fixture @@ -196,5 +196,44 @@ "publicAddress": "c96cc22b0a0e958c055eb6b357418e4a779b90f85750dfc9e618b36688eab944", "createdAt": null } + ], + "RawTransaction should equal": [ + { + "version": 2, + "id": "fb52a88e-3e8c-466f-bd34-c4dd7d75532d", + "name": "test", + "spendingKey": "4c3d51b6596832c4dc539aa5d6ecd036d338fbc21b6a64d26a7ccc838a87a000", + "viewKey": "a33795eafe7851f52fc4bfbe2a2ff60ad7bb409d6cc4bc19996a4d941a4f8cc526aee6da70f63bcac90ec4bd678b16bbe64299bed09da120bf28e321d3aab1aa", + "incomingViewKey": "cee508b90f3963e4099798a355ed0da0e123b193ff0f7cd6b7b4596caadf1e02", + "outgoingViewKey": "79f6d37e22fd3d9ca88b0d26a1dedebb4ef7e89fa31ac7632c1ee06a3f30628a", + "publicAddress": "6b971b1e7217ff4fe81c457c65d85ce8d7723fa52061a2194041efb556a99c19", + "createdAt": null + }, + { + "header": { + "sequence": 2, + "previousBlockHash": "88B6FA8D745A4E53BDA001318E60B04EE2E4EE06A38095688D58049CB6F15ACA", + "noteCommitment": { + "type": "Buffer", + "data": "base64:9DdIBOwIdxNRoPgyIcDSxyCQwTV2oALwWD2w/K2dPEM=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:v2mDXwx+HgVnlnZwDaTJfEzBSmmEHpHcvjPYjXbGJOg=" + }, + "target": "883423532389192164791648750371459257913741948437809479060803100646309888", + "randomness": "0", + "timestamp": 1682541250417, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 4, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAqZz03fllUAxM/C47JUjjmcVrt8H1IKtQY662BbzJsHOBBsM9VGn0kEUU/Da9UFfHfzW+n3Zud2xziSEG76e0SpoXl66oysLTkLvWcTycsumpWYVw5JNcPU7SBt2HACwfKN4awPDtSihU8dsjIbE+NBdMfLF6KMdBKjTC5fykZl0AjgmGflqizCNi3uucrM2Nm3HYjXIJvdv/R3AXA4TZD8BhduPvJEagPzLOcFxDwWuKLBAyjSrWU5SOmwrcqvJtvtJCwbIFNiUOIr5CO09PL+5ko/oVP4eZ2EHYNoKZRX0T8iZvPxVLuVIDlocEOn8zt2th/D2Erz1YHVbMw6B2VVoe9pmeJqQwwmI8yg1doZxhvPw8I7YjBPzwSGNQQp9x9vuW/n4O+bcopYA56O+eepc1r8CWU7+qmP9WjBCCskhK745L4B7hI3MTNwAyJ96m4S+PkHydl7m6QIZYIUGCLuYZSXKMd+QFXQJwfiyE8P+0I+gTtjaglmboCIZNCTyLbI4re2Xa2SXzqQk9agNVcyKHqG1j0D4qMVDBMCuvuhVCyFjBIy2yzQ9MCbV4mkE9AEsDjBdn4ehLB6Pi82M43awCFE0SblLFcEjryLy3OoZ3pK3IBVhliUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwxGhqNFhJQIu38oTMYoOTmInFtc4F/l21An0Z112s1dWXZ6OIGS8Xsdi8KJwm8vzAtBo7osJ/1NFTnjYT9uznAw==" + } + ] + } ] } \ No newline at end of file diff --git a/ironfish/src/primitives/noteEncrypted.ts b/ironfish/src/primitives/noteEncrypted.ts index 89ce607130..0464691abc 100644 --- a/ironfish/src/primitives/noteEncrypted.ts +++ b/ironfish/src/primitives/noteEncrypted.ts @@ -13,6 +13,7 @@ import bufio from 'bufio' import { Serde } from '../serde' import { Note } from './note' +//192 + 328 = 520 export const NOTE_ENCRYPTED_SERIALIZED_SIZE_IN_BYTE = PROOF_LENGTH + ENCRYPTED_NOTE_LENGTH export type NoteEncryptedHash = Buffer diff --git a/ironfish/src/primitives/rawTransaction.test.ts b/ironfish/src/primitives/rawTransaction.test.ts index 2d952dff23..2115654ddb 100644 --- a/ironfish/src/primitives/rawTransaction.test.ts +++ b/ironfish/src/primitives/rawTransaction.test.ts @@ -84,6 +84,10 @@ describe('RawTransaction', () => { const mintedValue = valuesByAsset.get(asset.id()) Assert.isNotUndefined(mintedValue) expect(mintedValue).toEqual(1n) + + // should have same size for posted transaction and estimated size from raw transaction + const serializedPost = posted.serialize() + expect(raw.size(account.publicAddress)).toEqual(Buffer.byteLength(serializedPost)) }) it('should throw an error if the max mint value is exceeded', async () => { diff --git a/ironfish/src/primitives/rawTransaction.ts b/ironfish/src/primitives/rawTransaction.ts index 8d91c531a3..11f1ee576b 100644 --- a/ironfish/src/primitives/rawTransaction.ts +++ b/ironfish/src/primitives/rawTransaction.ts @@ -5,11 +5,15 @@ import { AMOUNT_VALUE_LENGTH, ASSET_LENGTH, + PROOF_LENGTH, Transaction as NativeTransaction, TRANSACTION_EXPIRATION_LENGTH, TRANSACTION_FEE_LENGTH, + TRANSACTION_PUBLIC_KEY_RANDOMNESS_LENGTH, + TRANSACTION_SIGNATURE_LENGTH, } from '@ironfish/rust-nodejs' import { Asset, ASSET_ID_LENGTH } from '@ironfish/rust-nodejs' +import { BufferMap } from 'buffer-map' import bufio from 'bufio' import { Witness } from '../merkletree' import { NoteHasher } from '../merkletree/hasher' @@ -53,17 +57,54 @@ export class RawTransaction { > }[] = [] - size(): number { + size(senderPublicAddress: string): number { let size = 0 + size += 1 // version size += 8 // spends length size += 8 // notes length - size += 8 // fee - size += 4 // expiration - size += 64 // signature + size += 8 // mints length + size += 8 // burns length + size += TRANSACTION_FEE_LENGTH // fee + size += TRANSACTION_EXPIRATION_LENGTH // expiration + size += TRANSACTION_PUBLIC_KEY_RANDOMNESS_LENGTH // public key randomness + size += this.spends.length * SPEND_SERIALIZED_SIZE_IN_BYTE size += this.outputs.length * NOTE_ENCRYPTED_SERIALIZED_SIZE_IN_BYTE - size += this.mints.length * (ASSET_LENGTH + 8) + size += + this.mints.length * + (PROOF_LENGTH + ASSET_LENGTH + AMOUNT_VALUE_LENGTH + TRANSACTION_SIGNATURE_LENGTH) size += this.burns.length * (ASSET_ID_LENGTH + 8) - size += this.spends.length * SPEND_SERIALIZED_SIZE_IN_BYTE + size += TRANSACTION_SIGNATURE_LENGTH // signature + + // Each asset might have a change note, which would need to be accounted for + const assetTotals = new BufferMap() + for (const mint of this.mints) { + const asset = new Asset(senderPublicAddress, mint.name, mint.metadata) + const assetValue = assetTotals.get(asset.id()) + assetTotals.set(asset.id(), assetValue ? assetValue + mint.value : mint.value) + } + for (const burn of this.burns) { + const assetValue = assetTotals.get(burn.assetId) + assetTotals.set(burn.assetId, assetValue ? assetValue - burn.value : -burn.value) + } + for (const spend of this.spends) { + const assetValue = assetTotals.get(spend.note.assetId()) + assetTotals.set( + spend.note.assetId(), + assetValue ? assetValue - spend.note.value() : -spend.note.value(), + ) + } + for (const output of this.outputs) { + const assetValue = assetTotals.get(output.note.assetId()) + assetTotals.set( + output.note.assetId(), + assetValue ? assetValue + output.note.value() : output.note.value(), + ) + } + for (const [, value] of assetTotals) { + if (value !== 0n) { + size += NOTE_ENCRYPTED_SERIALIZED_SIZE_IN_BYTE + } + } return size } diff --git a/ironfish/src/primitives/spend.ts b/ironfish/src/primitives/spend.ts index 64182a71cf..e41306ae50 100644 --- a/ironfish/src/primitives/spend.ts +++ b/ironfish/src/primitives/spend.ts @@ -4,7 +4,7 @@ import { Nullifier } from './nullifier' -export const SPEND_SERIALIZED_SIZE_IN_BYTE = 388 +export const SPEND_SERIALIZED_SIZE_IN_BYTE = 356 export interface Spend { nullifier: Nullifier diff --git a/ironfish/src/primitives/transaction.ts b/ironfish/src/primitives/transaction.ts index 618861c199..b7367cc856 100644 --- a/ironfish/src/primitives/transaction.ts +++ b/ironfish/src/primitives/transaction.ts @@ -59,7 +59,7 @@ export class Transaction { // spend description this.spends = Array.from({ length: _spendsLength }, () => { - // proof + // proof 192 reader.seek(PROOF_LENGTH) // value commitment reader.seek(32) @@ -68,10 +68,10 @@ export class Transaction { const treeSize = reader.readU32() // 4 const nullifier = reader.readHash() // 32 - // signature + // signature 64 reader.seek(TRANSACTION_SIGNATURE_LENGTH) - // total serialized size: 192 + 32 + 32 + 32 + 4 + 32 + 64 = 388 bytes + // total serialized size: 192 + 32 + 32 + 4 + 32 + 64 = 388 bytes return { size: treeSize, commitment: rootHash, diff --git a/ironfish/src/rpc/routes/wallet/createTransaction.ts b/ironfish/src/rpc/routes/wallet/createTransaction.ts index 895fd5b7d4..09a16a8912 100644 --- a/ironfish/src/rpc/routes/wallet/createTransaction.ts +++ b/ironfish/src/rpc/routes/wallet/createTransaction.ts @@ -40,6 +40,7 @@ export type CreateTransactionRequest = { export type CreateTransactionResponse = { transaction: string + sizeInBytes: number } export const CreateTransactionRequestSchema: yup.ObjectSchema = yup @@ -91,6 +92,7 @@ export const CreateTransactionRequestSchema: yup.ObjectSchema = yup .object({ transaction: yup.string().defined(), + sizeInBytes: yup.number().defined(), }) .defined() @@ -183,9 +185,9 @@ router.register