From 31bccef181f691c16852ff1548dbfd479887cbdc 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 +++++++++++++++++ .../src/primitives/rawTransaction.test.ts | 4 ++ ironfish/src/primitives/rawTransaction.ts | 43 ++++++++++++++++--- ironfish/src/primitives/spend.ts | 2 +- ironfish/src/primitives/transaction.ts | 6 +-- ironfish/src/wallet/wallet.ts | 4 +- 7 files changed, 86 insertions(+), 18 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/rawTransaction.test.ts b/ironfish/src/primitives/rawTransaction.test.ts index 403f4041c3..5793da7d93 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.postedSize(account.publicAddress)).toEqual(serializedPost.byteLength) }) 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 dc15101a4f..ddbbc2fdd2 100644 --- a/ironfish/src/primitives/rawTransaction.ts +++ b/ironfish/src/primitives/rawTransaction.ts @@ -6,9 +6,12 @@ import { AMOUNT_VALUE_LENGTH, ASSET_LENGTH, generateKeyFromPrivateKey, + 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 bufio from 'bufio' @@ -16,6 +19,7 @@ import { Witness } from '../merkletree' import { NoteHasher } from '../merkletree/hasher' import { Side } from '../merkletree/merkletree' import { CurrencyUtils } from '../utils/currency' +import { AssetBalances } from '../wallet/assetBalances' import { BurnDescription } from './burnDescription' import { Note } from './note' import { @@ -54,17 +58,44 @@ export class RawTransaction { > }[] = [] - size(): number { + postedSize(publicAddress: 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 AssetBalances() + for (const mint of this.mints) { + const asset = new Asset(publicAddress, mint.name, mint.metadata) + assetTotals.increment(asset.id(), mint.value) + } + for (const burn of this.burns) { + assetTotals.increment(burn.assetId, -burn.value) + } + for (const spend of this.spends) { + assetTotals.increment(spend.note.assetId(), -spend.note.value()) + } + for (const output of this.outputs) { + assetTotals.increment(output.note.assetId(), 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..3013e7d2ec 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 = 356 bytes return { size: treeSize, commitment: rootHash, diff --git a/ironfish/src/wallet/wallet.ts b/ironfish/src/wallet/wallet.ts index 03c080ffa9..984e21bfbf 100644 --- a/ironfish/src/wallet/wallet.ts +++ b/ironfish/src/wallet/wallet.ts @@ -918,7 +918,7 @@ export class Wallet { } if (options.feeRate) { - raw.fee = getFee(options.feeRate, raw.size()) + raw.fee = getFee(options.feeRate, raw.postedSize(options.account.publicAddress)) } await this.fund(raw, { @@ -928,7 +928,7 @@ export class Wallet { }) if (options.feeRate) { - raw.fee = getFee(options.feeRate, raw.size()) + raw.fee = getFee(options.feeRate, raw.postedSize(options.account.publicAddress)) raw.spends = [] await this.fund(raw, {