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(); + } } }