diff --git a/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs b/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs index 0e47058b03..7bb5908911 100644 --- a/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs +++ b/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs @@ -744,29 +744,63 @@ impl LMDBDatabase { "block_accumulated_data_db", )?; - let output_rows = - lmdb_delete_keys_starting_with::(&write_txn, &self.utxos_db, &hash_hex)?; + self.delete_block_inputs_outputs(write_txn, &hash_hex)?; + self.delete_block_kernels(write_txn, &hash_hex)?; + Ok(()) + } + + fn delete_block_inputs_outputs(&self, txn: &WriteTransaction<'_>, hash: &str) -> Result<(), ChainStorageError> { + let output_rows = lmdb_delete_keys_starting_with::(txn, &self.utxos_db, hash)?; debug!(target: LOG_TARGET, "Deleted {} outputs...", output_rows.len()); + let inputs = lmdb_delete_keys_starting_with::(txn, &self.inputs_db, hash)?; + debug!(target: LOG_TARGET, "Deleted {} input(s)...", inputs.len()); + for utxo in &output_rows { trace!(target: LOG_TARGET, "Deleting UTXO `{}`", to_hex(&utxo.hash)); lmdb_delete( - &write_txn, + txn, &self.txos_hash_to_index_db, utxo.hash.as_slice(), "txos_hash_to_index_db", )?; if let Some(ref output) = utxo.output { + let output_hash = output.hash(); + // if an output was already spent in the block, it was never created as unspent, so dont delete it as it + // does not exist here + if inputs.iter().any(|r| r.input.output_hash() == output_hash) { + continue; + } lmdb_delete( - &write_txn, + txn, &*self.utxo_commitment_index, output.commitment.as_bytes(), "utxo_commitment_index", )?; } } - let kernels = - lmdb_delete_keys_starting_with::(&write_txn, &self.kernels_db, &hash_hex)?; + // Move inputs in this block back into the unspent set, any outputs spent within this block they will be removed + // by deleting all the block's outputs below + for row in inputs { + // If input spends an output in this block, don't add it to the utxo set + let output_hash = row.input.output_hash(); + if output_rows.iter().any(|r| r.hash == output_hash) { + continue; + } + trace!(target: LOG_TARGET, "Input moved to UTXO set: {}", row.input); + lmdb_insert( + txn, + &*self.utxo_commitment_index, + row.input.commitment.as_bytes(), + &row.input.output_hash(), + "utxo_commitment_index", + )?; + } + Ok(()) + } + + fn delete_block_kernels(&self, txn: &WriteTransaction<'_>, hash: &str) -> Result<(), ChainStorageError> { + let kernels = lmdb_delete_keys_starting_with::(txn, &self.kernels_db, hash)?; debug!(target: LOG_TARGET, "Deleted {} kernels...", kernels.len()); for kernel in kernels { trace!( @@ -775,7 +809,7 @@ impl LMDBDatabase { kernel.kernel.excess.to_hex() ); lmdb_delete( - &write_txn, + txn, &self.kernel_excess_index, kernel.kernel.excess.as_bytes(), "kernel_excess_index", @@ -789,33 +823,12 @@ impl LMDBDatabase { to_hex(&excess_sig_key) ); lmdb_delete( - &write_txn, + txn, &self.kernel_excess_sig_index, excess_sig_key.as_slice(), "kernel_excess_sig_index", )?; } - - let inputs = lmdb_delete_keys_starting_with::(&write_txn, &self.inputs_db, &hash_hex)?; - debug!(target: LOG_TARGET, "Deleted {} input(s)...", inputs.len()); - // Move inputs in this block back into the unspent set, any outputs spent within this block they will be removed - // by deleting all the block's outputs below - for row in inputs { - // If input spends an output in this block, don't add it to the utxo set - let output_hash = row.input.output_hash(); - if output_rows.iter().any(|r| r.hash == output_hash) { - continue; - } - trace!(target: LOG_TARGET, "Input moved to UTXO set: {}", row.input); - lmdb_insert( - &write_txn, - &*self.utxo_commitment_index, - row.input.commitment.as_bytes(), - &row.input.output_hash(), - "utxo_commitment_index", - )?; - } - Ok(()) } diff --git a/integration_tests/features/Mempool.feature b/integration_tests/features/Mempool.feature index 94b31f5ee6..36113cd9b9 100644 --- a/integration_tests/features/Mempool.feature +++ b/integration_tests/features/Mempool.feature @@ -186,4 +186,16 @@ Feature: Mempool When I submit transaction TX3 to BN1 When I submit transaction TX4 to BN1 When I submit transaction TX5 to BN1 - Then I wait until base node BN1 has 5 unconfirmed transactions in its mempool \ No newline at end of file + Then I wait until base node BN1 has 5 unconfirmed transactions in its mempool + + @critical + Scenario: Mempool unconfirmed transaction to mined transaction + Given I have 1 seed nodes + And I have a base node BN1 connected to all seed nodes + When I mine a block on BN1 with coinbase CB1 + When I mine 2 blocks on BN1 + When I create a custom fee transaction TX1 spending CB1 to UTX1 with fee 80 + When I submit transaction TX1 to BN1 + Then I wait until base node BN1 has 1 unconfirmed transactions in its mempool + When I mine 1 blocks on BN1 + Then I wait until base node BN1 has 0 unconfirmed transactions in its mempool \ No newline at end of file diff --git a/integration_tests/features/Reorgs.feature b/integration_tests/features/Reorgs.feature index 218201b2a0..008df39e8e 100644 --- a/integration_tests/features/Reorgs.feature +++ b/integration_tests/features/Reorgs.feature @@ -94,6 +94,49 @@ Feature: Reorgs When I submit transaction TX2 to PNODE1 Then PNODE1 has TX2 in MEMPOOL state + @critical @reorg + Scenario: Zero-conf reorg with spending + Given I have a base node NODE1 connected to all seed nodes + Given I have a base node NODE2 connected to node NODE1 + When I mine 14 blocks on NODE1 + When I mine a block on NODE1 with coinbase CB1 + When I mine 4 blocks on NODE1 + When I create a custom fee transaction TX1 spending CB1 to UTX1 with fee 100 + When I create a custom fee transaction TX11 spending UTX1 to UTX11 with fee 100 + When I submit transaction TX1 to NODE1 + When I submit transaction TX11 to NODE1 + When I mine 1 blocks on NODE1 + Then NODE1 has TX1 in MINED state + And NODE1 has TX11 in MINED state + And all nodes are at height 20 + And I stop node NODE1 + And node NODE2 is at height 20 + When I mine a block on NODE2 with coinbase CB2 + When I mine 3 blocks on NODE2 + When I create a custom fee transaction TX2 spending CB2 to UTX2 with fee 100 + When I create a custom fee transaction TX21 spending UTX2 to UTX21 with fee 100 + When I submit transaction TX2 to NODE2 + When I submit transaction TX21 to NODE2 + When I mine 1 blocks on NODE2 + Then node NODE2 is at height 25 + And NODE2 has TX2 in MINED state + And NODE2 has TX21 in MINED state + And I stop node NODE2 + When I start base node NODE1 + And node NODE1 is at height 20 + When I mine a block on NODE1 with coinbase CB3 + When I mine 3 blocks on NODE1 + When I create a custom fee transaction TX3 spending CB3 to UTX3 with fee 100 + When I create a custom fee transaction TX31 spending UTX3 to UTX31 with fee 100 + When I submit transaction TX3 to NODE1 + When I submit transaction TX31 to NODE1 + When I mine 1 blocks on NODE1 + Then NODE1 has TX3 in MINED state + And NODE1 has TX31 in MINED state + And node NODE1 is at height 25 + When I start base node NODE2 + Then all nodes are on the same chain tip + Scenario Outline: Massive multiple reorg # # Chain 1a: diff --git a/integration_tests/features/WalletFFI.feature b/integration_tests/features/WalletFFI.feature index f7edc13861..11b905051e 100644 --- a/integration_tests/features/WalletFFI.feature +++ b/integration_tests/features/WalletFFI.feature @@ -10,7 +10,22 @@ Feature: Wallet FFI 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" + @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 + 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 + And I wait 5 seconds + And I send 2000000 uT from wallet SENDER to wallet FFI_WALLET at fee 100 + 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 @long-running Scenario: As a client I want to retrieve a list of transactions I have made and received @@ -24,12 +39,16 @@ Feature: Wallet FFI 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 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 @@ -47,6 +66,15 @@ Feature: Wallet FFI Then I don't have contact with alias ALIAS in 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 @@ -57,16 +85,45 @@ Feature: Wallet FFI 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 + 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 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 + @long-running Scenario: As a client I want to be able to restore my wallet from seed words + 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 - 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 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 async 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 my connection status to the specifed Base Node Scenario: As a client I want async 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" diff --git a/integration_tests/features/support/steps.js b/integration_tests/features/support/steps.js index ca54c5d4ff..2d13f664b6 100644 --- a/integration_tests/features/support/steps.js +++ b/integration_tests/features/support/steps.js @@ -3365,17 +3365,19 @@ Then( "I wait for ffi wallet {word} to have at least {int} uT", { timeout: 60 * 1000 }, async function (name, amount) { - let success = false; + let wallet = this.getWallet(name); let retries = 1; + let balance = 0; const retries_limit = 12; - while (!success && retries <= retries_limit) { - if ((await this.getWallet(name).getBalance()) >= amount) { - success = true; + while (retries <= retries_limit) { + balance = await wallet.getBalance(); + if (balance >= amount) { + break; } await sleep(5000); ++retries; } - expect(success).to.be.true; + expect(balance, "Balance is not enough").to.be.greaterThanOrEqual(amount); } ); @@ -3501,3 +3503,109 @@ Then( } } ); + +When( + "I set base node {word} for ffi wallet {word}", + async function (node, wallet_name) { + let wallet = this.getWallet(wallet_name); + let peer = this.nodes[node].peerAddress().split("::"); + await wallet.addBaseNodePeer(peer[0], peer[1]); + } +); + +Then( + "I wait for ffi wallet {word} to have {int} pending outbound transaction(s)", + { timeout: 120 * 1000 }, + async function (wallet_name, count) { + let wallet = this.getWallet(wallet_name); + let broadcast = await wallet.getOutboundTransactionsCount(); + let retries = 1; + const retries_limit = 24; + while (broadcast != count && retries <= retries_limit) { + await sleep(5000); + broadcast = await wallet.getOutboundTransactionsCount(); + ++retries; + } + expect(broadcast, "Number of pending messages mismatch").to.be.equal(count); + } +); + +Then( + "I cancel all 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); + } +); + +Then( + "I recover wallet {word} into ffi wallet {word} from seed words on node {word}", + { timeout: 20 * 1000 }, + async function (wallet_name, ffi_wallet_name, node) { + let wallet = this.getWallet(wallet_name); + const seed_words_text = wallet.getSeedWords(); + await wallet.stop(); + await sleep(1000); + let ffi_wallet = await this.createAndAddFFIWallet( + ffi_wallet_name, + seed_words_text + ); + let peer = this.nodes[node].peerAddress().split("::"); + await ffi_wallet.addBaseNodePeer(peer[0], peer[1]); + await ffi_wallet.startRecovery(peer[0]); + } +); + +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]); + } +); + +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); + } + 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); + } + expect(wallet.utxo_validation_result).to.be.equal(0); +}); + +Then( + "Check callbacks for finished inbound tx on ffi wallet {word}", + async 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(); + } +); + +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(); + } +); diff --git a/integration_tests/features/support/world.js b/integration_tests/features/support/world.js index fd9a37637b..1bf8283768 100644 --- a/integration_tests/features/support/world.js +++ b/integration_tests/features/support/world.js @@ -106,9 +106,9 @@ class CustomWorld { this.walletPubkeys[name] = walletInfo.public_key; } - async createAndAddFFIWallet(name) { + async createAndAddFFIWallet(name, seed_words) { const wallet = new WalletFFIClient(name); - await wallet.startNew(); + await wallet.startNew(seed_words); this.walletsFFI[name] = wallet; this.walletPubkeys[name] = await wallet.getPublicKey(); return wallet; @@ -230,6 +230,10 @@ class CustomWorld { client.isWallet = true; return client; } + let ffi_wallet = this.walletsFFI[name.trim()]; + if (ffi_wallet) { + return ffi_wallet; + } return null; } diff --git a/integration_tests/helpers/ffi/byteVector.js b/integration_tests/helpers/ffi/byteVector.js new file mode 100644 index 0000000000..51f5d338bd --- /dev/null +++ b/integration_tests/helpers/ffi/byteVector.js @@ -0,0 +1,29 @@ +const WalletFFI = require("./walletFFI"); + +class ByteVector { + #byte_vector_ptr; + + constructor(byte_vector_ptr) { + this.#byte_vector_ptr = byte_vector_ptr; + } + + static async fromBuffer(buffer) { + let buf = Buffer.from(buffer, "utf-8"); // get the bytes + let len = buf.length; // get the length + return new ByteVector(await WalletFFI.byteVectorCreate(buf, len)); + } + + getLength() { + return WalletFFI.byteVectorGetLength(this.#byte_vector_ptr); + } + + getAt(position) { + return WalletFFI.byteVectorGetAt(this.#byte_vector_ptr, position); + } + + destroy() { + return WalletFFI.byteVectorDestroy(this.#byte_vector_ptr); + } +} + +module.exports = ByteVector; diff --git a/integration_tests/helpers/ffi/completedTransaction.js b/integration_tests/helpers/ffi/completedTransaction.js new file mode 100644 index 0000000000..a7a21c28cd --- /dev/null +++ b/integration_tests/helpers/ffi/completedTransaction.js @@ -0,0 +1,23 @@ +const WalletFFI = require("./walletFFI"); + +class CompletedTransaction { + #tari_completed_transaction_ptr; + + constructor(tari_completed_transaction_ptr) { + this.#tari_completed_transaction_ptr = tari_completed_transaction_ptr; + } + + isOutbound() { + return WalletFFI.completedTransactionIsOutbound( + this.#tari_completed_transaction_ptr + ); + } + + destroy() { + return WalletFFI.completedTransactionDestroy( + this.#tari_completed_transaction_ptr + ); + } +} + +module.exports = CompletedTransaction; diff --git a/integration_tests/helpers/ffi/completedTransactions.js b/integration_tests/helpers/ffi/completedTransactions.js new file mode 100644 index 0000000000..d2d4c96156 --- /dev/null +++ b/integration_tests/helpers/ffi/completedTransactions.js @@ -0,0 +1,39 @@ +const CompletedTransaction = require("./completedTransaction"); +const WalletFFI = require("./walletFFI"); + +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) + ); + } + + getLength() { + return WalletFFI.completedTransactionsGetLength( + this.#tari_completed_transactions_ptr + ); + } + + async getAt(position) { + return new CompletedTransaction( + await WalletFFI.completedTransactionsGetAt( + this.#tari_completed_transactions_ptr, + position + ) + ); + } + + destroy() { + return WalletFFI.completedTransactionsDestroy( + this.#tari_completed_transactions_ptr + ); + } +} + +module.exports = CompletedTransactions; diff --git a/integration_tests/helpers/ffi/contact.js b/integration_tests/helpers/ffi/contact.js new file mode 100644 index 0000000000..184c684a2b --- /dev/null +++ b/integration_tests/helpers/ffi/contact.js @@ -0,0 +1,33 @@ +const PublicKey = require("./publicKey"); +const WalletFFI = require("./walletFFI"); + +class Contact { + #tari_contact_ptr; + + constructor(tari_contact_ptr) { + this.#tari_contact_ptr = tari_contact_ptr; + } + + getPtr() { + return this.#tari_contact_ptr; + } + + async getAlias() { + const alias = await WalletFFI.contactGetAlias(this.#tari_contact_ptr); + const result = alias.readCString(); + await WalletFFI.stringDestroy(alias); + return result; + } + + async getPubkey() { + return new PublicKey( + await WalletFFI.contactGetPublicKey(this.#tari_contact_ptr) + ); + } + + destroy() { + return WalletFFI.contactDestroy(this.#tari_contact_ptr); + } +} + +module.exports = Contact; diff --git a/integration_tests/helpers/ffi/contacts.js b/integration_tests/helpers/ffi/contacts.js new file mode 100644 index 0000000000..d8803874ab --- /dev/null +++ b/integration_tests/helpers/ffi/contacts.js @@ -0,0 +1,30 @@ +const Contact = require("./contact"); +const WalletFFI = require("./walletFFI"); + +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)); + } + + getLength() { + return WalletFFI.contactsGetLength(this.#tari_contacts_ptr); + } + + async getAt(position) { + return new Contact( + await WalletFFI.contactsGetAt(this.#tari_contacts_ptr, position) + ); + } + + destroy() { + return WalletFFI.contactsDestroy(this.#tari_contacts_ptr); + } +} + +module.exports = Contacts; diff --git a/integration_tests/helpers/ffi/pendingInboundTransaction.js b/integration_tests/helpers/ffi/pendingInboundTransaction.js new file mode 100644 index 0000000000..32cae202fb --- /dev/null +++ b/integration_tests/helpers/ffi/pendingInboundTransaction.js @@ -0,0 +1,24 @@ +const WalletFFI = require("./walletFFI"); + +class PendingInboundTransaction { + #tari_pending_inbound_transaction_ptr; + + constructor(tari_pending_inbound_transaction_ptr) { + this.#tari_pending_inbound_transaction_ptr = + tari_pending_inbound_transaction_ptr; + } + + getStatus() { + return WalletFFI.pendingInboundTransactionGetStatus( + this.#tari_pending_inbound_transaction_ptr + ); + } + + destroy() { + return WalletFFI.pendingInboundTransactionDestroy( + this.#tari_pending_inbound_transaction_ptr + ); + } +} + +module.exports = PendingInboundTransaction; diff --git a/integration_tests/helpers/ffi/pendingInboundTransactions.js b/integration_tests/helpers/ffi/pendingInboundTransactions.js new file mode 100644 index 0000000000..6246b03429 --- /dev/null +++ b/integration_tests/helpers/ffi/pendingInboundTransactions.js @@ -0,0 +1,40 @@ +const PendingInboundTransaction = require("./pendingInboundTransaction"); +const WalletFFI = require("./walletFFI"); + +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) + ); + } + + getLength() { + return WalletFFI.pendingInboundTransactionsGetLength( + this.#tari_pending_inbound_transactions_ptr + ); + } + + async getAt(position) { + return new PendingInboundTransaction( + await WalletFFI.pendingInboundTransactionsGetAt( + this.#tari_pending_inbound_transactions_ptr, + position + ) + ); + } + + destroy() { + return WalletFFI.pendingInboundTransactionsDestroy( + this.#tari_pending_inbound_transactions_ptr + ); + } +} + +module.exports = PendingInboundTransactions; diff --git a/integration_tests/helpers/ffi/pendingOutboundTransaction.js b/integration_tests/helpers/ffi/pendingOutboundTransaction.js new file mode 100644 index 0000000000..eed2d722bb --- /dev/null +++ b/integration_tests/helpers/ffi/pendingOutboundTransaction.js @@ -0,0 +1,30 @@ +const WalletFFI = require("./walletFFI"); + +class PendingOutboundTransaction { + #tari_pending_outbound_transaction_ptr; + + constructor(tari_pending_outbound_transaction_ptr) { + this.#tari_pending_outbound_transaction_ptr = + tari_pending_outbound_transaction_ptr; + } + + getTransactionId() { + return WalletFFI.pendingOutboundTransactionGetTransactionId( + this.#tari_pending_outbound_transaction_ptr + ); + } + + getStatus() { + return WalletFFI.pendingOutboundTransactionGetStatus( + this.#tari_pending_outbound_transaction_ptr + ); + } + + destroy() { + return WalletFFI.pendingOutboundTransactionDestroy( + this.#tari_pending_outbound_transaction_ptr + ); + } +} + +module.exports = PendingOutboundTransaction; diff --git a/integration_tests/helpers/ffi/pendingOutboundTransactions.js b/integration_tests/helpers/ffi/pendingOutboundTransactions.js new file mode 100644 index 0000000000..28e408563d --- /dev/null +++ b/integration_tests/helpers/ffi/pendingOutboundTransactions.js @@ -0,0 +1,40 @@ +const PendingOutboundTransaction = require("./pendingOutboundTransaction"); +const WalletFFI = require("./walletFFI"); + +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) + ); + } + + getLength() { + return WalletFFI.pendingOutboundTransactionsGetLength( + this.#tari_pending_outbound_transactions_ptr + ); + } + + async getAt(position) { + return new PendingOutboundTransaction( + await WalletFFI.pendingOutboundTransactionsGetAt( + this.#tari_pending_outbound_transactions_ptr, + position + ) + ); + } + + destroy() { + return WalletFFI.pendingOutboundTransactionsDestroy( + this.#tari_pending_outbound_transactions_ptr + ); + } +} + +module.exports = PendingOutboundTransactions; diff --git a/integration_tests/helpers/ffi/publicKey.js b/integration_tests/helpers/ffi/publicKey.js new file mode 100644 index 0000000000..1165aa193d --- /dev/null +++ b/integration_tests/helpers/ffi/publicKey.js @@ -0,0 +1,65 @@ +const WalletFFI = require("./walletFFI"); +const ByteVector = require("./byteVector"); +const utf8 = require("utf8"); + +class PublicKey { + #tari_public_key_ptr; + + constructor(public_key) { + this.#tari_public_key_ptr = public_key; + } + + static fromPubkey(public_key) { + return new PublicKey(public_key); + } + + static async fromWallet(wallet) { + return new PublicKey(await WalletFFI.walletGetPublicKey(wallet)); + } + + 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)); + } + + static async fromBytes(bytes) { + return new PublicKey(await WalletFFI.publicKeyCreate(bytes)); + } + + getPtr() { + return this.#tari_public_key_ptr; + } + + async getBytes() { + return new ByteVector( + await WalletFFI.publicKeyGetBytes(this.#tari_public_key_ptr) + ); + } + + async getHex() { + const bytes = await this.getBytes(); + const length = await bytes.getLength(); + let byte_array = new Uint8Array(length); + for (let i = 0; i < length; ++i) { + byte_array[i] = await bytes.getAt(i); + } + await 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 + ); + const result = emoji_id.readCString(); + await WalletFFI.stringDestroy(emoji_id); + return result; + } + + destroy() { + return WalletFFI.publicKeyDestroy(this.#tari_public_key_ptr); + } +} + +module.exports = PublicKey; diff --git a/integration_tests/helpers/ffi/seedWords.js b/integration_tests/helpers/ffi/seedWords.js new file mode 100644 index 0000000000..86c05cab48 --- /dev/null +++ b/integration_tests/helpers/ffi/seedWords.js @@ -0,0 +1,46 @@ +const WalletFFI = require("./walletFFI"); + +class SeedWords { + #tari_seed_words_ptr; + + constructor(tari_seed_words_ptr) { + this.#tari_seed_words_ptr = tari_seed_words_ptr; + } + + static async fromString(seed_words_text) { + const seed_words = await WalletFFI.seedWordsCreate(); + const seed_words_list = seed_words_text.split(" "); + for (const seed_word of seed_words_list) { + await WalletFFI.seedWordsPushWord(seed_words, seed_word); + } + return new SeedWords(seed_words); + } + + static async fromWallet(wallet) { + return new SeedWords(await WalletFFI.walletGetSeedWords(wallet)); + } + + getLength() { + return WalletFFI.seedWordsGetLength(this.#tari_seed_words_ptr); + } + + getPtr() { + return this.#tari_seed_words_ptr; + } + + async getAt(position) { + const seed_word = await WalletFFI.seedWordsGetAt( + this.#tari_seed_words_ptr, + position + ); + const result = seed_word.readCString(); + await WalletFFI.stringDestroy(seed_word); + return result; + } + + destroy() { + return WalletFFI.seedWordsDestroy(this.#tari_seed_words_ptr); + } +} + +module.exports = SeedWords; diff --git a/integration_tests/helpers/walletFFI.js b/integration_tests/helpers/ffi/walletFFI.js similarity index 89% rename from integration_tests/helpers/walletFFI.js rename to integration_tests/helpers/ffi/walletFFI.js index b49fb26a01..194b640cca 100644 --- a/integration_tests/helpers/walletFFI.js +++ b/integration_tests/helpers/ffi/walletFFI.js @@ -8,6 +8,7 @@ const ref = require("ref-napi"); const dateFormat = require("dateformat"); const { spawn } = require("child_process"); const fs = require("fs"); + class WalletFFI { static byte_vector = ref.types.void; static byte_vector_ptr = ref.refType(this.byte_vector); @@ -63,28 +64,11 @@ class WalletFFI { static tari_excess_signature_ptr = ref.refType(this.tari_excess_signature); static #fn; - static error = ref.alloc("int"); + static error = ref.alloc(ref.types.int); static NULL = ref.NULL; static #loaded = false; static #ps = null; - // Callbacks - static #callback_received_transaction; - static #callback_received_transaction_reply; - static #callback_received_finalized_transaction; - static #callback_transaction_broadcast; - static #callback_transaction_mined; - static #callback_transaction_mined_unconfirmed; - static #callback_direct_send_result; - static #callback_store_and_forward_send_result; - static #callback_transaction_cancellation; - static #callback_utxo_validation_complete; - static #callback_stxo_validation_complete; - static #callback_invalid_txo_validation_complete; - static #callback_transaction_validation_complete; - static #callback_saf_message_received; - static #recovery_progress_callback; - static checkAsyncRes(resolve, reject, error_name) { return (err, res) => { if (err) reject(err); @@ -144,109 +128,45 @@ class WalletFFI { }tari_wallet_ffi`; // Init callbacks - // TODO: Add callback support for wallets - this.#callback_received_transaction = ffi.Callback( - "void", - [this.tari_pending_inbound_transaction_ptr], - function (_pending_inbound_transaction) { - console.log("callback_received_transaction"); - } - ); - this.#callback_received_transaction_reply = ffi.Callback( - "void", - [this.tari_completed_transaction_ptr], - function (_completed_transaction) { - console.log("callback_received_transaction_reply"); - } - ); - this.#callback_received_finalized_transaction = ffi.Callback( - "void", - [this.tari_completed_transaction_ptr], - function (_completed_transaction) { - console.log("callback_received_finalized_transaction"); - } - ); - this.#callback_transaction_broadcast = ffi.Callback( - "void", - [this.tari_completed_transaction_ptr], - function (_completed_transaction) { - console.log("callback_transaction_broadcast"); - } - ); - this.#callback_transaction_mined = ffi.Callback( - "void", - [this.tari_completed_transaction_ptr], - function (_completed_transaction) { - console.log("callback_transaction_mined"); - } - ); - this.#callback_transaction_mined_unconfirmed = ffi.Callback( - "void", - [this.tari_completed_transaction_ptr, "ulong long"], - function (_completed_transaction, _b) { - console.log("callback_transaction_mined_unconfirmed"); - } - ); - this.#callback_direct_send_result = ffi.Callback( - "void", - ["ulong long", "bool"], - function (_a, _b) { - console.log("callback_direct_send_result"); - } - ); - this.#callback_store_and_forward_send_result = ffi.Callback( - "void", - ["ulong long", "bool"], - function (_a, _b) { - console.log("callback_store_and_forward_send_result"); - } - ); - this.#callback_transaction_cancellation = ffi.Callback( - "void", - [this.tari_completed_transaction_ptr], - function (_completed_transaction) { - console.log("callback_transaction_cancellation"); - } - ); - this.#callback_utxo_validation_complete = ffi.Callback( - "void", - ["ulong long", "uchar"], - function (_a, _b) { - console.log("callback_utxo_validation_complete"); - } - ); - this.#callback_stxo_validation_complete = ffi.Callback( - "void", - ["ulong long", "uchar"], - function (_a, _b) { - console.log("callback_stxo_validation_complete"); - } - ); - this.#callback_invalid_txo_validation_complete = ffi.Callback( - "void", - ["ulong long", "uchar"], - function (_a, _b) { - console.log("callback_invalid_txo_validation_complete"); - } - ); - this.#callback_transaction_validation_complete = ffi.Callback( - "void", - ["ulong long", "uchar"], - function (_a, _b) { - console.log("callback_transaction_validation_complete"); - } - ); - this.#callback_saf_message_received = ffi.Callback("void", [], function () { - console.log("callback_saf_message_received"); - }); - this.#recovery_progress_callback = ffi.Callback( - "void", - ["uchar", "ulong long", "ulong long"], - function (_a, _b, _c) { - console.log("recovery_progress_callback"); - } - ); + this.createCallbackReceivedTransaction = (callback) => + ffi.Callback( + "void", + [this.tari_pending_inbound_transaction_ptr], + callback + ); + this.createCallbackReceivedTransactionReply = (callback) => + ffi.Callback("void", [this.tari_completed_transaction_ptr], callback); + this.createCallbackReceivedFinalizedTransaction = (callback) => + ffi.Callback("void", [this.tari_completed_transaction_ptr], callback); + this.createCallbackTransactionBroadcast = (callback) => + ffi.Callback("void", [this.tari_completed_transaction_ptr], callback); + this.createCallbackTransactionMined = (callback) => + ffi.Callback("void", [this.tari_completed_transaction_ptr], callback); + this.createCallbackTransactionMinedUnconfirmed = (callback) => + ffi.Callback( + "void", + [this.tari_completed_transaction_ptr, "uint64"], + callback + ); + this.createCallbackDirectSendResult = (callback) => + ffi.Callback("void", ["uint64", "bool"], callback); + this.createCallbackStoreAndForwardSendResult = (callback) => + ffi.Callback("void", ["uint64", "bool"], callback); + this.createCallbackTransactionCancellation = (callback) => + ffi.Callback("void", [this.tari_completed_transaction_ptr], callback); + this.createCallbackUtxoValidationComplete = (callback) => + ffi.Callback("void", ["uint64", "uchar"], callback); + this.createCallbackStxoValidationComplete = (callback) => + ffi.Callback("void", ["uint64", "uchar"], callback); + this.createCallbackInvalidTxoValidationComplete = (callback) => + ffi.Callback("void", ["uint64", "uchar"], callback); + this.createCallbackTransactionValidationComplete = (callback) => + ffi.Callback("void", ["uint64", "uchar"], callback); + this.createCallbackSafMessageReceived = (callback) => + ffi.Callback("void", [], callback); + this.createRecoveryProgressCallback = (callback) => + ffi.Callback("void", ["uchar", "uint64", "uint64"], callback); // Load the library this.#fn = ffi.Library(outputProcess, { transport_memory_create: [this.tari_transport_type_ptr, []], @@ -325,11 +245,11 @@ class WalletFFI { [this.tari_completed_transaction_ptr, "int*"], ], completed_transaction_get_amount: [ - "ulong long", + "uint64", [this.tari_completed_transaction_ptr, "int*"], ], completed_transaction_get_fee: [ - "ulong long", + "uint64", [this.tari_completed_transaction_ptr, "int*"], ], completed_transaction_get_message: [ @@ -341,11 +261,11 @@ class WalletFFI { [this.tari_completed_transaction_ptr, "int*"], ], completed_transaction_get_transaction_id: [ - "ulong long", + "uint64", [this.tari_completed_transaction_ptr, "int*"], ], completed_transaction_get_timestamp: [ - "ulong long", + "uint64", [this.tari_completed_transaction_ptr, "int*"], ], completed_transaction_is_valid: [ @@ -357,7 +277,7 @@ class WalletFFI { [this.tari_completed_transaction_ptr, "int*"], ], completed_transaction_get_confirmations: [ - "ulong long", + "uint64", [this.tari_completed_transaction_ptr, "int*"], ], completed_transaction_destroy: [ @@ -392,7 +312,7 @@ class WalletFFI { [this.tari_completed_transactions_ptr], ], pending_outbound_transaction_get_transaction_id: [ - "ulong long", + "uint64", [this.tari_pending_outbound_transaction_ptr, "int*"], ], pending_outbound_transaction_get_destination_public_key: [ @@ -400,11 +320,11 @@ class WalletFFI { [this.tari_pending_outbound_transaction_ptr, "int*"], ], pending_outbound_transaction_get_amount: [ - "ulong long", + "uint64", [this.tari_pending_outbound_transaction_ptr, "int*"], ], pending_outbound_transaction_get_fee: [ - "ulong long", + "uint64", [this.tari_pending_outbound_transaction_ptr, "int*"], ], pending_outbound_transaction_get_message: [ @@ -412,7 +332,7 @@ class WalletFFI { [this.tari_pending_outbound_transaction_ptr, "int*"], ], pending_outbound_transaction_get_timestamp: [ - "ulong long", + "uint64", [this.tari_pending_outbound_transaction_ptr, "int*"], ], pending_outbound_transaction_get_status: [ @@ -436,7 +356,7 @@ class WalletFFI { [this.tari_pending_outbound_transactions_ptr], ], pending_inbound_transaction_get_transaction_id: [ - "ulong long", + "uint64", [this.tari_pending_inbound_transaction_ptr, "int*"], ], pending_inbound_transaction_get_source_public_key: [ @@ -448,11 +368,11 @@ class WalletFFI { [this.tari_pending_inbound_transaction_ptr, "int*"], ], pending_inbound_transaction_get_amount: [ - "ulong long", + "uint64", [this.tari_pending_inbound_transaction_ptr, "int*"], ], pending_inbound_transaction_get_timestamp: [ - "ulong long", + "uint64", [this.tari_pending_inbound_transaction_ptr, "int*"], ], pending_inbound_transaction_get_status: [ @@ -482,8 +402,8 @@ class WalletFFI { this.tari_transport_type_ptr, "string", "string", - "ulong long", - "ulong long", + "uint64", + "uint64", "string", "int*", ], @@ -538,44 +458,34 @@ class WalletFFI { "bool", [this.tari_wallet_ptr, this.tari_contact_ptr, "int*"], ], - wallet_get_available_balance: [ - "ulong long", - [this.tari_wallet_ptr, "int*"], - ], + wallet_get_available_balance: ["uint64", [this.tari_wallet_ptr, "int*"]], wallet_get_pending_incoming_balance: [ - "ulong long", + "uint64", [this.tari_wallet_ptr, "int*"], ], wallet_get_pending_outgoing_balance: [ - "ulong long", + "uint64", [this.tari_wallet_ptr, "int*"], ], wallet_get_fee_estimate: [ - "ulong long", - [ - this.tari_wallet_ptr, - "ulong long", - "ulong long", - "ulong long", - "ulong long", - "int*", - ], + "uint64", + [this.tari_wallet_ptr, "uint64", "uint64", "uint64", "uint64", "int*"], ], wallet_get_num_confirmations_required: [ - "ulong long", + "uint64", [this.tari_wallet_ptr, "int*"], ], wallet_set_num_confirmations_required: [ "void", - [this.tari_wallet_ptr, "ulong long", "int*"], + [this.tari_wallet_ptr, "uint64", "int*"], ], wallet_send_transaction: [ - "ulong long", + "uint64", [ this.tari_wallet_ptr, this.tari_public_key_ptr, - "ulong long", - "ulong long", + "uint64", + "uint64", "string", "int*", ], @@ -606,45 +516,39 @@ class WalletFFI { ], wallet_get_completed_transaction_by_id: [ this.tari_completed_transaction_ptr, - [this.tari_wallet_ptr, "ulong long", "int*"], + [this.tari_wallet_ptr, "uint64", "int*"], ], wallet_get_pending_outbound_transaction_by_id: [ this.tari_pending_outbound_transaction_ptr, - [this.tari_wallet_ptr, "ulong long", "int*"], + [this.tari_wallet_ptr, "uint64", "int*"], ], wallet_get_pending_inbound_transaction_by_id: [ this.tari_pending_inbound_transaction_ptr, - [this.tari_wallet_ptr, "ulong long", "int*"], + [this.tari_wallet_ptr, "uint64", "int*"], ], wallet_get_cancelled_transaction_by_id: [ this.tari_completed_transaction_ptr, - [this.tari_wallet_ptr, "ulong long", "int*"], + [this.tari_wallet_ptr, "uint64", "int*"], ], wallet_import_utxo: [ - "ulong long", + "uint64", [ this.tari_wallet_ptr, - "ulong long", + "uint64", this.tari_private_key_ptr, this.tari_public_key_ptr, "string", "int*", ], ], - wallet_start_utxo_validation: [ - "ulong long", - [this.tari_wallet_ptr, "int*"], - ], - wallet_start_stxo_validation: [ - "ulong long", - [this.tari_wallet_ptr, "int*"], - ], + wallet_start_utxo_validation: ["uint64", [this.tari_wallet_ptr, "int*"]], + wallet_start_stxo_validation: ["uint64", [this.tari_wallet_ptr, "int*"]], wallet_start_invalid_txo_validation: [ - "ulong long", + "uint64", [this.tari_wallet_ptr, "int*"], ], wallet_start_transaction_validation: [ - "ulong long", + "uint64", [this.tari_wallet_ptr, "int*"], ], wallet_restart_transaction_broadcast: [ @@ -655,17 +559,17 @@ class WalletFFI { wallet_set_normal_power_mode: ["void", [this.tari_wallet_ptr, "int*"]], wallet_cancel_pending_transaction: [ "bool", - [this.tari_wallet_ptr, "ulong long", "int*"], + [this.tari_wallet_ptr, "uint64", "int*"], ], wallet_coin_split: [ - "ulong long", + "uint64", [ this.tari_wallet_ptr, - "ulong long", - "ulong long", - "ulong long", + "uint64", + "uint64", + "uint64", "string", - "ulong long", + "uint64", "int*", ], ], @@ -709,6 +613,7 @@ class WalletFFI { ) ); } + static transportTcpCreate(listener_address) { return new Promise((resolve, reject) => this.#fn.transport_tcp_create.async( @@ -718,6 +623,7 @@ class WalletFFI { ) ); } + static transportTorCreate( control_server_address, tor_cookie, @@ -737,6 +643,7 @@ class WalletFFI { ) ); } + static transportMemoryGetAddress(transport) { return new Promise((resolve, reject) => this.#fn.transport_memory_get_address.async( @@ -746,6 +653,7 @@ class WalletFFI { ) ); } + static transportTypeDestroy(transport) { return new Promise((resolve, reject) => this.#fn.transport_type_destroy.async( @@ -754,6 +662,7 @@ class WalletFFI { ) ); } + static stringDestroy(s) { return new Promise((resolve, reject) => this.#fn.string_destroy.async( @@ -762,6 +671,7 @@ class WalletFFI { ) ); } + static byteVectorCreate(byte_array, element_count) { return new Promise((resolve, reject) => this.#fn.byte_vector_create.async( @@ -772,6 +682,7 @@ class WalletFFI { ) ); } + static byteVectorGetAt(ptr, i) { return new Promise((resolve, reject) => this.#fn.byte_vector_get_at.async( @@ -782,6 +693,7 @@ class WalletFFI { ) ); } + static byteVectorGetLength(vec) { return new Promise((resolve, reject) => this.#fn.byte_vector_get_length.async( @@ -791,6 +703,7 @@ class WalletFFI { ) ); } + static byteVectorDestroy(bytes) { return new Promise((resolve, reject) => this.#fn.byte_vector_destroy.async( @@ -799,6 +712,7 @@ class WalletFFI { ) ); } + static publicKeyCreate(bytes) { return new Promise((resolve, reject) => this.#fn.public_key_create.async( @@ -808,6 +722,7 @@ class WalletFFI { ) ); } + static publicKeyGetBytes(public_key) { return new Promise((resolve, reject) => this.#fn.public_key_get_bytes.async( @@ -817,6 +732,7 @@ class WalletFFI { ) ); } + static publicKeyFromPrivateKey(secret_key) { return new Promise((resolve, reject) => this.#fn.public_key_from_private_key.async( @@ -826,6 +742,7 @@ class WalletFFI { ) ); } + static publicKeyFromHex(hex) { return new Promise((resolve, reject) => this.#fn.public_key_from_hex.async( @@ -835,6 +752,7 @@ class WalletFFI { ) ); } + static publicKeyDestroy(pk) { return new Promise((resolve, reject) => this.#fn.public_key_destroy.async( @@ -843,6 +761,7 @@ class WalletFFI { ) ); } + static publicKeyToEmojiId(pk) { return new Promise((resolve, reject) => this.#fn.public_key_to_emoji_id.async( @@ -852,6 +771,7 @@ class WalletFFI { ) ); } + static emojiIdToPublicKey(emoji) { return new Promise((resolve, reject) => this.#fn.emoji_id_to_public_key.async( @@ -861,6 +781,7 @@ class WalletFFI { ) ); } + static privateKeyCreate(bytes) { return new Promise((resolve, reject) => this.#fn.private_key_create.async( @@ -870,6 +791,7 @@ class WalletFFI { ) ); } + static privateKeyGenerate() { return new Promise((resolve, reject) => this.#fn.private_key_generate.async( @@ -877,6 +799,7 @@ class WalletFFI { ) ); } + static privateKeyGetBytes(private_key) { return new Promise((resolve, reject) => this.#fn.private_key_get_bytes.async( @@ -886,6 +809,7 @@ class WalletFFI { ) ); } + static privateKeyFromHex(hex) { return new Promise((resolve, reject) => this.#fn.private_key_from_hex.async( @@ -895,6 +819,7 @@ class WalletFFI { ) ); } + static privateKeyDestroy(pk) { return new Promise((resolve, reject) => this.#fn.private_key_destroy.async( @@ -903,6 +828,7 @@ class WalletFFI { ) ); } + static seedWordsCreate() { return new Promise((resolve, reject) => this.#fn.seed_words_create.async( @@ -910,6 +836,7 @@ class WalletFFI { ) ); } + static seedWordsGetLength(seed_words) { return new Promise((resolve, reject) => this.#fn.seed_words_get_length.async( @@ -919,6 +846,7 @@ class WalletFFI { ) ); } + static seedWordsGetAt(seed_words, position) { return new Promise((resolve, reject) => this.#fn.seed_words_get_at.async( @@ -929,6 +857,7 @@ class WalletFFI { ) ); } + static seedWordsPushWord(seed_words, word) { return new Promise((resolve, reject) => this.#fn.seed_words_push_word.async( @@ -939,6 +868,7 @@ class WalletFFI { ) ); } + static seedWordsDestroy(seed_words) { return new Promise((resolve, reject) => this.#fn.seed_words_destroy.async( @@ -947,6 +877,7 @@ class WalletFFI { ) ); } + static contactCreate(alias, public_key) { return new Promise((resolve, reject) => this.#fn.contact_create.async( @@ -957,6 +888,7 @@ class WalletFFI { ) ); } + static contactGetAlias(contact) { return new Promise((resolve, reject) => this.#fn.contact_get_alias.async( @@ -966,6 +898,7 @@ class WalletFFI { ) ); } + static contactGetPublicKey(contact) { return new Promise((resolve, reject) => this.#fn.contact_get_public_key.async( @@ -975,6 +908,7 @@ class WalletFFI { ) ); } + static contactDestroy(contact) { return new Promise((resolve, reject) => this.#fn.contact_destroy.async( @@ -983,6 +917,7 @@ class WalletFFI { ) ); } + static contactsGetLength(contacts) { return new Promise((resolve, reject) => this.#fn.contacts_get_length.async( @@ -992,6 +927,7 @@ class WalletFFI { ) ); } + static contactsGetAt(contacts, position) { return new Promise((resolve, reject) => this.#fn.contacts_get_at.async( @@ -1002,6 +938,7 @@ class WalletFFI { ) ); } + static contactsDestroy(contacts) { return new Promise((resolve, reject) => this.#fn.contacts_destroy.async( @@ -1010,6 +947,7 @@ class WalletFFI { ) ); } + static completedTransactionGetDestinationPublicKey(transaction) { return new Promise((resolve, reject) => this.#fn.completed_transaction_get_destination_public_key.async( @@ -1023,6 +961,7 @@ class WalletFFI { ) ); } + static completedTransactionGetSourcePublicKey(transaction) { return new Promise((resolve, reject) => this.#fn.completed_transaction_get_source_public_key.async( @@ -1036,6 +975,7 @@ class WalletFFI { ) ); } + static completedTransactionGetAmount(transaction) { return new Promise((resolve, reject) => this.#fn.completed_transaction_get_amount.async( @@ -1045,6 +985,7 @@ class WalletFFI { ) ); } + static completedTransactionGetFee(transaction) { return new Promise((resolve, reject) => this.#fn.completed_transaction_get_fee.async( @@ -1054,6 +995,7 @@ class WalletFFI { ) ); } + static completedTransactionGetMessage(transaction) { return new Promise((resolve, reject) => this.#fn.completed_transaction_get_message.async( @@ -1063,6 +1005,7 @@ class WalletFFI { ) ); } + static completedTransactionGetStatus(transaction) { return new Promise((resolve, reject) => this.#fn.completed_transaction_get_status.async( @@ -1072,6 +1015,7 @@ class WalletFFI { ) ); } + static completedTransactionGetTransactionId(transaction) { return new Promise((resolve, reject) => this.#fn.completed_transaction_get_transaction_id.async( @@ -1085,6 +1029,7 @@ class WalletFFI { ) ); } + static completedTransactionGetTimestamp(transaction) { return new Promise((resolve, reject) => this.#fn.completed_transaction_get_timestamp.async( @@ -1094,6 +1039,7 @@ class WalletFFI { ) ); } + static completedTransactionIsValid(tx) { return new Promise((resolve, reject) => this.#fn.completed_transaction_is_valid.async( @@ -1103,6 +1049,7 @@ class WalletFFI { ) ); } + static completedTransactionIsOutbound(tx) { return new Promise((resolve, reject) => this.#fn.completed_transaction_is_outbound.async( @@ -1112,6 +1059,7 @@ class WalletFFI { ) ); } + static completedTransactionGetConfirmations(transaction) { return new Promise((resolve, reject) => this.#fn.completed_transaction_get_confirmations.async( @@ -1125,6 +1073,7 @@ class WalletFFI { ) ); } + static completedTransactionDestroy(transaction) { return new Promise((resolve, reject) => this.#fn.completed_transaction_destroy.async( @@ -1133,6 +1082,7 @@ class WalletFFI { ) ); } + static completedTransactionGetExcess(transaction) { return new Promise((resolve, reject) => this.#fn.completed_transaction_get_excess.async( @@ -1142,6 +1092,7 @@ class WalletFFI { ) ); } + static completedTransactionGetPublicNonce(transaction) { return new Promise((resolve, reject) => this.#fn.completed_transaction_get_public_nonce.async( @@ -1155,6 +1106,7 @@ class WalletFFI { ) ); } + static completedTransactionGetSignature(transaction) { return new Promise((resolve, reject) => this.#fn.completed_transaction_get_signature.async( @@ -1164,6 +1116,7 @@ class WalletFFI { ) ); } + static excessDestroy(excess) { return new Promise((resolve, reject) => this.#fn.excess_destroy.async( @@ -1172,6 +1125,7 @@ class WalletFFI { ) ); } + static nonceDestroy(nonce) { return new Promise((resolve, reject) => this.#fn.nonce_destroy.async( @@ -1180,6 +1134,7 @@ class WalletFFI { ) ); } + static signatureDestroy(signature) { return new Promise((resolve, reject) => this.#fn.signature_destroy.async( @@ -1188,6 +1143,7 @@ class WalletFFI { ) ); } + static completedTransactionsGetLength(transactions) { return new Promise((resolve, reject) => this.#fn.completed_transactions_get_length.async( @@ -1197,6 +1153,7 @@ class WalletFFI { ) ); } + static completedTransactionsGetAt(transactions, position) { return new Promise((resolve, reject) => this.#fn.completed_transactions_get_at.async( @@ -1207,6 +1164,7 @@ class WalletFFI { ) ); } + static completedTransactionsDestroy(transactions) { return new Promise((resolve, reject) => this.#fn.completed_transactions_destroy.async( @@ -1215,6 +1173,7 @@ class WalletFFI { ) ); } + static pendingOutboundTransactionGetTransactionId(transaction) { return new Promise((resolve, reject) => this.#fn.pending_outbound_transaction_get_transaction_id.async( @@ -1228,6 +1187,7 @@ class WalletFFI { ) ); } + static pendingOutboundTransactionGetDestinationPublicKey(transaction) { return new Promise((resolve, reject) => this.#fn.pending_outbound_transaction_get_destination_public_key.async( @@ -1241,6 +1201,7 @@ class WalletFFI { ) ); } + static pendingOutboundTransactionGetAmount(transaction) { return new Promise((resolve, reject) => this.#fn.pending_outbound_transaction_get_amount.async( @@ -1254,6 +1215,7 @@ class WalletFFI { ) ); } + static pendingOutboundTransactionGetFee(transaction) { return new Promise((resolve, reject) => this.#fn.pending_outbound_transaction_get_fee.async( @@ -1263,6 +1225,7 @@ class WalletFFI { ) ); } + static pendingOutboundTransactionGetMessage(transaction) { return new Promise((resolve, reject) => this.#fn.pending_outbound_transaction_get_message.async( @@ -1276,6 +1239,7 @@ class WalletFFI { ) ); } + static pendingOutboundTransactionGetTimestamp(transaction) { return new Promise((resolve, reject) => this.#fn.pending_outbound_transaction_get_timestamp.async( @@ -1289,6 +1253,7 @@ class WalletFFI { ) ); } + static pendingOutboundTransactionGetStatus(transaction) { return new Promise((resolve, reject) => this.#fn.pending_outbound_transaction_get_status.async( @@ -1302,6 +1267,7 @@ class WalletFFI { ) ); } + static pendingOutboundTransactionDestroy(transaction) { return new Promise((resolve, reject) => this.#fn.pending_outbound_transaction_destroy.async( @@ -1310,6 +1276,7 @@ class WalletFFI { ) ); } + static pendingOutboundTransactionsGetLength(transactions) { return new Promise((resolve, reject) => this.#fn.pending_outbound_transactions_get_length.async( @@ -1323,6 +1290,7 @@ class WalletFFI { ) ); } + static pendingOutboundTransactionsGetAt(transactions, position) { return new Promise((resolve, reject) => this.#fn.pending_outbound_transactions_get_at.async( @@ -1333,6 +1301,7 @@ class WalletFFI { ) ); } + static pendingOutboundTransactionsDestroy(transactions) { return new Promise((resolve, reject) => this.#fn.pending_outbound_transactions_destroy.async( @@ -1345,6 +1314,7 @@ class WalletFFI { ) ); } + static pendingInboundTransactionGetTransactionId(transaction) { return new Promise((resolve, reject) => this.#fn.pending_inbound_transaction_get_transaction_id.async( @@ -1358,6 +1328,7 @@ class WalletFFI { ) ); } + static pendingInboundTransactionGetSourcePublicKey(transaction) { return new Promise((resolve, reject) => this.#fn.pending_inbound_transaction_get_source_public_key.async( @@ -1371,6 +1342,7 @@ class WalletFFI { ) ); } + static pendingInboundTransactionGetMessage(transaction) { return new Promise((resolve, reject) => this.#fn.pending_inbound_transaction_get_message.async( @@ -1384,6 +1356,7 @@ class WalletFFI { ) ); } + static pendingInboundTransactionGetAmount(transaction) { return new Promise((resolve, reject) => this.#fn.pending_inbound_transaction_get_amount.async( @@ -1397,6 +1370,7 @@ class WalletFFI { ) ); } + static pendingInboundTransactionGetTimestamp(transaction) { return new Promise((resolve, reject) => this.#fn.pending_inbound_transaction_get_timestamp.async( @@ -1410,6 +1384,7 @@ class WalletFFI { ) ); } + static pendingInboundTransactionGetStatus(transaction) { return new Promise((resolve, reject) => this.#fn.pending_inbound_transaction_get_status.async( @@ -1423,6 +1398,7 @@ class WalletFFI { ) ); } + static pendingInboundTransactionDestroy(transaction) { return new Promise((resolve, reject) => this.#fn.pending_inbound_transaction_destroy.async( @@ -1431,6 +1407,7 @@ class WalletFFI { ) ); } + static pendingInboundTransactionsGetLength(transactions) { return new Promise((resolve, reject) => this.#fn.pending_inbound_transactions_get_length.async( @@ -1444,6 +1421,7 @@ class WalletFFI { ) ); } + static pendingInboundTransactionsGetAt(transactions, position) { return new Promise((resolve, reject) => this.#fn.pending_inbound_transactions_get_at.async( @@ -1454,6 +1432,7 @@ class WalletFFI { ) ); } + static pendingInboundTransactionsDestroy(transactions) { return new Promise((resolve, reject) => this.#fn.pending_inbound_transactions_destroy.async( @@ -1462,6 +1441,7 @@ class WalletFFI { ) ); } + static commsConfigCreate( public_address, transport, @@ -1485,6 +1465,7 @@ class WalletFFI { ) ); } + static commsConfigDestroy(wc) { return new Promise((resolve, reject) => this.#fn.comms_config_destroy.async( @@ -1493,13 +1474,28 @@ class WalletFFI { ) ); } + static walletCreate( config, log_path, num_rolling_log_files, size_per_log_file_bytes, passphrase, - seed_words + 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 ) { return new Promise((resolve, reject) => this.#fn.wallet_create.async( @@ -1509,25 +1505,26 @@ class WalletFFI { size_per_log_file_bytes, passphrase, seed_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, + 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, this.error, this.checkAsyncRes(resolve, reject, "walletCreate") ) ); } + static walletSignMessage(wallet, msg) { return new Promise((resolve, reject) => this.#fn.wallet_sign_message.async( @@ -1538,6 +1535,7 @@ class WalletFFI { ) ); } + static walletVerifyMessageSignature(wallet, public_key, hex_sig_nonce, msg) { return new Promise((resolve, reject) => this.#fn.wallet_verify_message_signature.async( @@ -1550,6 +1548,7 @@ class WalletFFI { ) ); } + static walletAddBaseNodePeer(wallet, public_key, address) { return new Promise((resolve, reject) => this.#fn.wallet_add_base_node_peer.async( @@ -1561,6 +1560,7 @@ class WalletFFI { ) ); } + static walletUpsertContact(wallet, contact) { return new Promise((resolve, reject) => this.#fn.wallet_upsert_contact.async( @@ -1571,6 +1571,7 @@ class WalletFFI { ) ); } + static walletRemoveContact(wallet, contact) { return new Promise((resolve, reject) => this.#fn.wallet_remove_contact.async( @@ -1581,6 +1582,7 @@ class WalletFFI { ) ); } + static walletGetAvailableBalance(wallet) { return new Promise((resolve, reject) => this.#fn.wallet_get_available_balance.async( @@ -1590,6 +1592,7 @@ class WalletFFI { ) ); } + static walletGetPendingIncomingBalance(wallet) { return new Promise((resolve, reject) => this.#fn.wallet_get_pending_incoming_balance.async( @@ -1599,6 +1602,7 @@ class WalletFFI { ) ); } + static walletGetPendingOutgoingBalance(wallet) { return new Promise((resolve, reject) => this.#fn.wallet_get_pending_outgoing_balance.async( @@ -1608,6 +1612,7 @@ class WalletFFI { ) ); } + static walletGetFeeEstimate( wallet, amount, @@ -1627,6 +1632,7 @@ class WalletFFI { ) ); } + static walletGetNumConfirmationsRequired(wallet) { return new Promise((resolve, reject) => this.#fn.wallet_get_num_confirmations_required.async( @@ -1636,6 +1642,7 @@ class WalletFFI { ) ); } + static walletSetNumConfirmationsRequired(wallet, num) { return new Promise((resolve, reject) => this.#fn.wallet_set_num_confirmations_required.async( @@ -1646,6 +1653,7 @@ class WalletFFI { ) ); } + static walletSendTransaction( wallet, destination, @@ -1665,6 +1673,7 @@ class WalletFFI { ) ); } + static walletGetContacts(wallet) { return new Promise((resolve, reject) => this.#fn.wallet_get_contacts.async( @@ -1674,6 +1683,7 @@ class WalletFFI { ) ); } + static walletGetCompletedTransactions(wallet) { return new Promise((resolve, reject) => this.#fn.wallet_get_completed_transactions.async( @@ -1683,6 +1693,7 @@ class WalletFFI { ) ); } + static walletGetPendingOutboundTransactions(wallet) { return new Promise((resolve, reject) => this.#fn.wallet_get_pending_outbound_transactions.async( @@ -1696,6 +1707,7 @@ class WalletFFI { ) ); } + static walletGetPublicKey(wallet) { return new Promise((resolve, reject) => this.#fn.wallet_get_public_key.async( @@ -1705,6 +1717,7 @@ class WalletFFI { ) ); } + static walletGetPendingInboundTransactions(wallet) { return new Promise((resolve, reject) => this.#fn.wallet_get_pending_inbound_transactions.async( @@ -1718,6 +1731,7 @@ class WalletFFI { ) ); } + static walletGetCancelledTransactions(wallet) { return new Promise((resolve, reject) => this.#fn.wallet_get_cancelled_transactions.async( @@ -1727,6 +1741,7 @@ class WalletFFI { ) ); } + static walletGetCompletedTransactionById(wallet, transaction_id) { return new Promise((resolve, reject) => this.#fn.wallet_get_completed_transaction_by_id.async( @@ -1737,6 +1752,7 @@ class WalletFFI { ) ); } + static walletGetPendingOutboundTransactionById(wallet, transaction_id) { return new Promise((resolve, reject) => this.#fn.wallet_get_pending_outbound_transaction_by_id.async( @@ -1751,6 +1767,7 @@ class WalletFFI { ) ); } + static walletGetPendingInboundTransactionById(wallet, transaction_id) { return new Promise((resolve, reject) => this.#fn.wallet_get_pending_inbound_transaction_by_id.async( @@ -1765,6 +1782,7 @@ class WalletFFI { ) ); } + static walletGetCancelledTransactionById(wallet, transaction_id) { return new Promise((resolve, reject) => this.#fn.wallet_get_cancelled_transaction_by_id.async( @@ -1775,6 +1793,7 @@ class WalletFFI { ) ); } + static walletImportUtxo( wallet, amount, @@ -1794,6 +1813,7 @@ class WalletFFI { ) ); } + static walletStartUtxoValidation(wallet) { return new Promise((resolve, reject) => this.#fn.wallet_start_utxo_validation.async( @@ -1803,6 +1823,7 @@ class WalletFFI { ) ); } + static walletStartStxoValidation(wallet) { return new Promise((resolve, reject) => this.#fn.wallet_start_stxo_validation.async( @@ -1812,6 +1833,7 @@ class WalletFFI { ) ); } + static walletStartInvalidTxoValidation(wallet) { return new Promise((resolve, reject) => this.#fn.wallet_start_invalid_txo_validation.async( @@ -1821,6 +1843,7 @@ class WalletFFI { ) ); } + static walletStartTransactionValidation(wallet) { return new Promise((resolve, reject) => this.#fn.wallet_start_transaction_validation.async( @@ -1830,6 +1853,7 @@ class WalletFFI { ) ); } + static walletRestartTransactionBroadcast(wallet) { return new Promise((resolve, reject) => this.#fn.wallet_restart_transaction_broadcast.async( @@ -1839,6 +1863,7 @@ class WalletFFI { ) ); } + static walletSetLowPowerMode(wallet) { return new Promise((resolve, reject) => this.#fn.wallet_set_low_power_mode.async( @@ -1848,6 +1873,7 @@ class WalletFFI { ) ); } + static walletSetNormalPowerMode(wallet) { return new Promise((resolve, reject) => this.#fn.wallet_set_normal_power_mode.async( @@ -1857,6 +1883,7 @@ class WalletFFI { ) ); } + static walletCancelPendingTransaction(wallet, transaction_id) { return new Promise((resolve, reject) => this.#fn.wallet_cancel_pending_transaction.async( @@ -1867,6 +1894,7 @@ class WalletFFI { ) ); } + static walletCoinSplit(wallet, amount, count, fee, msg, lock_height) { return new Promise((resolve, reject) => this.#fn.wallet_coin_split.async( @@ -1881,6 +1909,7 @@ class WalletFFI { ) ); } + static walletGetSeedWords(wallet) { return new Promise((resolve, reject) => this.#fn.wallet_get_seed_words.async( @@ -1890,6 +1919,7 @@ class WalletFFI { ) ); } + static walletApplyEncryption(wallet, passphrase) { return new Promise((resolve, reject) => this.#fn.wallet_apply_encryption.async( @@ -1900,6 +1930,7 @@ class WalletFFI { ) ); } + static walletRemoveEncryption(wallet) { return new Promise((resolve, reject) => this.#fn.wallet_remove_encryption.async( @@ -1909,6 +1940,7 @@ class WalletFFI { ) ); } + static walletSetKeyValue(wallet, key, value) { return new Promise((resolve, reject) => this.#fn.wallet_set_key_value.async( @@ -1920,6 +1952,7 @@ class WalletFFI { ) ); } + static walletGetValue(wallet, key) { return new Promise((resolve, reject) => this.#fn.wallet_get_value.async( @@ -1930,6 +1963,7 @@ class WalletFFI { ) ); } + static walletClearValue(wallet, key) { return new Promise((resolve, reject) => this.#fn.wallet_clear_value.async( @@ -1940,6 +1974,7 @@ class WalletFFI { ) ); } + static walletIsRecoveryInProgress(wallet) { return new Promise((resolve, reject) => this.#fn.wallet_is_recovery_in_progress.async( @@ -1949,17 +1984,23 @@ class WalletFFI { ) ); } - static walletStartRecovery(wallet, base_node_public_key) { + + static walletStartRecovery( + wallet, + base_node_public_key, + recovery_progress_callback + ) { return new Promise((resolve, reject) => this.#fn.wallet_start_recovery.async( wallet, base_node_public_key, - this.#recovery_progress_callback, + recovery_progress_callback, this.error, this.checkAsyncRes(resolve, reject, "walletStartRecovery") ) ); } + static walletDestroy(wallet) { return new Promise((resolve, reject) => this.#fn.wallet_destroy.async( @@ -1968,6 +2009,7 @@ class WalletFFI { ) ); } + static filePartialBackup(original_file_path, backup_file_path) { return new Promise((resolve, reject) => this.#fn.file_partial_backup.async( @@ -1978,6 +2020,7 @@ class WalletFFI { ) ); } + static logDebugMessage(msg) { return new Promise((resolve, reject) => this.#fn.log_debug_message.async( @@ -1986,6 +2029,7 @@ class WalletFFI { ) ); } + static getEmojiSet() { return new Promise((resolve, reject) => this.#fn.get_emoji_set.async( @@ -1993,6 +2037,7 @@ class WalletFFI { ) ); } + static emojiSetDestroy(emoji_set) { return new Promise((resolve, reject) => this.#fn.emoji_set_destroy.async( @@ -2001,6 +2046,7 @@ class WalletFFI { ) ); } + static emojiSetGetAt(emoji_set, position) { return new Promise((resolve, reject) => this.#fn.emoji_set_get_at.async( @@ -2011,6 +2057,7 @@ class WalletFFI { ) ); } + static emojiSetGetLength(emoji_set) { return new Promise((resolve, reject) => this.#fn.emoji_set_get_length.async( diff --git a/integration_tests/helpers/walletFFIClient.js b/integration_tests/helpers/walletFFIClient.js index c43cab4394..60596c8e34 100644 --- a/integration_tests/helpers/walletFFIClient.js +++ b/integration_tests/helpers/walletFFIClient.js @@ -1,41 +1,183 @@ -const WalletFFI = require("./walletFFI"); +const WalletFFI = require("./ffi/walletFFI"); 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"); class WalletFFIClient { #name; #wallet; #comms_config; #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; + } constructor(name) { this.#wallet = null; this.#name = name; this.baseDir = ""; - } + this.clearCallbackCounters(); - static async #convertPubkeyToHex(public_key) { - const bytes = await WalletFFI.publicKeyGetBytes(public_key); - const length = await WalletFFI.byteVectorGetLength(bytes); - const public_key_hex = ( - await Promise.all( - Array(length) - .fill() - .map((_, i) => WalletFFI.byteVectorGetAt(bytes, i)) - ) - ) - .map((byte) => byte.toString(16).padStart(2, "0")) - .join(""); - await WalletFFI.byteVectorDestroy(bytes); - return public_key_hex; + // 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(); } - async startNew() { + async startNew(seed_words_text) { this.#port = await getFreePort(19000, 25000); const name = `WalletFFI${this.#port}-${this.#name}`; this.baseDir = `./temp/base_nodes/${dateFormat( @@ -54,14 +196,56 @@ class WalletFFIClient { 600, "localnet" ); + await this.start(seed_words_text); + } + + 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, - 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 ); + if (seed_words) await seed_words.destroy(); + } + + 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; + } + + recoveryInProgress() { + return this.recoveryInProgress; } async stop() { @@ -69,19 +253,17 @@ class WalletFFIClient { } async getPublicKey() { - const public_key = await WalletFFI.walletGetPublicKey(this.#wallet); - const pubkey_hex = await WalletFFIClient.#convertPubkeyToHex(public_key); - await WalletFFI.publicKeyDestroy(public_key); - return pubkey_hex; + const public_key = await PublicKey.fromWallet(this.#wallet); + const public_key_hex = public_key.getHex(); + public_key.destroy(); + return public_key_hex; } async getEmojiId() { - const public_key = await WalletFFI.walletGetPublicKey(this.#wallet); - const emoji_id = await WalletFFI.publicKeyToEmojiId(public_key); - const result = emoji_id.readCString(); - await WalletFFI.stringDestroy(emoji_id); - await WalletFFI.publicKeyDestroy(public_key); - return result; + const public_key = await PublicKey.fromWallet(this.#wallet); + const emoji_id = await public_key.getEmojiId(); + public_key.destroy(); + return emoji_id; } async getBalance() { @@ -89,23 +271,27 @@ class WalletFFIClient { } async addBaseNodePeer(public_key_hex, address) { - const public_key = await WalletFFI.publicKeyFromHex(public_key_hex); + const public_key = await PublicKey.fromString(public_key_hex); expect( - await WalletFFI.walletAddBaseNodePeer(this.#wallet, public_key, address) + await WalletFFI.walletAddBaseNodePeer( + this.#wallet, + public_key.getPtr(), + address + ) ).to.be.true; - await WalletFFI.publicKeyDestroy(public_key); + await public_key.destroy(); } async sendTransaction(destination, amount, fee_per_gram, message) { - const destinationPubkey = await WalletFFI.publicKeyFromHex(destination); + const dest_public_key = await PublicKey.fromString(destination); const result = await WalletFFI.walletSendTransaction( this.#wallet, - destinationPubkey, + dest_public_key.getPtr(), amount, fee_per_gram, message ); - await WalletFFI.publicKeyDestroy(destinationPubkey); + await dest_public_key.destroy(); return result; } @@ -114,76 +300,93 @@ class WalletFFIClient { } async getCompletedTransactions() { - const txs = await WalletFFI.walletGetCompletedTransactions(this.#wallet); - const length = await WalletFFI.completedTransactionsGetLength(txs); + 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 WalletFFI.completedTransactionsGetAt(txs, i); - if (await WalletFFI.completedTransactionIsOutbound(tx)) { + const tx = await txs.getAt(i); + if (await tx.isOutbound()) { ++outbound; } else { ++inbound; } - await WalletFFI.completedTransactionDestroy(tx); + tx.destroy(); } - await WalletFFI.completedTransactionsDestroy(txs); + txs.destroy(); return [outbound, inbound]; } async getBroadcastTransactionsCount() { let broadcast_tx_cnt = 0; - const txs = await WalletFFI.walletGetPendingOutboundTransactions( - this.#wallet - ); - const length = await WalletFFI.pendingOutboundTransactionsGetLength(txs); + const txs = await PendingOutboundTransactions.fromWallet(this.#wallet); + const length = await txs.getLength(); for (let i = 0; i < length; ++i) { - const tx = await WalletFFI.pendingOutboundTransactionsGetAt(txs, i); - const status = await WalletFFI.pendingOutboundTransactionGetStatus(tx); - await WalletFFI.pendingOutboundTransactionDestroy(tx); + const tx = await txs.getAt(i); + const status = await tx.getStatus(); + tx.destroy(); if (status === 1) { + // Broadcast broadcast_tx_cnt += 1; } } - await WalletFFI.pendingOutboundTransactionsDestroy(txs); + await txs.destroy(); return broadcast_tx_cnt; } + 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; + } + async addContact(alias, pubkey_hex) { - const public_key = await WalletFFI.publicKeyFromHex(pubkey_hex); - const contact = await WalletFFI.contactCreate(alias, public_key); - await WalletFFI.publicKeyDestroy(public_key); - expect(await WalletFFI.walletUpsertContact(this.#wallet, contact)).to.be - .true; - await WalletFFI.contactDestroy(contact); + 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(); } async #findContact(lookup_alias) { - const contacts = await WalletFFI.walletGetContacts(this.#wallet); - const length = await WalletFFI.contactsGetLength(contacts); + const contacts = await Contacts.fromWallet(this.#wallet); + const length = await contacts.getLength(); let contact; for (let i = 0; i < length; ++i) { - contact = await WalletFFI.contactsGetAt(contacts, i); - const alias = await WalletFFI.contactGetAlias(contact); - const found = alias.readCString() === lookup_alias; - await WalletFFI.stringDestroy(alias); + contact = await contacts.getAt(i); + const alias = await contact.getAlias(); + const found = alias === lookup_alias; if (found) { break; } - await WalletFFI.contactDestroy(contact); + contact.destroy(); contact = undefined; } - await WalletFFI.contactsDestroy(contacts); + contacts.destroy(); return contact; } async getContact(alias) { const contact = await this.#findContact(alias); if (contact) { - const pubkey = await WalletFFI.contactGetPublicKey(contact); - const pubkey_hex = await WalletFFIClient.#convertPubkeyToHex(pubkey); - await WalletFFI.publicKeyDestroy(pubkey); - await WalletFFI.contactDestroy(contact); + const pubkey = await contact.getPubkey(); + const pubkey_hex = pubkey.getHex(); + pubkey.destroy(); + contact.destroy(); return pubkey_hex; } } @@ -191,11 +394,48 @@ class WalletFFIClient { async removeContact(alias) { const contact = await this.#findContact(alias); if (contact) { - expect(await WalletFFI.walletRemoveContact(this.#wallet, contact)).to.be - .true; - await WalletFFI.contactDestroy(contact); + expect( + await WalletFFI.walletRemoveContact(this.#wallet, contact.getPtr()) + ).to.be.true; + contact.destroy(); } } + + async identify() { + return { + public_key: await this.getPublicKey(), + }; + } + + 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(); + } + txs.destroy(); + return cancelled; + } + + startUtxoValidation() { + this.utxo_validation_complete = false; + return WalletFFI.walletStartUtxoValidation(this.#wallet); + } + + startStxoValidation() { + this.stxo_validation_complete = false; + return WalletFFI.walletStartStxoValidation(this.#wallet); + } } module.exports = WalletFFIClient; diff --git a/integration_tests/package-lock.json b/integration_tests/package-lock.json index 2f0f4f4ec8..db000117b7 100644 --- a/integration_tests/package-lock.json +++ b/integration_tests/package-lock.json @@ -3331,6 +3331,11 @@ "punycode": "^2.1.0" } }, + "utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", + "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==" + }, "util-arity": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/util-arity/-/util-arity-1.1.0.tgz", diff --git a/integration_tests/package.json b/integration_tests/package.json index f65c3fddff..9437b3061f 100644 --- a/integration_tests/package.json +++ b/integration_tests/package.json @@ -49,6 +49,7 @@ "sha3": "^2.1.3", "synchronized-promise": "^0.3.1", "tari_crypto": "^0.9.1", + "utf8": "^3.0.0", "wallet-grpc-client": "file:../clients/wallet_grpc_client" } }