From 8d82fd01414d41ae7f3bd2215174f28e7f7582d4 Mon Sep 17 00:00:00 2001 From: David Main <51991544+StriderDM@users.noreply.github.com> Date: Mon, 30 Aug 2021 13:34:32 +0200 Subject: [PATCH] refactor: refactor wallet ffi cucumber tests (#3259) Description Refactored WalletFFI.feature into a working state, tested locally. Further refactoring and dead code removal would be beneficial. Motivation and Context Necessary to get WalletFFI.feature working. How Has This Been Tested? Tested locally, each scenario tested with: `./node_modules/.bin/cucumber-js --name "${scenario_name}"` --- .circleci/config.yml | 4 +- applications/ffi_client/index.js | 2 + base_layer/wallet_ffi/src/lib.rs | 12 +- integration_tests/features/WalletFFI.feature | 165 +- integration_tests/features/support/steps.js | 521 ++++-- integration_tests/features/support/world.js | 6 +- integration_tests/helpers/ffi/byteVector.js | 41 +- integration_tests/helpers/ffi/commsConfig.js | 43 + .../helpers/ffi/completedTransaction.js | 93 +- .../helpers/ffi/completedTransactions.js | 31 +- integration_tests/helpers/ffi/contact.js | 40 +- integration_tests/helpers/ffi/contacts.js | 25 +- integration_tests/helpers/ffi/emojiSet.js | 36 + integration_tests/helpers/ffi/ffiInterface.js | 1473 +++++++++++++++++ .../helpers/ffi/pendingInboundTransaction.js | 60 +- .../helpers/ffi/pendingInboundTransactions.js | 32 +- .../helpers/ffi/pendingOutboundTransaction.js | 64 +- .../ffi/pendingOutboundTransactions.js | 32 +- integration_tests/helpers/ffi/privateKey.js | 67 + integration_tests/helpers/ffi/publicKey.js | 72 +- integration_tests/helpers/ffi/seedWords.js | 42 +- .../helpers/ffi/transportType.js | 85 + integration_tests/helpers/ffi/wallet.js | 451 +++++ integration_tests/helpers/walletFFIClient.js | 475 ++---- 24 files changed, 3121 insertions(+), 751 deletions(-) create mode 100644 integration_tests/helpers/ffi/commsConfig.js create mode 100644 integration_tests/helpers/ffi/emojiSet.js create mode 100644 integration_tests/helpers/ffi/ffiInterface.js create mode 100644 integration_tests/helpers/ffi/privateKey.js create mode 100644 integration_tests/helpers/ffi/transportType.js create mode 100644 integration_tests/helpers/ffi/wallet.js diff --git a/.circleci/config.yml b/.circleci/config.yml index 0478f5aaae..f665baf02c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -129,10 +129,10 @@ commands: when: always - run: name: Run ffi cucumber scenarios - command: cd integration_tests && mkdir -p cucumber_output && node_modules/.bin/cucumber-js --tags "not @long-running and not @broken and not @flaky and @wallet-ffi" --format json:cucumber_output/tests-ffi.cucumber --exit + command: cd integration_tests && mkdir -p cucumber_output && node_modules/.bin/cucumber-js --tags "not @long-running and not @broken and not @flaky and @wallet-ffi" --format json:cucumber_output/tests_ffi.cucumber --exit - run: name: Generate report (ffi) - command: cd integration_tests && touch cucumber_output/tests-ffi.cucumber && node ./generate_report.js cucumber_output/tests-ffi.cucumber temp/reports/cucumber_ffi_report.html + command: cd integration_tests && node ./generate_report.js "cucumber_output/tests_ffi.cucumber" "temp/reports/cucumber_ffi_report.html" when: always # - run: # name: Run flaky/broken cucumber scenarios (Always pass) diff --git a/applications/ffi_client/index.js b/applications/ffi_client/index.js index bf1a47d3c2..aa9473740b 100644 --- a/applications/ffi_client/index.js +++ b/applications/ffi_client/index.js @@ -1,6 +1,8 @@ // this is nasty // ¯\_(ツ)_/¯ +// TODO: Use implementation in cucumber tests instead (see helpers/ffi). + const lib = require("./lib"); const ref = require("ref-napi"); const ffi = require("ffi-napi"); diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index d8b01d9b69..2aae750b3e 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -917,14 +917,14 @@ pub unsafe extern "C" fn seed_words_push_word( (*seed_words).0.push(word_string); if (*seed_words).0.len() >= 24 { - if let Err(e) = TariPrivateKey::from_mnemonic(&(*seed_words).0) { + return if let Err(e) = TariPrivateKey::from_mnemonic(&(*seed_words).0) { log::error!(target: LOG_TARGET, "Problem building private key from seed phrase"); error = LibWalletError::from(e).code; ptr::swap(error_out, &mut error as *mut c_int); - return SeedWordPushResult::InvalidSeedPhrase as u8; + SeedWordPushResult::InvalidSeedPhrase as u8 } else { - return SeedWordPushResult::SeedPhraseComplete as u8; - } + SeedWordPushResult::SeedPhraseComplete as u8 + }; } SeedWordPushResult::SuccessfulPush as u8 @@ -2858,7 +2858,7 @@ pub unsafe extern "C" fn wallet_create( match TariPrivateKey::from_mnemonic(&(*seed_words).0) { Ok(private_key) => Some(private_key), Err(e) => { - error!(target: LOG_TARGET, "Mnemonic Error for given seed words: {}", e); + error!(target: LOG_TARGET, "Mnemonic Error for given seed words: {:?}", e); error = LibWalletError::from(e).code; ptr::swap(error_out, &mut error as *mut c_int); return ptr::null_mut(); @@ -2947,7 +2947,7 @@ pub unsafe extern "C" fn wallet_create( // lets ensure the wallet tor_id is saved, this could have been changed during wallet startup if let Some(hs) = w.comms.hidden_service() { if let Err(e) = runtime.block_on(w.db.set_tor_identity(hs.tor_identity().clone())) { - warn!(target: LOG_TARGET, "Could not save tor identity to db: {}", e); + warn!(target: LOG_TARGET, "Could not save tor identity to db: {:?}", e); } } // Start Callback Handler diff --git a/integration_tests/features/WalletFFI.feature b/integration_tests/features/WalletFFI.feature index 11b905051e..9432989270 100644 --- a/integration_tests/features/WalletFFI.feature +++ b/integration_tests/features/WalletFFI.feature @@ -1,129 +1,136 @@ @wallet-ffi Feature: Wallet FFI + # Increase heap memory available to nodejs if frequent crashing occurs with + # error being be similar to this: `0x1a32cd5 V8_Fatal(char const*, ...)` - Scenario: As a client I want to send Tari to a Public Key - # It's a subtest of "As a client I want to retrieve a list of transactions I have made and received" - - Scenario: As a client I want to specify a custom fee when I send tari - # It's a subtest of "As a client I want to retrieve a list of transactions I have made and received" - - Scenario: As a client I want to receive Tari via my Public Key while I am online - # It's a subtest of "As a client I want to retrieve a list of transactions I have made and received" + # It's just calling the encrypt function, we don't test if it's actually encrypted + Scenario: As a client I want to be able to protect my wallet with a passphrase + Given I have a base node BASE + And I have a ffi wallet FFI_WALLET connected to base node BASE + And I set passphrase PASSPHRASE of ffi wallet FFI_WALLET + And I stop ffi wallet FFI_WALLET - @long-running @broken - Scenario: As a client I want to receive Tari via my Public Key sent while I am offline when I come back online + Scenario: As a client I want to see my whoami info Given I have a base node BASE - And I have wallet SENDER connected to base node BASE - And I have mining node MINER connected to base node BASE and wallet SENDER - And mining node MINER mines 4 blocks - Then I wait for wallet SENDER to have at least 1000000 uT And I have a ffi wallet FFI_WALLET connected to base node BASE - And I stop wallet FFI_WALLET + Then I want to get public key of ffi wallet FFI_WALLET + And I want to get emoji id of ffi wallet FFI_WALLET + And I stop ffi wallet FFI_WALLET + + Scenario: As a client I want to be able to restore my ffi wallet from seed words + Given I have a base node BASE + And I have wallet SPECTATOR connected to base node BASE + And I have mining node MINER connected to base node BASE and wallet SPECTATOR + And mining node MINER mines 10 blocks + Then I wait for wallet SPECTATOR to have at least 1000000 uT + Then I recover wallet SPECTATOR into ffi wallet FFI_WALLET from seed words on node BASE + And I wait for ffi wallet FFI_WALLET to have at least 1000000 uT + And I stop ffi wallet FFI_WALLET + + Scenario: As a client I want to set the base node + Given I have a base node BASE1 + Given I have a base node BASE2 + And I have a ffi wallet FFI_WALLET connected to base node BASE1 + And I set base node BASE2 for ffi wallet FFI_WALLET + And I stop ffi wallet FFI_WALLET + And I stop node BASE1 And I wait 5 seconds - And I send 2000000 uT from wallet SENDER to wallet FFI_WALLET at fee 100 + And I restart ffi wallet FFI_WALLET + # Possibly check SAF messages, no way to get current connected base node peer from the library itself afaik + # Good idea just to add a fn to do this to the library. + # Then I wait for ffi wallet FFI_WALLET to receive 1 SAF message And I wait 5 seconds - And I start wallet FFI_WALLET - And wallet SENDER detects all transactions are at least Broadcast - And mining node MINER mines 10 blocks - Then I wait for ffi wallet FFI_WALLET to have at least 1000000 uT + And I stop ffi wallet FFI_WALLET - @long-running - Scenario: As a client I want to retrieve a list of transactions I have made and received + Scenario: As a client I want to cancel a transaction Given I have a base node BASE And I have wallet SENDER connected to base node BASE And I have mining node MINER connected to base node BASE and wallet SENDER - And mining node MINER mines 4 blocks + And mining node MINER mines 10 blocks Then I wait for wallet SENDER to have at least 1000000 uT And I have a ffi wallet FFI_WALLET connected to base node BASE And I send 2000000 uT from wallet SENDER to wallet FFI_WALLET at fee 100 And wallet SENDER detects all transactions are at least Broadcast And mining node MINER mines 10 blocks Then I wait for ffi wallet FFI_WALLET to have at least 1000000 uT - And Check callbacks for finished inbound tx on ffi wallet FFI_WALLET And I have wallet RECEIVER connected to base node BASE + And I stop wallet RECEIVER And I send 1000000 uT from ffi wallet FFI_WALLET to wallet RECEIVER at fee 100 - And ffi wallet FFI_WALLET has 1 broadcast transaction - And mining node MINER mines 4 blocks - Then I wait for wallet RECEIVER to have at least 1000000 uT - And Check callbacks for finished outbound tx on ffi wallet FFI_WALLET - And I have 1 received and 1 send transaction in ffi wallet FFI_WALLET - And I start STXO validation on wallet FFI_WALLET - And I start UTXO validation on wallet FFI_WALLET - - # It's just calling the encrypt function, we don't test if it's actually encrypted - Scenario: As a client I want to be able to protect my wallet with a passphrase - Given I have a base node BASE - And I have a ffi wallet FFI_WALLET connected to base node BASE - And I set passphrase PASSPHRASE of ffi wallet FFI_WALLET + Then I wait for ffi wallet FFI_WALLET to have 1 pending outbound transaction + Then I cancel all outbound transactions on ffi wallet FFI_WALLET and it will cancel 1 transaction + And I stop ffi wallet FFI_WALLET Scenario: As a client I want to manage contacts Given I have a base node BASE And I have a ffi wallet FFI_WALLET connected to base node BASE And I have wallet WALLET connected to base node BASE + And I wait 5 seconds And I add contact with alias ALIAS and pubkey WALLET to ffi wallet FFI_WALLET Then I have contact with alias ALIAS and pubkey WALLET in ffi wallet FFI_WALLET When I remove contact with alias ALIAS from ffi wallet FFI_WALLET Then I don't have contact with alias ALIAS in ffi wallet FFI_WALLET + And I stop ffi wallet FFI_WALLET - Scenario: As a client I want to set the base node (should be persisted) - Given I have a base node BASE1 - Given I have a base node BASE2 - And I have a ffi wallet FFI_WALLET connected to base node BASE1 - And I set base node BASE2 for ffi wallet FFI_WALLET - Then BASE2 is connected to FFI_WALLET - And I stop wallet FFI_WALLET - And I wait 5 seconds - And I start wallet FFI_WALLET - Then BASE2 is connected to FFI_WALLET - - Scenario: As a client I want to see my public_key, emoji ID, address (whoami) - Given I have a base node BASE - And I have a ffi wallet FFI_WALLET connected to base node BASE - Then I want to get public key of ffi wallet FFI_WALLET - And I want to get emoji id of ffi wallet FFI_WALLET - - Scenario: As a client I want to get my balance - # It's a subtest of "As a client I want to retrieve a list of transactions I have made and received" - - @long-running - Scenario: As a client I want to cancel a transaction + Scenario: As a client I want to retrieve a list of transactions I have made and received Given I have a base node BASE And I have wallet SENDER connected to base node BASE And I have mining node MINER connected to base node BASE and wallet SENDER - And mining node MINER mines 4 blocks + And mining node MINER mines 10 blocks Then I wait for wallet SENDER to have at least 1000000 uT And I have a ffi wallet FFI_WALLET connected to base node BASE And I send 2000000 uT from wallet SENDER to wallet FFI_WALLET at fee 100 - And wallet SENDER detects all transactions are at least Broadcast And mining node MINER mines 10 blocks Then I wait for ffi wallet FFI_WALLET to have at least 1000000 uT And I have wallet RECEIVER connected to base node BASE - And I stop wallet RECEIVER And I send 1000000 uT from ffi wallet FFI_WALLET to wallet RECEIVER at fee 100 - Then I wait for ffi wallet FFI_WALLET to have 1 pending outbound transaction - Then I cancel all transactions on ffi wallet FFI_WALLET and it will cancel 1 transaction + And mining node MINER mines 10 blocks + Then I wait for wallet RECEIVER to have at least 1000000 uT + And I have 1 received and 1 send transaction in ffi wallet FFI_WALLET + And I start STXO validation on ffi wallet FFI_WALLET + And I start UTXO validation on ffi wallet FFI_WALLET + And I stop ffi wallet FFI_WALLET - @long-running - Scenario: As a client I want to be able to restore my wallet from seed words + Scenario: As a client I want to receive Tari via my Public Key sent while I am offline when I come back online Given I have a base node BASE - And I have wallet WALLET connected to base node BASE - And I have mining node MINER connected to base node BASE and wallet WALLET - And mining node MINER mines 4 blocks - Then I wait for wallet WALLET to have at least 1000000 uT - Then I recover wallet WALLET into ffi wallet FFI_WALLET from seed words on node BASE - And I wait for recovery of wallet FFI_WALLET to finish - And I wait for ffi wallet FFI_WALLET to have at least 1000000 uT + And I have wallet SENDER connected to base node BASE + And I have mining node MINER connected to base node BASE and wallet SENDER + And mining node MINER mines 10 blocks + Then I wait for wallet SENDER to have at least 1000000 uT + And I have a ffi wallet FFI_WALLET connected to base node BASE + And I stop ffi wallet FFI_WALLET + And I wait 10 seconds + And I send 2000000 uT from wallet SENDER to wallet FFI_WALLET at fee 100 + And I wait 5 seconds + And I restart ffi wallet FFI_WALLET + Then I wait for ffi wallet FFI_WALLET to receive 1 transaction + Then I wait for ffi wallet FFI_WALLET to receive 1 finalization + # Assume tx will be mined to reduce time taken for test, balance is tested in later scenarios. + # And mining node MINER mines 10 blocks + # Then I wait for ffi wallet FFI_WALLET to have at least 1000000 uT + And I stop ffi wallet FFI_WALLET + + # Scenario: As a client I want to get my balance + # It's a subtest of "As a client I want to retrieve a list of transactions I have made and received" + + #Scenario: As a client I want to send Tari to a Public Key + # It's a subtest of "As a client I want to retrieve a list of transactions I have made and received" - Scenario: As a client I want to be able to initiate TXO and TX validation with the specifed base node. + #Scenario: As a client I want to specify a custom fee when I send tari # It's a subtest of "As a client I want to retrieve a list of transactions I have made and received" - Scenario: As a client I want async feedback about the progress of sending and receiving a transaction + #Scenario: As a client I want to receive Tari via my Public Key while I am online # It's a subtest of "As a client I want to retrieve a list of transactions I have made and received" - Scenario: As a client I want async feedback about my connection status to the specifed Base Node + # Scenario: As a client I want to be able to initiate TXO and TX validation with the specifed base node. + # It's a subtest of "As a client I want to retrieve a list of transactions I have made and received" + + # Scenario: As a client I want feedback about the progress of sending and receiving a transaction + # It's a subtest of "As a client I want to retrieve a list of transactions I have made and received" - Scenario: As a client I want async feedback about the wallet restoration process + # Scenario: As a client I want feedback about my connection status to the specifed Base Node + + # Scenario: As a client I want feedback about the wallet restoration process # As a client I want to be able to restore my wallet from seed words - Scenario: As a client I want async feedback about TXO and TX validation processes -# It's a subtest of "As a client I want to retrieve a list of transactions I have made and received" + # Scenario: As a client I want feedback about TXO and TX validation processes + # It's a subtest of "As a client I want to retrieve a list of transactions I have made and received" diff --git a/integration_tests/features/support/steps.js b/integration_tests/features/support/steps.js index fcedbaeeff..cb2ff9d3b2 100644 --- a/integration_tests/features/support/steps.js +++ b/integration_tests/features/support/steps.js @@ -3379,29 +3379,6 @@ When( } ); -When( - "I have a ffi wallet {word} connected to base node {word}", - { timeout: 20 * 1000 }, - async function (name, node) { - let wallet = await this.createAndAddFFIWallet(name); - let peer = this.nodes[node].peerAddress().split("::"); - await wallet.addBaseNodePeer(peer[0], peer[1]); - } -); - -Then( - "I want to get public key of ffi wallet {word}", - { timeout: 20 * 1000 }, - async function (name) { - let wallet = this.getWallet(name); - let public_key = await wallet.getPublicKey(); - expect(public_key.length).to.be.equal( - 64, - `Public key has wrong length : ${public_key}` - ); - } -); - Then( /I wait until base node (.*) has (.*) unconfirmed transactions in its mempool/, { timeout: 180 * 1000 }, @@ -3429,57 +3406,120 @@ Then( ); Then( - "I want to get emoji id of ffi wallet {word}", + /node (.*) lists heights (\d+) to (\d+)/, + async function (node, first, last) { + const client = this.getClient(node); + const start = first; + const end = last; + let heights = []; + + for (let i = start; i <= end; i++) { + heights.push(i); + } + const blocks = await client.getBlocks(heights); + const results = blocks.map((result) => + parseInt(result.block.header.height) + ); + let i = 0; // for ordering check + for (let height = start; height <= end; height++) { + expect(results[i]).equal(height); + i++; + } + } +); + +Then( + "I wait for recovery of wallet {word} to finish", + { timeout: 600 * 1000 }, + async function (wallet_name) { + const wallet = this.getWallet(wallet_name); + while (wallet.recoveryInProgress) { + await sleep(1000); + } + expect(wallet.recoveryProgress[1]).to.be.greaterThan(0); + expect(wallet.recoveryProgress[0]).to.be.equal(wallet.recoveryProgress[1]); + } +); + +When( + "I have {int} base nodes with pruning horizon {int} force syncing on node {word}", + { timeout: 190 * 1000 }, + async function (nodes_count, horizon, force_sync_to) { + const promises = []; + const force_sync_address = this.getNode(force_sync_to).peerAddress(); + for (let i = 0; i < nodes_count; i++) { + const base_node = this.createNode(`BaseNode${i}`, { + pruningHorizon: horizon, + }); + base_node.setPeerSeeds([force_sync_address]); + base_node.setForceSyncPeers([force_sync_address]); + promises.push( + base_node.startNew().then(() => this.addNode(`BaseNode${i}`, base_node)) + ); + } + await Promise.all(promises); + } +); + +//region FFI +When( + "I have ffi wallet {word} connected to base node {word}", { timeout: 20 * 1000 }, - async function (name) { + async function (name, node) { + let wallet = await this.createAndAddFFIWallet(name); + let peer = this.nodes[node].peerAddress().split("::"); + wallet.addBaseNodePeer(peer[0], peer[1]); + } +); + +Then( + "I want to get public key of ffi wallet {word}", + { timeout: 20 * 1000 }, + function (name) { let wallet = this.getWallet(name); - let emoji_id = await wallet.getEmojiId(); - expect(emoji_id.length).to.be.equal( - 22 * 3, // 22 emojis, 3 bytes per one emoji - `Emoji id has wrong length : ${emoji_id}` + let public_key = wallet.identify(); + expect(public_key.length).to.be.equal( + 64, + `Public key has wrong length : ${public_key}` ); } ); Then( - "I wait for ffi wallet {word} to have at least {int} uT", - { timeout: 60 * 1000 }, - async function (name, amount) { + "I want to get emoji id of ffi wallet {word}", + { timeout: 20 * 1000 }, + async function (name) { let wallet = this.getWallet(name); - let retries = 1; - let balance = 0; - const retries_limit = 12; - while (retries <= retries_limit) { - balance = await wallet.getBalance(); - if (balance >= amount) { - break; - } - await sleep(5000); - ++retries; - } - expect(balance, "Balance is not enough").to.be.greaterThanOrEqual(amount); + let emoji_id = wallet.identifyEmoji(); + console.log(emoji_id); + expect(emoji_id.length).to.be.equal( + 22 * 3, // 22 emojis, 3 bytes per one emoji + `Emoji id has wrong length : ${emoji_id}` + ); } ); When( "I send {int} uT from ffi wallet {word} to wallet {word} at fee {int}", { timeout: 20 * 1000 }, - async function (amount, sender, receiver, fee) { - await this.getWallet(sender).sendTransaction( - await this.getWalletPubkey(receiver), + function (amount, sender, receiver, fee) { + let ffi_wallet = this.getWallet(sender); + let result = ffi_wallet.sendTransaction( + this.getWalletPubkey(receiver), amount, fee, `Send from ffi ${sender} to ${receiver} at fee ${fee}` ); + console.log(result); } ); When( "I set passphrase {word} of ffi wallet {word}", { timeout: 20 * 1000 }, - async function (passphrase, name) { + function (passphrase, name) { let wallet = this.getWallet(name); - await wallet.applyEncryption(passphrase); + wallet.applyEncryption(passphrase); } ); @@ -3488,17 +3528,29 @@ Then( { timeout: 120 * 1000 }, async function (received, send, name) { let wallet = this.getWallet(name); - let [outbound, inbound] = await wallet.getCompletedTransactions(); - let retries = 1; - const retries_limit = 23; - while ( - (inbound != received || outbound != send) && - retries <= retries_limit - ) { - await sleep(5000); - [outbound, inbound] = await wallet.getCompletedTransactions(); - ++retries; + let completed = wallet.getCompletedTxs(); + let inbound = 0; + let outbound = 0; + let length = completed.getLength(); + let inboundTxs = wallet.getInboundTxs(); + inbound += inboundTxs.getLength(); + inboundTxs.destroy(); + let outboundTxs = wallet.getOutboundTxs(); + outbound += outboundTxs.getLength(); + outboundTxs.destroy(); + for (let i = 0; i < length; i++) { + { + let tx = completed.getAt(i); + if (tx.isOutbound()) { + outbound++; + } else { + inbound++; + } + tx.destroy(); + } } + completed.destroy(); + expect(outbound, "Outbound transaction count mismatch").to.be.equal(send); expect(inbound, "Inbound transaction count mismatch").to.be.equal(received); } @@ -3526,70 +3578,86 @@ Then( When( "I add contact with alias {word} and pubkey {word} to ffi wallet {word}", { timeout: 20 * 1000 }, - async function (alias, wallet_name, ffi_wallet_name) { + function (alias, wallet_name, ffi_wallet_name) { let ffi_wallet = this.getWallet(ffi_wallet_name); - await ffi_wallet.addContact(alias, await this.getWalletPubkey(wallet_name)); + ffi_wallet.addContact(alias, this.getWalletPubkey(wallet_name)); } ); Then( "I have contact with alias {word} and pubkey {word} in ffi wallet {word}", { timeout: 20 * 1000 }, - async function (alias, wallet_name, ffi_wallet_name) { + function (alias, wallet_name, ffi_wallet_name) { + let wallet = this.getWalletPubkey(wallet_name); let ffi_wallet = this.getWallet(ffi_wallet_name); - expect(await this.getWalletPubkey(wallet_name)).to.be.equal( - await ffi_wallet.getContact(alias) - ); + let contacts = ffi_wallet.getContactList(); + let length = contacts.getLength(); + let found = false; + for (let i = 0; i < length; i++) { + { + let contact = contacts.getAt(i); + let hex = contact.getPubkeyHex(); + if (wallet === hex) { + found = true; + } + contact.destroy(); + } + } + contacts.destroy(); + expect(found).to.be.equal(true); } ); When( "I remove contact with alias {word} from ffi wallet {word}", { timeout: 20 * 1000 }, - async function (alias, walllet_name) { - let wallet = this.getWallet(walllet_name); - await wallet.removeContact(alias); + function (alias, wallet_name) { + let ffi_wallet = this.getWallet(wallet_name); + let contacts = ffi_wallet.getContactList(); + let length = contacts.getLength(); + for (let i = 0; i < length; i++) { + { + let contact = contacts.getAt(i); + let calias = contact.getAlias(); + if (alias === calias) { + ffi_wallet.removeContact(contact); + } + contact.destroy(); + } + } + contacts.destroy(); } ); Then( "I don't have contact with alias {word} in ffi wallet {word}", { timeout: 20 * 1000 }, - async function (alias, wallet_name) { - let wallet = this.getWallet(wallet_name); - expect(await wallet.getContact("alias")).to.be.undefined; - } -); - -Then( - /node (.*) lists heights (\d+) to (\d+)/, - async function (node, first, last) { - const client = this.getClient(node); - const start = first; - const end = last; - let heights = []; - - for (let i = start; i <= end; i++) { - heights.push(i); - } - const blocks = await client.getBlocks(heights); - const results = blocks.map((result) => - parseInt(result.block.header.height) - ); - let i = 0; // for ordering check - for (let height = start; height <= end; height++) { - expect(results[i]).equal(height); - i++; + function (alias, wallet_name) { + let ffi_wallet = this.getWallet(wallet_name); + let contacts = ffi_wallet.getContactList(); + let length = contacts.getLength(); + let found = false; + for (let i = 0; i < length; i++) { + { + let contact = contacts.getAt(i); + let calias = contact.getAlias(); + if (alias === calias) { + found = true; + } + contact.destroy(); + } } + contacts.destroy(); + expect(found).to.be.equal(false); } ); When( "I set base node {word} for ffi wallet {word}", - async function (node, wallet_name) { + function (node, wallet_name) { let wallet = this.getWallet(wallet_name); let peer = this.nodes[node].peerAddress().split("::"); - await wallet.addBaseNodePeer(peer[0], peer[1]); + wallet.addBaseNodePeer(peer[0], peer[1]); } ); @@ -3598,26 +3666,48 @@ Then( { timeout: 120 * 1000 }, async function (wallet_name, count) { let wallet = this.getWallet(wallet_name); - let broadcast = await wallet.getOutboundTransactionsCount(); + let broadcast = wallet.getOutboundTransactions(); + let length = broadcast.getLength(); + broadcast.destroy(); let retries = 1; const retries_limit = 24; - while (broadcast != count && retries <= retries_limit) { + while (length != count && retries <= retries_limit) { await sleep(5000); - broadcast = await wallet.getOutboundTransactionsCount(); + broadcast = wallet.getOutboundTransactions(); + length = broadcast.getLength(); + broadcast.destroy(); ++retries; } - expect(broadcast, "Number of pending messages mismatch").to.be.equal(count); + expect(length, "Number of pending messages mismatch").to.be.equal(count); } ); Then( - "I cancel all transactions on ffi wallet {word} and it will cancel {int} transaction", + "I cancel all outbound transactions on ffi wallet {word} and it will cancel {int} transaction", async function (wallet_name, count) { const wallet = this.getWallet(wallet_name); - expect( - await wallet.cancelAllOutboundTransactions(), - "Number of cancelled transactions" - ).to.be.equal(count); + let txs = wallet.getOutboundTransactions(); + let cancelled = 0; + for (let i = 0; i < txs.getLength(); i++) { + let tx = txs.getAt(i); + let cancellation = wallet.cancelPendingTransaction(tx.getTransactionID()); + tx.destroy(); + if (cancellation) { + cancelled++; + } + } + txs.destroy(); + expect(cancelled).to.be.equal(count); + } +); + +Given( + /I have a ffi wallet (.*) connected to base node (.*)/, + { timeout: 20 * 1000 }, + async function (walletName, nodeName) { + let ffi_wallet = await this.createAndAddFFIWallet(walletName, null); + let peer = this.nodes[nodeName].peerAddress().split("::"); + ffi_wallet.addBaseNodePeer(peer[0], peer[1]); } ); @@ -3634,78 +3724,207 @@ Then( seed_words_text ); let peer = this.nodes[node].peerAddress().split("::"); - await ffi_wallet.addBaseNodePeer(peer[0], peer[1]); - await ffi_wallet.startRecovery(peer[0]); + ffi_wallet.addBaseNodePeer(peer[0], peer[1]); + ffi_wallet.startRecovery(peer[0]); } ); Then( - "I wait for recovery of wallet {word} to finish", - { timeout: 600 * 1000 }, + "Check callbacks for finished inbound tx on ffi wallet {word}", async function (wallet_name) { const wallet = this.getWallet(wallet_name); - while (wallet.recoveryInProgress) { - await sleep(1000); + expect(wallet.receivedTransaction).to.be.greaterThanOrEqual(1); + expect(wallet.transactionBroadcast).to.be.greaterThanOrEqual(1); + wallet.clearCallbackCounters(); + } +); + +Then( + "Check callbacks for finished outbound tx on ffi wallet {word}", + async function (wallet_name) { + const wallet = this.getWallet(wallet_name); + expect(wallet.receivedTransactionReply).to.be.greaterThanOrEqual(1); + expect(wallet.transactionBroadcast).to.be.greaterThanOrEqual(1); + wallet.clearCallbackCounters(); + } +); + +Then( + /I wait for ffi wallet (.*) to receive (.*) transaction/, + { timeout: 710 * 1000 }, + async function (wallet_name, amount) { + let wallet = this.getWallet(wallet_name); + + console.log("\n"); + console.log( + "Waiting for " + wallet_name + " to receive " + amount + " transaction(s)" + ); + + await waitFor( + async () => { + return wallet.getCounters().received >= amount; + }, + true, + 700 * 1000, + 5 * 1000, + 5 + ); + + if (!(wallet.getCounters().received >= amount)) { + console.log("Counter not adequate!"); + } else { + console.log(wallet.getCounters()); } - expect(wallet.recoveryProgress[1]).to.be.greaterThan(0); - expect(wallet.recoveryProgress[0]).to.be.equal(wallet.recoveryProgress[1]); + expect(wallet.getCounters().received >= amount).to.equal(true); } ); -Then("I start STXO validation on wallet {word}", async function (wallet_name) { - const wallet = this.getWallet(wallet_name); - await wallet.startStxoValidation(); - while (!wallet.stxo_validation_complete) { - await sleep(1000); +Then( + /I wait for ffi wallet (.*) to receive (.*) finalization/, + { timeout: 710 * 1000 }, + async function (wallet_name, amount) { + let wallet = this.getWallet(wallet_name); + + console.log("\n"); + console.log( + "Waiting for " + + wallet_name + + " to receive " + + amount + + " transaction finalization(s)" + ); + + await waitFor( + async () => { + return wallet.getCounters().finalized >= amount; + }, + true, + 700 * 1000, + 5 * 1000, + 5 + ); + + if (!(wallet.getCounters().finalized >= amount)) { + console.log("Counter not adequate!"); + } else { + console.log(wallet.getCounters()); + } + expect(wallet.getCounters().finalized >= amount).to.equal(true); } - expect(wallet.stxo_validation_result).to.be.equal(0); -}); +); -Then("I start UTXO validation on wallet {word}", async function (wallet_name) { - const wallet = this.getWallet(wallet_name); - await wallet.startUtxoValidation(); - while (!wallet.utxo_validation_complete) { - await sleep(1000); +Then( + /I wait for ffi wallet (.*) to receive (.*) SAF message/, + { timeout: 710 * 1000 }, + async function (wallet_name, amount) { + let wallet = this.getWallet(wallet_name); + + console.log("\n"); + console.log( + "Waiting for " + + wallet_name + + " to receive " + + amount + + " SAF messages(s)" + ); + + await waitFor( + async () => { + return wallet.getCounters().saf >= amount; + }, + true, + 700 * 1000, + 5 * 1000, + 5 + ); + + if (!(wallet.getCounters().saf >= amount)) { + console.log("Counter not adequate!"); + } else { + console.log(wallet.getCounters()); + } + expect(wallet.getCounters().saf >= amount).to.equal(true); } - expect(wallet.utxo_validation_result).to.be.equal(0); -}); +); Then( - "Check callbacks for finished inbound tx on ffi wallet {word}", - async function (wallet_name) { + /I wait for ffi wallet (.*) to have at least (.*) uT/, + { timeout: 710 * 1000 }, + async function (wallet_name, amount) { + let wallet = this.getWallet(wallet_name); + + console.log("\n"); + console.log( + "Waiting for " + wallet_name + " balance to be at least " + amount + " uT" + ); + + let count = 0; + + while (!(wallet.getBalance().available >= amount)) { + await sleep(1000); + count++; + if (count > 700) { + break; + } + } + + let balance = wallet.getBalance().available; + + if (!(balance >= amount)) { + console.log("Balance not adequate!"); + } else { + console.log(wallet.getBalance()); + } + expect(balance >= amount).to.equal(true); + } +); + +Then( + "I wait for recovery of ffi wallet {word} to finish", + { timeout: 600 * 1000 }, + function (wallet_name) { const wallet = this.getWallet(wallet_name); - expect(wallet.receivedTransaction).to.be.greaterThanOrEqual(1); - expect(wallet.transactionBroadcast).to.be.greaterThanOrEqual(1); - wallet.clearCallbackCounters(); + while (!wallet.recoveryFinished) { + sleep(1000).then(); + } } ); +When(/I start ffi wallet (.*)/, async function (walletName) { + let wallet = this.getWallet(walletName); + await wallet.startNew(null, null); +}); + +When(/I restart ffi wallet (.*)/, async function (walletName) { + let wallet = this.getWallet(walletName); + await wallet.restart(); +}); + +When(/I stop ffi wallet (.*)/, function (walletName) { + let wallet = this.getWallet(walletName); + wallet.stop(); + wallet.resetCounters(); +}); + Then( - "Check callbacks for finished outbound tx on ffi wallet {word}", + "I start STXO validation on ffi wallet {word}", async function (wallet_name) { const wallet = this.getWallet(wallet_name); - expect(wallet.receivedTransactionReply).to.be.greaterThanOrEqual(1); - expect(wallet.transactionBroadcast).to.be.greaterThanOrEqual(1); - wallet.clearCallbackCounters(); + await wallet.startStxoValidation(); + while (!wallet.getStxoValidationStatus().stxo_validation_complete) { + await sleep(1000); + } } ); -When( - "I have {int} base nodes with pruning horizon {int} force syncing on node {word}", - { timeout: 190 * 1000 }, - async function (nodes_count, horizon, force_sync_to) { - const promises = []; - const force_sync_address = this.getNode(force_sync_to).peerAddress(); - for (let i = 0; i < nodes_count; i++) { - const base_node = this.createNode(`BaseNode${i}`, { - pruningHorizon: horizon, - }); - base_node.setPeerSeeds([force_sync_address]); - base_node.setForceSyncPeers([force_sync_address]); - promises.push( - base_node.startNew().then(() => this.addNode(`BaseNode${i}`, base_node)) - ); +Then( + "I start UTXO validation on ffi wallet {word}", + async function (wallet_name) { + const wallet = this.getWallet(wallet_name); + await wallet.startUtxoValidation(); + while (!wallet.getUtxoValidationStatus().utxo_validation_complete) { + await sleep(1000); } - await Promise.all(promises); } ); +//endregion diff --git a/integration_tests/features/support/world.js b/integration_tests/features/support/world.js index 6ca3d0f699..d089f789ff 100644 --- a/integration_tests/features/support/world.js +++ b/integration_tests/features/support/world.js @@ -106,11 +106,11 @@ class CustomWorld { this.walletPubkeys[name] = walletInfo.public_key; } - async createAndAddFFIWallet(name, seed_words) { + async createAndAddFFIWallet(name, seed_words = null, passphrase = null) { const wallet = new WalletFFIClient(name); - await wallet.startNew(seed_words); + await wallet.startNew(seed_words, passphrase); this.walletsFFI[name] = wallet; - this.walletPubkeys[name] = await wallet.getPublicKey(); + this.walletPubkeys[name] = wallet.identify(); return wallet; } diff --git a/integration_tests/helpers/ffi/byteVector.js b/integration_tests/helpers/ffi/byteVector.js index 51f5d338bd..245cb4320e 100644 --- a/integration_tests/helpers/ffi/byteVector.js +++ b/integration_tests/helpers/ffi/byteVector.js @@ -1,28 +1,51 @@ -const WalletFFI = require("./walletFFI"); +const InterfaceFFI = require("./ffiInterface"); class ByteVector { #byte_vector_ptr; - constructor(byte_vector_ptr) { - this.#byte_vector_ptr = byte_vector_ptr; + pointerAssign(ptr) { + // Prevent pointer from being leaked in case of re-assignment + if (this.#byte_vector_ptr) { + this.destroy(); + this.#byte_vector_ptr = ptr; + } else { + this.#byte_vector_ptr = ptr; + } } - static async fromBuffer(buffer) { - let buf = Buffer.from(buffer, "utf-8"); // get the bytes + fromBytes(input) { + let buf = Buffer.from(input, "utf-8"); // ensure encoding is utf=8, js default is utf-16 let len = buf.length; // get the length - return new ByteVector(await WalletFFI.byteVectorCreate(buf, len)); + let result = new ByteVector(); + result.pointerAssign(InterfaceFFI.byteVectorCreate(buf, len)); + return result; + } + + getBytes() { + let result = []; + for (let i = 0; i < this.getLength(); i++) { + result.push(this.getAt(i)); + } + return result; } getLength() { - return WalletFFI.byteVectorGetLength(this.#byte_vector_ptr); + return InterfaceFFI.byteVectorGetLength(this.#byte_vector_ptr); } getAt(position) { - return WalletFFI.byteVectorGetAt(this.#byte_vector_ptr, position); + return InterfaceFFI.byteVectorGetAt(this.#byte_vector_ptr, position); + } + + getPtr() { + return this.#byte_vector_ptr; } destroy() { - return WalletFFI.byteVectorDestroy(this.#byte_vector_ptr); + if (this.#byte_vector_ptr) { + InterfaceFFI.byteVectorDestroy(this.#byte_vector_ptr); + this.#byte_vector_ptr = undefined; //prevent double free segfault + } } } diff --git a/integration_tests/helpers/ffi/commsConfig.js b/integration_tests/helpers/ffi/commsConfig.js new file mode 100644 index 0000000000..9bb9ddcb7a --- /dev/null +++ b/integration_tests/helpers/ffi/commsConfig.js @@ -0,0 +1,43 @@ +const InterfaceFFI = require("./ffiInterface"); +const utf8 = require("utf8"); + +class CommsConfig { + #comms_config_ptr; + + constructor( + public_address, + transport_ptr, + database_name, + datastore_path, + discovery_timeout_in_secs, + saf_message_duration_in_secs, + network + ) { + let sanitize_address = utf8.encode(public_address); + let sanitize_db_name = utf8.encode(database_name); + let sanitize_db_path = utf8.encode(datastore_path); + let sanitize_network = utf8.encode(network); + this.#comms_config_ptr = InterfaceFFI.commsConfigCreate( + sanitize_address, + transport_ptr, + sanitize_db_name, + sanitize_db_path, + discovery_timeout_in_secs, + saf_message_duration_in_secs, + sanitize_network + ); + } + + getPtr() { + return this.#comms_config_ptr; + } + + destroy() { + if (this.#comms_config_ptr) { + InterfaceFFI.commsConfigDestroy(this.#comms_config_ptr); + this.#comms_config_ptr = undefined; //prevent double free segfault + } + } +} + +module.exports = CommsConfig; diff --git a/integration_tests/helpers/ffi/completedTransaction.js b/integration_tests/helpers/ffi/completedTransaction.js index a7a21c28cd..cc23f22ecf 100644 --- a/integration_tests/helpers/ffi/completedTransaction.js +++ b/integration_tests/helpers/ffi/completedTransaction.js @@ -1,23 +1,104 @@ -const WalletFFI = require("./walletFFI"); +const InterfaceFFI = require("./ffiInterface"); +const PublicKey = require("./publicKey"); class CompletedTransaction { #tari_completed_transaction_ptr; - constructor(tari_completed_transaction_ptr) { - this.#tari_completed_transaction_ptr = tari_completed_transaction_ptr; + pointerAssign(ptr) { + if (this.#tari_completed_transaction_ptr) { + this.destroy(); + this.#tari_completed_transaction_ptr = ptr; + } else { + this.#tari_completed_transaction_ptr = ptr; + } + } + + getPtr() { + return this.#tari_completed_transaction_ptr; } isOutbound() { - return WalletFFI.completedTransactionIsOutbound( + return InterfaceFFI.completedTransactionIsOutbound( this.#tari_completed_transaction_ptr ); } - destroy() { - return WalletFFI.completedTransactionDestroy( + getDestinationPublicKey() { + let result = new PublicKey(); + result.pointerAssign( + InterfaceFFI.completedTransactionGetDestinationPublicKey( + this.#tari_completed_transaction_ptr + ) + ); + return result; + } + + getSourcePublicKey() { + let result = new PublicKey(); + result.pointerAssign( + InterfaceFFI.completedTransactionGetSourcePublicKey( + this.#tari_completed_transaction_ptr + ) + ); + return result; + } + + getAmount() { + return InterfaceFFI.completedTransactionGetAmount( + this.#tari_completed_transaction_ptr + ); + } + + getFee() { + return InterfaceFFI.completedTransactionGetFee( + this.#tari_completed_transaction_ptr + ); + } + + getMessage() { + return InterfaceFFI.completedTransactionGetMessage( + this.#tari_completed_transaction_ptr + ); + } + + getStatus() { + return InterfaceFFI.completedTransactionGetStatus( + this.#tari_completed_transaction_ptr + ); + } + + getTransactionID() { + return InterfaceFFI.completedTransactionGetTransactionId( + this.#tari_completed_transaction_ptr + ); + } + + getTimestamp() { + return InterfaceFFI.completedTransactionGetTimestamp( + this.#tari_completed_transaction_ptr + ); + } + + isValid() { + return InterfaceFFI.completedTransactionIsValid( + this.#tari_completed_transaction_ptr + ); + } + + getConfirmations() { + return InterfaceFFI.completedTransactionGetConfirmations( this.#tari_completed_transaction_ptr ); } + + destroy() { + if (this.#tari_completed_transaction_ptr) { + InterfaceFFI.completedTransactionDestroy( + this.#tari_completed_transaction_ptr + ); + this.#tari_completed_transaction_ptr = undefined; //prevent double free segfault + } + } } module.exports = CompletedTransaction; diff --git a/integration_tests/helpers/ffi/completedTransactions.js b/integration_tests/helpers/ffi/completedTransactions.js index d2d4c96156..2b8387bb72 100644 --- a/integration_tests/helpers/ffi/completedTransactions.js +++ b/integration_tests/helpers/ffi/completedTransactions.js @@ -1,38 +1,37 @@ const CompletedTransaction = require("./completedTransaction"); -const WalletFFI = require("./walletFFI"); +const InterfaceFFI = require("./ffiInterface"); class CompletedTransactions { #tari_completed_transactions_ptr; - constructor(tari_completed_transactions_ptr) { - this.#tari_completed_transactions_ptr = tari_completed_transactions_ptr; - } - - static async fromWallet(wallet) { - return new CompletedTransactions( - await WalletFFI.walletGetCompletedTransactions(wallet) - ); + constructor(ptr) { + this.#tari_completed_transactions_ptr = ptr; } getLength() { - return WalletFFI.completedTransactionsGetLength( + return InterfaceFFI.completedTransactionsGetLength( this.#tari_completed_transactions_ptr ); } - async getAt(position) { - return new CompletedTransaction( - await WalletFFI.completedTransactionsGetAt( + getAt(position) { + let result = new CompletedTransaction(); + result.pointerAssign( + InterfaceFFI.completedTransactionsGetAt( this.#tari_completed_transactions_ptr, position ) ); + return result; } destroy() { - return WalletFFI.completedTransactionsDestroy( - this.#tari_completed_transactions_ptr - ); + if (this.#tari_completed_transactions_ptr) { + InterfaceFFI.completedTransactionsDestroy( + this.#tari_completed_transactions_ptr + ); + this.#tari_completed_transactions_ptr = undefined; //prevent double free segfault + } } } diff --git a/integration_tests/helpers/ffi/contact.js b/integration_tests/helpers/ffi/contact.js index 184c684a2b..ea72376e75 100644 --- a/integration_tests/helpers/ffi/contact.js +++ b/integration_tests/helpers/ffi/contact.js @@ -1,32 +1,52 @@ const PublicKey = require("./publicKey"); -const WalletFFI = require("./walletFFI"); +const InterfaceFFI = require("./ffiInterface"); class Contact { #tari_contact_ptr; - constructor(tari_contact_ptr) { - this.#tari_contact_ptr = tari_contact_ptr; + pointerAssign(ptr) { + // Prevent pointer from being leaked in case of re-assignment + if (this.#tari_contact_ptr) { + this.destroy(); + this.#tari_contact_ptr = ptr; + } else { + this.#tari_contact_ptr = ptr; + } } getPtr() { return this.#tari_contact_ptr; } - async getAlias() { - const alias = await WalletFFI.contactGetAlias(this.#tari_contact_ptr); + getAlias() { + const alias = InterfaceFFI.contactGetAlias(this.#tari_contact_ptr); const result = alias.readCString(); - await WalletFFI.stringDestroy(alias); + InterfaceFFI.stringDestroy(alias); return result; } - async getPubkey() { - return new PublicKey( - await WalletFFI.contactGetPublicKey(this.#tari_contact_ptr) + getPubkey() { + let result = new PublicKey(); + result.pointerAssign( + InterfaceFFI.contactGetPublicKey(this.#tari_contact_ptr) ); + return result; + } + + getPubkeyHex() { + let result = ""; + let pk = new PublicKey(); + pk.pointerAssign(InterfaceFFI.contactGetPublicKey(this.#tari_contact_ptr)); + result = pk.getHex(); + pk.destroy(); + return result; } destroy() { - return WalletFFI.contactDestroy(this.#tari_contact_ptr); + if (this.#tari_contact_ptr) { + InterfaceFFI.contactDestroy(this.#tari_contact_ptr); + this.#tari_contact_ptr = undefined; //prevent double free segfault + } } } diff --git a/integration_tests/helpers/ffi/contacts.js b/integration_tests/helpers/ffi/contacts.js index d8803874ab..1f7db81fcc 100644 --- a/integration_tests/helpers/ffi/contacts.js +++ b/integration_tests/helpers/ffi/contacts.js @@ -1,29 +1,30 @@ const Contact = require("./contact"); -const WalletFFI = require("./walletFFI"); +const InterfaceFFI = require("./ffiInterface"); class Contacts { #tari_contacts_ptr; - constructor(tari_contacts_ptr) { - this.#tari_contacts_ptr = tari_contacts_ptr; - } - - static async fromWallet(wallet) { - return new Contacts(await WalletFFI.walletGetContacts(wallet)); + constructor(ptr) { + this.#tari_contacts_ptr = ptr; } getLength() { - return WalletFFI.contactsGetLength(this.#tari_contacts_ptr); + return InterfaceFFI.contactsGetLength(this.#tari_contacts_ptr); } - async getAt(position) { - return new Contact( - await WalletFFI.contactsGetAt(this.#tari_contacts_ptr, position) + getAt(position) { + let result = new Contact(); + result.pointerAssign( + InterfaceFFI.contactsGetAt(this.#tari_contacts_ptr, position) ); + return result; } destroy() { - return WalletFFI.contactsDestroy(this.#tari_contacts_ptr); + if (this.#tari_contacts_ptr) { + InterfaceFFI.contactsDestroy(this.#tari_contacts_ptr); + this.#tari_contacts_ptr = undefined; //prevent double free segfault + } } } diff --git a/integration_tests/helpers/ffi/emojiSet.js b/integration_tests/helpers/ffi/emojiSet.js new file mode 100644 index 0000000000..f94e2ae746 --- /dev/null +++ b/integration_tests/helpers/ffi/emojiSet.js @@ -0,0 +1,36 @@ +const InterfaceFFI = require("./ffiInterface"); + +class EmojiSet { + #emoji_set_ptr; + + constructor() { + this.#emoji_set_ptr = InterfaceFFI.getEmojiSet(); + } + + getLength() { + return InterfaceFFI.emojiSetGetLength(this.#emoji_set_ptr); + } + + getAt(position) { + return InterfaceFFI.emojiSetGetAt(this.#emoji_set_ptr, position); + } + + list() { + let set = []; + for (let i = 0; i < this.getLength(); i++) { + let item = this.getAt(i); + set.push(Buffer.from(item.getBytes(), "utf-8").toString()); + item.destroy(); + } + return set; + } + + destroy() { + if (this.#emoji_set_ptr) { + InterfaceFFI.byteVectorDestroy(this.#emoji_set_ptr); + this.#emoji_set_ptr = undefined; //prevent double free segfault + } + } +} + +module.exports = EmojiSet; diff --git a/integration_tests/helpers/ffi/ffiInterface.js b/integration_tests/helpers/ffi/ffiInterface.js new file mode 100644 index 0000000000..e39632cd05 --- /dev/null +++ b/integration_tests/helpers/ffi/ffiInterface.js @@ -0,0 +1,1473 @@ +/** + * This library was AUTO-GENERATED. Do not modify manually! + */ + +const { expect } = require("chai"); +const ffi = require("ffi-napi"); +const ref = require("ref-napi"); +const dateFormat = require("dateformat"); +const { spawn } = require("child_process"); +const fs = require("fs"); + +class InterfaceFFI { + //region Compile + static compile() { + return new Promise((resolve, _reject) => { + const cmd = "cargo"; + const args = [ + "build", + "--release", + "--package", + "tari_wallet_ffi", + "-Z", + "unstable-options", + "--out-dir", + process.cwd() + "/temp/out", + ]; + const baseDir = `./temp/base_nodes/${dateFormat( + new Date(), + "yyyymmddHHMM" + )}/WalletFFI-compile`; + if (!fs.existsSync(baseDir)) { + fs.mkdirSync(baseDir, { recursive: true }); + fs.mkdirSync(baseDir + "/log", { recursive: true }); + } + const ps = spawn(cmd, args, { + cwd: baseDir, + env: { ...process.env }, + }); + ps.on("close", (_code) => { + resolve(ps); + }); + ps.stderr.on("data", (data) => { + console.log("stderr : ", data.toString()); + }); + ps.on("error", (error) => { + console.log("error : ", error.toString()); + }); + expect(ps.error).to.be.an("undefined"); + this.#ps = ps; + }); + } + //endregion + + //region Interface + static #fn; + + static #loaded = false; + static #ps = null; + + static async Init() { + if (this.#loaded) { + return; + } + + this.#loaded = true; + await this.compile(); + const outputProcess = `${process.cwd()}/temp/out/${ + process.platform === "win32" ? "" : "lib" + }tari_wallet_ffi`; + + // Load the library + this.#fn = ffi.Library(outputProcess, { + transport_memory_create: ["pointer", ["void"]], + transport_tcp_create: ["pointer", ["string", "int*"]], + transport_tor_create: [ + "pointer", + ["string", "pointer", "ushort", "string", "string", "int*"], + ], + transport_memory_get_address: ["char*", ["pointer", "int*"]], + transport_type_destroy: ["void", ["pointer"]], + string_destroy: ["void", ["string"]], + byte_vector_create: ["pointer", ["uchar*", "uint", "int*"]], + byte_vector_get_at: ["uchar", ["pointer", "uint", "int*"]], + byte_vector_get_length: ["uint", ["pointer", "int*"]], + byte_vector_destroy: ["void", ["pointer"]], + public_key_create: ["pointer", ["pointer", "int*"]], + public_key_get_bytes: ["pointer", ["pointer", "int*"]], + public_key_from_private_key: ["pointer", ["pointer", "int*"]], + public_key_from_hex: ["pointer", ["string", "int*"]], + public_key_destroy: ["void", ["pointer"]], + public_key_to_emoji_id: ["char*", ["pointer", "int*"]], + emoji_id_to_public_key: ["pointer", ["string", "int*"]], + private_key_create: ["pointer", ["pointer", "int*"]], + private_key_generate: ["pointer", ["void"]], + private_key_get_bytes: ["pointer", ["pointer", "int*"]], + private_key_from_hex: ["pointer", ["string", "int*"]], + private_key_destroy: ["void", ["pointer"]], + seed_words_create: ["pointer", ["void"]], + seed_words_get_length: ["uint", ["pointer", "int*"]], + seed_words_get_at: ["char*", ["pointer", "uint", "int*"]], + seed_words_push_word: ["uchar", ["pointer", "string", "int*"]], + seed_words_destroy: ["void", ["pointer"]], + contact_create: ["pointer", ["string", "pointer", "int*"]], + contact_get_alias: ["char*", ["pointer", "int*"]], + contact_get_public_key: ["pointer", ["pointer", "int*"]], + contact_destroy: ["void", ["pointer"]], + contacts_get_length: ["uint", ["pointer", "int*"]], + contacts_get_at: ["pointer", ["pointer", "uint", "int*"]], + contacts_destroy: ["void", ["pointer"]], + completed_transaction_get_destination_public_key: [ + "pointer", + ["pointer", "int*"], + ], + completed_transaction_get_source_public_key: [ + "pointer", + ["pointer", "int*"], + ], + completed_transaction_get_amount: ["uint64", ["pointer", "int*"]], + completed_transaction_get_fee: ["uint64", ["pointer", "int*"]], + completed_transaction_get_message: ["char*", ["pointer", "int*"]], + completed_transaction_get_status: ["int", ["pointer", "int*"]], + completed_transaction_get_transaction_id: ["uint64", ["pointer", "int*"]], + completed_transaction_get_timestamp: ["uint64", ["pointer", "int*"]], + completed_transaction_is_valid: ["bool", ["pointer", "int*"]], + completed_transaction_is_outbound: ["bool", ["pointer", "int*"]], + completed_transaction_get_confirmations: ["uint64", ["pointer", "int*"]], + completed_transaction_destroy: ["void", ["pointer"]], + //completed_transaction_get_excess: [ + //this.tari_excess_ptr, + // [this.tari_completed_transaction_ptr, "int*"], + //], + //completed_transaction_get_public_nonce: [ + // this.tari_excess_public_nonce_ptr, + // [this.tari_completed_transaction_ptr, "int*"], + //], + //completed_transaction_get_signature: [ + // this.tari_excess_signature_ptr, + // [this.tari_completed_transaction_ptr, "int*"], + //], + // excess_destroy: ["void", [this.tari_excess_ptr]], + // nonce_destroy: ["void", [this.tari_excess_public_nonce_ptr]], + // signature_destroy: ["void", [this.tari_excess_signature_ptr]], + completed_transactions_get_length: ["uint", ["pointer", "int*"]], + completed_transactions_get_at: ["pointer", ["pointer", "uint", "int*"]], + completed_transactions_destroy: ["void", ["pointer"]], + pending_outbound_transaction_get_transaction_id: [ + "uint64", + ["pointer", "int*"], + ], + pending_outbound_transaction_get_destination_public_key: [ + "pointer", + ["pointer", "int*"], + ], + pending_outbound_transaction_get_amount: ["uint64", ["pointer", "int*"]], + pending_outbound_transaction_get_fee: ["uint64", ["pointer", "int*"]], + pending_outbound_transaction_get_message: ["char*", ["pointer", "int*"]], + pending_outbound_transaction_get_timestamp: [ + "uint64", + ["pointer", "int*"], + ], + pending_outbound_transaction_get_status: ["int", ["pointer", "int*"]], + pending_outbound_transaction_destroy: ["void", ["pointer"]], + pending_outbound_transactions_get_length: ["uint", ["pointer", "int*"]], + pending_outbound_transactions_get_at: [ + "pointer", + ["pointer", "uint", "int*"], + ], + pending_outbound_transactions_destroy: ["void", ["pointer"]], + pending_inbound_transaction_get_transaction_id: [ + "uint64", + ["pointer", "int*"], + ], + pending_inbound_transaction_get_source_public_key: [ + "pointer", + ["pointer", "int*"], + ], + pending_inbound_transaction_get_message: ["char*", ["pointer", "int*"]], + pending_inbound_transaction_get_amount: ["uint64", ["pointer", "int*"]], + pending_inbound_transaction_get_timestamp: [ + "uint64", + ["pointer", "int*"], + ], + pending_inbound_transaction_get_status: ["int", ["pointer", "int*"]], + pending_inbound_transaction_destroy: ["void", ["pointer"]], + pending_inbound_transactions_get_length: ["uint", ["pointer", "int*"]], + pending_inbound_transactions_get_at: [ + "pointer", + ["pointer", "uint", "int*"], + ], + pending_inbound_transactions_destroy: ["void", ["pointer"]], + comms_config_create: [ + "pointer", + [ + "string", + "pointer", + "string", + "string", + "uint64", + "uint64", + "string", + "int*", + ], + ], + comms_config_destroy: ["void", ["pointer"]], + wallet_create: [ + "pointer", + [ + "pointer", + "string", + "uint", + "uint", + "string", + "pointer", + "pointer", + "pointer", + "pointer", + "pointer", + "pointer", + "pointer", + "pointer", + "pointer", + "pointer", + "pointer", + "pointer", + "pointer", + "pointer", + "pointer", + "bool*", + "int*", + ], + ], + wallet_sign_message: ["char*", ["pointer", "string", "int*"]], + wallet_verify_message_signature: [ + "bool", + ["pointer", "pointer", "string", "string", "int*"], + ], + wallet_add_base_node_peer: [ + "bool", + ["pointer", "pointer", "string", "int*"], + ], + wallet_upsert_contact: ["bool", ["pointer", "pointer", "int*"]], + wallet_remove_contact: ["bool", ["pointer", "pointer", "int*"]], + wallet_get_available_balance: ["uint64", ["pointer", "int*"]], + wallet_get_pending_incoming_balance: ["uint64", ["pointer", "int*"]], + wallet_get_pending_outgoing_balance: ["uint64", ["pointer", "int*"]], + wallet_get_fee_estimate: [ + "uint64", + ["pointer", "uint64", "uint64", "uint64", "uint64", "int*"], + ], + wallet_get_num_confirmations_required: ["uint64", ["pointer", "int*"]], + wallet_set_num_confirmations_required: [ + "void", + ["pointer", "uint64", "int*"], + ], + wallet_send_transaction: [ + "uint64", + ["pointer", "pointer", "uint64", "uint64", "string", "int*"], + ], + wallet_get_contacts: ["pointer", ["pointer", "int*"]], + wallet_get_completed_transactions: ["pointer", ["pointer", "int*"]], + wallet_get_pending_outbound_transactions: [ + "pointer", + ["pointer", "int*"], + ], + wallet_get_public_key: ["pointer", ["pointer", "int*"]], + wallet_get_pending_inbound_transactions: ["pointer", ["pointer", "int*"]], + wallet_get_cancelled_transactions: ["pointer", ["pointer", "int*"]], + wallet_get_completed_transaction_by_id: [ + "pointer", + ["pointer", "uint64", "int*"], + ], + wallet_get_pending_outbound_transaction_by_id: [ + "pointer", + ["pointer", "uint64", "int*"], + ], + wallet_get_pending_inbound_transaction_by_id: [ + "pointer", + ["pointer", "uint64", "int*"], + ], + wallet_get_cancelled_transaction_by_id: [ + "pointer", + ["pointer", "uint64", "int*"], + ], + wallet_import_utxo: [ + "uint64", + ["pointer", "uint64", "pointer", "pointer", "string", "int*"], + ], + wallet_start_utxo_validation: ["uint64", ["pointer", "int*"]], + wallet_start_stxo_validation: ["uint64", ["pointer", "int*"]], + wallet_start_invalid_txo_validation: ["uint64", ["pointer", "int*"]], + wallet_start_transaction_validation: ["uint64", ["pointer", "int*"]], + wallet_restart_transaction_broadcast: ["bool", ["pointer", "int*"]], + wallet_set_low_power_mode: ["void", ["pointer", "int*"]], + wallet_set_normal_power_mode: ["void", ["pointer", "int*"]], + wallet_cancel_pending_transaction: [ + "bool", + ["pointer", "uint64", "int*"], + ], + wallet_coin_split: [ + "uint64", + ["pointer", "uint64", "uint64", "uint64", "string", "uint64", "int*"], + ], + wallet_get_seed_words: ["pointer", ["pointer", "int*"]], + wallet_apply_encryption: ["void", ["pointer", "string", "int*"]], + wallet_remove_encryption: ["void", ["pointer", "int*"]], + wallet_set_key_value: ["bool", ["pointer", "string", "string", "int*"]], + wallet_get_value: ["char*", ["pointer", "string", "int*"]], + wallet_clear_value: ["bool", ["pointer", "string", "int*"]], + wallet_is_recovery_in_progress: ["bool", ["pointer", "int*"]], + wallet_start_recovery: [ + "bool", + ["pointer", "pointer", "pointer", "int*"], + ], + wallet_destroy: ["void", ["pointer"]], + file_partial_backup: ["void", ["string", "string", "int*"]], + log_debug_message: ["void", ["string"]], + get_emoji_set: ["pointer", ["void"]], + emoji_set_destroy: ["void", ["pointer"]], + emoji_set_get_at: ["pointer", ["pointer", "uint", "int*"]], + emoji_set_get_length: ["uint", ["pointer", "int*"]], + }); + } + //endregion + + static checkErrorResult(error, error_name) { + expect(error.deref()).to.equal(0, `Error in ${error_name}`); + } + + //region Helpers + static initError() { + let error = Buffer.alloc(4); + error.writeInt32LE(-1, 0); + error.type = ref.types.int; + return error; + } + + static initBool() { + let boolean = ref.alloc(ref.types.bool); + return boolean; + } + + static filePartialBackup(original_file_path, backup_file_path) { + let error = this.initError(); + let result = this.#fn.file_partial_backup( + original_file_path, + backup_file_path, + error + ); + this.checkErrorResult(error, `filePartialBackup`); + return result; + } + + static logDebugMessage(msg) { + this.#fn.log_debug_message(msg); + } + //endregion + + //region String + static stringDestroy(s) { + this.#fn.string_destroy(s); + } + //endregion + + // region ByteVector + static byteVectorCreate(byte_array, element_count) { + let error = this.initError(); + let result = this.#fn.byte_vector_create(byte_array, element_count, error); + this.checkErrorResult(error, `byteVectorCreate`); + return result; + } + + static byteVectorGetAt(ptr, i) { + let error = this.initError(); + let result = this.#fn.byte_vector_get_at(ptr, i, error); + this.checkErrorResult(error, `byteVectorGetAt`); + return result; + } + + static byteVectorGetLength(ptr) { + let error = this.initError(); + let result = this.#fn.byte_vector_get_length(ptr, error); + this.checkErrorResult(error, `byteVectorGetLength`); + return result; + } + + static byteVectorDestroy(ptr) { + this.#fn.byte_vector_destroy(ptr); + } + //endregion + + //region PrivateKey + static privateKeyCreate(ptr) { + let error = this.initError(); + let result = this.#fn.private_key_create(ptr, error); + this.checkErrorResult(error, `privateKeyCreate`); + return result; + } + + static privateKeyGenerate() { + return this.#fn.private_key_generate(); + } + + static privateKeyGetBytes(ptr) { + let error = this.initError(); + let result = this.#fn.private_key_get_bytes(ptr, error); + this.checkErrorResult(error, "privateKeyGetBytes"); + return result; + } + + static privateKeyFromHex(hex) { + let error = this.initError(); + let result = this.#fn.private_key_from_hex(hex, error); + this.checkErrorResult(error, "privateKeyFromHex"); + return result; + } + + static privateKeyDestroy(ptr) { + this.#fn.private_key_destroy(ptr); + } + + //endregion + + //region PublicKey + static publicKeyCreate(ptr) { + let error = this.initError(); + let result = this.#fn.public_key_create(ptr, error); + this.checkErrorResult(error, `publicKeyCreate`); + return result; + } + + static publicKeyGetBytes(ptr) { + let error = this.initError(); + let result = this.#fn.public_key_get_bytes(ptr, error); + this.checkErrorResult(error, `publicKeyGetBytes`); + return result; + } + + static publicKeyFromPrivateKey(ptr) { + let error = this.initError(); + let result = this.#fn.public_key_from_private_key(ptr, error); + this.checkErrorResult(error, `publicKeyFromPrivateKey`); + return result; + } + + static publicKeyFromHex(hex) { + let error = this.initError(); + let result = this.#fn.public_key_from_hex(hex, error); + this.checkErrorResult(error, `publicKeyFromHex`); + return result; + } + + static emojiIdToPublicKey(emoji) { + let error = this.initError(); + let result = this.#fn.emoji_id_to_public_key(emoji, error); + this.checkErrorResult(error, `emojiIdToPublicKey`); + return result; + } + + static publicKeyToEmojiId(ptr) { + let error = this.initError(); + let result = this.#fn.public_key_to_emoji_id(ptr, error); + this.checkErrorResult(error, `publicKeyToEmojiId`); + return result; + } + + static publicKeyDestroy(ptr) { + this.#fn.public_key_destroy(ptr); + } + //endregion + + //region TransportType + static transportMemoryCreate() { + return this.#fn.transport_memory_create(); + } + + static transportTcpCreate(listener_address) { + let error = this.initError(); + let result = this.#fn.transport_tcp_create(listener_address, error); + this.checkErrorResult(error, `transportTcpCreate`); + return result; + } + + static transportTorCreate( + control_server_address, + tor_cookie, + tor_port, + socks_username, + socks_password + ) { + let error = this.initError(); + let result = this.#fn.transport_tor_create( + control_server_address, + tor_cookie, + tor_port, + socks_username, + socks_password, + error + ); + this.checkErrorResult(error, `transportTorCreate`); + return result; + } + + static transportMemoryGetAddress(transport) { + let error = this.initError(); + let result = this.#fn.transport_memory_get_address(transport, error); + this.checkErrorResult(error, `transportMemoryGetAddress`); + return result; + } + + static transportTypeDestroy(transport) { + this.#fn.transport_type_destroy(transport); + } + //endregion + + //region EmojiSet + static getEmojiSet() { + return this.#fn.this.#fn.get_emoji_set(); + } + + static emojiSetDestroy(ptr) { + this.#fn.emoji_set_destroy(ptr); + } + + static emojiSetGetAt(ptr, position) { + let error = this.initError(); + let result = this.#fn.emoji_set_get_at(ptr, position, error); + this.checkErrorResult(error, `emojiSetGetAt`); + return result; + } + + static emojiSetGetLength(ptr) { + let error = this.initError(); + let result = this.#fn.emoji_set_get_length(ptr, error); + this.checkErrorResult(error, `emojiSetGetLength`); + return result; + } + //endregion + + //region SeedWords + static seedWordsCreate() { + return this.#fn.seed_words_create(); + } + + static seedWordsGetLength(ptr) { + let error = this.initError(); + let result = this.#fn.seed_words_get_length(ptr, error); + this.checkErrorResult(error, `emojiSetGetLength`); + return result; + } + + static seedWordsGetAt(ptr, position) { + let error = this.initError(); + let result = this.#fn.seed_words_get_at(ptr, position, error); + this.checkErrorResult(error, `seedWordsGetAt`); + return result; + } + + static seedWordsPushWord(ptr, word) { + let error = this.initError(); + let result = this.#fn.seed_words_push_word(ptr, word, error); + this.checkErrorResult(error, `seedWordsPushWord`); + return result; + } + + static seedWordsDestroy(ptr) { + this.#fn.seed_words_destroy(ptr); + } + //endregion + + //region CommsConfig + static commsConfigCreate( + public_address, + transport, + database_name, + datastore_path, + discovery_timeout_in_secs, + saf_message_duration_in_secs, + network + ) { + let error = this.initError(); + let result = this.#fn.comms_config_create( + public_address, + transport, + database_name, + datastore_path, + discovery_timeout_in_secs, + saf_message_duration_in_secs, + network, + error + ); + this.checkErrorResult(error, `commsConfigCreate`); + return result; + } + + static commsConfigDestroy(ptr) { + this.#fn.comms_config_destroy(ptr); + } + //endregion + + //region Contact + static contactCreate(alias, public_key) { + let error = this.initError(); + let result = this.#fn.contact_create(alias, public_key, error); + this.checkErrorResult(error, `contactCreate`); + return result; + } + + static contactGetAlias(ptr) { + let error = this.initError(); + let result = this.#fn.contact_get_alias(ptr, error); + this.checkErrorResult(error, `contactGetAlias`); + return result; + } + + static contactGetPublicKey(ptr) { + let error = this.initError(); + let result = this.#fn.contact_get_public_key(ptr, error); + this.checkErrorResult(error, `contactGetPublicKey`); + return result; + } + + static contactDestroy(ptr) { + this.#fn.contact_destroy(ptr); + } + //endregion + + //region Contacts (List) + static contactsGetLength(ptr) { + let error = this.initError(); + let result = this.#fn.contacts_get_length(ptr, error); + this.checkErrorResult(error, `contactsGetLength`); + return result; + } + + static contactsGetAt(ptr, position) { + let error = this.initError(); + let result = this.#fn.contacts_get_at(ptr, position, error); + this.checkErrorResult(error, `contactsGetAt`); + return result; + } + + static contactsDestroy(ptr) { + this.#fn.contacts_destroy(ptr); + } + //endregion + + //region CompletedTransaction + static completedTransactionGetDestinationPublicKey(ptr) { + let error = this.initError(); + let result = this.#fn.completed_transaction_get_destination_public_key( + ptr, + error + ); + this.checkErrorResult(error, `completedTransactionGetDestinationPublicKey`); + return result; + } + + static completedTransactionGetSourcePublicKey(ptr) { + let error = this.initError(); + let result = this.#fn.completed_transaction_get_source_public_key( + ptr, + error + ); + this.checkErrorResult(error, `completedTransactionGetSourcePublicKey`); + return result; + } + + static completedTransactionGetAmount(ptr) { + let error = this.initError(); + let result = this.#fn.completed_transaction_get_amount(ptr, error); + this.checkErrorResult(error, `completedTransactionGetAmount`); + return result; + } + + static completedTransactionGetFee(ptr) { + let error = this.initError(); + let result = this.#fn.completed_transaction_get_fee(ptr, error); + this.checkErrorResult(error, `completedTransactionGetFee`); + return result; + } + + static completedTransactionGetMessage(ptr) { + let error = this.initError(); + let result = this.#fn.completed_transaction_get_message(ptr, error); + this.checkErrorResult(error, `completedTransactionGetMessage`); + return result; + } + + static completedTransactionGetStatus(ptr) { + let error = this.initError(); + let result = this.#fn.completed_transaction_get_status(ptr, error); + this.checkErrorResult(error, `completedTransactionGetStatus`); + return result; + } + + static completedTransactionGetTransactionId(ptr) { + let error = this.initError(); + let result = this.#fn.completed_transaction_get_transaction_id(ptr, error); + this.checkErrorResult(error, `completedTransactionGetTransactionId`); + return result; + } + + static completedTransactionGetTimestamp(ptr) { + let error = this.initError(); + let result = this.#fn.completed_transaction_get_timestamp(ptr, error); + this.checkErrorResult(error, `completedTransactionGetTimestamp`); + return result; + } + + static completedTransactionIsValid(ptr) { + let error = this.initError(); + let result = this.#fn.completed_transaction_is_valid(ptr, error); + this.checkErrorResult(error, `completedTransactionIsValid`); + return result; + } + + static completedTransactionIsOutbound(ptr) { + let error = this.initError(); + let result = this.#fn.completed_transaction_is_outbound(ptr, error); + this.checkErrorResult(error, `completedTransactionGetConfirmations`); + return result; + } + + static completedTransactionGetConfirmations(ptr) { + let error = this.initError(); + let result = this.#fn.completed_transaction_get_confirmations(ptr, error); + this.checkErrorResult(error, `completedTransactionGetConfirmations`); + return result; + } + + static completedTransactionDestroy(ptr) { + this.#fn.completed_transaction_destroy(ptr); + } + + //endregion + + /* + //Flagged as design flaw in the FFI lib + + static completedTransactionGetExcess(transaction) { + return new Promise((resolve, reject) => + this.#fn.completed_transaction_get_excess.async( + transaction, + this.error, + this.checkAsyncRes(resolve, reject, "completedTransactionGetExcess") + ) + ); + } + + static completedTransactionGetPublicNonce(transaction) { + return new Promise((resolve, reject) => + this.#fn.completed_transaction_get_public_nonce.async( + transaction, + this.error, + this.checkAsyncRes( + resolve, + reject, + "completedTransactionGetPublicNonce" + ) + ) + ); + } + + static completedTransactionGetSignature(transaction) { + return new Promise((resolve, reject) => + this.#fn.completed_transaction_get_signature.async( + transaction, + this.error, + this.checkAsyncRes(resolve, reject, "completedTransactionGetSignature") + ) + ); + } + + static excessDestroy(excess) { + return new Promise((resolve, reject) => + this.#fn.excess_destroy.async( + excess, + this.checkAsyncRes(resolve, reject, "excessDestroy") + ) + ); + } + + static nonceDestroy(nonce) { + return new Promise((resolve, reject) => + this.#fn.nonce_destroy.async( + nonce, + this.checkAsyncRes(resolve, reject, "nonceDestroy") + ) + ); + } + + static signatureDestroy(signature) { + return new Promise((resolve, reject) => + this.#fn.signature_destroy.async( + signature, + this.checkAsyncRes(resolve, reject, "signatureDestroy") + ) + ); + } + */ + + //region CompletedTransactions (List) + static completedTransactionsGetLength(ptr) { + let error = this.initError(); + let result = this.#fn.completed_transactions_get_length(ptr, error); + this.checkErrorResult(error, `contactsGetAt`); + return result; + } + + static completedTransactionsGetAt(ptr, position) { + let error = this.initError(); + let result = this.#fn.completed_transactions_get_at(ptr, position, error); + this.checkErrorResult(error, `contactsGetAt`); + return result; + } + + static completedTransactionsDestroy(transactions) { + this.#fn.completed_transactions_destroy(transactions); + } + //endregion + + //region PendingOutboundTransaction + static pendingOutboundTransactionGetTransactionId(ptr) { + let error = this.initError(); + let result = this.#fn.pending_outbound_transaction_get_transaction_id( + ptr, + error + ); + this.checkErrorResult(error, `pendingOutboundTransactionGetTransactionId`); + return result; + } + + static pendingOutboundTransactionGetDestinationPublicKey(ptr) { + let error = this.initError(); + let result = + this.#fn.pending_outbound_transaction_get_destination_public_key( + ptr, + error + ); + this.checkErrorResult( + error, + `pendingOutboundTransactionGetDestinationPublicKey` + ); + return result; + } + + static pendingOutboundTransactionGetAmount(ptr) { + let error = this.initError(); + let result = this.#fn.pending_outbound_transaction_get_amount(ptr, error); + this.checkErrorResult(error, `pendingOutboundTransactionGetAmount`); + return result; + } + + static pendingOutboundTransactionGetFee(ptr) { + let error = this.initError(); + let result = this.#fn.pending_outbound_transaction_get_fee(ptr, error); + this.checkErrorResult(error, `pendingOutboundTransactionGetFee`); + return result; + } + + static pendingOutboundTransactionGetMessage(ptr) { + let error = this.initError(); + let result = this.#fn.pending_outbound_transaction_get_message(ptr, error); + this.checkErrorResult(error, `pendingOutboundTransactionGetMessage`); + return result; + } + + static pendingOutboundTransactionGetTimestamp(ptr) { + let error = this.initError(); + let result = this.#fn.pending_outbound_transaction_get_timestamp( + ptr, + error + ); + this.checkErrorResult(error, `pendingOutboundTransactionGetTimestamp`); + return result; + } + + static pendingOutboundTransactionGetStatus(ptr) { + let error = this.initError(); + let result = this.#fn.pending_outbound_transaction_get_status(ptr, error); + this.checkErrorResult(error, `pendingOutboundTransactionGetStatus`); + return result; + } + + static pendingOutboundTransactionDestroy(ptr) { + this.#fn.pending_outbound_transaction_destroy(ptr); + } + //endregion + + //region PendingOutboundTransactions (List) + static pendingOutboundTransactionsGetLength(ptr) { + let error = this.initError(); + let result = this.#fn.pending_outbound_transactions_get_length(ptr, error); + this.checkErrorResult(error, `pendingOutboundTransactionsGetLength`); + return result; + } + + static pendingOutboundTransactionsGetAt(ptr, position) { + let error = this.initError(); + let result = this.#fn.pending_outbound_transactions_get_at( + ptr, + position, + error + ); + this.checkErrorResult(error, `pendingOutboundTransactionsGetAt`); + return result; + } + + static pendingOutboundTransactionsDestroy(ptr) { + this.#fn.pending_outbound_transactions_destroy(ptr); + } + //endregion + + //region PendingInboundTransaction + static pendingInboundTransactionGetTransactionId(ptr) { + let error = this.initError(); + let result = this.#fn.pending_inbound_transaction_get_transaction_id( + ptr, + error + ); + this.checkErrorResult(error, `pendingInboundTransactionGetTransactionId`); + return result; + } + + static pendingInboundTransactionGetSourcePublicKey(ptr) { + let error = this.initError(); + let result = this.#fn.pending_inbound_transaction_get_source_public_key( + ptr, + error + ); + this.checkErrorResult(error, `pendingInboundTransactionGetSourcePublicKey`); + return result; + } + + static pendingInboundTransactionGetMessage(ptr) { + let error = this.initError(); + let result = this.#fn.pending_inbound_transaction_get_message(ptr, error); + this.checkErrorResult(error, `pendingInboundTransactionGetMessage`); + return result; + } + + static pendingInboundTransactionGetAmount(ptr) { + let error = this.initError(); + let result = this.#fn.pending_inbound_transaction_get_amount(ptr, error); + this.checkErrorResult(error, `pendingInboundTransactionGetAmount`); + return result; + } + + static pendingInboundTransactionGetTimestamp(ptr) { + let error = this.initError(); + let result = this.#fn.pending_inbound_transaction_get_timestamp(ptr, error); + this.checkErrorResult(error, `pendingInboundTransactionGetTimestamp`); + return result; + } + + static pendingInboundTransactionGetStatus(ptr) { + let error = this.initError(); + let result = this.#fn.pending_inbound_transaction_get_status(ptr, error); + this.checkErrorResult(error, `pendingInboundTransactionGetStatus`); + return result; + } + + static pendingInboundTransactionDestroy(ptr) { + this.#fn.pending_inbound_transaction_destroy(ptr); + } + //endregion + + //region PendingInboundTransactions (List) + static pendingInboundTransactionsGetLength(ptr) { + let error = this.initError(); + let result = this.#fn.pending_inbound_transactions_get_length(ptr, error); + this.checkErrorResult(error, `pendingInboundTransactionsGetLength`); + return result; + } + + static pendingInboundTransactionsGetAt(ptr, position) { + let error = this.initError(); + let result = this.#fn.pending_inbound_transactions_get_at( + ptr, + position, + error + ); + this.checkErrorResult(error, `pendingInboundTransactionsGetAt`); + return result; + } + + static pendingInboundTransactionsDestroy(ptr) { + this.#fn.pending_inbound_transactions_destroy(ptr); + } + //endregion + + //region Wallet + + //region Callbacks + static createCallbackReceivedTransaction(fn) { + return ffi.Callback("void", ["pointer"], fn); + } + + static createCallbackReceivedTransactionReply(fn) { + return ffi.Callback("void", ["pointer"], fn); + } + + static createCallbackReceivedFinalizedTransaction(fn) { + return ffi.Callback("void", ["pointer"], fn); + } + + static createCallbackTransactionBroadcast(fn) { + return ffi.Callback("void", ["pointer"], fn); + } + + static createCallbackTransactionMined(fn) { + return ffi.Callback("void", ["pointer"], fn); + } + + static createCallbackTransactionMinedUnconfirmed(fn) { + return ffi.Callback("void", ["pointer", "uint64"], fn); + } + + static createCallbackDirectSendResult(fn) { + return ffi.Callback("void", ["uint64", "bool"], fn); + } + + static createCallbackStoreAndForwardSendResult(fn) { + return ffi.Callback("void", ["uint64", "bool"], fn); + } + + static createCallbackTransactionCancellation(fn) { + return ffi.Callback("void", ["pointer"], fn); + } + static createCallbackUtxoValidationComplete(fn) { + return ffi.Callback("void", ["uint64", "uchar"], fn); + } + static createCallbackStxoValidationComplete(fn) { + return ffi.Callback("void", ["uint64", "uchar"], fn); + } + static createCallbackInvalidTxoValidationComplete(fn) { + return ffi.Callback("void", ["uint64", "uchar"], fn); + } + static createCallbackTransactionValidationComplete(fn) { + return ffi.Callback("void", ["uint64", "uchar"], fn); + } + static createCallbackSafMessageReceived(fn) { + return ffi.Callback("void", ["void"], fn); + } + static createRecoveryProgressCallback(fn) { + return ffi.Callback("void", ["uchar", "uint64", "uint64"], fn); + } + //endregion + + static walletCreate( + config, + log_path, + num_rolling_log_files, + size_per_log_file_bytes, + passphrase, + seed_words, + callback_received_transaction, + callback_received_transaction_reply, + callback_received_finalized_transaction, + callback_transaction_broadcast, + callback_transaction_mined, + callback_transaction_mined_unconfirmed, + callback_direct_send_result, + callback_store_and_forward_send_result, + callback_transaction_cancellation, + callback_utxo_validation_complete, + callback_stxo_validation_complete, + callback_invalid_txo_validation_complete, + callback_transaction_validation_complete, + callback_saf_message_received + ) { + let error = this.initError(); + let recovery_in_progress = this.initBool(); + + let result = this.#fn.wallet_create( + config, + log_path, + num_rolling_log_files, + size_per_log_file_bytes, + passphrase, + seed_words, + callback_received_transaction, + callback_received_transaction_reply, + callback_received_finalized_transaction, + callback_transaction_broadcast, + callback_transaction_mined, + callback_transaction_mined_unconfirmed, + callback_direct_send_result, + callback_store_and_forward_send_result, + callback_transaction_cancellation, + callback_utxo_validation_complete, + callback_stxo_validation_complete, + callback_invalid_txo_validation_complete, + callback_transaction_validation_complete, + callback_saf_message_received, + recovery_in_progress, + error + ); + this.checkErrorResult(error, `walletCreate`); + if (recovery_in_progress) { + console.log("Wallet recovery is in progress"); + } + return result; + } + + static walletGetPublicKey(ptr) { + let error = this.initError(); + let result = this.#fn.wallet_get_public_key(ptr, error); + this.checkErrorResult(error, `walletGetPublicKey`); + return result; + } + + static walletSignMessage(ptr, msg) { + let error = this.initError(); + let result = this.#fn.wallet_sign_message(ptr, msg, error); + this.checkErrorResult(error, `walletSignMessage`); + return result; + } + + static walletVerifyMessageSignature(ptr, public_key_ptr, hex_sig_nonce, msg) { + let error = this.initError(); + let result = this.#fn.wallet_verify_message_signature( + ptr, + public_key_ptr, + hex_sig_nonce, + msg, + error + ); + this.checkErrorResult(error, `walletVerifyMessageSignature`); + return result; + } + + static walletAddBaseNodePeer(ptr, public_key_ptr, address) { + let error = this.initError(); + let result = this.#fn.wallet_add_base_node_peer( + ptr, + public_key_ptr, + address, + error + ); + this.checkErrorResult(error, `walletAddBaseNodePeer`); + return result; + } + + static walletUpsertContact(ptr, contact_ptr) { + let error = this.initError(); + let result = this.#fn.wallet_upsert_contact(ptr, contact_ptr, error); + this.checkErrorResult(error, `walletUpsertContact`); + return result; + } + + static walletRemoveContact(ptr, contact_ptr) { + let error = this.initError(); + let result = this.#fn.wallet_remove_contact(ptr, contact_ptr, error); + this.checkErrorResult(error, `walletRemoveContact`); + return result; + } + + static walletGetAvailableBalance(ptr) { + let error = this.initError(); + let result = this.#fn.wallet_get_available_balance(ptr, error); + this.checkErrorResult(error, `walletGetAvailableBalance`); + return result; + } + + static walletGetPendingIncomingBalance(ptr) { + let error = this.initError(); + let result = this.#fn.wallet_get_pending_incoming_balance(ptr, error); + this.checkErrorResult(error, `walletGetPendingIncomingBalance`); + return result; + } + + static walletGetPendingOutgoingBalance(ptr) { + let error = this.initError(); + let result = this.#fn.wallet_get_pending_outgoing_balance(ptr, error); + this.checkErrorResult(error, `walletGetPendingOutgoingBalance`); + return result; + } + + static walletGetFeeEstimate( + ptr, + amount, + fee_per_gram, + num_kernels, + num_outputs + ) { + let error = this.initError(); + let result = this.#fn.wallet_get_fee_estimate( + ptr, + amount, + fee_per_gram, + num_kernels, + num_outputs, + error + ); + this.checkErrorResult(error, `walletGetFeeEstimate`); + return result; + } + + static walletGetNumConfirmationsRequired(ptr) { + let error = this.initError(); + let result = this.#fn.wallet_get_num_confirmations_required(ptr, error); + this.checkErrorResult(error, `walletGetNumConfirmationsRequired`); + return result; + } + + static walletSetNumConfirmationsRequired(ptr, num) { + let error = this.initError(); + this.#fn.wallet_set_num_confirmations_required(ptr, num, error); + this.checkErrorResult(error, `walletSetNumConfirmationsRequired`); + } + + static walletSendTransaction( + ptr, + destination, + amount, + fee_per_gram, + message + ) { + let error = this.initError(); + let result = this.#fn.wallet_send_transaction( + ptr, + destination, + amount, + fee_per_gram, + message, + error + ); + this.checkErrorResult(error, `walletSendTransaction`); + return result; + } + + static walletGetContacts(ptr) { + let error = this.initError(); + let result = this.#fn.wallet_get_contacts(ptr, error); + this.checkErrorResult(error, `walletGetContacts`); + return result; + } + + static walletGetCompletedTransactions(ptr) { + let error = this.initError(); + let result = this.#fn.wallet_get_completed_transactions(ptr, error); + this.checkErrorResult(error, `walletGetCompletedTransactions`); + return result; + } + + static walletGetPendingOutboundTransactions(ptr) { + let error = this.initError(); + let result = this.#fn.wallet_get_pending_outbound_transactions(ptr, error); + this.checkErrorResult(error, `walletGetPendingOutboundTransactions`); + return result; + } + + static walletGetPendingInboundTransactions(ptr) { + let error = this.initError(); + let result = this.#fn.wallet_get_pending_inbound_transactions(ptr, error); + this.checkErrorResult(error, `walletGetPendingInboundTransactions`); + return result; + } + + static walletGetCancelledTransactions(ptr) { + let error = this.initError(); + let result = this.#fn.wallet_get_cancelled_transactions(ptr, error); + this.checkErrorResult(error, `walletGetCancelledTransactions`); + return result; + } + + static walletGetCompletedTransactionById(ptr, transaction_id) { + let error = this.initError(); + let result = this.#fn.wallet_get_completed_transaction_by_id( + ptr, + transaction_id, + error + ); + this.checkErrorResult(error, `walletGetCompletedTransactionById`); + return result; + } + + static walletGetPendingOutboundTransactionById(ptr, transaction_id) { + let error = this.initError(); + let result = this.#fn.wallet_get_pending_outbound_transaction_by_id( + ptr, + transaction_id, + error + ); + this.checkErrorResult(error, `walletGetPendingOutboundTransactionById`); + return result; + } + + static walletGetPendingInboundTransactionById(ptr, transaction_id) { + let error = this.initError(); + let result = this.#fn.wallet_get_pending_inbound_transaction_by_id( + ptr, + transaction_id, + error + ); + this.checkErrorResult(error, `walletGetPendingInboundTransactionById`); + return result; + } + + static walletGetCancelledTransactionById(ptr, transaction_id) { + let error = this.initError(); + let result = this.#fn.wallet_get_cancelled_transaction_by_id( + ptr, + transaction_id, + error + ); + this.checkErrorResult(error, `walletGetCancelledTransactionById`); + return result; + } + + static walletImportUtxo( + ptr, + amount, + spending_key_ptr, + source_public_key_ptr, + message + ) { + let error = this.initError(); + let result = this.#fn.wallet_import_utxo( + ptr, + amount, + spending_key_ptr, + source_public_key_ptr, + message, + error + ); + this.checkErrorResult(error, `walletImportUtxo`); + return result; + } + + static walletStartUtxoValidation(ptr) { + let error = this.initError(); + let result = this.#fn.wallet_start_utxo_validation(ptr, error); + this.checkErrorResult(error, `walletStartUtxoValidation`); + return result; + } + + static walletStartStxoValidation(ptr) { + let error = this.initError(); + let result = this.#fn.wallet_start_stxo_validation(ptr, error); + this.checkErrorResult(error, `walletStartStxoValidation`); + return result; + } + + static walletStartInvalidTxoValidation(ptr) { + let error = this.initError(); + let result = this.#fn.wallet_start_invalid_txo_validation(ptr, error); + this.checkErrorResult(error, `walletStartInvalidUtxoValidation`); + return result; + } + + static walletStartTransactionValidation(ptr) { + let error = this.initError(); + let result = this.#fn.wallet_start_transaction_validation(ptr, error); + this.checkErrorResult(error, `walletStartTransactionValidation`); + return result; + } + + static walletRestartTransactionBroadcast(ptr) { + let error = this.initError(); + let result = this.#fn.wallet_restart_transaction_broadcast(ptr, error); + this.checkErrorResult(error, `walletRestartTransactionBroadcast`); + return result; + } + + static walletSetLowPowerMode(ptr) { + let error = this.initError(); + this.#fn.wallet_set_low_power_mode(ptr, error); + this.checkErrorResult(error, `walletSetLowPowerMode`); + } + + static walletSetNormalPowerMode(ptr) { + let error = this.initError(); + this.#fn.wallet_set_normal_power_mode(ptr, error); + this.checkErrorResult(error, `walletSetNormalPowerMode`); + } + + static walletCancelPendingTransaction(ptr, transaction_id) { + let error = this.initError(); + let result = this.#fn.wallet_cancel_pending_transaction( + ptr, + transaction_id, + error + ); + this.checkErrorResult(error, `walletCancelPendingTransaction`); + return result; + } + + static walletCoinSplit(ptr, amount, count, fee, msg, lock_height) { + let error = this.initError(); + let result = this.#fn.wallet_coin_split( + ptr, + amount, + count, + fee, + msg, + lock_height, + error + ); + this.checkErrorResult(error, `walletCoinSplit`); + return result; + } + + static walletGetSeedWords(ptr) { + let error = this.initError(); + let result = this.#fn.wallet_get_seed_words(ptr, error); + this.checkErrorResult(error, `walletGetSeedWords`); + return result; + } + + static walletApplyEncryption(ptr, passphrase) { + let error = this.initError(); + this.#fn.wallet_apply_encryption(ptr, passphrase, error); + this.checkErrorResult(error, `walletApplyEncryption`); + } + + static walletRemoveEncryption(ptr) { + let error = this.initError(); + this.#fn.wallet_remove_encryption(ptr, error); + this.checkErrorResult(error, `walletRemoveEncryption`); + } + + static walletSetKeyValue(ptr, key_ptr, value) { + let error = this.initError(); + let result = this.#fn.wallet_set_key_value(ptr, key_ptr, value, error); + this.checkErrorResult(error, `walletSetKeyValue`); + return result; + } + + static walletGetValue(ptr, key_ptr) { + let error = this.initError(); + let result = this.#fn.wallet_get_value(ptr, key_ptr, error); + this.checkErrorResult(error, `walletGetValue`); + return result; + } + + static walletClearValue(ptr, key_ptr) { + let error = this.initError(); + let result = this.#fn.wallet_clear_value(ptr, key_ptr, error); + this.checkErrorResult(error, `walletClearValue`); + return result; + } + + static walletIsRecoveryInProgress(ptr) { + let error = this.initError(); + let result = this.#fn.wallet_is_recovery_in_progress(ptr, error); + this.checkErrorResult(error, `walletIsRecoveryInProgress`); + return result; + } + + static walletStartRecovery( + ptr, + base_node_public_key_ptr, + recovery_progress_callback + ) { + let error = this.initError(); + let result = this.#fn.wallet_start_recovery( + ptr, + base_node_public_key_ptr, + recovery_progress_callback, + error + ); + this.checkErrorResult(error, `walletStartRecovery`); + return result; + } + + static walletDestroy(ptr) { + this.#fn.wallet_destroy(ptr); + } + //endregion +} +module.exports = InterfaceFFI; diff --git a/integration_tests/helpers/ffi/pendingInboundTransaction.js b/integration_tests/helpers/ffi/pendingInboundTransaction.js index 32cae202fb..dc2071e24b 100644 --- a/integration_tests/helpers/ffi/pendingInboundTransaction.js +++ b/integration_tests/helpers/ffi/pendingInboundTransaction.js @@ -1,24 +1,70 @@ -const WalletFFI = require("./walletFFI"); +const InterfaceFFI = require("./ffiInterface"); +const PublicKey = require("./publicKey"); class PendingInboundTransaction { #tari_pending_inbound_transaction_ptr; - constructor(tari_pending_inbound_transaction_ptr) { - this.#tari_pending_inbound_transaction_ptr = - tari_pending_inbound_transaction_ptr; + pointerAssign(ptr) { + if (this.#tari_pending_inbound_transaction_ptr) { + this.destroy(); + this.#tari_pending_inbound_transaction_ptr = ptr; + } else { + this.#tari_pending_inbound_transaction_ptr = ptr; + } + } + + getPtr() { + return this.#tari_pending_inbound_transaction_ptr; + } + + getSourcePublicKey() { + let result = new PublicKey(); + result.pointerAssign( + InterfaceFFI.pendingInboundTransactionGetSourcePublicKey( + this.#tari_pending_inbound_transaction_ptr + ) + ); + return result; + } + + getAmount() { + return InterfaceFFI.pendingInboundTransactionGetAmount( + this.#tari_pending_inbound_transaction_ptr + ); + } + + getMessage() { + return InterfaceFFI.pendingInboundTransactionGetMessage( + this.#tari_pending_inbound_transaction_ptr + ); } getStatus() { - return WalletFFI.pendingInboundTransactionGetStatus( + return InterfaceFFI.pendingInboundTransactionGetStatus( this.#tari_pending_inbound_transaction_ptr ); } - destroy() { - return WalletFFI.pendingInboundTransactionDestroy( + getTransactionID() { + return InterfaceFFI.pendingInboundTransactionGetTransactionId( + this.#tari_pending_inbound_transaction_ptr + ); + } + + getTimestamp() { + return InterfaceFFI.pendingInboundTransactionGetTimestamp( this.#tari_pending_inbound_transaction_ptr ); } + + destroy() { + if (this.#tari_pending_inbound_transaction_ptr) { + InterfaceFFI.pendingInboundTransactionDestroy( + this.#tari_pending_inbound_transaction_ptr + ); + this.#tari_pending_inbound_transaction_ptr = undefined; //prevent double free segfault + } + } } module.exports = PendingInboundTransaction; diff --git a/integration_tests/helpers/ffi/pendingInboundTransactions.js b/integration_tests/helpers/ffi/pendingInboundTransactions.js index 6246b03429..2500b41a04 100644 --- a/integration_tests/helpers/ffi/pendingInboundTransactions.js +++ b/integration_tests/helpers/ffi/pendingInboundTransactions.js @@ -1,39 +1,37 @@ const PendingInboundTransaction = require("./pendingInboundTransaction"); -const WalletFFI = require("./walletFFI"); +const InterfaceFFI = require("./ffiInterface"); class PendingInboundTransactions { #tari_pending_inbound_transactions_ptr; - constructor(tari_pending_inbound_transactions_ptr) { - this.#tari_pending_inbound_transactions_ptr = - tari_pending_inbound_transactions_ptr; - } - - static async fromWallet(wallet) { - return new PendingInboundTransactions( - await WalletFFI.walletGetPendingInboundTransactions(wallet) - ); + constructor(ptr) { + this.#tari_pending_inbound_transactions_ptr = ptr; } getLength() { - return WalletFFI.pendingInboundTransactionsGetLength( + return InterfaceFFI.pendingInboundTransactionsGetLength( this.#tari_pending_inbound_transactions_ptr ); } - async getAt(position) { - return new PendingInboundTransaction( - await WalletFFI.pendingInboundTransactionsGetAt( + getAt(position) { + let result = new PendingInboundTransaction(); + result.pointerAssign( + InterfaceFFI.pendingInboundTransactionsGetAt( this.#tari_pending_inbound_transactions_ptr, position ) ); + return result; } destroy() { - return WalletFFI.pendingInboundTransactionsDestroy( - this.#tari_pending_inbound_transactions_ptr - ); + if (this.#tari_pending_inbound_transactions_ptr) { + InterfaceFFI.pendingInboundTransactionsDestroy( + this.#tari_pending_inbound_transactions_ptr + ); + this.#tari_pending_inbound_transactions_ptr = undefined; //prevent double free segfault + } } } diff --git a/integration_tests/helpers/ffi/pendingOutboundTransaction.js b/integration_tests/helpers/ffi/pendingOutboundTransaction.js index eed2d722bb..0fc2ca47b9 100644 --- a/integration_tests/helpers/ffi/pendingOutboundTransaction.js +++ b/integration_tests/helpers/ffi/pendingOutboundTransaction.js @@ -1,30 +1,76 @@ -const WalletFFI = require("./walletFFI"); +const InterfaceFFI = require("./ffiInterface"); +const PublicKey = require("./publicKey"); class PendingOutboundTransaction { #tari_pending_outbound_transaction_ptr; - constructor(tari_pending_outbound_transaction_ptr) { - this.#tari_pending_outbound_transaction_ptr = - tari_pending_outbound_transaction_ptr; + pointerAssign(ptr) { + if (this.#tari_pending_outbound_transaction_ptr) { + this.#tari_pending_outbound_transaction_ptr = ptr; + this.destroy(); + } else { + this.#tari_pending_outbound_transaction_ptr = ptr; + } } - getTransactionId() { - return WalletFFI.pendingOutboundTransactionGetTransactionId( + getPtr() { + return this.#tari_pending_outbound_transaction_ptr; + } + + getDestinationPublicKey() { + let result = new PublicKey(); + result.pointerAssign( + InterfaceFFI.pendingOutboundTransactionGetDestinationPublicKey( + this.#tari_pending_outbound_transaction_ptr + ) + ); + return result; + } + + getAmount() { + return InterfaceFFI.pendingOutboundTransactionGetAmount( + this.#tari_pending_outbound_transaction_ptr + ); + } + + getFee() { + return InterfaceFFI.pendingOutboundTransactionGetFee( + this.#tari_pending_outbound_transaction_ptr + ); + } + + getMessage() { + return InterfaceFFI.pendingOutboundTransactionGetMessage( this.#tari_pending_outbound_transaction_ptr ); } getStatus() { - return WalletFFI.pendingOutboundTransactionGetStatus( + return InterfaceFFI.pendingOutboundTransactionGetStatus( this.#tari_pending_outbound_transaction_ptr ); } - destroy() { - return WalletFFI.pendingOutboundTransactionDestroy( + getTransactionID() { + return InterfaceFFI.pendingOutboundTransactionGetTransactionId( + this.#tari_pending_outbound_transaction_ptr + ); + } + + getTimestamp() { + return InterfaceFFI.pendingOutboundTransactionGetTimestamp( this.#tari_pending_outbound_transaction_ptr ); } + + destroy() { + if (this.#tari_pending_outbound_transaction_ptr) { + InterfaceFFI.pendingOutboundTransactionDestroy( + this.#tari_pending_outbound_transaction_ptr + ); + this.#tari_pending_outbound_transaction_ptr = undefined; //prevent double free segfault + } + } } module.exports = PendingOutboundTransaction; diff --git a/integration_tests/helpers/ffi/pendingOutboundTransactions.js b/integration_tests/helpers/ffi/pendingOutboundTransactions.js index 28e408563d..45de06033b 100644 --- a/integration_tests/helpers/ffi/pendingOutboundTransactions.js +++ b/integration_tests/helpers/ffi/pendingOutboundTransactions.js @@ -1,39 +1,37 @@ const PendingOutboundTransaction = require("./pendingOutboundTransaction"); -const WalletFFI = require("./walletFFI"); +const InterfaceFFI = require("./ffiInterface"); class PendingOutboundTransactions { #tari_pending_outbound_transactions_ptr; - constructor(tari_pending_outbound_transactions_ptr) { - this.#tari_pending_outbound_transactions_ptr = - tari_pending_outbound_transactions_ptr; - } - - static async fromWallet(wallet) { - return new PendingOutboundTransactions( - await WalletFFI.walletGetPendingOutboundTransactions(wallet) - ); + constructor(ptr) { + this.#tari_pending_outbound_transactions_ptr = ptr; } getLength() { - return WalletFFI.pendingOutboundTransactionsGetLength( + return InterfaceFFI.pendingOutboundTransactionsGetLength( this.#tari_pending_outbound_transactions_ptr ); } - async getAt(position) { - return new PendingOutboundTransaction( - await WalletFFI.pendingOutboundTransactionsGetAt( + getAt(position) { + let result = new PendingOutboundTransaction(); + result.pointerAssign( + InterfaceFFI.pendingOutboundTransactionsGetAt( this.#tari_pending_outbound_transactions_ptr, position ) ); + return result; } destroy() { - return WalletFFI.pendingOutboundTransactionsDestroy( - this.#tari_pending_outbound_transactions_ptr - ); + if (this.#tari_pending_outbound_transactions_ptr) { + InterfaceFFI.pendingOutboundTransactionsDestroy( + this.#tari_pending_outbound_transactions_ptr + ); + this.#tari_pending_outbound_transactions_ptr = undefined; //prevent double free segfault + } } } diff --git a/integration_tests/helpers/ffi/privateKey.js b/integration_tests/helpers/ffi/privateKey.js new file mode 100644 index 0000000000..7115ab8a1d --- /dev/null +++ b/integration_tests/helpers/ffi/privateKey.js @@ -0,0 +1,67 @@ +const InterfaceFFI = require("./ffiInterface"); +const ByteVector = require("./byteVector"); +const utf8 = require("utf8"); + +class PrivateKey { + #tari_private_key_ptr; + + pointerAssign(ptr) { + // Prevent pointer from being leaked in case of re-assignment + if (this.#tari_private_key_ptr) { + this.#tari_private_key_ptr = ptr; + this.destroy(); + } else { + this.#tari_private_key_ptr = ptr; + } + } + + generate() { + this.#tari_private_key_ptr = InterfaceFFI.privateKeyGenerate(); + } + + fromHexString(hex) { + let sanitize = utf8.encode(hex); // Make sure it's not UTF-16 encoded (JS default) + let result = new PrivateKey(); + result.pointerAssign(InterfaceFFI.privateKeyFromHex(sanitize)); + return result; + } + + fromByteVector(byte_vector) { + let result = new PrivateKey(); + result.pointerAssign(InterfaceFFI.privateKeyCreate(byte_vector.getPtr())); + return result; + } + + getPtr() { + return this.#tari_private_key_ptr; + } + + getBytes() { + let result = new ByteVector(); + result.pointerAssign( + InterfaceFFI.privateKeyGetBytes(this.#tari_private_key_ptr) + ); + return result; + } + + getHex() { + const bytes = this.getBytes(); + const length = bytes.getLength(); + let byte_array = new Uint8Array(length); + for (let i = 0; i < length; i++) { + byte_array[i] = bytes.getAt(i); + } + bytes.destroy(); + let buffer = Buffer.from(byte_array, 0); + return buffer.toString("hex"); + } + + destroy() { + if (this.#tari_private_key_ptr) { + InterfaceFFI.privateKeyDestroy(this.#tari_private_key_ptr); + this.#tari_private_key_ptr = undefined; //prevent double free segfault + } + } +} + +module.exports = PrivateKey; diff --git a/integration_tests/helpers/ffi/publicKey.js b/integration_tests/helpers/ffi/publicKey.js index 1165aa193d..7e1476c3d5 100644 --- a/integration_tests/helpers/ffi/publicKey.js +++ b/integration_tests/helpers/ffi/publicKey.js @@ -1,64 +1,82 @@ -const WalletFFI = require("./walletFFI"); +const InterfaceFFI = require("./ffiInterface"); const ByteVector = require("./byteVector"); const utf8 = require("utf8"); class PublicKey { #tari_public_key_ptr; - constructor(public_key) { - this.#tari_public_key_ptr = public_key; + pointerAssign(ptr) { + // Prevent pointer from being leaked in case of re-assignment + if (this.#tari_public_key_ptr) { + this.destroy(); + this.#tari_public_key_ptr = ptr; + } else { + this.#tari_public_key_ptr = ptr; + } } - static fromPubkey(public_key) { - return new PublicKey(public_key); + fromPrivateKey(key) { + let result = new PublicKey(); + result.pointerAssign(InterfaceFFI.publicKeyFromPrivateKey(key.getPtr())); + return result; } - static async fromWallet(wallet) { - return new PublicKey(await WalletFFI.walletGetPublicKey(wallet)); + static fromHexString(hex) { + let sanitize = utf8.encode(hex); // Make sure it's not UTF-16 encoded (JS default) + let result = new PublicKey(); + result.pointerAssign(InterfaceFFI.publicKeyFromHex(sanitize)); + return result; } - static async fromString(public_key_hex) { - let sanitize = utf8.encode(public_key_hex); // Make sure it's not UTF-16 encoded (JS default) - return new PublicKey(await WalletFFI.publicKeyFromHex(sanitize)); + fromEmojiID(emoji) { + let sanitize = utf8.encode(emoji); // Make sure it's not UTF-16 encoded (JS default) + let result = new PublicKey(); + result.pointerAssign(InterfaceFFI.emojiIdToPublicKey(sanitize)); + return result; } - static async fromBytes(bytes) { - return new PublicKey(await WalletFFI.publicKeyCreate(bytes)); + fromByteVector(byte_vector) { + let result = new PublicKey(); + result.pointerAssign(InterfaceFFI.publicKeyCreate(byte_vector.getPtr())); + return result; } getPtr() { return this.#tari_public_key_ptr; } - async getBytes() { - return new ByteVector( - await WalletFFI.publicKeyGetBytes(this.#tari_public_key_ptr) + getBytes() { + let result = new ByteVector(); + result.pointerAssign( + InterfaceFFI.publicKeyGetBytes(this.#tari_public_key_ptr) ); + return result; } - async getHex() { - const bytes = await this.getBytes(); - const length = await bytes.getLength(); + getHex() { + const bytes = this.getBytes(); + const length = bytes.getLength(); let byte_array = new Uint8Array(length); - for (let i = 0; i < length; ++i) { - byte_array[i] = await bytes.getAt(i); + for (let i = 0; i < length; i++) { + byte_array[i] = bytes.getAt(i); } - await bytes.destroy(); + bytes.destroy(); let buffer = Buffer.from(byte_array, 0); return buffer.toString("hex"); } - async getEmojiId() { - const emoji_id = await WalletFFI.publicKeyToEmojiId( - this.#tari_public_key_ptr - ); + getEmojiId() { + const emoji_id = InterfaceFFI.publicKeyToEmojiId(this.#tari_public_key_ptr); const result = emoji_id.readCString(); - await WalletFFI.stringDestroy(emoji_id); + InterfaceFFI.stringDestroy(emoji_id); return result; } destroy() { - return WalletFFI.publicKeyDestroy(this.#tari_public_key_ptr); + if (this.#tari_public_key_ptr) { + InterfaceFFI.publicKeyDestroy(this.#tari_public_key_ptr); + this.#tari_public_key_ptr = undefined; //prevent double free segfault + } } } diff --git a/integration_tests/helpers/ffi/seedWords.js b/integration_tests/helpers/ffi/seedWords.js index 86c05cab48..e191bc38a9 100644 --- a/integration_tests/helpers/ffi/seedWords.js +++ b/integration_tests/helpers/ffi/seedWords.js @@ -1,45 +1,55 @@ -const WalletFFI = require("./walletFFI"); +const InterfaceFFI = require("./ffiInterface"); +const utf8 = require("utf8"); class SeedWords { #tari_seed_words_ptr; - constructor(tari_seed_words_ptr) { - this.#tari_seed_words_ptr = tari_seed_words_ptr; + pointerAssign(ptr) { + // Prevent pointer from being leaked in case of re-assignment + if (this.#tari_seed_words_ptr) { + this.destroy(); + this.#tari_seed_words_ptr = ptr; + } else { + this.#tari_seed_words_ptr = ptr; + } } - static async fromString(seed_words_text) { - const seed_words = await WalletFFI.seedWordsCreate(); + static fromText(seed_words_text) { + const seed_words = new SeedWords(); + seed_words.pointerAssign(InterfaceFFI.seedWordsCreate()); const seed_words_list = seed_words_text.split(" "); for (const seed_word of seed_words_list) { - await WalletFFI.seedWordsPushWord(seed_words, seed_word); + InterfaceFFI.seedWordsPushWord( + seed_words.getPtr(), + utf8.encode(seed_word) + ); } - return new SeedWords(seed_words); - } - - static async fromWallet(wallet) { - return new SeedWords(await WalletFFI.walletGetSeedWords(wallet)); + return seed_words; } getLength() { - return WalletFFI.seedWordsGetLength(this.#tari_seed_words_ptr); + return InterfaceFFI.seedWordsGetLength(this.#tari_seed_words_ptr); } getPtr() { return this.#tari_seed_words_ptr; } - async getAt(position) { - const seed_word = await WalletFFI.seedWordsGetAt( + getAt(position) { + const seed_word = InterfaceFFI.seedWordsGetAt( this.#tari_seed_words_ptr, position ); const result = seed_word.readCString(); - await WalletFFI.stringDestroy(seed_word); + InterfaceFFI.stringDestroy(seed_word); return result; } destroy() { - return WalletFFI.seedWordsDestroy(this.#tari_seed_words_ptr); + if (this.#tari_seed_words_ptr) { + InterfaceFFI.seedWordsDestroy(this.#tari_seed_words_ptr); + this.#tari_seed_words_ptr = undefined; //prevent double free segfault + } } } diff --git a/integration_tests/helpers/ffi/transportType.js b/integration_tests/helpers/ffi/transportType.js new file mode 100644 index 0000000000..0826c423b5 --- /dev/null +++ b/integration_tests/helpers/ffi/transportType.js @@ -0,0 +1,85 @@ +const InterfaceFFI = require("./ffiInterface"); +const utf8 = require("utf8"); + +class TransportType { + #tari_transport_type_ptr; + #type = "None"; + + pointerAssign(ptr, type) { + // Prevent pointer from being leaked in case of re-assignment + if (this.#tari_transport_type_ptr) { + this.destroy(); + this.#tari_transport_type_ptr = ptr; + this.#type = type; + } else { + this.#tari_transport_type_ptr = ptr; + this.#type = type; + } + } + + getPtr() { + return this.#tari_transport_type_ptr; + } + + getType() { + return this.#type; + } + + static createMemory() { + let result = new TransportType(); + result.pointerAssign(InterfaceFFI.transportMemoryCreate(), "Memory"); + return result; + } + + static createTCP(listener_address) { + let sanitize = utf8.encode(listener_address); // Make sure it's not UTF-16 encoded (JS default) + let result = new TransportType(); + result.pointerAssign(InterfaceFFI.transportTcpCreate(sanitize), "TCP"); + return result; + } + + static createTor( + control_server_address, + tor_cookie, + tor_port, + socks_username, + socks_password + ) { + let sanitize_address = utf8.encode(control_server_address); + let sanitize_username = utf8.encode(socks_username); + let sanitize_password = utf8.encode(socks_password); + let result = new TransportType(); + result.pointerAssign( + InterfaceFFI.transportTorCreate( + sanitize_address, + tor_cookie.getPtr(), + tor_port, + sanitize_username, + sanitize_password + ), + "Tor" + ); + return result; + } + + getAddress() { + if (this.#type === "Memory") { + let c_address = InterfaceFFI.transportMemoryGetAddress(this.getPtr()); + let result = c_address.readCString(); + InterfaceFFI.stringDestroy(c_address); + return result; + } else { + return "N/A"; + } + } + + destroy() { + this.#type = "None"; + if (this.#tari_transport_type_ptr) { + InterfaceFFI.transportTypeDestroy(this.#tari_transport_type_ptr); + this.#tari_transport_type_ptr = undefined; //prevent double free segfault + } + } +} + +module.exports = TransportType; diff --git a/integration_tests/helpers/ffi/wallet.js b/integration_tests/helpers/ffi/wallet.js new file mode 100644 index 0000000000..cf357183c0 --- /dev/null +++ b/integration_tests/helpers/ffi/wallet.js @@ -0,0 +1,451 @@ +const InterfaceFFI = require("./ffiInterface"); +const PublicKey = require("./publicKey"); +const CompletedTransaction = require("./completedTransaction"); +const CompletedTransactions = require("./completedTransactions"); +const PendingInboundTransaction = require("./pendingInboundTransaction"); +const PendingInboundTransactions = require("./pendingInboundTransactions"); +// const PendingOutboundTransaction = require("./pendingOutboundTransaction"); +const PendingOutboundTransactions = require("./pendingOutboundTransactions"); +const Contact = require("./contact"); +const Contacts = require("./contacts"); +// const SeedWords = require("./seedWords"); + +const utf8 = require("utf8"); + +class Wallet { + #wallet_ptr; + #log_path = ""; + receivedTransaction = 0; + receivedTransactionReply = 0; + transactionBroadcast = 0; + transactionMined = 0; + saf_messages = 0; + + utxo_validation_complete = false; + utxo_validation_result = 0; + stxo_validation_complete = false; + stxo_validation_result = 0; + + getUtxoValidationStatus() { + return { + utxo_validation_complete: this.utxo_validation_complete, + utxo_validation_result: this.utxo_validation_result, + }; + } + + getStxoValidationStatus() { + return { + stxo_validation_complete: this.stxo_validation_complete, + stxo_validation_result: this.stxo_validation_result, + }; + } + + clearCallbackCounters() { + this.receivedTransaction = + this.receivedTransactionReply = + this.transactionBroadcast = + this.transactionMined = + this.saf_messages = + this.cancelled = + this.minedunconfirmed = + this.finalized = + 0; + } + + getCounters() { + return { + received: this.receivedTransaction, + replyreceived: this.receivedTransactionReply, + broadcast: this.transactionBroadcast, + finalized: this.finalized, + minedunconfirmed: this.minedunconfirmed, + cancelled: this.cancelled, + mined: this.transactionMined, + saf: this.saf_messages, + }; + } + + constructor( + comms_config_ptr, + log_path, + passphrase, + seed_words_ptr, + num_rolling_log_file = 50, + log_size_bytes = 102400 + ) { + this.receivedTransaction = 0; + this.receivedTransactionReply = 0; + this.transactionBroadcast = 0; + this.transactionMined = 0; + this.saf_messages = 0; + this.cancelled = 0; + this.minedunconfirmed = 0; + this.finalized = 0; + this.recoveryFinished = true; + let sanitize = null; + let words = null; + if (passphrase) { + sanitize = utf8.encode(passphrase); + } + if (seed_words_ptr) { + words = seed_words_ptr; + } + this.#log_path = log_path; + this.#wallet_ptr = InterfaceFFI.walletCreate( + comms_config_ptr, + utf8.encode(this.#log_path), //`${this.baseDir}/log/wallet.log`, + num_rolling_log_file, + log_size_bytes, + sanitize, + words, + this.#callback_received_transaction, + this.#callback_received_transaction_reply, + this.#callback_received_finalized_transaction, + this.#callback_transaction_broadcast, + this.#callback_transaction_mined, + this.#callback_transaction_mined_unconfirmed, + this.#callback_direct_send_result, + this.#callback_store_and_forward_send_result, + this.#callback_transaction_cancellation, + this.#callback_utxo_validation_complete, + this.#callback_stxo_validation_complete, + this.#callback_invalid_txo_validation_complete, + this.#callback_transaction_validation_complete, + this.#callback_saf_message_received + ); + } + + //region Callbacks + #onReceivedTransaction = (ptr) => { + // refer to outer scope in callback function otherwise this is null + let tx = new PendingInboundTransaction(); + tx.pointerAssign(ptr); + console.log( + `${new Date().toISOString()} received Transaction with txID ${tx.getTransactionID()}` + ); + tx.destroy(); + this.receivedTransaction += 1; + }; + + #onReceivedTransactionReply = (ptr) => { + let tx = new CompletedTransaction(); + tx.pointerAssign(ptr); + console.log( + `${new Date().toISOString()} received reply for Transaction with txID ${tx.getTransactionID()}.` + ); + tx.destroy(); + this.receivedTransactionReply += 1; + }; + + #onReceivedFinalizedTransaction = (ptr) => { + let tx = new CompletedTransaction(); + tx.pointerAssign(ptr); + console.log( + `${new Date().toISOString()} received finalization for Transaction with txID ${tx.getTransactionID()}.` + ); + tx.destroy(); + this.finalized += 1; + }; + + #onTransactionBroadcast = (ptr) => { + let tx = new CompletedTransaction(); + tx.pointerAssign(ptr); + console.log( + `${new Date().toISOString()} Transaction with txID ${tx.getTransactionID()} was broadcast.` + ); + tx.destroy(); + this.transactionBroadcast += 1; + }; + + #onTransactionMined = (ptr) => { + let tx = new CompletedTransaction(); + tx.pointerAssign(ptr); + console.log( + `${new Date().toISOString()} Transaction with txID ${tx.getTransactionID()} was mined.` + ); + tx.destroy(); + this.transactionMined += 1; + }; + + #onTransactionMinedUnconfirmed = (ptr, confirmations) => { + let tx = new CompletedTransaction(); + tx.pointerAssign(ptr); + console.log( + `${new Date().toISOString()} Transaction with txID ${tx.getTransactionID()} is mined unconfirmed with ${confirmations} confirmations.` + ); + tx.destroy(); + this.minedunconfirmed += 1; + }; + + #onTransactionCancellation = (ptr) => { + let tx = new CompletedTransaction(); + tx.pointerAssign(ptr); + console.log( + `${new Date().toISOString()} Transaction with txID ${tx.getTransactionID()} was cancelled` + ); + tx.destroy(); + this.cancelled += 1; + }; + + #onDirectSendResult = (id, success) => { + console.log( + `${new Date().toISOString()} callbackDirectSendResult(${id},${success})` + ); + }; + + #onStoreAndForwardSendResult = (id, success) => { + console.log( + `${new Date().toISOString()} callbackStoreAndForwardSendResult(${id},${success})` + ); + }; + + #onUtxoValidationComplete = (request_key, validation_results) => { + console.log( + `${new Date().toISOString()} callbackUtxoValidationComplete(${request_key},${validation_results})` + ); + this.utxo_validation_complete = true; + this.utxo_validation_result = validation_results; + }; + + #onStxoValidationComplete = (request_key, validation_results) => { + console.log( + `${new Date().toISOString()} callbackStxoValidationComplete(${request_key},${validation_results})` + ); + this.stxo_validation_complete = true; + this.stxo_validation_result = validation_results; + }; + + #onInvalidTxoValidationComplete = (request_key, validation_results) => { + console.log( + `${new Date().toISOString()} callbackInvalidTxoValidationComplete(${request_key},${validation_results})` + ); + //this.invalidtxo_validation_complete = true; + //this.invalidtxo_validation_result = validation_results; + }; + + #onTransactionValidationComplete = (request_key, validation_results) => { + console.log( + `${new Date().toISOString()} callbackTransactionValidationComplete(${request_key},${validation_results})` + ); + //this.transaction_validation_complete = true; + //this.transaction_validation_result = validation_results; + }; + + #onSafMessageReceived = () => { + console.log(`${new Date().toISOString()} callbackSafMessageReceived()`); + this.saf_messages += 1; + }; + + #onRecoveryProgress = (a, b, c) => { + console.log( + `${new Date().toISOString()} recoveryProgressCallback(${a},${b},${c})` + ); + if (a === 4) { + console.log(`Recovery completed, funds recovered: ${c} uT`); + } + }; + + #callback_received_transaction = + InterfaceFFI.createCallbackReceivedTransaction(this.#onReceivedTransaction); + #callback_received_transaction_reply = + InterfaceFFI.createCallbackReceivedTransactionReply( + this.#onReceivedTransactionReply + ); + #callback_received_finalized_transaction = + InterfaceFFI.createCallbackReceivedFinalizedTransaction( + this.#onReceivedFinalizedTransaction + ); + #callback_transaction_broadcast = + InterfaceFFI.createCallbackTransactionBroadcast( + this.#onTransactionBroadcast + ); + #callback_transaction_mined = InterfaceFFI.createCallbackTransactionMined( + this.#onTransactionMined + ); + #callback_transaction_mined_unconfirmed = + InterfaceFFI.createCallbackTransactionMinedUnconfirmed( + this.#onTransactionMinedUnconfirmed + ); + #callback_direct_send_result = InterfaceFFI.createCallbackDirectSendResult( + this.#onDirectSendResult + ); + #callback_store_and_forward_send_result = + InterfaceFFI.createCallbackStoreAndForwardSendResult( + this.#onStoreAndForwardSendResult + ); + #callback_transaction_cancellation = + InterfaceFFI.createCallbackTransactionCancellation( + this.#onTransactionCancellation + ); + #callback_utxo_validation_complete = + InterfaceFFI.createCallbackUtxoValidationComplete( + this.#onUtxoValidationComplete + ); + #callback_stxo_validation_complete = + InterfaceFFI.createCallbackStxoValidationComplete( + this.#onStxoValidationComplete + ); + #callback_invalid_txo_validation_complete = + InterfaceFFI.createCallbackInvalidTxoValidationComplete( + this.#onInvalidTxoValidationComplete + ); + #callback_transaction_validation_complete = + InterfaceFFI.createCallbackTransactionValidationComplete( + this.#onTransactionValidationComplete + ); + #callback_saf_message_received = + InterfaceFFI.createCallbackSafMessageReceived(this.#onSafMessageReceived); + #recoveryProgressCallback = InterfaceFFI.createRecoveryProgressCallback( + this.#onRecoveryProgress + ); + //endregion + + startRecovery(base_node_pubkey) { + let node_pubkey = PublicKey.fromHexString(utf8.encode(base_node_pubkey)); + InterfaceFFI.walletStartRecovery( + this.#wallet_ptr, + node_pubkey.getPtr(), + this.#recoveryProgressCallback + ); + node_pubkey.destroy(); + } + + recoveryInProgress() { + return InterfaceFFI.walletIsRecoveryInProgress(this.#wallet_ptr); + } + + getPublicKey() { + let ptr = InterfaceFFI.walletGetPublicKey(this.#wallet_ptr); + let pk = new PublicKey(); + pk.pointerAssign(ptr); + let result = pk.getHex(); + pk.destroy(); + return result; + } + + getEmojiId() { + let ptr = InterfaceFFI.walletGetPublicKey(this.#wallet_ptr); + let pk = new PublicKey(); + pk.pointerAssign(ptr); + let result = pk.getEmojiId(); + pk.destroy(); + return result; + } + + getBalance() { + let available = InterfaceFFI.walletGetAvailableBalance(this.#wallet_ptr); + let pendingIncoming = InterfaceFFI.walletGetPendingIncomingBalance( + this.#wallet_ptr + ); + let pendingOutgoing = InterfaceFFI.walletGetPendingOutgoingBalance( + this.#wallet_ptr + ); + return { + pendingIn: pendingIncoming, + pendingOut: pendingOutgoing, + available: available, + }; + } + + addBaseNodePeer(public_key_hex, address) { + let public_key = PublicKey.fromHexString(utf8.encode(public_key_hex)); + let result = InterfaceFFI.walletAddBaseNodePeer( + this.#wallet_ptr, + public_key.getPtr(), + utf8.encode(address) + ); + public_key.destroy(); + return result; + } + + sendTransaction(destination, amount, fee_per_gram, message) { + let dest_public_key = PublicKey.fromHexString(utf8.encode(destination)); + let result = InterfaceFFI.walletSendTransaction( + this.#wallet_ptr, + dest_public_key.getPtr(), + amount, + fee_per_gram, + utf8.encode(message) + ); + dest_public_key.destroy(); + return result; + } + + applyEncryption(passphrase) { + InterfaceFFI.walletApplyEncryption( + this.#wallet_ptr, + utf8.encode(passphrase) + ); + } + + getCompletedTransactions() { + let list_ptr = InterfaceFFI.walletGetCompletedTransactions( + this.#wallet_ptr + ); + return new CompletedTransactions(list_ptr); + } + + getInboundTransactions() { + let list_ptr = InterfaceFFI.walletGetPendingInboundTransactions( + this.#wallet_ptr + ); + return new PendingInboundTransactions(list_ptr); + } + + getOutboundTransactions() { + let list_ptr = InterfaceFFI.walletGetPendingOutboundTransactions( + this.#wallet_ptr + ); + return new PendingOutboundTransactions(list_ptr); + } + + getContacts() { + let list_ptr = InterfaceFFI.walletGetContacts(this.#wallet_ptr); + return new Contacts(list_ptr); + } + + addContact(alias, pubkey_hex) { + let public_key = PublicKey.fromHexString(utf8.encode(pubkey_hex)); + let contact = new Contact(); + contact.pointerAssign( + InterfaceFFI.contactCreate(utf8.encode(alias), public_key.getPtr()) + ); + let result = InterfaceFFI.walletUpsertContact( + this.#wallet_ptr, + contact.getPtr() + ); + contact.destroy(); + public_key.destroy(); + return result; + } + + removeContact(contact) { + let result = InterfaceFFI.walletRemoveContact( + this.#wallet_ptr, + contact.getPtr() + ); + contact.destroy(); + return result; + } + + cancelPendingTransaction(tx_id) { + return InterfaceFFI.walletCancelPendingTransaction(this.#wallet_ptr, tx_id); + } + + startUtxoValidation() { + return InterfaceFFI.walletStartUtxoValidation(this.#wallet_ptr); + } + + startStxoValidation() { + return InterfaceFFI.walletStartStxoValidation(this.#wallet_ptr); + } + + destroy() { + if (this.#wallet_ptr) { + InterfaceFFI.walletDestroy(this.#wallet_ptr); + this.#wallet_ptr = undefined; //prevent double free segfault + } + } +} + +module.exports = Wallet; diff --git a/integration_tests/helpers/walletFFIClient.js b/integration_tests/helpers/walletFFIClient.js index 60596c8e34..8835f03dee 100644 --- a/integration_tests/helpers/walletFFIClient.js +++ b/integration_tests/helpers/walletFFIClient.js @@ -1,440 +1,187 @@ -const WalletFFI = require("./ffi/walletFFI"); +const SeedWords = require("./ffi/seedWords"); +const TransportType = require("./ffi/transportType"); +const CommsConfig = require("./ffi/commsConfig"); +const Wallet = require("./ffi/wallet"); const { getFreePort } = require("./util"); const dateFormat = require("dateformat"); -const { expect } = require("chai"); -const PublicKey = require("./ffi/publicKey"); -const CompletedTransactions = require("./ffi/completedTransactions"); -const PendingOutboundTransactions = require("./ffi/pendingOutboundTransactions"); -const Contact = require("./ffi/contact"); -const Contacts = require("./ffi/contacts"); -const SeedWords = require("./ffi/seedWords"); +const InterfaceFFI = require("./ffi/ffiInterface"); class WalletFFIClient { #name; #wallet; #comms_config; + #transport; + #seed_words; + #pass_phrase; #port; - #callback_received_transaction; - #callback_received_transaction_reply; - #callback_received_finalized_transaction; - #callback_transaction_broadcast; - #callback_transaction_mined; - #callback_transaction_mined_unconfirmed; - #callback_direct_send_result; - #callback_store_and_forward_send_result; - #callback_transaction_cancellation; - #callback_utxo_validation_complete; - #callback_stxo_validation_complete; - #callback_invalid_txo_validation_complete; - #callback_transaction_validation_complete; - #callback_saf_message_received; - #recovery_progress_callback; - - #callbackReceivedTransaction = (..._args) => { - console.log(`${new Date().toISOString()} callbackReceivedTransaction`); - this.receivedTransaction += 1; - }; - #callbackReceivedTransactionReply = (..._args) => { - console.log(`${new Date().toISOString()} callbackReceivedTransactionReply`); - this.receivedTransactionReply += 1; - }; - #callbackReceivedFinalizedTransaction = (..._args) => { - console.log( - `${new Date().toISOString()} callbackReceivedFinalizedTransaction` - ); - }; - #callbackTransactionBroadcast = (..._args) => { - console.log(`${new Date().toISOString()} callbackTransactionBroadcast`); - this.transactionBroadcast += 1; - }; - #callbackTransactionMined = (..._args) => { - console.log(`${new Date().toISOString()} callbackTransactionMined`); - this.transactionMined += 1; - }; - #callbackTransactionMinedUnconfirmed = (..._args) => { - console.log( - `${new Date().toISOString()} callbackTransactionMinedUnconfirmed` - ); - }; - #callbackDirectSendResult = (..._args) => { - console.log(`${new Date().toISOString()} callbackDirectSendResult`); - }; - #callbackStoreAndForwardSendResult = (..._args) => { - console.log( - `${new Date().toISOString()} callbackStoreAndForwardSendResult` - ); - }; - #callbackTransactionCancellation = (..._args) => { - console.log(`${new Date().toISOString()} callbackTransactionCancellation`); - }; - #callbackUtxoValidationComplete = (_request_key, validation_results) => { - console.log(`${new Date().toISOString()} callbackUtxoValidationComplete`); - this.utxo_validation_complete = true; - this.utxo_validation_result = validation_results; - }; - #callbackStxoValidationComplete = (_request_key, validation_results) => { - console.log(`${new Date().toISOString()} callbackStxoValidationComplete`); - this.stxo_validation_complete = true; - this.stxo_validation_result = validation_results; - }; - #callbackInvalidTxoValidationComplete = (..._args) => { - console.log( - `${new Date().toISOString()} callbackInvalidTxoValidationComplete` - ); - }; - #callbackTransactionValidationComplete = (..._args) => { - console.log( - `${new Date().toISOString()} callbackTransactionValidationComplete` - ); - }; - #callbackSafMessageReceived = (..._args) => { - console.log(`${new Date().toISOString()} callbackSafMessageReceived`); - }; - #recoveryProgressCallback = (a, b, c) => { - console.log(`${new Date().toISOString()} recoveryProgressCallback`); - if (a == 3) - // Progress - this.recoveryProgress = [b, c]; - if (a == 4) - // Completed - this.recoveryInProgress = false; - }; - - clearCallbackCounters() { - this.receivedTransaction = - this.receivedTransactionReply = - this.transactionBroadcast = - this.transactionMined = - 0; - } + baseDir = ""; constructor(name) { - this.#wallet = null; this.#name = name; - this.baseDir = ""; - this.clearCallbackCounters(); - - // Create the ffi callbacks - this.#callback_received_transaction = - WalletFFI.createCallbackReceivedTransaction( - this.#callbackReceivedTransaction - ); - this.#callback_received_transaction_reply = - WalletFFI.createCallbackReceivedTransactionReply( - this.#callbackReceivedTransactionReply - ); - this.#callback_received_finalized_transaction = - WalletFFI.createCallbackReceivedFinalizedTransaction( - this.#callbackReceivedFinalizedTransaction - ); - this.#callback_transaction_broadcast = - WalletFFI.createCallbackTransactionBroadcast( - this.#callbackTransactionBroadcast - ); - this.#callback_transaction_mined = WalletFFI.createCallbackTransactionMined( - this.#callbackTransactionMined - ); - this.#callback_transaction_mined_unconfirmed = - WalletFFI.createCallbackTransactionMinedUnconfirmed( - this.#callbackTransactionMinedUnconfirmed - ); - this.#callback_direct_send_result = - WalletFFI.createCallbackDirectSendResult(this.#callbackDirectSendResult); - this.#callback_store_and_forward_send_result = - WalletFFI.createCallbackStoreAndForwardSendResult( - this.#callbackStoreAndForwardSendResult - ); - this.#callback_transaction_cancellation = - WalletFFI.createCallbackTransactionCancellation( - this.#callbackTransactionCancellation - ); - this.#callback_utxo_validation_complete = - WalletFFI.createCallbackUtxoValidationComplete( - this.#callbackUtxoValidationComplete - ); - this.#callback_stxo_validation_complete = - WalletFFI.createCallbackStxoValidationComplete( - this.#callbackStxoValidationComplete - ); - this.#callback_invalid_txo_validation_complete = - WalletFFI.createCallbackInvalidTxoValidationComplete( - this.#callbackInvalidTxoValidationComplete - ); - this.#callback_transaction_validation_complete = - WalletFFI.createCallbackTransactionValidationComplete( - this.#callbackTransactionValidationComplete - ); - this.#callback_saf_message_received = - WalletFFI.createCallbackSafMessageReceived( - this.#callbackSafMessageReceived - ); - this.#recovery_progress_callback = WalletFFI.createRecoveryProgressCallback( - this.#recoveryProgressCallback - ); } static async Init() { - await WalletFFI.Init(); + await InterfaceFFI.Init(); } - async startNew(seed_words_text) { + async startNew(seed_words_text, pass_phrase) { this.#port = await getFreePort(19000, 25000); const name = `WalletFFI${this.#port}-${this.#name}`; this.baseDir = `./temp/base_nodes/${dateFormat( new Date(), "yyyymmddHHMM" )}/${name}`; - const tcp = await WalletFFI.transportTcpCreate( - `/ip4/0.0.0.0/tcp/${this.#port}` - ); - this.#comms_config = await WalletFFI.commsConfigCreate( + this.#transport = TransportType.createTCP(`/ip4/0.0.0.0/tcp/${this.#port}`); + this.#comms_config = new CommsConfig( `/ip4/0.0.0.0/tcp/${this.#port}`, - tcp, + this.#transport.getPtr(), "wallet.dat", this.baseDir, 30, 600, "localnet" ); - await this.start(seed_words_text); + this.#start(seed_words_text, pass_phrase); } - async start(seed_words_text) { - let seed_words; - let seed_words_ptr = WalletFFI.NULL; - if (seed_words_text) { - seed_words = await SeedWords.fromString(seed_words_text); - seed_words_ptr = seed_words.getPtr(); - } - this.#wallet = await WalletFFI.walletCreate( - this.#comms_config, - `${this.baseDir}/log/wallet.log`, - 50, - 102400, - WalletFFI.NULL, - seed_words_ptr, - this.#callback_received_transaction, - this.#callback_received_transaction_reply, - this.#callback_received_finalized_transaction, - this.#callback_transaction_broadcast, - this.#callback_transaction_mined, - this.#callback_transaction_mined_unconfirmed, - this.#callback_direct_send_result, - this.#callback_store_and_forward_send_result, - this.#callback_transaction_cancellation, - this.#callback_utxo_validation_complete, - this.#callback_stxo_validation_complete, - this.#callback_invalid_txo_validation_complete, - this.#callback_transaction_validation_complete, - this.#callback_saf_message_received + async restart(seed_words_text, pass_phrase) { + this.#transport = TransportType.createTCP(`/ip4/0.0.0.0/tcp/${this.#port}`); + this.#comms_config = new CommsConfig( + `/ip4/0.0.0.0/tcp/${this.#port}`, + this.#transport.getPtr(), + "wallet.dat", + this.baseDir, + 30, + 600, + "localnet" ); - if (seed_words) await seed_words.destroy(); + this.#start(seed_words_text, pass_phrase); } - async startRecovery(base_node_pubkey) { - const node_pubkey = await PublicKey.fromString(base_node_pubkey); - expect( - await WalletFFI.walletStartRecovery( - this.#wallet, - node_pubkey.getPtr(), - this.#recovery_progress_callback - ) - ).to.be.true; - node_pubkey.destroy(); - this.recoveryInProgress = true; + getStxoValidationStatus() { + return this.#wallet.getStxoValidationStatus(); } - recoveryInProgress() { - return this.recoveryInProgress; + getUtxoValidationStatus() { + return this.#wallet.getUtxoValidationStatus(); + } + identify() { + return this.#wallet.getPublicKey(); } - async stop() { - await WalletFFI.walletDestroy(this.#wallet); + identifyEmoji() { + return this.#wallet.getEmojiId(); } - async getPublicKey() { - const public_key = await PublicKey.fromWallet(this.#wallet); - const public_key_hex = public_key.getHex(); - public_key.destroy(); - return public_key_hex; + getBalance() { + return this.#wallet.getBalance(); } - async getEmojiId() { - const public_key = await PublicKey.fromWallet(this.#wallet); - const emoji_id = await public_key.getEmojiId(); - public_key.destroy(); - return emoji_id; + addBaseNodePeer(public_key_hex, address) { + return this.#wallet.addBaseNodePeer(public_key_hex, address); } - async getBalance() { - return await WalletFFI.walletGetAvailableBalance(this.#wallet); + addContact(alias, pubkey_hex) { + return this.#wallet.addContact(alias, pubkey_hex); } - async addBaseNodePeer(public_key_hex, address) { - const public_key = await PublicKey.fromString(public_key_hex); - expect( - await WalletFFI.walletAddBaseNodePeer( - this.#wallet, - public_key.getPtr(), - address - ) - ).to.be.true; - await public_key.destroy(); + getContactList() { + return this.#wallet.getContacts(); } - async sendTransaction(destination, amount, fee_per_gram, message) { - const dest_public_key = await PublicKey.fromString(destination); - const result = await WalletFFI.walletSendTransaction( - this.#wallet, - dest_public_key.getPtr(), - amount, - fee_per_gram, - message - ); - await dest_public_key.destroy(); - return result; + getCompletedTxs() { + return this.#wallet.getCompletedTransactions(); } - async applyEncryption(passphrase) { - await WalletFFI.walletApplyEncryption(this.#wallet, passphrase); + getInboundTxs() { + return this.#wallet.getInboundTransactions(); } - async getCompletedTransactions() { - const txs = await CompletedTransactions.fromWallet(this.#wallet); - const length = await txs.getLength(); - let outbound = 0; - let inbound = 0; - for (let i = 0; i < length; ++i) { - const tx = await txs.getAt(i); - if (await tx.isOutbound()) { - ++outbound; - } else { - ++inbound; - } - tx.destroy(); - } - txs.destroy(); - return [outbound, inbound]; + getOutboundTxs() { + return this.#wallet.getOutboundTransactions(); } - async getBroadcastTransactionsCount() { - let broadcast_tx_cnt = 0; - const txs = await PendingOutboundTransactions.fromWallet(this.#wallet); - const length = await txs.getLength(); - for (let i = 0; i < length; ++i) { - const tx = await txs.getAt(i); - const status = await tx.getStatus(); - tx.destroy(); - if (status === 1) { - // Broadcast - broadcast_tx_cnt += 1; - } - } - await txs.destroy(); - return broadcast_tx_cnt; + removeContact(contact) { + return this.#wallet.removeContact(contact); } - async getOutboundTransactionsCount() { - let outbound_tx_cnt = 0; - const txs = await PendingOutboundTransactions.fromWallet(this.#wallet); - const length = await txs.getLength(); - for (let i = 0; i < length; ++i) { - const tx = await txs.getAt(i); - const status = await tx.getStatus(); - if (status === 4) { - // Pending - outbound_tx_cnt += 1; - } - tx.destroy(); - } - await txs.destroy(); - return outbound_tx_cnt; + startRecovery(base_node_pubkey) { + this.#wallet.startRecovery(base_node_pubkey); } - async addContact(alias, pubkey_hex) { - const public_key = await PublicKey.fromString(pubkey_hex); - const contact = new Contact( - await WalletFFI.contactCreate(alias, public_key.getPtr()) - ); - public_key.destroy(); - expect(await WalletFFI.walletUpsertContact(this.#wallet, contact.getPtr())) - .to.be.true; - contact.destroy(); + checkRecoveryInProgress() { + return this.#wallet.recoveryInProgress(); } - async #findContact(lookup_alias) { - const contacts = await Contacts.fromWallet(this.#wallet); - const length = await contacts.getLength(); - let contact; - for (let i = 0; i < length; ++i) { - contact = await contacts.getAt(i); - const alias = await contact.getAlias(); - const found = alias === lookup_alias; - if (found) { - break; - } - contact.destroy(); - contact = undefined; - } - contacts.destroy(); - return contact; + applyEncryption(passphrase) { + this.#wallet.applyEncryption(passphrase); } - async getContact(alias) { - const contact = await this.#findContact(alias); - if (contact) { - const pubkey = await contact.getPubkey(); - const pubkey_hex = pubkey.getHex(); - pubkey.destroy(); - contact.destroy(); - return pubkey_hex; - } + startStxoValidation() { + this.#wallet.startStxoValidation(); } - async removeContact(alias) { - const contact = await this.#findContact(alias); - if (contact) { - expect( - await WalletFFI.walletRemoveContact(this.#wallet, contact.getPtr()) - ).to.be.true; - contact.destroy(); - } + startUtxoValidation() { + this.#wallet.startUtxoValidation(); + } + + getCounters() { + return this.#wallet.getCounters(); + } + resetCounters() { + this.#wallet.clearCallbackCounters(); } - async identify() { - return { - public_key: await this.getPublicKey(), - }; + sendTransaction(destination, amount, fee_per_gram, message) { + return this.#wallet.sendTransaction( + destination, + amount, + fee_per_gram, + message + ); } - async cancelAllOutboundTransactions() { - const txs = await PendingOutboundTransactions.fromWallet(this.#wallet); - const length = await txs.getLength(); - let cancelled = 0; - for (let i = 0; i < length; ++i) { - const tx = await txs.getAt(i); - if ( - await WalletFFI.walletCancelPendingTransaction( - this.#wallet, - await tx.getTransactionId() - ) - ) { - ++cancelled; - } - tx.destroy(); + #start( + seed_words_text, + pass_phrase, + rolling_log_files = 50, + byte_size_per_log = 102400 + ) { + this.#pass_phrase = pass_phrase; + if (seed_words_text) { + let seed_words = SeedWords.fromText(seed_words_text); + this.#seed_words = seed_words; } - txs.destroy(); - return cancelled; + + let log_path = `${this.baseDir}/log/wallet.log`; + this.#wallet = new Wallet( + this.#comms_config.getPtr(), + log_path, + this.#pass_phrase, + this.#seed_words ? this.#seed_words.getPtr() : null, + rolling_log_files, + byte_size_per_log + ); } - startUtxoValidation() { - this.utxo_validation_complete = false; - return WalletFFI.walletStartUtxoValidation(this.#wallet); + getOutboundTransactions() { + return this.#wallet.getOutboundTransactions(); } - startStxoValidation() { - this.stxo_validation_complete = false; - return WalletFFI.walletStartStxoValidation(this.#wallet); + cancelPendingTransaction(tx_id) { + return this.#wallet.cancelPendingTransaction(tx_id); + } + + stop() { + if (this.#wallet) { + this.#wallet.destroy(); + } + if (this.#comms_config) { + this.#comms_config.destroy(); + } + if (this.#seed_words) { + this.#seed_words.destroy(); + } } }