From ff631355094c39c7cf3d94b34defff009b6252bc Mon Sep 17 00:00:00 2001 From: Byron Hambly Date: Tue, 1 Jun 2021 11:59:32 +0200 Subject: [PATCH] [wallet-ffi] `wallet_create` takes seed words for recovery Breaking change in wallet FFI, new argument in `wallet_create` to specify optional seed words for recovery. Also starts alpha quality NodeJS FFI wallet client for testing purposes. --- applications/ffi_client/.gitignore | 6 + applications/ffi_client/README.md | 18 ++ applications/ffi_client/index.js | 244 +++++++++++++++++ applications/ffi_client/lib/index.js | 81 ++++++ applications/ffi_client/lib/types.js | 42 +++ applications/ffi_client/package-lock.json | 252 ++++++++++++++++++ applications/ffi_client/package.json | 20 ++ applications/ffi_client/recovery.js | 236 ++++++++++++++++ .../tari_console_wallet/src/recovery.rs | 13 +- .../tari_console_wallet/src/wallet_modes.rs | 26 +- .../wallet/src/utxo_scanner_service/mod.rs | 3 +- .../src/utxo_scanner_service/utxo_scanning.rs | 14 +- base_layer/wallet/src/wallet.rs | 8 +- base_layer/wallet_ffi/mobile_build.sh | 2 +- base_layer/wallet_ffi/src/lib.rs | 193 ++++++++++++-- base_layer/wallet_ffi/wallet.h | 1 + 16 files changed, 1108 insertions(+), 51 deletions(-) create mode 100644 applications/ffi_client/.gitignore create mode 100644 applications/ffi_client/README.md create mode 100644 applications/ffi_client/index.js create mode 100644 applications/ffi_client/lib/index.js create mode 100644 applications/ffi_client/lib/types.js create mode 100644 applications/ffi_client/package-lock.json create mode 100644 applications/ffi_client/package.json create mode 100644 applications/ffi_client/recovery.js diff --git a/applications/ffi_client/.gitignore b/applications/ffi_client/.gitignore new file mode 100644 index 00000000000..f036d6b1499 --- /dev/null +++ b/applications/ffi_client/.gitignore @@ -0,0 +1,6 @@ +libtari_wallet_ffi.* +wallet/ +bak/ +recovered/ +seeds.txt +recovery/ \ No newline at end of file diff --git a/applications/ffi_client/README.md b/applications/ffi_client/README.md new file mode 100644 index 00000000000..137bbc018b6 --- /dev/null +++ b/applications/ffi_client/README.md @@ -0,0 +1,18 @@ +# NodeJS FFI Client + +Still a work in progress. + +## Install deps + +- `npm install` + +## Build FFI lib + +- Build the FFI lib: `cargo build -p tari_wallet_ffi --release --lib` +- Copy the lib into this folder: `cp target/release/libtari_wallet_ffi.dylib /path/to/here` + +_(.dylib for macOS, .so for Linux, .dll for windows)_ + +## Run + +- `npm start` - runs index.js file diff --git a/applications/ffi_client/index.js b/applications/ffi_client/index.js new file mode 100644 index 00000000000..bf1a47d3c25 --- /dev/null +++ b/applications/ffi_client/index.js @@ -0,0 +1,244 @@ +// this is nasty +// ¯\_(ツ)_/¯ + +const lib = require("./lib"); +const ref = require("ref-napi"); +const ffi = require("ffi-napi"); + +const i32 = ref.types.int32; +const u8 = ref.types.uint8; +const u64 = ref.types.uint64; +const bool = ref.types.bool; + +try { + let err = ref.alloc(i32); + // console.log(err); + + console.log("Create Tor transport..."); + let tor = lib.transport_tor_create( + "/ip4/127.0.0.1/tcp/9051", + ref.NULL, + 9051, + ref.NULL, + ref.NULL, + err + ); + + // todo: error handling + + console.log("Create Comms config..."); + let comms = lib.comms_config_create( + "/ip4/0.0.0.0/tcp/9838", + tor, + "wallet.dat", + "./wallet", + 30, + 600, + err + ); + + // callback_received_transaction: unsafe extern "C" fn(*mut TariPendingInboundTransaction), + const receivedTx = ffi.Callback("void", ["pointer"], function (ptr) { + console.log("receivedTx: ", ptr); + }); + // callback_received_transaction_reply: unsafe extern "C" fn(*mut TariCompletedTransaction), + const receivedTxReply = ffi.Callback("void", ["pointer"], function (ptr) { + console.log("receivedTxReply: ", ptr); + }); + // callback_received_finalized_transaction: unsafe extern "C" fn(*mut TariCompletedTransaction), + const receivedFinalized = ffi.Callback("void", ["pointer"], function (ptr) { + console.log("receivedFinalized: ", ptr); + }); + // callback_transaction_broadcast: unsafe extern "C" fn(*mut TariCompletedTransaction), + const txBroadcast = ffi.Callback("void", ["pointer"], function (ptr) { + console.log("txBroadcast: ", ptr); + }); + // callback_transaction_mined: unsafe extern "C" fn(*mut TariCompletedTransaction), + const txMined = ffi.Callback("void", ["pointer"], function (ptr) { + console.log("txMined: ", ptr); + }); + // callback_transaction_mined_unconfirmed: unsafe extern "C" fn(*mut TariCompletedTransaction, u64), + const txMinedUnconfirmed = ffi.Callback( + "void", + ["pointer"], + function (ptr, confirmations) { + console.log("txMinedUnconfirmed: ", ptr, confirmations); + } + ); + // callback_direct_send_result: unsafe extern "C" fn(c_ulonglong, bool), + const directSendResult = ffi.Callback("void", [u64, bool], function (i, j) { + console.log("directSendResult: ", i, j); + }); + // callback_store_and_forward_send_result: unsafe extern "C" fn(c_ulonglong, bool), + const safResult = ffi.Callback("void", [u64, bool], function (i, j) { + console.log("safResult: ", i, j); + }); + // callback_transaction_cancellation: unsafe extern "C" fn(*mut TariCompletedTransaction), + const txCancelled = ffi.Callback("void", ["pointer"], function (ptr) { + console.log("txCancelled: ", ptr); + }); + // callback_utxo_validation_complete: unsafe extern "C" fn(u64, u8), + const utxoValidation = ffi.Callback("void", [u64, u8], function (i, j) { + console.log("utxoValidation: ", i, j); + }); + // callback_stxo_validation_complete: unsafe extern "C" fn(u64, u8), + const stxoValidation = ffi.Callback("void", [u64, u8], function (i, j) { + console.log("stxoValidation: ", i, j); + }); + // callback_invalid_txo_validation_complete: unsafe extern "C" fn(u64, u8), + const itxoValidation = ffi.Callback("void", [u64, u8], function (i, j) { + console.log("itxoValidation: ", i, j); + }); + // callback_transaction_validation_complete: unsafe extern "C" fn(u64, u8), + const txValidation = ffi.Callback("void", [u64, u8], function (i, j) { + console.log("txValidation: ", i, j); + }); + // callback_saf_messages_received: unsafe extern "C" fn(), + const safsReceived = ffi.Callback("void", [], function () { + console.log("safsReceived"); + }); + + console.log("Create Wallet..."); + let wallet = lib.wallet_create( + comms, + "./wallet/logs/wallet.log", + 5, + 10240, + ref.NULL, // passphrase + ref.NULL, // seed words + receivedTx, + receivedTxReply, + receivedFinalized, + txBroadcast, + txMined, + txMinedUnconfirmed, + directSendResult, + safResult, + txCancelled, + utxoValidation, + stxoValidation, + itxoValidation, + txValidation, + safsReceived, + err + ); + + // look ma, zero confs! + lib.wallet_set_num_confirmations_required(wallet, 0, err); + // console.log(err.deref()); + let confs = lib.wallet_get_num_confirmations_required(wallet, err); + // console.log("confs", confs); + // console.log(err.deref()); + + let s = lib.wallet_get_seed_words(wallet, err); + // console.log("seeds words", s); + // console.log("err", err); + let seedWords = []; + for (let i = 0; i < 24; i++) { + let word = lib.seed_words_get_at(s, i, err); + // console.log("word", word); + // console.log("err", err.deref()); + seedWords.push(word); + } + console.log("seedWords", seedWords); + + function getBalance() { + try { + console.log("==="); + console.log("Balance"); + console.log("==="); + let available = lib.wallet_get_available_balance(wallet, err); + console.log(" available : ", available); + let pending_in = lib.wallet_get_pending_incoming_balance(wallet, err); + console.log(" pending_in: ", pending_in); + console.log("==="); + } catch (e) { + console.error("balance error: ", e); + } + } + let j = setInterval(getBalance, 10000); + + const u8ArrayFromHex = (hexString) => + new Uint8Array( + hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)) + ); + const u8ArrayToHex = (bytes) => + bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, "0"), ""); + + // let myPublicKey = lib.wallet_get_public_key(wallet, err); + // console.log(myPublicKey); + // console.log(err); + // let temp = lib.public_key_get_bytes(myPublicKey, err); + // console.log("temp", temp.deref()); + + // process.exit(); + + let publicKeyHex = + "0c3fe3c23866ed3827e1cd72aae0c9d364d860d597993104e90d9a9401e52f05"; + let publicKeyBytes = u8ArrayFromHex(publicKeyHex); + let publicKeyByteVector = lib.byte_vector_create(publicKeyBytes, 32, err); + let publicKey = lib.public_key_create(publicKeyByteVector, err); + + console.log("Set base node peer...", publicKeyHex); + lib.wallet_add_base_node_peer( + wallet, + publicKey, + "/onion3/2m2xnylrsqbaozsndkbmfisxxbwh2vgvs6oyfak2qah4snnxykrf7zad:18141", + err + ); + + setTimeout(function () { + try { + console.log("start tx validation"); + let id = lib.wallet_start_transaction_validation(wallet, err); + console.log("tx validation request id", id); + + console.log("start utxo validation"); + id = lib.wallet_start_utxo_validation(wallet, err); + console.log("utxo validation request id", id); + } catch (e) { + console.error("validation error: ", e); + } + }, 5000); + + console.log("Wallet running..."); + console.log("Ctrl+C to quit."); + process.stdin.resume(); + + function exitHandler(options, exitCode) { + try { + console.log("exitHandler"); + console.log("options", options); + console.log("exitCode", exitCode); + if (options.cleanup) { + console.log("Exiting..."); + lib.wallet_destroy(wallet); + console.log("Goodbye."); + } + if (exitCode || exitCode === 0) console.log("\nExit code:", exitCode); + if (options.exit) process.exit(); + } catch (e) { + console.error("exitHandler error", e); + } + } + + process.on("exit", exitHandler.bind(null, { cleanup: true, signal: "exit" })); + process.on( + "SIGINT", + exitHandler.bind(null, { exit: true, signal: "SIGINT" }) + ); + process.on( + "SIGUSR1", + exitHandler.bind(null, { exit: true, signal: "SIGUSR1" }) + ); + process.on( + "SIGUSR2", + exitHandler.bind(null, { exit: true, signal: "SIGUSR2" }) + ); + process.on( + "uncaughtException", + exitHandler.bind(null, { exit: true, signal: "uncaughtException" }) + ); +} catch (e) { + console.error("ERROR: ", e); +} diff --git a/applications/ffi_client/lib/index.js b/applications/ffi_client/lib/index.js new file mode 100644 index 00000000000..db77ed0a1f8 --- /dev/null +++ b/applications/ffi_client/lib/index.js @@ -0,0 +1,81 @@ +const ffi = require("ffi-napi"); + +const { + strPtr, + errPtr, + transportRef, + commsConfigRef, + walletRef, + fn, + bool, + u8, + u16, + i32, + u32, + u64, + u8Array, + u8ArrayPtr, + byteVectorRef, + publicKeyRef, + strArray, + strArrayPtr, +} = require("./types"); + +// todo: check if the lib exists first + +console.log("Set up library..."); +const libWallet = ffi.Library("./libtari_wallet_ffi.dylib", { + byte_vector_create: [byteVectorRef, [u8ArrayPtr, u32, errPtr]], + comms_config_create: [ + commsConfigRef, + ["string", transportRef, "string", "string", u64, u64, errPtr], + ], + public_key_create: [publicKeyRef, [byteVectorRef, errPtr]], + public_key_get_bytes: [u8ArrayPtr, [publicKeyRef, errPtr]], + seed_words_create: [strPtr, []], + seed_words_get_at: ["string", [strArrayPtr, u32, errPtr]], + seed_words_push_word: [u8, [strPtr, "string", errPtr]], + transport_tor_create: [ + transportRef, + ["string", u8ArrayPtr, u16, "string", "string", errPtr], + ], + wallet_add_base_node_peer: [bool, [walletRef, u8ArrayPtr, "string", errPtr]], + wallet_create: [ + walletRef, + [ + commsConfigRef, + "string", + u32, + u32, + "string", + "string", + fn, + fn, + fn, + fn, + fn, + fn, + fn, + fn, + fn, + fn, + fn, + fn, + fn, + fn, + errPtr, + ], + ], + wallet_destroy: ["void", [walletRef]], + wallet_get_available_balance: [u64, [walletRef, errPtr]], + wallet_get_pending_incoming_balance: [u64, [walletRef, errPtr]], + wallet_get_public_key: [publicKeyRef, [walletRef, errPtr]], + wallet_get_seed_words: [strArrayPtr, [walletRef, errPtr]], + wallet_get_num_confirmations_required: [u64, [walletRef, errPtr]], + wallet_set_num_confirmations_required: ["void", [walletRef, u64, errPtr]], + wallet_start_transaction_validation: [u64, [walletRef, errPtr]], + wallet_start_utxo_validation: [u64, [walletRef, errPtr]], + wallet_start_recovery: [bool, [walletRef, publicKeyRef, fn, errPtr]], +}); + +module.exports = libWallet; diff --git a/applications/ffi_client/lib/types.js b/applications/ffi_client/lib/types.js new file mode 100644 index 00000000000..7815eb5ab83 --- /dev/null +++ b/applications/ffi_client/lib/types.js @@ -0,0 +1,42 @@ +const ref = require("ref-napi"); +const ArrayType = require("ref-array-napi"); + +const strPtr = ref.refType(ref.types.CString); +const errPtr = ref.refType(ref.types.int32); +const transportRef = ref.refType(ref.types.void); +const commsConfigRef = ref.refType(ref.types.void); +const walletRef = ref.refType(ref.types.void); +const fn = ref.refType(ref.types.void); +const bool = ref.types.bool; +const u8 = ref.types.uint8; +const u16 = ref.types.uint16; +const i32 = ref.types.int32; +const u32 = ref.types.uint32; +const u64 = ref.types.uint64; +const u8Array = ArrayType(ref.types.uint8); +const u8ArrayPtr = ref.refType(u8Array); +const byteVectorRef = ref.refType(u8Array); +const publicKeyRef = ref.refType(ref.types.void); +const strArray = ArrayType("string"); +const strArrayPtr = ref.refType(ArrayType("string")); + +module.exports = { + strPtr, + errPtr, + transportRef, + commsConfigRef, + walletRef, + fn, + bool, + u8, + u16, + i32, + u32, + u64, + u8Array, + u8ArrayPtr, + byteVectorRef, + publicKeyRef, + strArray, + strArrayPtr, +}; diff --git a/applications/ffi_client/package-lock.json b/applications/ffi_client/package-lock.json new file mode 100644 index 00000000000..86709b835e1 --- /dev/null +++ b/applications/ffi_client/package-lock.json @@ -0,0 +1,252 @@ +{ + "name": "ffi_client", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "array-index": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-index/-/array-index-1.0.0.tgz", + "integrity": "sha1-7FanSe4QPk4Ix5C5w1PfFgVbl/k=", + "requires": { + "debug": "^2.2.0", + "es6-symbol": "^3.0.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "requires": { + "ms": "2.1.2" + } + }, + "es5-ext": { + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "ext": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", + "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "requires": { + "type": "^2.0.0" + }, + "dependencies": { + "type": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.5.0.tgz", + "integrity": "sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==" + } + } + }, + "ffi-napi": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/ffi-napi/-/ffi-napi-4.0.3.tgz", + "integrity": "sha512-PMdLCIvDY9mS32RxZ0XGb95sonPRal8aqRhLbeEtWKZTe2A87qRFG9HjOhvG8EX2UmQw5XNRMIOT+1MYlWmdeg==", + "requires": { + "debug": "^4.1.1", + "get-uv-event-loop-napi-h": "^1.0.5", + "node-addon-api": "^3.0.0", + "node-gyp-build": "^4.2.1", + "ref-napi": "^2.0.1 || ^3.0.2", + "ref-struct-di": "^1.1.0" + } + }, + "get-symbol-from-current-process-h": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-from-current-process-h/-/get-symbol-from-current-process-h-1.0.2.tgz", + "integrity": "sha512-syloC6fsCt62ELLrr1VKBM1ggOpMdetX9hTrdW77UQdcApPHLmf7CI7OKcN1c9kYuNxKcDe4iJ4FY9sX3aw2xw==" + }, + "get-uv-event-loop-napi-h": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/get-uv-event-loop-napi-h/-/get-uv-event-loop-napi-h-1.0.6.tgz", + "integrity": "sha512-t5c9VNR84nRoF+eLiz6wFrEp1SE2Acg0wS+Ysa2zF0eROes+LzOfuTaVHxGy8AbS8rq7FHEJzjnCZo1BupwdJg==", + "requires": { + "get-symbol-from-current-process-h": "^1.0.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + }, + "node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==" + }, + "node-gyp-build": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", + "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==" + }, + "ref-array-napi": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/ref-array-napi/-/ref-array-napi-1.2.2.tgz", + "integrity": "sha512-EGQzUQpyqD/hN9eIn3uF68UPBmwJXdWkumHCmvK3ncjw128bkjd8TbJ51ur+2PZ4UrfCOQCcPQkuWZ6mNHch9A==", + "requires": { + "array-index": "1", + "debug": "2", + "ref-napi": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "ref-napi": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/ref-napi/-/ref-napi-3.0.3.tgz", + "integrity": "sha512-LiMq/XDGcgodTYOMppikEtJelWsKQERbLQsYm0IOOnzhwE9xYZC7x8txNnFC9wJNOkPferQI4vD4ZkC0mDyrOA==", + "requires": { + "debug": "^4.1.1", + "get-symbol-from-current-process-h": "^1.0.2", + "node-addon-api": "^3.0.0", + "node-gyp-build": "^4.2.1" + } + }, + "ref-struct-di": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ref-struct-di/-/ref-struct-di-1.1.1.tgz", + "integrity": "sha512-2Xyn/0Qgz89VT+++WP0sTosdm9oeowLP23wRJYhG4BFdMUrLj3jhwHZNEytYNYgtPKLNTP3KJX4HEgBvM1/Y2g==", + "requires": { + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "ref-struct-napi": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ref-struct-napi/-/ref-struct-napi-1.1.1.tgz", + "integrity": "sha512-YgS5/d7+kT5zgtySYI5ieH0hREdv+DabgDvoczxsui0f9VLm0rrDcWEj4DHKehsH+tJnVMsLwuyctWgvdEcVRw==", + "requires": { + "debug": "2", + "ref-napi": "^1.4.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node-addon-api": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" + }, + "ref-napi": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/ref-napi/-/ref-napi-1.5.2.tgz", + "integrity": "sha512-hwyNmWpUkt1bDWDW4aiwCoC+SJfJO69UIdjqssNqdaS0sYJpgqzosGg/rLtk69UoQ8drZdI9yyQefM7eEMM3Gw==", + "requires": { + "debug": "^3.1.0", + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.1" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + } + } + }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" + } + } +} diff --git a/applications/ffi_client/package.json b/applications/ffi_client/package.json new file mode 100644 index 00000000000..dd4496066e9 --- /dev/null +++ b/applications/ffi_client/package.json @@ -0,0 +1,20 @@ +{ + "name": "ffi_client", + "version": "1.0.0", + "description": "", + "author": "The Tari Development Community", + "main": "index.js", + "scripts": { + "start": "node index.js", + "recovery": "node recovery.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "license": "MIT", + "dependencies": { + "ffi-napi": "^4.0.3", + "ref-array-napi": "^1.2.2", + "ref-napi": "^3.0.3", + "ref-struct-napi": "^1.1.1" + }, + "devDependencies": {} +} diff --git a/applications/ffi_client/recovery.js b/applications/ffi_client/recovery.js new file mode 100644 index 00000000000..0c51d3daa3b --- /dev/null +++ b/applications/ffi_client/recovery.js @@ -0,0 +1,236 @@ +const lib = require("./lib"); +const ref = require("ref-napi"); +const ffi = require("ffi-napi"); + +const i32 = ref.types.int32; +const u8 = ref.types.uint8; +const u64 = ref.types.uint64; +const bool = ref.types.bool; + +try { + let seeds = []; + if (!process.env.SEED_WORDS) { + console.error( + "Set your SEED_WORDS env var to your list of seed words separated by single spaces. eg:" + ); + console.error('SEED_WORDS="one two three ..." npm run recovery'); + process.exit(); + } else { + seeds = process.env.SEED_WORDS.split(" "); + } + + if (seeds.length !== 24) { + console.error( + `Wrong number of seed words: expected 24, got ${seeds.length}.` + ); + process.exit(); + } + + let err = ref.alloc(i32); + // console.log(err); + + console.log("Create Tor transport..."); + let tor = lib.transport_tor_create( + "/ip4/127.0.0.1/tcp/9051", + ref.NULL, + 9051, + ref.NULL, + ref.NULL, + err + ); + + // todo: error handling + + console.log("Create Comms config..."); + let comms = lib.comms_config_create( + "/ip4/0.0.0.0/tcp/9838", + tor, + "wallet.dat", + "./recovery", + 30, + 600, + err + ); + + // callback_received_transaction: unsafe extern "C" fn(*mut TariPendingInboundTransaction), + const receivedTx = ffi.Callback("void", ["pointer"], function (ptr) { + console.log("receivedTx: ", ptr); + }); + // callback_received_transaction_reply: unsafe extern "C" fn(*mut TariCompletedTransaction), + const receivedTxReply = ffi.Callback("void", ["pointer"], function (ptr) { + console.log("receivedTxReply: ", ptr); + }); + // callback_received_finalized_transaction: unsafe extern "C" fn(*mut TariCompletedTransaction), + const receivedFinalized = ffi.Callback("void", ["pointer"], function (ptr) { + console.log("receivedFinalized: ", ptr); + }); + // callback_transaction_broadcast: unsafe extern "C" fn(*mut TariCompletedTransaction), + const txBroadcast = ffi.Callback("void", ["pointer"], function (ptr) { + console.log("txBroadcast: ", ptr); + }); + // callback_transaction_mined: unsafe extern "C" fn(*mut TariCompletedTransaction), + const txMined = ffi.Callback("void", ["pointer"], function (ptr) { + console.log("txMined: ", ptr); + }); + // callback_transaction_mined_unconfirmed: unsafe extern "C" fn(*mut TariCompletedTransaction, u64), + const txMinedUnconfirmed = ffi.Callback( + "void", + ["pointer"], + function (ptr, confirmations) { + console.log("txMinedUnconfirmed: ", ptr, confirmations); + } + ); + // callback_direct_send_result: unsafe extern "C" fn(c_ulonglong, bool), + const directSendResult = ffi.Callback("void", [u64, bool], function (i, j) { + console.log("directSendResult: ", i, j); + }); + // callback_store_and_forward_send_result: unsafe extern "C" fn(c_ulonglong, bool), + const safResult = ffi.Callback("void", [u64, bool], function (i, j) { + console.log("safResult: ", i, j); + }); + // callback_transaction_cancellation: unsafe extern "C" fn(*mut TariCompletedTransaction), + const txCancelled = ffi.Callback("void", ["pointer"], function (ptr) { + console.log("txCancelled: ", ptr); + }); + // callback_utxo_validation_complete: unsafe extern "C" fn(u64, u8), + const utxoValidation = ffi.Callback("void", [u64, u8], function (i, j) { + console.log("utxoValidation: ", i, j); + }); + // callback_stxo_validation_complete: unsafe extern "C" fn(u64, u8), + const stxoValidation = ffi.Callback("void", [u64, u8], function (i, j) { + console.log("stxoValidation: ", i, j); + }); + // callback_invalid_txo_validation_complete: unsafe extern "C" fn(u64, u8), + const itxoValidation = ffi.Callback("void", [u64, u8], function (i, j) { + console.log("itxoValidation: ", i, j); + }); + // callback_transaction_validation_complete: unsafe extern "C" fn(u64, u8), + const txValidation = ffi.Callback("void", [u64, u8], function (i, j) { + console.log("txValidation: ", i, j); + }); + // callback_saf_messages_received: unsafe extern "C" fn(), + const safsReceived = ffi.Callback("void", [], function () { + console.log("safsReceived"); + }); + + const recovery = ffi.Callback("void", [u64, u64], function (current, total) { + console.log("recovery scanning UTXOs: ", { current }, { total }); + getBalance(); + if (current == total) { + process.exit(); + } + }); + + const seedWords = lib.seed_words_create(); + + for (const word of seeds) { + // console.log(word); + let pushResult = lib.seed_words_push_word(seedWords, word, err); + // console.log("r", pushResult); + // console.log("err", err); + } + + console.log("Create Wallet from seed words..."); + let wallet = lib.wallet_create( + comms, + "./recovery/logs/wallet.log", + 5, + 10240, + ref.NULL, // passphrase + seedWords, // seed words + receivedTx, + receivedTxReply, + receivedFinalized, + txBroadcast, + txMined, + txMinedUnconfirmed, + directSendResult, + safResult, + txCancelled, + utxoValidation, + stxoValidation, + itxoValidation, + txValidation, + safsReceived, + err + ); + + getBalance(); + + const u8ArrayFromHex = (hexString) => + new Uint8Array( + hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)) + ); + let publicKeyHex = + "0c3fe3c23866ed3827e1cd72aae0c9d364d860d597993104e90d9a9401e52f05"; + let publicKeyBytes = u8ArrayFromHex(publicKeyHex); + let publicKeyByteVector = lib.byte_vector_create(publicKeyBytes, 32, err); + let publicKey = lib.public_key_create(publicKeyByteVector, err); + + console.log("Set base node peer...", publicKeyHex); + lib.wallet_add_base_node_peer( + wallet, + publicKey, + "/onion3/2m2xnylrsqbaozsndkbmfisxxbwh2vgvs6oyfak2qah4snnxykrf7zad:18141", + err + ); + + console.log("Starting recovery..."); + const temp = lib.wallet_start_recovery(wallet, publicKey, recovery, err); + console.log("started", temp, err.deref()); + + process.stdin.resume(); + + function exitHandler(options, exitCode) { + try { + console.log("exitHandler"); + console.log("options", options); + console.log("exitCode", exitCode); + if (options.cleanup) { + getBalance(); + console.log("Exiting..."); + lib.wallet_destroy(wallet); + console.log("Goodbye."); + } + if (exitCode || exitCode === 0) console.log("\nExit code:", exitCode); + if (options.exit) process.exit(); + } catch (e) { + console.error("exitHandler error", e); + } + } + + process.on("exit", exitHandler.bind(null, { cleanup: true, signal: "exit" })); + process.on( + "SIGINT", + exitHandler.bind(null, { exit: true, signal: "SIGINT" }) + ); + process.on( + "SIGUSR1", + exitHandler.bind(null, { exit: true, signal: "SIGUSR1" }) + ); + process.on( + "SIGUSR2", + exitHandler.bind(null, { exit: true, signal: "SIGUSR2" }) + ); + process.on( + "uncaughtException", + exitHandler.bind(null, { exit: true, signal: "uncaughtException" }) + ); + + function getBalance() { + try { + console.log("==="); + console.log("Balance"); + console.log("==="); + let available = lib.wallet_get_available_balance(wallet, err); + console.log(" available : ", available); + let pending_in = lib.wallet_get_pending_incoming_balance(wallet, err); + console.log(" pending_in: ", pending_in); + console.log("==="); + } catch (e) { + console.error("balance error: ", e); + } + } +} catch (e) { + console.error("ERROR: ", e); +} diff --git a/applications/tari_console_wallet/src/recovery.rs b/applications/tari_console_wallet/src/recovery.rs index de0c8ee03d5..9205f13cd01 100644 --- a/applications/tari_console_wallet/src/recovery.rs +++ b/applications/tari_console_wallet/src/recovery.rs @@ -35,6 +35,8 @@ use tari_wallet::{ WalletSqlite, }; +use crate::wallet_modes::PeerConfig; + pub const LOG_TARGET: &str = "wallet::recovery"; /// Prompt the user to input their seed words in a single line. @@ -76,13 +78,20 @@ pub fn get_private_key_from_seed_words(seed_words: Vec) -> Result) -> Result<(), ExitCodes> { +pub async fn wallet_recovery(wallet: &WalletSqlite, base_node_config: &PeerConfig) -> Result<(), ExitCodes> { println!("\nPress Ctrl-C to stop the recovery process\n"); // We dont care about the shutdown signal here, so we just create one let shutdown = Shutdown::new(); let shutdown_signal = shutdown.to_signal(); + + let peer_public_keys = base_node_config + .get_all_peers() + .iter() + .map(|peer| peer.public_key.clone()) + .collect(); + let mut recovery_task = UtxoScannerService::::builder() - .with_peer_seeds(peer_seeds) + .with_peers(peer_public_keys) .with_retry_limit(10) .build_with_wallet(wallet, shutdown_signal); diff --git a/applications/tari_console_wallet/src/wallet_modes.rs b/applications/tari_console_wallet/src/wallet_modes.rs index 0c4ddc744d8..2d386277ee2 100644 --- a/applications/tari_console_wallet/src/wallet_modes.rs +++ b/applications/tari_console_wallet/src/wallet_modes.rs @@ -94,6 +94,25 @@ impl PeerConfig { )) } } + + pub fn get_all_peers(&self) -> Vec { + let num_peers = self.base_node_peers.len(); + let num_seeds = self.peer_seeds.len(); + + let mut peers = if let Some(peer) = self.base_node_custom.clone() { + let mut peers = Vec::with_capacity(1 + num_peers + num_seeds); + peers.push(peer); + peers + } else { + let mut peers = Vec::with_capacity(num_peers + num_seeds); + peers + }; + + peers.extend(self.base_node_peers.clone()); + peers.extend(self.peer_seeds.clone()); + + peers + } } pub fn command_mode( @@ -182,13 +201,8 @@ pub fn recovery_mode( notify_script: Option, wallet_mode: WalletMode, ) -> Result<(), ExitCodes> { - let peer_seed_public_keys: Vec = base_node_config - .peer_seeds - .iter() - .map(|f| f.public_key.clone()) - .collect(); println!("Starting recovery..."); - match handle.block_on(wallet_recovery(&wallet, peer_seed_public_keys)) { + match handle.block_on(wallet_recovery(&wallet, &base_node_config)) { Ok(_) => println!("Wallet recovered!"), Err(e) => { error!(target: LOG_TARGET, "Recovery failed: {}", e); diff --git a/base_layer/wallet/src/utxo_scanner_service/mod.rs b/base_layer/wallet/src/utxo_scanner_service/mod.rs index 8e85cf837a7..98f64ec89e9 100644 --- a/base_layer/wallet/src/utxo_scanner_service/mod.rs +++ b/base_layer/wallet/src/utxo_scanner_service/mod.rs @@ -102,10 +102,9 @@ where T: WalletBackend + 'static let transaction_service = handles.expect_handle::(); let output_manager_service = handles.expect_handle::(); let connectivity_manager = handles.expect_handle::(); - let peer_seeds = Vec::new(); let scanning_service = UtxoScannerService::::builder() - .with_peer_seeds(peer_seeds) + .with_peers(vec![]) .with_retry_limit(10) .with_scanning_interval(interval) .with_mode(UtxoScannerMode::Scanning) diff --git a/base_layer/wallet/src/utxo_scanner_service/utxo_scanning.rs b/base_layer/wallet/src/utxo_scanner_service/utxo_scanning.rs index c12a6c70a8d..6692978ae04 100644 --- a/base_layer/wallet/src/utxo_scanner_service/utxo_scanning.rs +++ b/base_layer/wallet/src/utxo_scanner_service/utxo_scanning.rs @@ -97,7 +97,7 @@ impl Default for UtxoScannerMode { #[derive(Debug, Default, Clone)] pub struct UtxoScannerServiceBuilder { retry_limit: usize, - peer_seeds: Vec, + peers: Vec, mode: Option, scanning_interval: Option, } @@ -127,8 +127,8 @@ impl UtxoScannerServiceBuilder { self } - pub fn with_peer_seeds(&mut self, peer_seeds: Vec) -> &mut Self { - self.peer_seeds = peer_seeds; + pub fn with_peers(&mut self, peer_public_keys: Vec) -> &mut Self { + self.peers = peer_public_keys; self } @@ -160,7 +160,7 @@ impl UtxoScannerServiceBuilder { .scanning_interval .unwrap_or_else(|| Duration::from_secs(60 * 60 * 12)); UtxoScannerService::new( - self.peer_seeds.drain(..).collect(), + self.peers.drain(..).collect(), self.retry_limit, self.mode.clone().unwrap_or_default(), resources, @@ -197,7 +197,7 @@ impl UtxoScannerServiceBuilder { .scanning_interval .unwrap_or_else(|| Duration::from_secs(60 * 60 * 12)); UtxoScannerService::new( - self.peer_seeds.drain(..).collect(), + self.peers.drain(..).collect(), self.retry_limit, self.mode.clone().unwrap_or_default(), resources, @@ -871,8 +871,8 @@ where TBackend: WalletBackend + 'static }); }, _ = shutdown => { - // this will stop the task if its running, and let that thread exit gracefully - self.is_running.store(false, Ordering::Relaxed); + // this will stop the task if its running, and let that thread exit gracefully + self.is_running.store(false, Ordering::Relaxed); info!(target: LOG_TARGET, "UTXO scanning service shutting down because it received the shutdown signal"); return Ok(()); } diff --git a/base_layer/wallet/src/wallet.rs b/base_layer/wallet/src/wallet.rs index f66e1e51034..c01e0f0817f 100644 --- a/base_layer/wallet/src/wallet.rs +++ b/base_layer/wallet/src/wallet.rs @@ -466,11 +466,11 @@ async fn read_or_create_master_secret_key( let master_secret_key = match recovery_master_key { None => match db.get_master_secret_key().await? { None => { - let sk = CommsSecretKey::random(&mut OsRng); - db.set_master_secret_key(sk.clone()).await?; - sk + let secret_key = CommsSecretKey::random(&mut OsRng); + db.set_master_secret_key(secret_key.clone()).await?; + secret_key }, - Some(sk) => sk, + Some(secret_key) => secret_key, }, Some(recovery_key) => { db.set_master_secret_key(recovery_key.clone()).await?; diff --git a/base_layer/wallet_ffi/mobile_build.sh b/base_layer/wallet_ffi/mobile_build.sh index efee51f4c89..92b468f3d71 100755 --- a/base_layer/wallet_ffi/mobile_build.sh +++ b/base_layer/wallet_ffi/mobile_build.sh @@ -81,7 +81,7 @@ if [ -n "${DEPENDENCIES}" ] && [ "${BUILD_IOS}" -eq 1 ] && [ "${MACHINE}" == "Ma cargo-lipo lipo --release > ${IOS_LOG_PATH}/cargo.txt 2>&1 cd ../.. cd target || exit - cd universal || exit + cd aarch64-apple-ios || exit cd release || exit cp libtari_wallet_ffi.a "${DEPENDENCIES}/MobileWallet/TariLib/" cd ../../.. || exit diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index 035eef0b978..ae3208b122a 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -251,6 +251,7 @@ pub struct ByteVector(Vec); // declared like this so that it can be exp #[derive(Debug, PartialEq)] pub struct EmojiSet(Vec); +#[derive(Debug, PartialEq)] pub struct TariSeedWords(Vec); pub struct TariWallet { @@ -829,7 +830,7 @@ pub unsafe extern "C" fn seed_words_create() -> *mut TariSeedWords { /// as an out parameter. /// /// ## Returns -/// `c_uint` - Returns number of elements in , zero if contacts is null +/// `c_uint` - Returns number of elements in seed_words, zero if seed_words is null /// /// # Safety /// None @@ -2740,6 +2741,7 @@ unsafe fn init_logging(log_path: *const c_char, num_rolling_log_files: c_uint, s Err(_) => warn!(target: LOG_TARGET, "Logging has already been initialized"), } } + /// Creates a TariWallet /// /// ## Arguments @@ -2753,12 +2755,13 @@ unsafe fn init_logging(log_path: *const c_char, num_rolling_log_files: c_uint, s /// `passphrase` - An optional string that represents the passphrase used to /// encrypt/decrypt the databases for this wallet. If it is left Null no encryption is used. If the databases have been /// encrypted then the correct passphrase is required or this function will fail. -/// `callback_received_transaction` - The callback function pointer matching the -/// function signature. This will be called when an inbound transaction is received. -/// `callback_received_transaction_reply` - The callback function pointer matching the function signature. This will be -/// called when a reply is received for a pending outbound transaction -/// `callback_received_finalized_transaction` - The callback function pointer matching the function signature. This will -/// be called when a Finalized version on an Inbound transaction is received +/// `seed_words` - An optional instance of TariSeedWords, used to create a wallet for recovery purposes. +/// If this is null, then a new master key is created for the wallet. +/// `callback_received_transaction` - The callback function pointer matching the function signature. This will be +/// called when an inbound transaction is received. `callback_received_transaction_reply` - The callback function +/// pointer matching the function signature. This will be called when a reply is received for a pending outbound +/// transaction `callback_received_finalized_transaction` - The callback function pointer matching the function +/// signature. This will be called when a Finalized version on an Inbound transaction is received /// `callback_transaction_broadcast` - The callback function pointer matching the function signature. This will be /// called when a Finalized transaction is detected a Broadcast to a base node mempool. /// `callback_transaction_mined` - The callback function pointer matching the function signature. This will be called @@ -2798,6 +2801,7 @@ pub unsafe extern "C" fn wallet_create( num_rolling_log_files: c_uint, size_per_log_file_bytes: c_uint, passphrase: *const c_char, + seed_words: *const TariSeedWords, callback_received_transaction: unsafe extern "C" fn(*mut TariPendingInboundTransaction), callback_received_transaction_reply: unsafe extern "C" fn(*mut TariCompletedTransaction), callback_received_finalized_transaction: unsafe extern "C" fn(*mut TariCompletedTransaction), @@ -2814,6 +2818,8 @@ pub unsafe extern "C" fn wallet_create( callback_saf_messages_received: unsafe extern "C" fn(), error_out: *mut c_int, ) -> *mut TariWallet { + use tari_key_manager::mnemonic::Mnemonic; + let mut error = 0; ptr::swap(error_out, &mut error as *mut c_int); if config.is_null() { @@ -2836,6 +2842,20 @@ pub unsafe extern "C" fn wallet_create( None }; + let recovery_master_key = if seed_words.is_null() { + None + } else { + 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 = LibWalletError::from(e).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + } + }; + let mut runtime = match Runtime::new() { Ok(r) => r, Err(e) => { @@ -2862,7 +2882,7 @@ pub unsafe extern "C" fn wallet_create( return ptr::null_mut(); }, }; - let wallet_db = WalletDatabase::new(wallet_backend); + let wallet_database = WalletDatabase::new(wallet_backend); debug!(target: LOG_TARGET, "Databases Initialized"); @@ -2870,7 +2890,7 @@ pub unsafe extern "C" fn wallet_create( let mut comms_config = (*config).clone(); comms_config.transport_type = match comms_config.transport_type { Tor(mut tor_config) => { - tor_config.identity = match runtime.block_on(wallet_db.get_tor_id()) { + tor_config.identity = match runtime.block_on(wallet_database.get_tor_id()) { Ok(Some(v)) => Some(Box::new(v)), _ => None, }; @@ -2880,28 +2900,29 @@ pub unsafe extern "C" fn wallet_create( }; let shutdown = Shutdown::new(); + let wallet_config = WalletConfig::new( + comms_config, + factories, + Some(TransactionServiceConfig { + direct_send_timeout: (*config).dht.discovery_request_timeout, + ..Default::default() + }), + None, + Network::Weatherwax, + None, + None, + None, + None, + ); w = runtime.block_on(Wallet::start( - WalletConfig::new( - comms_config, - factories, - Some(TransactionServiceConfig { - direct_send_timeout: (*config).dht.discovery_request_timeout, - ..Default::default() - }), - None, - Network::Weatherwax, - None, - None, - None, - None, - ), - wallet_db, + wallet_config, + wallet_database, transaction_backend.clone(), output_manager_backend, contacts_backend, shutdown.to_signal(), - None, + recovery_master_key, )); match w { @@ -4901,7 +4922,7 @@ pub unsafe extern "C" fn wallet_get_seed_words(wallet: *mut TariWallet, error_ou .runtime .block_on((*wallet).wallet.output_manager_service.get_seed_words()) { - Ok(sw) => Box::into_raw(Box::new(TariSeedWords(sw))), + Ok(seed_words) => Box::into_raw(Box::new(TariSeedWords(seed_words))), Err(e) => { error = LibWalletError::from(WalletError::OutputManagerError(e)).code; ptr::swap(error_out, &mut error as *mut c_int); @@ -5240,7 +5261,7 @@ pub unsafe extern "C" fn wallet_is_recovery_in_progress(wallet: *mut TariWallet, } } -/// Check if a Wallet has the data of an In Progress Recovery in its database. +/// Starts the Wallet recovery process. /// /// ## Arguments /// `wallet` - The TariWallet pointer. @@ -5279,9 +5300,9 @@ pub unsafe extern "C" fn wallet_start_recovery( } let shutdown_signal = (*wallet).shutdown.to_signal(); - let peer_seed_public_keys: Vec = vec![(*base_node_public_key).clone()]; + let peer_public_keys: Vec = vec![(*base_node_public_key).clone()]; let mut recovery_task = UtxoScannerService::::builder() - .with_peer_seeds(peer_seed_public_keys) + .with_peers(peer_public_keys) .with_retry_limit(10) .build_with_wallet(&(*wallet).wallet, shutdown_signal); @@ -6089,6 +6110,7 @@ mod test { 2, 10000, ptr::null(), + ptr::null(), received_tx_callback, received_tx_reply_callback, received_tx_finalized_callback, @@ -6136,6 +6158,7 @@ mod test { 0, 0, ptr::null(), + ptr::null(), received_tx_callback_bob, received_tx_reply_callback_bob, received_tx_finalized_callback_bob, @@ -6659,6 +6682,7 @@ mod test { 0, 0, ptr::null(), + ptr::null(), received_tx_callback, received_tx_reply_callback, received_tx_finalized_callback, @@ -6697,6 +6721,7 @@ mod test { 0, 0, ptr::null(), + ptr::null(), received_tx_callback, received_tx_reply_callback, received_tx_finalized_callback, @@ -6794,6 +6819,7 @@ mod test { 0, 0, ptr::null(), + ptr::null(), received_tx_callback, received_tx_reply_callback, received_tx_finalized_callback, @@ -6841,6 +6867,7 @@ mod test { 0, 0, ptr::null(), + ptr::null(), received_tx_callback, received_tx_reply_callback, received_tx_finalized_callback, @@ -6870,6 +6897,7 @@ mod test { 0, 0, wrong_passphrase_const_str, + ptr::null(), received_tx_callback, received_tx_reply_callback, received_tx_finalized_callback, @@ -6894,6 +6922,7 @@ mod test { 0, 0, passphrase_const_str, + ptr::null(), received_tx_callback, received_tx_reply_callback, received_tx_finalized_callback, @@ -6938,6 +6967,7 @@ mod test { 0, 0, ptr::null(), + ptr::null(), received_tx_callback, received_tx_reply_callback, received_tx_finalized_callback, @@ -7005,6 +7035,7 @@ mod test { 0, 0, ptr::null(), + ptr::null(), received_tx_callback, received_tx_reply_callback, received_tx_finalized_callback, @@ -7119,6 +7150,110 @@ mod test { ); } } + + // create a new wallet + let db_name = CString::new(random_string(8).as_str()).unwrap(); + let db_name_str: *const c_char = CString::into_raw(db_name) as *const c_char; + let temp_dir = tempdir().unwrap(); + let db_path = CString::new(temp_dir.path().to_str().unwrap()).unwrap(); + let db_path_str: *const c_char = CString::into_raw(db_path) as *const c_char; + let transport_type = transport_memory_create(); + let address = transport_memory_get_address(transport_type, error_ptr); + let address_str = CStr::from_ptr(address).to_str().unwrap().to_owned(); + let address_str = CString::new(address_str).unwrap().into_raw() as *const c_char; + + let config = comms_config_create( + address_str, + transport_type, + db_name_str, + db_path_str, + 20, + 10800, + error_ptr, + ); + + let wallet = wallet_create( + config, + ptr::null(), + 0, + 0, + ptr::null(), + ptr::null(), + received_tx_callback, + received_tx_reply_callback, + received_tx_finalized_callback, + broadcast_callback, + mined_callback, + mined_unconfirmed_callback, + direct_send_callback, + store_and_forward_send_callback, + tx_cancellation_callback, + utxo_validation_complete_callback, + stxo_validation_complete_callback, + invalid_txo_validation_complete_callback, + transaction_validation_complete_callback, + saf_messages_received_callback, + error_ptr, + ); + + let seed_words = wallet_get_seed_words(wallet, error_ptr); + assert_eq!(error, 0); + let public_key = wallet_get_public_key(wallet, error_ptr); + assert_eq!(error, 0); + + // use seed words to create recovery wallet + let db_name = CString::new(random_string(8).as_str()).unwrap(); + let db_name_str: *const c_char = CString::into_raw(db_name) as *const c_char; + let temp_dir = tempdir().unwrap(); + let db_path = CString::new(temp_dir.path().to_str().unwrap()).unwrap(); + let db_path_str: *const c_char = CString::into_raw(db_path) as *const c_char; + let transport_type = transport_memory_create(); + let address = transport_memory_get_address(transport_type, error_ptr); + let address_str = CStr::from_ptr(address).to_str().unwrap().to_owned(); + let address_str = CString::new(address_str).unwrap().into_raw() as *const c_char; + + let config = comms_config_create( + address_str, + transport_type, + db_name_str, + db_path_str, + 20, + 10800, + error_ptr, + ); + + let recovered_wallet = wallet_create( + config, + ptr::null(), + 0, + 0, + ptr::null(), + seed_words, + received_tx_callback, + received_tx_reply_callback, + received_tx_finalized_callback, + broadcast_callback, + mined_callback, + mined_unconfirmed_callback, + direct_send_callback, + store_and_forward_send_callback, + tx_cancellation_callback, + utxo_validation_complete_callback, + stxo_validation_complete_callback, + invalid_txo_validation_complete_callback, + transaction_validation_complete_callback, + saf_messages_received_callback, + error_ptr, + ); + assert_eq!(error, 0); + + let recovered_seed_words = wallet_get_seed_words(recovered_wallet, error_ptr); + assert_eq!(error, 0); + let recovered_public_key = wallet_get_public_key(recovered_wallet, error_ptr); + assert_eq!(error, 0); + + assert_eq!(*seed_words, *recovered_seed_words); + assert_eq!(*public_key, *recovered_public_key); } } } diff --git a/base_layer/wallet_ffi/wallet.h b/base_layer/wallet_ffi/wallet.h index 1da9871131c..8b895975732 100644 --- a/base_layer/wallet_ffi/wallet.h +++ b/base_layer/wallet_ffi/wallet.h @@ -462,6 +462,7 @@ struct TariWallet *wallet_create(struct TariWalletConfig *config, unsigned int num_rolling_log_files, unsigned int size_per_log_file_bytes, const char *passphrase, + const char *seed_words, void (*callback_received_transaction)(struct TariPendingInboundTransaction*), void (*callback_received_transaction_reply)(struct TariCompletedTransaction*), void (*callback_received_finalized_transaction)(struct TariCompletedTransaction*),