diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cbf907c..8e5551fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - spending_counter - settings_new - settings_get +- fragment_from_raw +- fragment_id +- fragment_delete + +### Changed + +pending_transactions now returns transactions in order relative to the same +wallet type instead of arbitrary order. First starting with deadalus/yoroi/free +utxo keys (those are all exclusive) in order of creating, and then the account +transactions, also in order of creation (and signing). ## [0.6.0-pre1] diff --git a/bindings/wallet-c/src/lib.rs b/bindings/wallet-c/src/lib.rs index 7fb99f10..3a732d0d 100644 --- a/bindings/wallet-c/src/lib.rs +++ b/bindings/wallet-c/src/lib.rs @@ -6,6 +6,7 @@ use std::{ }; pub use wallet::Settings as SettingsRust; use wallet_core::c::{ + fragment::{fragment_delete, fragment_from_raw, fragment_id}, symmetric_cipher_decrypt, vote, wallet_convert, wallet_convert_ignored, wallet_convert_transactions_get, wallet_convert_transactions_size, wallet_delete_conversion, wallet_delete_error, wallet_delete_proposal, wallet_delete_settings, wallet_delete_wallet, @@ -13,8 +14,8 @@ use wallet_core::c::{ wallet_spending_counter, wallet_total_value, wallet_vote_cast, }; use wallet_core::{ - Conversion as ConversionRust, Error as ErrorRust, Proposal as ProposalRust, - Wallet as WalletRust, + Conversion as ConversionRust, Error as ErrorRust, Fragment as FragmentRust, + Proposal as ProposalRust, Wallet as WalletRust, }; #[repr(C)] @@ -26,6 +27,8 @@ pub struct Conversion {} #[repr(C)] pub struct Proposal {} #[repr(C)] +pub struct Fragment; +#[repr(C)] pub struct Error {} #[repr(C)] @@ -35,6 +38,7 @@ pub type WalletPtr = *mut Wallet; pub type SettingsPtr = *mut Settings; pub type ConversionPtr = *mut Conversion; pub type ProposalPtr = *mut Proposal; +pub type FragmentPtr = *mut Fragment; pub type ErrorPtr = *mut Error; pub type EncryptingVoteKeyPtr = *mut EncryptingVoteKey; @@ -657,6 +661,70 @@ pub unsafe extern "C" fn iohk_jormungandr_wallet_error_details(error: ErrorPtr) } } +/// deserialize a fragment from bytes +/// +/// # Parameters +/// +/// * `buffer` -- a pointer to the serialized fragment bytes. +/// * `buffer_length` -- the length of the serialized fragment bytes array. +/// * `fragment` -- the location of the pointer to the deserialized fragemnt. +/// +/// # Errors +/// +/// This functions may fail if: +/// +/// * `buffer` is a null pointer. +/// * `fragment` is a null pointer. +/// * `buffer` contains invalid fragment bytes. +/// +/// # Safety +/// +/// This function dereference raw pointers. Even though +/// the function checks if the pointers are null. Mind not to put random values +/// in or you may see unexpected behaviors +/// +/// Don't forget to delete the fragment object with `iohk_jormungandr_delete_fragment`. +#[no_mangle] +pub unsafe extern "C" fn iohk_jormungandr_fragment_from_raw( + buffer: *const u8, + buffer_length: usize, + fragment_out: *mut FragmentPtr, +) -> ErrorPtr { + fragment_from_raw( + buffer, + buffer_length, + fragment_out as *mut *mut FragmentRust, + ) + .into_c_api() as ErrorPtr +} + +/// get the ID of the provided fragment +/// +/// # Parameters +/// +/// * `fragment` -- a pointer to fragment. +/// * `fragment_id_out` -- a pointer to a pre-allocated 32 bytes array. +/// +/// # Errors +/// +/// This function would return an error if either of the provided pointers is null. +/// +/// # Safety +/// +/// This function dereference raw pointers. Even though +/// the function checks if the pointers are null. Mind not to put random values +/// in or you may see unexpected behaviors. +/// +/// `fragment_id_out` is expected to be an already allocated 32 byte array. Doing otherwise may +/// potentially result into an undefined behavior. +#[no_mangle] +pub unsafe extern "C" fn iohk_jormungandr_fragment_id( + fragment: FragmentPtr, + fragment_id_out: *mut u8, +) -> ErrorPtr { + fragment_id(fragment as *mut FragmentRust, fragment_id_out).into_c_api() as ErrorPtr +} + /// Delete a null terminated string that was allocated by this library /// /// # Safety @@ -754,3 +822,15 @@ pub extern "C" fn iohk_jormungandr_wallet_delete_conversion(conversion: Conversi pub extern "C" fn iohk_jormungandr_wallet_delete_proposal(proposal: ProposalPtr) { wallet_delete_proposal(proposal as *mut ProposalRust) } + +/// delete the pointer +/// +/// # Safety +/// +/// This function dereference raw pointers. Even though +/// the function checks if the pointers are null. Mind not to put random values +/// in or you may see unexpected behaviors +#[no_mangle] +pub unsafe extern "C" fn iohk_jormungandr_delete_fragment(fragment: FragmentPtr) { + fragment_delete(fragment as *mut FragmentRust); +} diff --git a/bindings/wallet-c/wallet.h b/bindings/wallet-c/wallet.h index ccbbc8db..4e6a918c 100644 --- a/bindings/wallet-c/wallet.h +++ b/bindings/wallet-c/wallet.h @@ -10,7 +10,7 @@ #ifndef IOHK_CHAIN_WALLET_LIBC_ #define IOHK_CHAIN_WALLET_LIBC_ -/* Generated with cbindgen:0.18.0 */ +/* Generated with cbindgen:0.19.0 */ /* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */ @@ -25,6 +25,13 @@ typedef enum Discrimination Discrimination_Test, } Discrimination; +typedef struct Fragment +{ + +} Fragment; + +typedef struct Fragment *FragmentPtr; + typedef struct Error { @@ -86,6 +93,70 @@ typedef struct LinearFee struct PerVoteCertificateFee per_vote_certificate_fees; } LinearFee; +/** + * delete the pointer + * + * # Safety + * + * This function dereference raw pointers. Even though + * the function checks if the pointers are null. Mind not to put random values + * in or you may see unexpected behaviors + */ +void iohk_jormungandr_delete_fragment(FragmentPtr fragment); + +/** + * deserialize a fragment from bytes + * + * # Parameters + * + * * `buffer` -- a pointer to the serialized fragment bytes. + * * `buffer_length` -- the length of the serialized fragment bytes array. + * * `fragment` -- the location of the pointer to the deserialized fragemnt. + * + * # Errors + * + * This functions may fail if: + * + * * `buffer` is a null pointer. + * * `fragment` is a null pointer. + * * `buffer` contains invalid fragment bytes. + * + * # Safety + * + * This function dereference raw pointers. Even though + * the function checks if the pointers are null. Mind not to put random values + * in or you may see unexpected behaviors + * + * Don't forget to delete the fragment object with `iohk_jormungandr_delete_fragment`. + */ +ErrorPtr iohk_jormungandr_fragment_from_raw(const uint8_t *buffer, + uintptr_t buffer_length, + FragmentPtr *fragment_out); + +/** + * get the ID of the provided fragment + * + * # Parameters + * + * * `fragment` -- a pointer to fragment. + * * `fragment_id_out` -- a pointer to a pre-allocated 32 bytes array. + * + * # Errors + * + * This function would return an error if either of the provided pointers is null. + * + * # Safety + * + * This function dereference raw pointers. Even though + * the function checks if the pointers are null. Mind not to put random values + * in or you may see unexpected behaviors. + * + * `fragment_id_out` is expected to be an already allocated 32 byte array. Doing otherwise may + * potentially result into an undefined behavior. + */ +ErrorPtr iohk_jormungandr_fragment_id(FragmentPtr fragment, + uint8_t *fragment_id_out); + /** * decrypt payload of the wallet transfer protocol * diff --git a/bindings/wallet-cordova/build_jni.py b/bindings/wallet-cordova/build_jni.py index b5e29290..6ed60883 100755 --- a/bindings/wallet-cordova/build_jni.py +++ b/bindings/wallet-cordova/build_jni.py @@ -21,7 +21,7 @@ def run(): for rust_target, android_target in targets.items(): - out = subprocess.run(["cargo", "rustc", "--release", "--target", + out = subprocess.run(["cross", "rustc", "--release", "--target", rust_target, "-p" "wallet-jni", "--", "-C", "lto"]) if out.returncode != 0: diff --git a/bindings/wallet-cordova/plugin.xml b/bindings/wallet-cordova/plugin.xml index f4b149ce..4fdcc80d 100644 --- a/bindings/wallet-cordova/plugin.xml +++ b/bindings/wallet-cordova/plugin.xml @@ -27,6 +27,7 @@ + diff --git a/bindings/wallet-cordova/src/android/WalletPlugin.java b/bindings/wallet-cordova/src/android/WalletPlugin.java index cfed004b..22731385 100644 --- a/bindings/wallet-cordova/src/android/WalletPlugin.java +++ b/bindings/wallet-cordova/src/android/WalletPlugin.java @@ -10,6 +10,7 @@ import com.iohk.jormungandrwallet.Conversion; import com.iohk.jormungandrwallet.Proposal; import com.iohk.jormungandrwallet.SymmetricCipher; +import com.iohk.jormungandrwallet.Fragment; import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaInterface; @@ -120,6 +121,9 @@ public boolean execute(final String action, final CordovaArgs args, final Callba case "SETTINGS_GET": settingsGet(args, callbackContext); break; + case "FRAGMENT_ID": + fragmentId(args, callbackContext); + break; case "WALLET_DELETE": walletDelete(args, callbackContext); break; @@ -467,6 +471,19 @@ private void walletId(final CordovaArgs args, final CallbackContext callbackCont } } + private void fragmentId(final CordovaArgs args, final CallbackContext callbackContext) throws JSONException { + final byte[] transaction = args.getArrayBuffer(0); + + try { + final long fragmentPtr = Fragment.fromBytes(transaction); + final byte[] id = Fragment.id(fragmentPtr); + Fragment.delete(fragmentPtr); + callbackContext.success(id); + } catch (final Exception e) { + callbackContext.error(e.getMessage()); + } + } + private void walletDelete(final CordovaArgs args, final CallbackContext callbackContext) throws JSONException { final Long walletPtr = args.getLong(0); diff --git a/bindings/wallet-cordova/src/ios/WalletPlugin.h b/bindings/wallet-cordova/src/ios/WalletPlugin.h index 5438f4eb..fb132b55 100644 --- a/bindings/wallet-cordova/src/ios/WalletPlugin.h +++ b/bindings/wallet-cordova/src/ios/WalletPlugin.h @@ -26,6 +26,8 @@ - (void)SETTINGS_NEW:(CDVInvokedUrlCommand*)command; - (void)SETTINGS_GET:(CDVInvokedUrlCommand*)command; +- (void)FRAGMENT_ID:(CDVInvokedUrlCommand*)command; + - (void)WALLET_DELETE:(CDVInvokedUrlCommand*)command; - (void)SETTINGS_DELETE:(CDVInvokedUrlCommand*)command; - (void)CONVERSION_DELETE:(CDVInvokedUrlCommand*)command; diff --git a/bindings/wallet-cordova/src/ios/WalletPlugin.m b/bindings/wallet-cordova/src/ios/WalletPlugin.m index 8b1a82af..3da408d2 100644 --- a/bindings/wallet-cordova/src/ios/WalletPlugin.m +++ b/bindings/wallet-cordova/src/ios/WalletPlugin.m @@ -578,6 +578,43 @@ - (void)SETTINGS_GET:(CDVInvokedUrlCommand*)command [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } +- (void)FRAGMENT_ID:(CDVInvokedUrlCommand*)command +{ + CDVPluginResult* pluginResult = nil; + + NSData* fragment_raw = [command.arguments objectAtIndex:0]; + + if ([fragment_raw isEqual:[NSNull null]]) { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR + messageAsString:@"missing argument"]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + return; + } + + FragmentPtr fragment_ptr = nil; + ErrorPtr result_fragment_from_raw = + iohk_jormungandr_fragment_from_raw(fragment_raw.bytes, fragment_raw.length, &fragment_ptr); + + if (result_fragment_from_raw != nil) { + pluginResult = jormungandr_error_to_plugin_result(result_fragment_from_raw); + } else { + uint8_t fragment_id[32]; + ErrorPtr result_fragment_id = iohk_jormungandr_fragment_id(fragment_ptr, fragment_id); + + if (result_fragment_id != nil) { + pluginResult = jormungandr_error_to_plugin_result(result_fragment_id); + } else { + NSData* returnValue = [NSData dataWithBytes:fragment_id length:32]; + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK + messageAsArrayBuffer:returnValue]; + } + + iohk_jormungandr_delete_fragment(fragment_ptr); + } + + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + - (void)WALLET_DELETE:(CDVInvokedUrlCommand*)command { NSString* wallet_ptr_raw = [command.arguments objectAtIndex:0]; diff --git a/bindings/wallet-cordova/tests/src/main.js b/bindings/wallet-cordova/tests/src/main.js index d5ab9699..f78860dc 100644 --- a/bindings/wallet-cordova/tests/src/main.js +++ b/bindings/wallet-cordova/tests/src/main.js @@ -45,6 +45,7 @@ const pendingTransactionsGet = promisifyP(primitives.pendingTransactionsGet); const symmetricCipherDecrypt = promisifyP(primitives.symmetricCipherDecrypt); const settingsGet = promisifyP(primitives.settingsGet); const settingsNew = promisifyP(primitives.settingsNew); +const fragmentId = promisifyP(primitives.fragmentId); const tests = [ @@ -159,6 +160,9 @@ const tests = [ expect(await spendingCounter(walletPtr)).toBe(1); + const pending = await getPendingTransactions(walletPtr); + expect(pending.length).toBe(1); + await deleteSettings(settingsPtr); await deleteWallet(walletPtr); await deleteProposal(proposalPtr); @@ -295,6 +299,36 @@ const tests = [ await deleteSettings(settingsPtr); }], + ['get vote fragment id', async function () { + const array = new Array(32); + for (let index = 0; index < array.length; index++) { + array[index] = index; + } + + const votePlanId = new Uint8Array(array); + const index = 0; + const numChoices = 3; + + const proposalPtr = await proposalNewPublic(votePlanId, index, numChoices); + const walletPtr = await restoreWallet(YOROI_WALLET); + const settingsPtr = await retrieveFunds(walletPtr, hexStringToBytes(BLOCK0)); + await walletSetState(walletPtr, 1000000, 0); + + const tx1 = await walletVote(walletPtr, settingsPtr, proposalPtr, 1); + const tx2 = await walletVote(walletPtr, settingsPtr, proposalPtr, 2); + + const id1 = await fragmentId(new Uint8Array(tx1)); + const id2 = await fragmentId(new Uint8Array(tx2)); + + const pendingTransactions = await getPendingTransactions(walletPtr); + + expect(uint8ArrayEquals(id1, pendingTransactions[0])).toBe(true); + expect(uint8ArrayEquals(id2, pendingTransactions[0])).toBe(true); + + await deleteSettings(settingsPtr); + await deleteWallet(walletPtr); + await deleteProposal(proposalPtr); + }], ] exports.defineAutoTests = function () { diff --git a/bindings/wallet-cordova/www/wallet.js b/bindings/wallet-cordova/www/wallet.js index 11be8146..b5148763 100644 --- a/bindings/wallet-cordova/www/wallet.js +++ b/bindings/wallet-cordova/www/wallet.js @@ -30,6 +30,7 @@ const PENDING_TRANSACTIONS_SIZE = 'PENDING_TRANSACTIONS_SIZE'; const SYMMETRIC_CIPHER_DECRYPT = 'SYMMETRIC_CIPHER_DECRYPT'; const SETTINGS_NEW = 'SETTINGS_NEW'; const SETTINGS_GET = 'SETTINGS_GET'; +const FRAGMENT_ID = 'FRAGMENT_ID'; const VOTE_PLAN_ID_LENGTH = 32; const FRAGMENT_ID_LENGTH = 32; @@ -91,6 +92,11 @@ var plugin = { TEST: 1 }, + /** + * @callback TransactionIdCallback + * @param {Uint8Array} id + */ + /** * @param {string} mnemonics a string with the mnemonic phrase * @param {pointerCallback} successCallback on success returns a pointer to a Wallet object @@ -255,7 +261,7 @@ var plugin = { /** * @param {string} ptr a pointer to a PendingTransactions object obtained with walletPendingTransactions * @param {number} index an index (starting from 0). Use pendingTransactionsSize to get the upper bound - * @param {function} successCallback callback that receives a transaction in binary form + * @param {TransactionIdCallback} successCallback callback that receives a transaction id in binary form * @param {errorCallback} errorCallback this function can fail if the index is out of range */ pendingTransactionsGet: function (ptr, index, successCallback, errorCallback) { @@ -386,6 +392,18 @@ var plugin = { exec(decodeBase64, errorCallback, NATIVE_CLASS_NAME, SETTINGS_GET, [settingsPtr]); }, + /** + * @param {Uint8Array} transaction + * @param {TransactionIdCallback} successCallback + * @param {errorCallback} errorCallback + */ + fragmentId: function (transaction, successCallback, errorCallback) { + argscheck.checkArgs('*ff', 'transactionId', arguments); + checkUint8Array({ name: 'transaction', testee: transaction }); + + exec(successCallback, errorCallback, NATIVE_CLASS_NAME, FRAGMENT_ID, [transaction.buffer]); + }, + /** * @param {string} ptr a pointer to a Wallet obtained with walletRestore * @param {function} successCallback indicates success. Does not return anything. diff --git a/bindings/wallet-core/src/c/fragment.rs b/bindings/wallet-core/src/c/fragment.rs new file mode 100644 index 00000000..be44e6c7 --- /dev/null +++ b/bindings/wallet-core/src/c/fragment.rs @@ -0,0 +1,73 @@ +use crate::{Error, Result}; +use chain_core::property::Deserialize; +use chain_impl_mockchain::fragment::{Fragment, FragmentRaw}; +use core::slice; + +use super::{FragmentPtr, NulPtr, FRAGMENT_ID_LENGTH}; + +/// # Safety +/// +/// buffer must be non null and point to buffer_length bytes of valid memory. +/// +pub unsafe fn fragment_from_raw( + buffer: *const u8, + buffer_length: usize, + fragment_out: *mut FragmentPtr, +) -> Result { + if buffer.is_null() { + return Error::invalid_input("buffer").with(NulPtr).into(); + } + + let fragment_out_ref = non_null_mut!(fragment_out); + + let bytes = slice::from_raw_parts(buffer, buffer_length); + + let raw = match FragmentRaw::deserialize(bytes) { + Ok(raw) => raw, + Err(_e) => return Error::invalid_fragment().into(), + }; + + let fragment = match Fragment::from_raw(&raw) { + Ok(fragment) => fragment, + Err(_e) => return Error::invalid_fragment().into(), + }; + + let fragment = Box::new(fragment); + + *fragment_out_ref = Box::into_raw(fragment); + + Result::success() +} + +/// # Safety +/// +/// fragment_ptr must be a pointer to memory allocated by this library, for +/// example, with `fragment_from_raw` +/// id_out must point to FRAGMENT_ID_LENGTH bytes of valid allocated writable +/// memory +/// This function checks for null pointers +/// +pub unsafe fn fragment_id(fragment_ptr: FragmentPtr, id_out: *mut u8) -> Result { + let fragment = non_null!(fragment_ptr); + + let id = fragment.hash(); + + let bytes = id.as_bytes(); + + assert_eq!(bytes.len(), FRAGMENT_ID_LENGTH); + + std::ptr::copy(bytes.as_ptr(), id_out, bytes.len()); + + Result::success() +} + +/// # Safety +/// +/// This function checks for null pointers, but take care that fragment_ptr was +/// previously allocated by this library for example with fragment_from_raw +/// +pub unsafe fn fragment_delete(fragment_ptr: FragmentPtr) { + if !fragment_ptr.is_null() { + Box::from_raw(fragment_ptr as FragmentPtr); + } +} diff --git a/bindings/wallet-core/src/c/mod.rs b/bindings/wallet-core/src/c/mod.rs index f60a9bdf..e2cdac8c 100644 --- a/bindings/wallet-core/src/c/mod.rs +++ b/bindings/wallet-core/src/c/mod.rs @@ -2,11 +2,12 @@ //! C style bindings that we have (wallet-c, wallet-jni...) #[macro_use] mod macros; +pub mod fragment; pub mod settings; pub mod vote; use crate::{Conversion, Error, Proposal, Result, Wallet}; -use chain_impl_mockchain::{transaction::Input, value::Value, vote::Choice}; +use chain_impl_mockchain::{fragment::Fragment, transaction::Input, value::Value, vote::Choice}; use std::convert::TryInto; use thiserror::Error; @@ -18,6 +19,7 @@ pub type ConversionPtr = *mut Conversion; pub type ProposalPtr = *mut Proposal; pub type ErrorPtr = *mut Error; pub type PendingTransactionsPtr = *mut PendingTransactions; +pub type FragmentPtr = *mut Fragment; #[derive(Debug, Error)] #[error("null pointer")] @@ -328,12 +330,7 @@ pub unsafe fn wallet_pending_transactions( let wallet = non_null!(wallet); let pending_transactions = PendingTransactions { - fragment_ids: wallet - .pending_transactions() - .iter() - .cloned() - .collect::>() - .into_boxed_slice(), + fragment_ids: wallet.pending_transactions().into_boxed_slice(), }; *pending_transactions_out = diff --git a/bindings/wallet-core/src/error.rs b/bindings/wallet-core/src/error.rs index 20dd09a9..eda80680 100644 --- a/bindings/wallet-core/src/error.rs +++ b/bindings/wallet-core/src/error.rs @@ -64,6 +64,9 @@ pub enum ErrorCode { /// wallet out of funds NotEnoughFunds = 9, + + /// invalid fragment + InvalidFragment = 10, } #[derive(Debug)] @@ -100,6 +103,9 @@ pub enum ErrorKind { /// wallet out of funds NotEnoughFunds, + + /// invalid fragment + InvalidFragment, } impl ErrorKind { @@ -118,6 +124,7 @@ impl ErrorKind { Self::SymmetricCipherInvalidPassword => ErrorCode::SymmetricCipherInvalidPassword, Self::InvalidVoteEncryptionKey => ErrorCode::InvalidVoteEncryptionKey, Self::NotEnoughFunds => ErrorCode::NotEnoughFunds, + Self::InvalidFragment => ErrorCode::InvalidFragment, } } } @@ -226,6 +233,13 @@ impl Error { } } + pub fn invalid_fragment() -> Self { + Self { + kind: ErrorKind::InvalidFragment, + details: None, + } + } + /// set some details to the `Result` object if the `Result` is of /// error kind /// @@ -369,6 +383,7 @@ impl Display for ErrorKind { Self::SymmetricCipherInvalidPassword => f.write_str("invalid decryption password"), Self::InvalidVoteEncryptionKey => f.write_str("invalid vote encryption key"), Self::NotEnoughFunds => f.write_str("not enough funds to create transaction"), + Self::InvalidFragment => f.write_str("invalid fragment"), } } } diff --git a/bindings/wallet-core/src/lib.rs b/bindings/wallet-core/src/lib.rs index da1b46f5..29b84aa4 100644 --- a/bindings/wallet-core/src/lib.rs +++ b/bindings/wallet-core/src/lib.rs @@ -12,7 +12,7 @@ pub use self::{ }; pub use ::wallet::Settings; pub use chain_impl_mockchain::{ - fragment::FragmentId, + fragment::{Fragment, FragmentId}, value::Value, vote::{Choice, Options, PayloadType}, }; diff --git a/bindings/wallet-core/src/wallet.rs b/bindings/wallet-core/src/wallet.rs index 52bade94..196fe873 100644 --- a/bindings/wallet-core/src/wallet.rs +++ b/bindings/wallet-core/src/wallet.rs @@ -244,29 +244,39 @@ impl Wallet { /// /// TODO: this might need to be updated to have a more user friendly /// API. Currently do this for simplicity - pub fn pending_transactions(&self) -> std::collections::HashSet { - let mut set = std::collections::HashSet::new(); + pub fn pending_transactions(&self) -> Vec { + let mut check = std::collections::HashSet::new(); + let mut result = vec![]; if let Some(daedalus) = &self.daedalus { for id in daedalus.pending_transactions() { - set.insert(*id); + if check.insert(*id) { + result.push(*id); + } } } if let Some(icarus) = &self.icarus { for id in icarus.pending_transactions() { - set.insert(*id); + if check.insert(*id) { + result.push(*id); + } } } for id in self.free_keys.pending_transactions() { - set.insert(*id); + if check.insert(*id) { + result.push(*id); + } } for id in self.account.pending_transactions() { - set.insert(*id); + if check.insert(*id) { + result.push(*id); + } } - set + + result } /// remove a given pending transaction returning the associated Inputs diff --git a/bindings/wallet-jni/java/WalletTest.java b/bindings/wallet-jni/java/WalletTest.java index 72dce911..3dadc409 100644 --- a/bindings/wallet-jni/java/WalletTest.java +++ b/bindings/wallet-jni/java/WalletTest.java @@ -1,10 +1,11 @@ import com.iohk.jormungandrwallet.Wallet; import com.iohk.jormungandrwallet.Settings; import com.iohk.jormungandrwallet.Conversion; +import com.iohk.jormungandrwallet.Fragment; import com.iohk.jormungandrwallet.Proposal; import com.iohk.jormungandrwallet.PendingTransactions; import com.iohk.jormungandrwallet.SymmetricCipher; -import com.iohk.jormungandrwallet.Settings; +import com.iohk.jormungandrwallet.Fragment; import java.util.Properties; import java.util.Enumeration; @@ -74,8 +75,8 @@ public void extractSettings() throws IOException { public void buildSettings() throws IOException { final byte[] blockId = hexStringToByteArray("182764b45bae25cc466143de8107618b37f0d28fe3daa0a0d39fd0ab5a2061e1"); final Settings.Discrimination discrimination = Settings.Discrimination.TEST; - final Settings.LinearFees expectedFees = new Settings.LinearFees(1, 2, 3, new Settings.PerCertificateFee(4, 5, 6), - new Settings.PerVoteCertificateFee(7, 8)); + final Settings.LinearFees expectedFees = new Settings.LinearFees(1, 2, 3, + new Settings.PerCertificateFee(4, 5, 6), new Settings.PerVoteCertificateFee(7, 8)); Settings.PerCertificateFee test = new Settings.PerCertificateFee(4, 5, 6); @@ -96,8 +97,10 @@ public void buildSettings() throws IOException { assertEquals(fees.perCertificateFee.certificateOwnerStakeDelegation, expectedFees.perCertificateFee.certificateOwnerStakeDelegation); - assertEquals(fees.perVoteCertificateFee.certificateVotePlan, expectedFees.perVoteCertificateFee.certificateVotePlan); - assertEquals(fees.perVoteCertificateFee.certificateVoteCast, expectedFees.perVoteCertificateFee.certificateVoteCast); + assertEquals(fees.perVoteCertificateFee.certificateVotePlan, + expectedFees.perVoteCertificateFee.certificateVotePlan); + assertEquals(fees.perVoteCertificateFee.certificateVoteCast, + expectedFees.perVoteCertificateFee.certificateVoteCast); final Settings.Discrimination foundDiscrimination = Settings.discrimination(settingsPtr); @@ -301,6 +304,47 @@ public void confirmVoteCast() throws IOException { } } + @Test + public void fragmentId() throws IOException { + final long walletPtr = Wallet.recover( + "neck bulb teach illegal soul cry monitor claw amount boring provide village rival draft stone"); + + final byte[] block0 = Files.readAllBytes(Paths.get("../../../test-vectors/block0")); + + final long settingsPtr = Wallet.initialFunds(walletPtr, block0); + + final byte[] id = new byte[Proposal.ID_SIZE]; + final long proposalPtr = Proposal.withPublicPayload(id, 0, 3); + + Wallet.setState(walletPtr, 10000000, 0); + + try { + final byte[] transaction = Wallet.voteCast(walletPtr, settingsPtr, proposalPtr, 1); + + final long fragment = Fragment.fromBytes(transaction); + final byte[] fragmentId = Fragment.id(fragment); + Fragment.delete(fragment); + + final long pending = Wallet.pendingTransactions(walletPtr); + + final int sizeBefore = PendingTransactions.len(pending); + + final byte[] expectedFragmentId = PendingTransactions.get(pending, 0); + + for (int i = 0; i < fragmentId.length; i++) { + assertEquals(fragmentId[i], expectedFragmentId[i]); + } + + PendingTransactions.delete(pending); + } catch (final Exception e) { + Proposal.delete(proposalPtr); + Settings.delete(settingsPtr); + Wallet.delete(walletPtr); + System.out.println(e.getMessage()); + throw e; + } + } + @Test public void privateVoteCast() throws IOException { final long walletPtr = Wallet.recover( diff --git a/bindings/wallet-jni/java/com/iohk/jormungandrwallet/Fragment.java b/bindings/wallet-jni/java/com/iohk/jormungandrwallet/Fragment.java new file mode 100644 index 00000000..701294d1 --- /dev/null +++ b/bindings/wallet-jni/java/com/iohk/jormungandrwallet/Fragment.java @@ -0,0 +1,13 @@ +package com.iohk.jormungandrwallet; + +public class Fragment { + static { + System.loadLibrary("wallet_jni"); + } + + public native static long fromBytes(byte[] buffer); + + public native static byte[] id(long fragmentPtr); + + public native static void delete (long fragmentPtr); +} diff --git a/bindings/wallet-jni/src/lib.rs b/bindings/wallet-jni/src/lib.rs index 8dcf6116..58a894d6 100644 --- a/bindings/wallet-jni/src/lib.rs +++ b/bindings/wallet-jni/src/lib.rs @@ -3,11 +3,14 @@ use jni::sys::{jbyte, jbyteArray, jint, jlong}; use jni::JNIEnv; use std::convert::TryInto; use std::ptr::{null, null_mut}; -use wallet_core::c::settings::{ - settings_block0_hash, settings_discrimination, settings_fees, settings_new, Discrimination, - LinearFee, PerCertificateFee, PerVoteCertificateFee, -}; use wallet_core::c::*; +use wallet_core::c::{ + fragment::{fragment_from_raw, fragment_id}, + settings::{ + settings_block0_hash, settings_discrimination, settings_fees, settings_new, Discrimination, + LinearFee, PerCertificateFee, PerVoteCertificateFee, + }, +}; /// /// # Safety @@ -1027,3 +1030,91 @@ pub extern "system" fn Java_com_iohk_jormungandrwallet_SymmetricCipher_decrypt( } } } + +/// +/// # Safety +/// +/// This function dereference raw pointers. Even though +/// the function checks if the pointers are null. Mind not to put random values +/// in or you may see unexpected behaviors +/// +#[no_mangle] +pub unsafe extern "system" fn Java_com_iohk_jormungandrwallet_Fragment_fromBytes( + env: JNIEnv, + _: JClass, + buffer: jbyteArray, +) -> jlong { + let len = env + .get_array_length(buffer) + .expect("Couldn't get block0 array length") as usize; + + let mut bytes = vec![0i8; len as usize]; + + env.get_byte_array_region(buffer, 0, &mut bytes).unwrap(); + + let mut ptr: FragmentPtr = null_mut(); + + let result = fragment_from_raw( + bytes.as_ptr().cast::(), + len, + &mut ptr as *mut FragmentPtr, + ); + + if let Some(error) = result.error() { + let _ = env.throw(error.to_string()); + } + + ptr as jlong +} + +/// +/// # Safety +/// +/// This function dereference raw pointers. Even though +/// the function checks if the pointers are null. Mind not to put random values +/// in or you may see unexpected behaviors +/// +#[no_mangle] +pub unsafe extern "system" fn Java_com_iohk_jormungandrwallet_Fragment_id( + env: JNIEnv, + _: JClass, + fragment: jlong, +) -> jbyteArray { + let mut id = [0u8; FRAGMENT_ID_LENGTH]; + + let result = fragment_id(fragment as FragmentPtr, id.as_mut_ptr()); + + let array = env + .new_byte_array(id.len() as jint) + .expect("Failed to create new byte array"); + + match result.error() { + None => { + let slice = std::slice::from_raw_parts(id.as_ptr() as *const jbyte, id.len()); + + env.set_byte_array_region(array, 0, slice) + .expect("Couldn't copy array to jvm"); + } + Some(error) => { + let _ = env.throw(error.to_string()); + } + }; + + array +} + +/// +/// # Safety +/// +/// This function dereference raw pointers. Even though +/// the function checks if the pointers are null. Mind not to put random values +/// in or you may see unexpected behaviors +/// +#[no_mangle] +pub unsafe extern "system" fn Java_com_iohk_jormungandrwallet_Fragment_delete( + _env: JNIEnv, + _: JClass, + fragment: jlong, +) { + fragment::fragment_delete(fragment as FragmentPtr); +}