From 895a5361c72b3f4d930547b0c84f9f72a95afa0e Mon Sep 17 00:00:00 2001 From: Jason Spafford Date: Wed, 21 Feb 2024 15:04:21 -0800 Subject: [PATCH] Change Note memos from string to 32 byte buffer (#4743) * changes NativeNote napi to take memo as buffer (#4740) * changes NativeNote napi to take memo as buffer the NativeNote napi constructor takes the memo to be a utf-8 string that is then converted to bytes when constructing a Memo this prevents clients from easily constructing a memo from arbitrary bytes changes constructor to take the memo as a JsBuffer clients treating memos as strings should convert to buffers before constructing Notes * makes memo byte processing uniform with assetid changes From trait implementation to use byte array of length 32 * Start changing memo from string to Buffer * Remove unused memo_length * Push up wallet test changes * Push up blockchain test fix * fixes assorted test uses of string memos * Fixed syntax error * fixes napi handling of copying bytes from short memos * fixes wallet.test.slow.ts * Change buffer length to max check * Add test for sendTransaction * adds tests for createTransaction use hex string in memoHex to specify memo enforce maximum memo length * fixes memos in demo.test.slow * changes test memo for consistency * adds createTransaction test with no memo * makes memoHex optional in createTransaction yup schema * allows only one of memo or memoHex updates createTransaction and sendTransaction, adds test for each * Change createMinersFee task to take buffer memo * Change buffer to 0 length * Change ternary to if statements * Change ternary in sendTransaction to ifs * Fix broken tests --------- Co-authored-by: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Co-authored-by: Hugh Cunningham --- ironfish-rust-nodejs/index.d.ts | 2 +- ironfish-rust-nodejs/src/structs/note.rs | 18 ++- ironfish-rust-nodejs/tests/demo.test.slow.ts | 6 +- ironfish-rust/src/note.rs | 6 + ironfish/src/blockchain/blockchain.test.ts | 2 +- ironfish/src/blockchain/blockchain.ts | 7 +- ironfish/src/consensus/verifier.test.ts | 4 +- ironfish/src/genesis/addGenesisTransaction.ts | 2 +- ironfish/src/genesis/makeGenesisBlock.ts | 6 +- .../primitives/rawTransaction.test.slow.ts | 8 +- .../src/primitives/rawTransaction.test.ts | 4 +- .../src/primitives/transaction.test.perf.ts | 4 +- .../src/primitives/transaction.test.slow.ts | 4 +- .../primitives/transactionVerify.test.perf.ts | 4 +- .../createTransaction.test.ts.fixture | 92 ++++++++++++++ .../sendTransaction.test.ts.fixture | 48 +++++++ .../routes/wallet/createTransaction.test.ts | 120 +++++++++++++++++- .../rpc/routes/wallet/createTransaction.ts | 24 +++- .../wallet/multisig/integration.test.slow.ts | 2 +- .../rpc/routes/wallet/sendTransaction.test.ts | 58 +++++++++ .../src/rpc/routes/wallet/sendTransaction.ts | 36 ++++-- ironfish/src/strategy.test.slow.ts | 8 +- ironfish/src/testUtilities/fixtures/blocks.ts | 6 +- .../testUtilities/fixtures/transactions.ts | 6 +- .../src/testUtilities/helpers/transaction.ts | 4 +- ironfish/src/testUtilities/utils.ts | 5 +- ironfish/src/wallet/wallet.test.slow.ts | 30 ++--- ironfish/src/wallet/wallet.test.ts | 14 +- ironfish/src/wallet/wallet.ts | 2 +- ironfish/src/workerPool/pool.ts | 2 +- .../workerPool/tasks/createMinersFee.test.ts | 6 +- .../src/workerPool/tasks/createMinersFee.ts | 10 +- .../tasks/workerMessages.test.perf.ts | 2 +- 33 files changed, 465 insertions(+), 87 deletions(-) diff --git a/ironfish-rust-nodejs/index.d.ts b/ironfish-rust-nodejs/index.d.ts index 2c0e094b0c..118ae69952 100644 --- a/ironfish-rust-nodejs/index.d.ts +++ b/ironfish-rust-nodejs/index.d.ts @@ -165,7 +165,7 @@ export class NoteEncrypted { } export type NativeNote = Note export class Note { - constructor(owner: string, value: bigint, memo: string, assetId: Buffer, sender: string) + constructor(owner: string, value: bigint, memo: Buffer, assetId: Buffer, sender: string) static deserialize(jsBytes: Buffer): NativeNote serialize(): Buffer /** diff --git a/ironfish-rust-nodejs/src/structs/note.rs b/ironfish-rust-nodejs/src/structs/note.rs index b0eb73f875..2af093177a 100644 --- a/ironfish-rust-nodejs/src/structs/note.rs +++ b/ironfish-rust-nodejs/src/structs/note.rs @@ -2,6 +2,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use std::cmp; + use ironfish::{ assets::asset::ID_LENGTH as ASSET_ID_LENGTH, note::{AMOUNT_VALUE_SIZE, MEMO_SIZE, SCALAR_SIZE}, @@ -54,7 +56,7 @@ impl NativeNote { pub fn new( owner: String, value: BigInt, - memo: String, + memo: JsBuffer, asset_id: JsBuffer, sender: String, ) -> Result { @@ -62,6 +64,12 @@ impl NativeNote { let owner_address = ironfish::PublicAddress::from_hex(&owner).map_err(to_napi_err)?; let sender_address = ironfish::PublicAddress::from_hex(&sender).map_err(to_napi_err)?; + let memo_buffer = memo.into_value()?; + let memo_vec = memo_buffer.as_ref(); + let num_to_copy = cmp::min(memo_vec.len(), MEMO_SIZE); + let mut memo_bytes = [0; MEMO_SIZE]; + memo_bytes[..num_to_copy].copy_from_slice(&memo_vec[..num_to_copy]); + let buffer = asset_id.into_value()?; let asset_id_vec = buffer.as_ref(); let mut asset_id_bytes = [0; ASSET_ID_LENGTH]; @@ -69,7 +77,13 @@ impl NativeNote { let asset_id = asset_id_bytes.try_into().map_err(to_napi_err)?; Ok(NativeNote { - note: Note::new(owner_address, value_u64, memo, asset_id, sender_address), + note: Note::new( + owner_address, + value_u64, + memo_bytes, + asset_id, + sender_address, + ), }) } diff --git a/ironfish-rust-nodejs/tests/demo.test.slow.ts b/ironfish-rust-nodejs/tests/demo.test.slow.ts index fead11f7fb..3115b6b1ee 100644 --- a/ironfish-rust-nodejs/tests/demo.test.slow.ts +++ b/ironfish-rust-nodejs/tests/demo.test.slow.ts @@ -59,7 +59,7 @@ describe('Demonstrate the Sapling API', () => { const key = generateKey() const transaction = new Transaction(LATEST_TRANSACTION_VERSION) - const note = new Note(key.publicAddress, 20n, 'test', Asset.nativeId(), key.publicAddress) + const note = new Note(key.publicAddress, 20n, Buffer.from('test'), Asset.nativeId(), key.publicAddress) transaction.output(note) const serializedPostedTransaction = transaction.post_miners_fee(key.spendingKey) @@ -96,7 +96,7 @@ describe('Demonstrate the Sapling API', () => { const recipientKey = generateKey() const minersFeeTransaction = new Transaction(LATEST_TRANSACTION_VERSION) - const minersFeeNote = new Note(key.publicAddress, 20n, 'miner', Asset.nativeId(), key.publicAddress) + const minersFeeNote = new Note(key.publicAddress, 20n, Buffer.from('miner'), Asset.nativeId(), key.publicAddress) minersFeeTransaction.output(minersFeeNote) const postedMinersFeeTransaction = new TransactionPosted(minersFeeTransaction.post_miners_fee(key.spendingKey)) @@ -105,7 +105,7 @@ describe('Demonstrate the Sapling API', () => { transaction.setExpiration(10) const encryptedNote = new NoteEncrypted(postedMinersFeeTransaction.getNote(0)) const decryptedNote = Note.deserialize(encryptedNote.decryptNoteForOwner(key.incomingViewKey)!) - const newNote = new Note(recipientKey.publicAddress, 15n, 'receive', Asset.nativeId(), minersFeeNote.owner()) + const newNote = new Note(recipientKey.publicAddress, 15n, Buffer.from('receive'), Asset.nativeId(), minersFeeNote.owner()) let currentHash = encryptedNote.hash() let authPath = Array.from({ length: 32 }, (_, depth) => { diff --git a/ironfish-rust/src/note.rs b/ironfish-rust/src/note.rs index a9e0f7cb11..912398a690 100644 --- a/ironfish-rust/src/note.rs +++ b/ironfish-rust/src/note.rs @@ -57,6 +57,12 @@ impl From for Memo { } } +impl From<[u8; MEMO_SIZE]> for Memo { + fn from(value: [u8; MEMO_SIZE]) -> Self { + Memo(value) + } +} + impl fmt::Display for Memo { /// This can be lossy because it assumes that the /// memo is in valid UTF-8 format. diff --git a/ironfish/src/blockchain/blockchain.test.ts b/ironfish/src/blockchain/blockchain.test.ts index 37d20e375b..2dd4a0fca4 100644 --- a/ironfish/src/blockchain/blockchain.test.ts +++ b/ironfish/src/blockchain/blockchain.test.ts @@ -1718,7 +1718,7 @@ describe('Blockchain', () => { new NativeNote( account.publicAddress, 3n, - '', + Buffer.alloc(32), assetId, account.publicAddress, ).serialize(), diff --git a/ironfish/src/blockchain/blockchain.ts b/ironfish/src/blockchain/blockchain.ts index ee69f0d576..70b3f47f9a 100644 --- a/ironfish/src/blockchain/blockchain.ts +++ b/ironfish/src/blockchain/blockchain.ts @@ -1538,7 +1538,12 @@ export class Blockchain { const amount = totalTransactionFees + BigInt(reward) const transactionVersion = this.consensus.getActiveTransactionVersion(blockSequence) - return this.workerPool.createMinersFee(minerSpendKey, amount, '', transactionVersion) + return this.workerPool.createMinersFee( + minerSpendKey, + amount, + Buffer.alloc(0), + transactionVersion, + ) } newBlockHeaderFromRaw( diff --git a/ironfish/src/consensus/verifier.test.ts b/ironfish/src/consensus/verifier.test.ts index dd6d34d526..7b13dbd4d3 100644 --- a/ironfish/src/consensus/verifier.test.ts +++ b/ironfish/src/consensus/verifier.test.ts @@ -245,14 +245,14 @@ describe('Verifier', () => { const minerNote1 = new NativeNote( owner, BigInt(reward / 2), - '', + Buffer.from(''), Asset.nativeId(), owner, ) const minerNote2 = new NativeNote( owner, BigInt(reward / 2), - '', + Buffer.from(''), Asset.nativeId(), owner, ) diff --git a/ironfish/src/genesis/addGenesisTransaction.ts b/ironfish/src/genesis/addGenesisTransaction.ts index 64f03958f7..144525dd8b 100644 --- a/ironfish/src/genesis/addGenesisTransaction.ts +++ b/ironfish/src/genesis/addGenesisTransaction.ts @@ -101,7 +101,7 @@ export async function addGenesisTransaction( const note = new NativeNote( alloc.publicAddress, BigInt(alloc.amountInOre), - alloc.memo, + Buffer.from(alloc.memo, 'hex'), Asset.nativeId(), account.publicAddress, ) diff --git a/ironfish/src/genesis/makeGenesisBlock.ts b/ironfish/src/genesis/makeGenesisBlock.ts index 69c269aab2..df8f1ff068 100644 --- a/ironfish/src/genesis/makeGenesisBlock.ts +++ b/ironfish/src/genesis/makeGenesisBlock.ts @@ -56,7 +56,7 @@ export async function makeGenesisBlock( const genesisNote = new NativeNote( genesisKey.publicAddress, allocationSum, - '', + Buffer.alloc(0), Asset.nativeId(), genesisKey.publicAddress, ) @@ -72,7 +72,7 @@ export async function makeGenesisBlock( const note = new NativeNote( minersFeeKey.publicAddress, BigInt(0), - '', + Buffer.alloc(0), Asset.nativeId(), minersFeeKey.publicAddress, ) @@ -141,7 +141,7 @@ export async function makeGenesisBlock( const note = new NativeNote( alloc.publicAddress, BigInt(alloc.amountInOre), - alloc.memo, + Buffer.from(alloc.memo, 'hex'), Asset.nativeId(), genesisNote.owner(), ) diff --git a/ironfish/src/primitives/rawTransaction.test.slow.ts b/ironfish/src/primitives/rawTransaction.test.slow.ts index 789cf25f5a..cf7e19128f 100644 --- a/ironfish/src/primitives/rawTransaction.test.slow.ts +++ b/ironfish/src/primitives/rawTransaction.test.slow.ts @@ -46,7 +46,7 @@ function createTestRawTransaction( new NativeNote( account.publicAddress, 123456789n, - 'some memo', + Buffer.from('some memo'), Asset.nativeId(), generateKey().publicAddress, ).serialize(), @@ -102,7 +102,7 @@ function createTestRawTransaction( new NativeNote( account.publicAddress, 123456789n, - 'some memo', + Buffer.from('some memo'), TEST_ASSET_ID_1, generateKey().publicAddress, ).serialize(), @@ -112,7 +112,7 @@ function createTestRawTransaction( new NativeNote( account.publicAddress, 123456789n, - 'some memo', + Buffer.from('some memo'), TEST_ASSET_ID_2, generateKey().publicAddress, ).serialize(), @@ -130,7 +130,7 @@ function createTestRawTransaction( new NativeNote( generateKey().publicAddress, 123456789n - raw.fee, - 'some memo', + Buffer.from('some memo'), Asset.nativeId(), account.publicAddress, ).serialize(), diff --git a/ironfish/src/primitives/rawTransaction.test.ts b/ironfish/src/primitives/rawTransaction.test.ts index 1f562ac34c..8f4b374385 100644 --- a/ironfish/src/primitives/rawTransaction.test.ts +++ b/ironfish/src/primitives/rawTransaction.test.ts @@ -212,7 +212,7 @@ describe('RawTransactionSerde', () => { new NativeNote( generateKey().publicAddress, 5n, - 'memo', + Buffer.from('memo'), asset.id(), account.publicAddress, ).serialize(), @@ -290,7 +290,7 @@ describe('RawTransactionSerde', () => { new NativeNote( generateKey().publicAddress, 5n, - 'memo', + Buffer.from('memo'), asset.id(), accountA.publicAddress, ).serialize(), diff --git a/ironfish/src/primitives/transaction.test.perf.ts b/ironfish/src/primitives/transaction.test.perf.ts index adcfb7793f..21a628edd7 100644 --- a/ironfish/src/primitives/transaction.test.perf.ts +++ b/ironfish/src/primitives/transaction.test.perf.ts @@ -84,13 +84,13 @@ describe('Transaction', () => { const spendAmount = BigInt(numSpends) * CurrencyUtils.decodeIron(20) const outputAmount = BigIntUtils.divide(spendAmount, BigInt(numOutputs)) - const outputs: { publicAddress: string; amount: bigint; memo: string; assetId: Buffer }[] = + const outputs: { publicAddress: string; amount: bigint; memo: Buffer; assetId: Buffer }[] = [] for (let i = 0; i < numOutputs; i++) { outputs.push({ publicAddress: account.publicAddress, amount: BigInt(outputAmount), - memo: '', + memo: Buffer.from(''), assetId: Asset.nativeId(), }) } diff --git a/ironfish/src/primitives/transaction.test.slow.ts b/ironfish/src/primitives/transaction.test.slow.ts index d2157f035f..5df0af299e 100644 --- a/ironfish/src/primitives/transaction.test.slow.ts +++ b/ironfish/src/primitives/transaction.test.slow.ts @@ -61,7 +61,7 @@ describe('Transaction', () => { { publicAddress: accountB.publicAddress, amount: BigInt(1), - memo: '', + memo: Buffer.from(''), assetId: Asset.nativeId(), }, ], @@ -86,7 +86,7 @@ describe('Transaction', () => { const expiration = 10 const amount = 10n const burnAmount = 1n - const memo = 'Hello World' + const memo = Buffer.from('Hello World') const assetName = 'Testcoin' const metadata = 'testcoin metadata' const asset = new Asset(account.publicAddress, assetName, metadata) diff --git a/ironfish/src/primitives/transactionVerify.test.perf.ts b/ironfish/src/primitives/transactionVerify.test.perf.ts index f1e287a0d7..85fcf18bed 100644 --- a/ironfish/src/primitives/transactionVerify.test.perf.ts +++ b/ironfish/src/primitives/transactionVerify.test.perf.ts @@ -123,13 +123,13 @@ describe('Verify Transaction', () => { const spendAmount = BigInt(numSpends) * CurrencyUtils.decodeIron(20) const outputAmount = BigIntUtils.divide(spendAmount, BigInt(numOutputs)) - const outputs: { publicAddress: string; amount: bigint; memo: string; assetId: Buffer }[] = + const outputs: { publicAddress: string; amount: bigint; memo: Buffer; assetId: Buffer }[] = [] for (let i = 0; i < numOutputs; i++) { outputs.push({ publicAddress: account.publicAddress, amount: BigInt(outputAmount), - memo: '', + memo: Buffer.from(''), assetId: Asset.nativeId(), }) } diff --git a/ironfish/src/rpc/routes/wallet/__fixtures__/createTransaction.test.ts.fixture b/ironfish/src/rpc/routes/wallet/__fixtures__/createTransaction.test.ts.fixture index fa015fc38b..7b6e736d70 100644 --- a/ironfish/src/rpc/routes/wallet/__fixtures__/createTransaction.test.ts.fixture +++ b/ironfish/src/rpc/routes/wallet/__fixtures__/createTransaction.test.ts.fixture @@ -838,5 +838,97 @@ } ] } + ], + "Route wallet/createTransaction should create transaction using memoHex": [ + { + "version": 4, + "id": "65f292fe-447b-4fdc-9f5d-1d87c679d878", + "name": "existingAccount", + "spendingKey": "d1f0fa57871b472b27632df610a969d2b17482e140c5582368d56a34671b2f19", + "viewKey": "9b053fb9039ecbbed113b60f4125749c1b4e4954e2dcce7f754f8d508fc4737048f54f4b94bad2afc837bf36602a5650b605f2a8496dbf4c91db1f86c87fd770", + "incomingViewKey": "381d6f627054582fbe93b21e8536a5a97fe0c46f18e8afde66b0af3d0397cd06", + "outgoingViewKey": "0247cefeb6b8f4d581e2432acdc44e21fe9dc77fd6249bc64568257853b5e729", + "publicAddress": "a1edf124480c1c5f256a6f7e19424bcb3a8d680977c966f990fde74ec01365e9", + "createdAt": { + "hash": { + "type": "Buffer", + "data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY=" + }, + "sequence": 1 + }, + "proofAuthorizingKey": "8779d66619335e2335e87b8222776695925cc6589f2ff74a7dc3e14ee62f0703" + }, + { + "header": { + "sequence": 2, + "previousBlockHash": "4791D7AE9F97DF100EF1558E84772D6A09B43762388283F75C6F20A32A88AA86", + "noteCommitment": { + "type": "Buffer", + "data": "base64:EL+5Xwlj0WiPwsQ49awyIAJ39N6n436tcyPPRR5R8TY=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:2gMem3TOMZHj4Qrlal7qhDd3i0xw5IKQqqzkVa88CtA=" + }, + "target": "9282972777491357380673661573939192202192629606981189395159182914949423", + "randomness": "0", + "timestamp": 1708044968503, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 4, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AgAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAHl6TyxC6GpTvkB5Wf5vzk9mqaOEhEsDTA3WMBcM65XKw+cgRFJUfhpo4SFSQ2kRwWgrJwnMhf3hvkzjOkdUoD3bvtEsL/+x+e9rMuQYQiueVJhbkGZ326NYmn0SBHL+0Td0ZfAkdvh+is9mnpUJGeORFjKDRMwYcp69CE+LroI4Nz/7Fh1Jww4BO875FKdsIyWlvWlA0Kha8PrC4kbh/r0nUWC5QOdCgjyGFWnWbgMmQNYJnzaa5X+IsB/yTOScJLbNyq4fK9hVWKlY0J2NE1glupWCQeeD+r1MNJXVV/4SBhulGpuK857tIe3B0oYISfiwyLGUA03LBhx0ILgwlLZa65dPEZyUl5VqBV+6zq9wV+0kZ9xAhiXGm47+HLHJNNrU2dNbq7PKGkq8+zwg4l9x+WNJux2IvC/5yCpfmTeSd/f5+Ldd0YqpRu+JSEY3NQT5rxGAVakmXwZebXHMJNxolt0SK37qnhE9cD6NoqRjzo/4UhK36rR98uIslMnfTcAMvHwFgmnNQluCeWQs9u7CYFNsxP9rxXO+tdEAzSU7jcb5C1yVkXW54Bl0/9/mSUjTVcKJqthADr9Uv2gNKDub/HdcqGR7ojCTnJpZTnDFLnPwwvL07KUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw4DPc6qEcpuUdHhUOah+t/QdZrujnul5cMg+Sc8HM9nEsIVuUI7enLPsLNuQAWCPnEaD0QdE2NMV2fLduZ9h3AA==" + } + ] + } + ], + "Route wallet/createTransaction should create transaction with no memo": [ + { + "version": 4, + "id": "b3e4c164-c479-4dd9-a8a2-36f754d430d8", + "name": "existingAccount", + "spendingKey": "29aba0d30212cdcff8b50163e0e647b7615e55299e1886f9dcc79c27f1f6edca", + "viewKey": "23b50ea197210babcd7df30c4864ccf45f7851d7cd99cf8b81ae208d2f865909c3255fbd9de37a09fe16355e978d033c49bd9dcfdc4445eb78cfc692c623d91c", + "incomingViewKey": "03568087c837bcf481fcf404b6693713df3a26fbb243071b79affabac29ade03", + "outgoingViewKey": "caace199576008230996da764a7bce6b1a663665015bb959d1c1c71518649e93", + "publicAddress": "7741877c7c4771b5304d04a7bd6c2662c97d295962981eeeeb85b6fa113fba86", + "createdAt": { + "hash": { + "type": "Buffer", + "data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY=" + }, + "sequence": 1 + }, + "proofAuthorizingKey": "1bbcbba0e6170165c90753badcfb85554c77fd740847fcb4ab065347fadc5b06" + }, + { + "header": { + "sequence": 2, + "previousBlockHash": "4791D7AE9F97DF100EF1558E84772D6A09B43762388283F75C6F20A32A88AA86", + "noteCommitment": { + "type": "Buffer", + "data": "base64:tMR7WCLDXwulsqXQYgRWmqN9wR28LdWUxVL0nNDIVWE=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:iJ2RTsbkXO0yziEuDD6jjKg7h6NJFXQVTCvHb4kxI1Q=" + }, + "target": "9282972777491357380673661573939192202192629606981189395159182914949423", + "randomness": "0", + "timestamp": 1708109450669, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 4, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AgAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAZ2OGDWLTDMoZGTWUg+9zG/MbUjmVXXJsIEYwrr4OBcCsQlx2VlkfoLnfCxjpbs+NeyNZWTL5jn2a55ri4edKHKzf6Mom1CpKiw1/SZKZ25GsM5PAfFzJ+5QKDjhwBgl4FZBLctLGm5QKrpeRnRW6iblNk2TMKS8NlyB46HyE7sMOnQKavjAdsJiV6gHU11RB2hRlMX8KHAPHZXEkiUZqGrZsYEu7Yz0NypbNQCxgPhCwoJjHp91uyBRgWnqpPigv3erWxYJERQzLC5aGFWc7erj2qwyuv9Pxw8o/QNR8mt9Ldp9U5XQUtwgTCro24TqLZc6ZerUtfTODAnbwnzvWsLSd8oHiz+8WkDWkt2aCY52yJvReDZSjzjOpJTa5YwMw7vmZ3isOxVx9FYjRbGGsO+U0LPx8JImeHx6dS+zbJYAKtlQ/BPi6ivFv+/C3FYD8bZbJA5Oa5wh1GblzJjxqSXQ4hev6gOHPzrke0pl3UW01Ctvbf47iYsxLtahevdWcGm40bkdF2UMgqFR1TsKGI2SU7qSmG5OhgM2K9SKOr/PYB6BlGEiGL9nPOyYu/VpAz6DCcTNP40MRSY5gddv1ivreDOBKdv3wikWJ8gNQzMKOKlglKGOICUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwSId5xa1d0KuLJG6wdXu/lcE6BT9N3cXx3xiT6kI/RyJZxnyjNLtTt34ta8CjrvVsU8n6ysi9LTDp3cI7eaiwAA==" + } + ] + } ] } \ No newline at end of file diff --git a/ironfish/src/rpc/routes/wallet/__fixtures__/sendTransaction.test.ts.fixture b/ironfish/src/rpc/routes/wallet/__fixtures__/sendTransaction.test.ts.fixture index 867335dcb8..43a7b36220 100644 --- a/ironfish/src/rpc/routes/wallet/__fixtures__/sendTransaction.test.ts.fixture +++ b/ironfish/src/rpc/routes/wallet/__fixtures__/sendTransaction.test.ts.fixture @@ -86,5 +86,53 @@ "type": "Buffer", "data": "base64:AgAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA9Wk4A5bql27+cRrhkWz4C/3DrSFMm5UDBMEcvQ4eTk+j6rcdBElwmK9Gq6efrglA96OY1YUWFPbU5/ohdxsyc0K4pdeD1bxo5+TfBdpJFzaYI4VNoRhRv4WaEyHZCTZT2XsTBPn7cZ2xn9SIawoEIqE3wGP/wwhj7jGXoJU7+IYFhszjrdE0IotEWu4dUqN+TtTAa0++eMaPIEjTeUF6Gnw0CTUMD7MwNhXmya41aGeYMAMJJ2Ub0IB2oASVRTlDhZBwCoRuc+5obNMB01pgN3ofImHaZs+aZUmEUQSZI4inOYhBJNWk8F0NDNM1XxtT5SGXh7EMGFDj5VDl9AIEjbHKZ9bblCevkH5B1UDnul8b4PyS94fWcAqhyPGhhfNJt5d/dR2Nw6DDd+/FG84ljyTLBzHPGhi2Ul0bcXnUUJHb8VOtyfIv5aCKRVJi6a80bhg6FmeFuIWDqRFmw52BFi6cxrUliEnNZCpSZoz0hqSlGZ4Sk3O7CW+Ozff+dMy7WeTieuSsy4wG1muoqJCtuGP3Q6cB3cvQAK9R0Y8BVz4UGok9y+ftyYUZAo9g26tZLtK2QxumXG31Aps1zwszyzNYSu8b0t0cycNyVPNvqvNfcD2y23Bk0Ulyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwiOgsJ/Q/qTjOs73XhNUGRd6/UXvKHadF1oG60xosn+Vi3YvjmDytdSHY/7dmxm0SFq+cBDuQIbH53CTlmccjBQ==" } + ], + "Route wallet/sendTransaction calls the send method on the wallet with single recipient": [ + { + "version": 4, + "id": "dd1859a4-6813-475d-b4a6-d849ed9e1820", + "name": "account", + "spendingKey": "3743e984102a2eb78b350b62b4ebb0c5e3cecb883533a91bef1b45ce7c950dcd", + "viewKey": "fcb979a1d68278840b2f655f7b3de3147337748daf13806682e1ad9b93cb47c6832128d485941eb65cda359eb9ad284e588ab64dfac2c5c4652940553b80ba21", + "incomingViewKey": "de184c1f7d366a2dcc8a8afde15ba7793245ca4c34221dcf1c6dfc5a59751204", + "outgoingViewKey": "e0d29fa94da051483c5c0ad76a68365113ec136004c0d0df4b63bf0eff22b623", + "publicAddress": "d4fa3eb7be26e28f4d5a0ec1d5a46789f6bf4e857e9d13621244cb21101007c6", + "createdAt": { + "hash": { + "type": "Buffer", + "data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY=" + }, + "sequence": 1 + }, + "proofAuthorizingKey": "f2bb5308ee3d0fa023ade90ca0277fb9a8f61cb29ea8dbbf6bdddf17e8c54c0b" + }, + { + "type": "Buffer", + "data": "base64:AgAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAzrMZM5FKugU6b36EHdbgPy1m8/QhbprA66XxE+5FYYKMed/AmHd/1Hq38FuxqknD52oqkGlV/CnU6QpBQOOQVYa5pte/wmq5myuGcumDUSyogwm9QTlkvPcqNOsm8knXik0u8qrewlHY+8/5Chivbqd7MZm/rVHyapdOd9kOyw4K9bigF846FIxPsHmGoW8hr+KBliDxfLtrRyl9s9n8ui6TG2PoqWSXvYRWa4wJuraQcEqokav6FTqjflcR68bF330rP67e/nebf9XbISO1zO6RuuimDUMVRRUQvhumCz/v4dIj95uniQdAwXI1ARQ4wpf70oQQedoCjlkBZ9HwjevhbPJ5Cn2XJszQxRdyz2c2EvjVrvN4TvptVdR7Pic/uGhi2CW7K06o8/jU80p+ZcWv9fPlqWc/BRhJOSscoMwS9ptKE4KpojuqiWvnTeM39+bYAKksgKyzCa7mkvAWDKqG5tZ1W3HajVgGezjca0PxhKUSIgvHjFIARYTfNZ3phNsrKfy1jYRk3oaZGbEZuvJZvVo0alzR1VYAjOTHEUjuyZFdUUDXou9Z1XK0OPBeuRLNQRRmCcxkbX7Z0oO+PROWIVt9QfzcrtI//kpiwpjJy03rM2Fab0lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwzApIYoVKt7CEzMHJE8ilLMzLF8QdYql80YWU5ciLh00/b12PwoOofqsJztt1FpXdhEiPn2WHTEf23+oF5KBrCA==" + } + ], + "Route wallet/sendTransaction allows you to pass in a hex encoded buffer": [ + { + "version": 4, + "id": "ee5aa4b8-9daa-4b4c-8202-6a94ae800a2b", + "name": "memo", + "spendingKey": "a7871e68e4578b858fba93d595a4abbf7a7e7b6450e357fcaa25608e3ab0b7fc", + "viewKey": "da770cd744c670e5db4224c62b55ec8586771868cc70eb7cf1ce825331166b4203bc0f3adcd699a63c46fe9fd4b2dc4138b618fb9c2fb2d4a112fe893f3235d8", + "incomingViewKey": "a10bd1258c10b06d548168059b9d22bc9d707f7d673d74b4d7f33174cd40dc05", + "outgoingViewKey": "e28804e220626ccf047eebde5072f60cc2baae93bdfa0c5bfd1cdd85faebcda6", + "publicAddress": "c2b646dedb8dcf50795d65b2c7dc2b3622ee66c4fd02f36891b45340886d49e7", + "createdAt": { + "hash": { + "type": "Buffer", + "data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY=" + }, + "sequence": 1 + }, + "proofAuthorizingKey": "2109977220ab3635175750f811b996d30e6a0b381da899db2369ef5e695a050d" + }, + { + "type": "Buffer", + "data": "base64:AgAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAACdqpDw+ig2sRIMKtuCWGwl+y72rk5RzO0XX1UD32H7W4+38k8xmQLldSbdaG7/R//JTfcl2HdzEQTS4WSt+MvkKKUyQMzJeRgS2fQpSF98+u2g0W6hhtV2fcLIAVKkjiU3FY4XIh4nimAqQ5QPQeYarLpfmMX/LH3hzOCpn4Q5gDl0wSPFvadHdIhnNDPTKyaA1ct9DtoAi+H6e77ZOJ8H9+Md46DZsrCza16e884rWmRtUGmBgD1oezo/Ry3BK3D8jHudMqNG3RGPfRmuMdLWHOf6j8ZPnIyYviQlXbJI0FwXFu4GtkHHeXv5UR6f50DfXxLs6/fXbt9zdJsl8WD95mHew2nE4tcwuGTJL0vxBvbHeTTRhAtoK7sCka6kMiNhDMc3/ASh5doUTDw+zO6o4IsUsLPj/Vsnmm6ayFAy5RkvTsc2WiH6g6YqC5T/bBJU+hpmIV4aIi/DGIoT57HbmgeJWP0v4Q8/sVDm5qHRWMUu2GrsiO615kDBzr0N0ghK7iOgRvs69hJil10M9b3fo4e9zZjyTWbXhhF8odUBDHlsSdccmpsY5/eeZUEvB39iNrSceKd19mTylR1goQoDXi8tqV+aFsKSR4UEPiyrgEBXr5ODm21Elyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwm1kuvtURi8zu2LGThNJUU8XZTV9osIakVYCgjt9UdRBi+n/p2+S4V93LmJA0REG4OFOSjQ4UycMEPHGyrEJSBw==" + } ] } \ No newline at end of file diff --git a/ironfish/src/rpc/routes/wallet/createTransaction.test.ts b/ironfish/src/rpc/routes/wallet/createTransaction.test.ts index aaebd670c3..5f98d2ad26 100644 --- a/ironfish/src/rpc/routes/wallet/createTransaction.test.ts +++ b/ironfish/src/rpc/routes/wallet/createTransaction.test.ts @@ -2,12 +2,13 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { Asset } from '@ironfish/rust-nodejs' +import { Asset, MEMO_LENGTH } from '@ironfish/rust-nodejs' import { RawTransactionSerde } from '../../../primitives/rawTransaction' import { useAccountFixture, useMinerBlockFixture } from '../../../testUtilities' import { createRouteTest } from '../../../testUtilities/routeTest' -import { AsyncUtils } from '../../../utils' +import { AsyncUtils, BufferUtils } from '../../../utils' import { RPC_ERROR_CODES } from '../../adapters/errors' +import { RpcRequestError } from '../../clients' const REQUEST_PARAMS = { account: 'existingAccount', @@ -508,4 +509,119 @@ describe('Route wallet/createTransaction', () => { expect(rawTransaction.spends.length).toBe(3) expect(rawTransaction.fee).toBe(1n) }) + + it('should create transaction using memoHex', async () => { + const sender = await useAccountFixture(routeTest.node.wallet, 'existingAccount') + const memoHex = 'deadbeef' + + const block = await useMinerBlockFixture( + routeTest.chain, + undefined, + sender, + routeTest.node.wallet, + ) + + await expect(routeTest.node.chain).toAddBlock(block) + + await routeTest.node.wallet.updateHead() + + const response = await routeTest.client.wallet.createTransaction({ + account: 'existingAccount', + outputs: [ + { + publicAddress: '0d804ea639b2547d1cd612682bf99f7cad7aad6d59fd5457f61272defcd4bf5b', + amount: BigInt(10).toString(), + memoHex, + assetId: Asset.nativeId().toString('hex'), + }, + ], + fee: undefined, + }) + + expect(response.status).toBe(200) + expect(response.content.transaction).toBeDefined() + + const rawTransactionBytes = Buffer.from(response.content.transaction, 'hex') + const rawTransaction = RawTransactionSerde.deserialize(rawTransactionBytes) + + // write memo to fixed-length buffer + const memoHexBuffer = Buffer.alloc(32) + memoHexBuffer.write(memoHex, 'hex') + + expect(rawTransaction.outputs.length).toBe(1) + expect(rawTransaction.outputs[0].note.memo()).toEqualBuffer(memoHexBuffer) + }) + + it('should create transaction with no memo', async () => { + const sender = await useAccountFixture(routeTest.node.wallet, 'existingAccount') + + const block = await useMinerBlockFixture( + routeTest.chain, + undefined, + sender, + routeTest.node.wallet, + ) + + await expect(routeTest.node.chain).toAddBlock(block) + + await routeTest.node.wallet.updateHead() + + const response = await routeTest.client.wallet.createTransaction({ + account: 'existingAccount', + outputs: [ + { + publicAddress: '0d804ea639b2547d1cd612682bf99f7cad7aad6d59fd5457f61272defcd4bf5b', + amount: BigInt(10).toString(), + assetId: Asset.nativeId().toString('hex'), + }, + ], + fee: undefined, + }) + + expect(response.status).toBe(200) + expect(response.content.transaction).toBeDefined() + + const rawTransactionBytes = Buffer.from(response.content.transaction, 'hex') + const rawTransaction = RawTransactionSerde.deserialize(rawTransactionBytes) + + expect(rawTransaction.outputs.length).toBe(1) + expect(rawTransaction.outputs[0].note.memo()).toEqualBuffer(Buffer.alloc(32, 0)) + expect(BufferUtils.toHuman(rawTransaction.outputs[0].note.memo())).toEqual('') + }) + + it('should enforce maximum memo length', async () => { + const memoHex = 'a'.repeat(MEMO_LENGTH * 2 + 1) + await expect(async () => + routeTest.client.wallet.createTransaction({ + account: 'existingAccount', + outputs: [ + { + publicAddress: '0d804ea639b2547d1cd612682bf99f7cad7aad6d59fd5457f61272defcd4bf5b', + amount: BigInt(10).toString(), + memoHex, + assetId: Asset.nativeId().toString('hex'), + }, + ], + fee: undefined, + }), + ).rejects.toThrow(RpcRequestError) + }) + + it('should allow only one of memo or memoHex to be set', async () => { + await expect(async () => + routeTest.client.wallet.createTransaction({ + account: 'existingAccount', + outputs: [ + { + publicAddress: '0d804ea639b2547d1cd612682bf99f7cad7aad6d59fd5457f61272defcd4bf5b', + amount: BigInt(10).toString(), + memo: 'abcd', + memoHex: 'abcd', + assetId: Asset.nativeId().toString('hex'), + }, + ], + fee: undefined, + }), + ).rejects.toThrow(RpcRequestError) + }) }) diff --git a/ironfish/src/rpc/routes/wallet/createTransaction.ts b/ironfish/src/rpc/routes/wallet/createTransaction.ts index 4610d2f279..d0f2ee1114 100644 --- a/ironfish/src/rpc/routes/wallet/createTransaction.ts +++ b/ironfish/src/rpc/routes/wallet/createTransaction.ts @@ -24,7 +24,8 @@ export type CreateTransactionRequest = { outputs: { publicAddress: string amount: string - memo: string + memo?: string + memoHex?: string assetId?: string }[] mints?: { @@ -59,7 +60,11 @@ export const CreateTransactionRequestSchema: yup.ObjectSchema { { publicAddress: account.publicAddress, amount: BigInt(2), - memo: '', + memo: Buffer.from(''), assetId: Asset.nativeId(), }, ], diff --git a/ironfish/src/rpc/routes/wallet/sendTransaction.test.ts b/ironfish/src/rpc/routes/wallet/sendTransaction.test.ts index 981b9d2f01..5a1c2c4bf4 100644 --- a/ironfish/src/rpc/routes/wallet/sendTransaction.test.ts +++ b/ironfish/src/rpc/routes/wallet/sendTransaction.test.ts @@ -8,6 +8,7 @@ import { useAccountFixture, useMinersTxFixture } from '../../../testUtilities/fi import { createRouteTest } from '../../../testUtilities/routeTest' import { NotEnoughFundsError } from '../../../wallet/errors' import { RPC_ERROR_CODES } from '../../adapters' +import { RpcRequestError } from '../../clients' const TEST_PARAMS = { account: 'existingAccount', @@ -244,4 +245,61 @@ describe('Route wallet/sendTransaction', () => { confirmations: 10, }) }) + + it('allows you to pass in a hex encoded buffer', async () => { + routeTest.chain.synced = true + + const account = await useAccountFixture(routeTest.node.wallet, 'memo') + const tx = await useMinersTxFixture(routeTest.node, account) + const sendSpy = jest.spyOn(routeTest.node.wallet, 'send').mockResolvedValue(tx) + + jest.spyOn(routeTest.node.wallet, 'getBalance').mockResolvedValueOnce({ + unconfirmed: BigInt(11), + confirmed: BigInt(11), + pending: BigInt(11), + available: BigInt(11), + unconfirmedCount: 0, + pendingCount: 0, + blockHash: null, + sequence: null, + }) + + const memo = Buffer.from('deadbeef', 'hex') + + const params = { + ...TEST_PARAMS, + outputs: [ + { + ...TEST_PARAMS.outputs[0], + memo: undefined, + memoHex: memo.toString('hex'), + }, + ], + } + + await routeTest.client.wallet.sendTransaction(params) + expect(sendSpy).toHaveBeenCalled() + + const sendMemo = sendSpy.mock.lastCall?.[0].outputs[0].memo + expect(sendMemo?.byteLength).toBe(memo.byteLength) + expect(sendMemo?.equals(memo)).toBe(true) + }) + + it('should allow only one of memo or memoHex to be set', async () => { + await expect(async () => + routeTest.client.wallet.sendTransaction({ + account: 'existingAccount', + outputs: [ + { + publicAddress: '0d804ea639b2547d1cd612682bf99f7cad7aad6d59fd5457f61272defcd4bf5b', + amount: BigInt(10).toString(), + memo: 'abcd', + memoHex: 'abcd', + assetId: Asset.nativeId().toString('hex'), + }, + ], + fee: undefined, + }), + ).rejects.toThrow(RpcRequestError) + }) }) diff --git a/ironfish/src/rpc/routes/wallet/sendTransaction.ts b/ironfish/src/rpc/routes/wallet/sendTransaction.ts index 985b057e04..0f6c5b0dd3 100644 --- a/ironfish/src/rpc/routes/wallet/sendTransaction.ts +++ b/ironfish/src/rpc/routes/wallet/sendTransaction.ts @@ -19,7 +19,8 @@ export type SendTransactionRequest = { outputs: { publicAddress: string amount: string - memo: string + memo?: string + memoHex?: string assetId?: string }[] fee?: string @@ -45,7 +46,11 @@ export const SendTransactionRequestSchema: yup.ObjectSchema( ) } - const outputs = request.data.outputs.map((output) => ({ - publicAddress: output.publicAddress, - amount: CurrencyUtils.decode(output.amount), - memo: output.memo, - assetId: output.assetId ? Buffer.from(output.assetId, 'hex') : Asset.nativeId(), - })) + const outputs = request.data.outputs.map((output) => { + if (output.memo && output.memoHex) { + throw new RpcValidationError('Only one of memo or memoHex may be set for each output') + } + + let memo: Buffer + if (output.memo) { + memo = Buffer.from(output.memo, 'utf-8') + } else if (output.memoHex) { + memo = Buffer.from(output.memoHex, 'hex') + } else { + memo = Buffer.alloc(0) + } + + return { + publicAddress: output.publicAddress, + amount: CurrencyUtils.decode(output.amount), + memo: memo, + assetId: output.assetId ? Buffer.from(output.assetId, 'hex') : Asset.nativeId(), + } + }) const params: Parameters[0] = { account, diff --git a/ironfish/src/strategy.test.slow.ts b/ironfish/src/strategy.test.slow.ts index 9891adffc6..0c5748ddeb 100644 --- a/ironfish/src/strategy.test.slow.ts +++ b/ironfish/src/strategy.test.slow.ts @@ -87,7 +87,7 @@ describe('Demonstrate the Sapling API', () => { it('Can create a miner reward', () => { const owner = generateKeyFromPrivateKey(spenderKey.spendingKey).publicAddress - minerNote = new NativeNote(owner, 42n, '', Asset.nativeId(), owner) + minerNote = new NativeNote(owner, 42n, Buffer.from(''), Asset.nativeId(), owner) const transaction = new NativeTransaction(LATEST_TRANSACTION_VERSION) transaction.output(minerNote) @@ -135,7 +135,7 @@ describe('Demonstrate the Sapling API', () => { const outputNote = new NativeNote( receiverKey.publicAddress, 40n, - '', + Buffer.from(''), Asset.nativeId(), minerNote.owner(), ) @@ -216,14 +216,14 @@ describe('Demonstrate the Sapling API', () => { const noteForSpender = new NativeNote( spenderKey.publicAddress, 10n, - '', + Buffer.from(''), Asset.nativeId(), receiverAddress, ) const receiverNoteToSelf = new NativeNote( receiverAddress, 29n, - '', + Buffer.from(''), Asset.nativeId(), receiverAddress, ) diff --git a/ironfish/src/testUtilities/fixtures/blocks.ts b/ironfish/src/testUtilities/fixtures/blocks.ts index 0b632a9313..23d4ca9517 100644 --- a/ironfish/src/testUtilities/fixtures/blocks.ts +++ b/ironfish/src/testUtilities/fixtures/blocks.ts @@ -184,7 +184,7 @@ export async function useBlockWithRawTxFixture( const note = new NativeNote( output.publicAddress, output.amount, - output.memo, + Buffer.from(output.memo, 'hex'), output.assetId, sender.publicAddress, ) @@ -255,7 +255,7 @@ export async function useBlockWithTx( { publicAddress: to.publicAddress, amount: BigInt(1), - memo: '', + memo: Buffer.alloc(32), assetId: Asset.nativeId(), }, ], @@ -314,7 +314,7 @@ export async function useBlockWithCustomTxs( { publicAddress: to?.publicAddress ?? from.publicAddress, amount: BigInt(1), - memo: '', + memo: Buffer.alloc(32), assetId: Asset.nativeId(), }, ], diff --git a/ironfish/src/testUtilities/fixtures/transactions.ts b/ironfish/src/testUtilities/fixtures/transactions.ts index e1fb0a7bea..84fd01f6f8 100644 --- a/ironfish/src/testUtilities/fixtures/transactions.ts +++ b/ironfish/src/testUtilities/fixtures/transactions.ts @@ -32,7 +32,7 @@ export async function usePostTxFixture(options: { outputs?: { publicAddress: string amount: bigint - memo: string + memo: Buffer assetId: Buffer }[] mints?: MintData[] @@ -72,7 +72,7 @@ export async function useTxFixture( { publicAddress: to.publicAddress, amount: BigInt(1), - memo: '', + memo: Buffer.alloc(32), assetId: Asset.nativeId(), }, ], @@ -117,7 +117,7 @@ export async function useUnsignedTxFixture( { publicAddress: to.publicAddress, amount: BigInt(1), - memo: '', + memo: Buffer.alloc(32), assetId: Asset.nativeId(), }, ], diff --git a/ironfish/src/testUtilities/helpers/transaction.ts b/ironfish/src/testUtilities/helpers/transaction.ts index e68db43046..9756ce0a04 100644 --- a/ironfish/src/testUtilities/helpers/transaction.ts +++ b/ironfish/src/testUtilities/helpers/transaction.ts @@ -35,7 +35,7 @@ export async function createRawTransaction(options: { outputs?: { publicAddress: string amount: bigint - memo: string + memo: Buffer assetId: Buffer }[] mints?: MintData[] @@ -47,7 +47,7 @@ export async function createRawTransaction(options: { outputs.push({ publicAddress: options.to.publicAddress, amount: options.amount ?? 1n, - memo: '', + memo: Buffer.alloc(32), assetId: options.assetId ?? Asset.nativeId(), }) } diff --git a/ironfish/src/testUtilities/utils.ts b/ironfish/src/testUtilities/utils.ts index 9f12d67331..dc8f961bff 100644 --- a/ironfish/src/testUtilities/utils.ts +++ b/ironfish/src/testUtilities/utils.ts @@ -45,12 +45,13 @@ export async function splitNotes( numOutputs: number, wallet: Wallet, ): Promise { - const outputs: { publicAddress: string; amount: bigint; memo: string; assetId: Buffer }[] = [] + const outputs: { publicAddress: string; amount: bigint; memo: Buffer; assetId: Buffer }[] = [] + for (let i = 0; i < numOutputs; i++) { outputs.push({ publicAddress: account.publicAddress, amount: BigInt(1), - memo: '', + memo: Buffer.alloc(32), assetId: Asset.nativeId(), }) } diff --git a/ironfish/src/wallet/wallet.test.slow.ts b/ironfish/src/wallet/wallet.test.slow.ts index 5122205b9a..010192a43a 100644 --- a/ironfish/src/wallet/wallet.test.slow.ts +++ b/ironfish/src/wallet/wallet.test.slow.ts @@ -128,7 +128,7 @@ describe('Wallet', () => { { publicAddress: generateKey().publicAddress, amount: BigInt(2), - memo: '', + memo: Buffer.alloc(32), assetId: Asset.nativeId(), }, ], @@ -195,7 +195,7 @@ describe('Wallet', () => { { publicAddress: generateKey().publicAddress, amount: BigInt(2), - memo: '', + memo: Buffer.alloc(32), assetId: Asset.nativeId(), }, ], @@ -264,19 +264,19 @@ describe('Wallet', () => { { publicAddress: generateKey().publicAddress, amount: BigInt(2), - memo: 'recipient 1', + memo: Buffer.from('recipient 1'), assetId: Asset.nativeId(), }, { publicAddress: generateKey().publicAddress, amount: BigInt(2), - memo: 'recipient 2', + memo: Buffer.from('recipient 2'), assetId: Asset.nativeId(), }, { publicAddress: generateKey().publicAddress, amount: BigInt(2), - memo: 'recipient 3', + memo: Buffer.from('recipient 3'), assetId: Asset.nativeId(), }, ], @@ -318,7 +318,7 @@ describe('Wallet', () => { { publicAddress: generateKey().publicAddress, amount: BigInt(2), - memo: '', + memo: Buffer.alloc(32), assetId: Asset.nativeId(), }, ], @@ -372,7 +372,7 @@ describe('Wallet', () => { { publicAddress: generateKey().publicAddress, amount: BigInt(2), - memo: '', + memo: Buffer.alloc(32), assetId: Asset.nativeId(), }, ], @@ -456,7 +456,7 @@ describe('Wallet', () => { { publicAddress: generateKey().publicAddress, amount: BigInt(2), - memo: '', + memo: Buffer.alloc(32), assetId: Asset.nativeId(), }, ], @@ -517,7 +517,7 @@ describe('Wallet', () => { { publicAddress: accountB.publicAddress, amount: BigInt(1), - memo: '', + memo: Buffer.alloc(32), assetId: Asset.nativeId(), }, ], @@ -549,7 +549,7 @@ describe('Wallet', () => { { publicAddress: accountC.publicAddress, amount: BigInt(1), - memo: '', + memo: Buffer.alloc(32), assetId: Asset.nativeId(), }, ], @@ -678,7 +678,7 @@ describe('Wallet', () => { { publicAddress: generateKey().publicAddress, amount: BigInt(2), - memo: '', + memo: Buffer.alloc(32), assetId: Asset.nativeId(), }, ], @@ -749,7 +749,7 @@ describe('Wallet', () => { { publicAddress: accountB.publicAddress, amount: BigInt(2), - memo: '', + memo: Buffer.alloc(32), assetId: Asset.nativeId(), }, ], @@ -862,7 +862,7 @@ describe('Wallet', () => { { publicAddress: accountB.publicAddress, amount: BigInt(2), - memo: '', + memo: Buffer.alloc(32), assetId: Asset.nativeId(), }, ], @@ -1228,7 +1228,7 @@ describe('Wallet', () => { { publicAddress: participantB.publicAddress, amount: BigInt(2), - memo: '', + memo: Buffer.alloc(32), assetId: Asset.nativeId(), }, ], @@ -1259,7 +1259,7 @@ describe('Wallet', () => { { publicAddress: recipient.publicAddress, amount: 2n, - memo: '', + memo: Buffer.alloc(32), assetId: Asset.nativeId(), }, ], diff --git a/ironfish/src/wallet/wallet.test.ts b/ironfish/src/wallet/wallet.test.ts index fad2a9351e..c8a5127332 100644 --- a/ironfish/src/wallet/wallet.test.ts +++ b/ironfish/src/wallet/wallet.test.ts @@ -1003,7 +1003,7 @@ describe('Wallet', () => { { publicAddress: '0d804ea639b2547d1cd612682bf99f7cad7aad6d59fd5457f61272defcd4bf5b', amount: 10n, - memo: '', + memo: Buffer.alloc(32), assetId: Asset.nativeId(), }, ], @@ -1037,7 +1037,7 @@ describe('Wallet', () => { { publicAddress: '0d804ea639b2547d1cd612682bf99f7cad7aad6d59fd5457f61272defcd4bf5b', amount: 10n, - memo: '', + memo: Buffer.alloc(32), assetId: Asset.nativeId(), }, ], @@ -1076,7 +1076,7 @@ describe('Wallet', () => { { publicAddress: '0d804ea639b2547d1cd612682bf99f7cad7aad6d59fd5457f61272defcd4bf5b', amount: 10n, - memo: '', + memo: Buffer.alloc(32), assetId: Asset.nativeId(), }, ], @@ -1113,7 +1113,7 @@ describe('Wallet', () => { { publicAddress: '0d804ea639b2547d1cd612682bf99f7cad7aad6d59fd5457f61272defcd4bf5b', amount: 10n, - memo: '', + memo: Buffer.alloc(32), assetId: Asset.nativeId(), }, ], @@ -1150,7 +1150,7 @@ describe('Wallet', () => { { publicAddress: '0d804ea639b2547d1cd612682bf99f7cad7aad6d59fd5457f61272defcd4bf5b', amount: 2000000000n, - memo: '', + memo: Buffer.alloc(32), assetId: Asset.nativeId(), }, ], @@ -1237,7 +1237,7 @@ describe('Wallet', () => { { publicAddress: accountB.publicAddress, amount: BigInt(1), - memo: '', + memo: Buffer.alloc(32), assetId: Asset.nativeId(), }, ], @@ -1336,7 +1336,7 @@ describe('Wallet', () => { { publicAddress: '0d804ea639b2547d1cd612682bf99f7cad7aad6d59fd5457f61272defcd4bf5b', amount: 20n, - memo: '', + memo: Buffer.alloc(32), assetId: asset.id(), }, ], diff --git a/ironfish/src/wallet/wallet.ts b/ironfish/src/wallet/wallet.ts index 6d80355ceb..5208a5c680 100644 --- a/ironfish/src/wallet/wallet.ts +++ b/ironfish/src/wallet/wallet.ts @@ -81,7 +81,7 @@ export enum TransactionType { export type TransactionOutput = { publicAddress: string amount: bigint - memo: string + memo: Buffer assetId: Buffer } diff --git a/ironfish/src/workerPool/pool.ts b/ironfish/src/workerPool/pool.ts index 5fe6043d82..85866c5f24 100644 --- a/ironfish/src/workerPool/pool.ts +++ b/ironfish/src/workerPool/pool.ts @@ -126,7 +126,7 @@ export class WorkerPool { async createMinersFee( spendKey: string, amount: bigint, - memo: string, + memo: Buffer, transactionVersion: TransactionVersion, ): Promise { const request = new CreateMinersFeeRequest(amount, memo, spendKey, transactionVersion) diff --git a/ironfish/src/workerPool/tasks/createMinersFee.test.ts b/ironfish/src/workerPool/tasks/createMinersFee.test.ts index 8eac264adc..5974e42ffd 100644 --- a/ironfish/src/workerPool/tasks/createMinersFee.test.ts +++ b/ironfish/src/workerPool/tasks/createMinersFee.test.ts @@ -17,7 +17,7 @@ describe('CreateMinersFeeRequest', () => { it('serializes the object to a buffer and deserializes to the original object', () => { const request = new CreateMinersFeeRequest( BigInt(0), - 'memo', + Buffer.from('memo'), 'spendKey', TransactionVersion.V1, ) @@ -47,7 +47,7 @@ describe('CreateMinersFeeTask', () => { const account = await useAccountFixture(nodeTest.wallet) const request = new CreateMinersFeeRequest( BigInt(0), - 'memo', + Buffer.from('memo'), account.spendingKey, TransactionVersion.V1, ) @@ -62,7 +62,7 @@ describe('CreateMinersFeeTask', () => { const account = await useAccountFixture(nodeTest.wallet) const request = new CreateMinersFeeRequest( BigInt(0), - 'memo', + Buffer.from('memo'), account.spendingKey, TransactionVersion.V2, ) diff --git a/ironfish/src/workerPool/tasks/createMinersFee.ts b/ironfish/src/workerPool/tasks/createMinersFee.ts index 73a412bc69..0f6a744a3b 100644 --- a/ironfish/src/workerPool/tasks/createMinersFee.ts +++ b/ironfish/src/workerPool/tasks/createMinersFee.ts @@ -10,13 +10,13 @@ import { WorkerTask } from './workerTask' export class CreateMinersFeeRequest extends WorkerMessage { readonly amount: bigint - readonly memo: string + readonly memo: Buffer readonly spendKey: string readonly transactionVersion: TransactionVersion constructor( amount: bigint, - memo: string, + memo: Buffer, spendKey: string, transactionVersion: TransactionVersion, jobId?: number, @@ -30,7 +30,7 @@ export class CreateMinersFeeRequest extends WorkerMessage { serializePayload(bw: bufio.StaticWriter | bufio.BufferWriter): void { bw.writeVarBytes(BigIntUtils.toBytesBE(this.amount)) - bw.writeVarString(this.memo, 'utf8') + bw.writeVarBytes(this.memo) bw.writeVarString(this.spendKey, 'utf8') bw.writeU8(this.transactionVersion) } @@ -38,7 +38,7 @@ export class CreateMinersFeeRequest extends WorkerMessage { static deserializePayload(jobId: number, buffer: Buffer): CreateMinersFeeRequest { const reader = bufio.read(buffer, true) const amount = BigIntUtils.fromBytesBE(reader.readVarBytes()) - const memo = reader.readVarString('utf8') + const memo = reader.readVarBytes() const spendKey = reader.readVarString('utf8') const transactionVersion = reader.readU8() return new CreateMinersFeeRequest(amount, memo, spendKey, transactionVersion, jobId) @@ -47,7 +47,7 @@ export class CreateMinersFeeRequest extends WorkerMessage { getSize(): number { return ( bufio.sizeVarBytes(BigIntUtils.toBytesBE(this.amount)) + - bufio.sizeVarString(this.memo, 'utf8') + + bufio.sizeVarBytes(this.memo) + bufio.sizeVarString(this.spendKey, 'utf8') + 1 // transactionVersion ) diff --git a/ironfish/src/workerPool/tasks/workerMessages.test.perf.ts b/ironfish/src/workerPool/tasks/workerMessages.test.perf.ts index f828a46dc4..93e765ca78 100644 --- a/ironfish/src/workerPool/tasks/workerMessages.test.perf.ts +++ b/ironfish/src/workerPool/tasks/workerMessages.test.perf.ts @@ -33,7 +33,7 @@ describe('WorkerMessages', () => { Assert.isNotNull(account.spendingKey) const message = new CreateMinersFeeRequest( CurrencyUtils.decodeIron(20), - 'hello world memo', + Buffer.from('hello world memo'), account.spendingKey, TransactionVersion.V1, )