diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6092dcaf3..1aa4a5998 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,10 +8,11 @@ jobs: name: Browser Tests runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: - toolchain: stable + toolchain: nightly + components: clippy target: wasm32-unknown-unknown override: true profile: minimal diff --git a/node-manager/Cargo.toml b/node-manager/Cargo.toml index 4857f03b4..a47559d25 100644 --- a/node-manager/Cargo.toml +++ b/node-manager/Cargo.toml @@ -11,7 +11,10 @@ cfg-if = "1.0.0" wasm-bindgen = "0.2.83" bip39 = { version = "1.0.1" } bitcoin = "0.29.1" +bdk = { git = "https://github.com/afilini/bdk", branch = "upgrade/rust-bitcoin-29", default-features = false, features = ["keys-bip39"] } getrandom = { version = "0.2", features = ["js"] } +serde = { version = "^1.0", features = ["derive"] } +serde_json = { version = "^1.0" } gloo-storage = "0.2.2" # The `console_error_panic_hook` crate provides better debugging of panics by diff --git a/node-manager/rust-toolchain.toml b/node-manager/rust-toolchain.toml new file mode 100644 index 000000000..271800cb2 --- /dev/null +++ b/node-manager/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" \ No newline at end of file diff --git a/node-manager/src/error.rs b/node-manager/src/error.rs new file mode 100644 index 000000000..95347d806 --- /dev/null +++ b/node-manager/src/error.rs @@ -0,0 +1,87 @@ +use gloo_storage::errors::StorageError; +use std::fmt; + +#[derive(Debug)] +#[allow(dead_code)] +// copied from LDK lite +/// An error that possibly needs to be handled by the user. +pub enum Error { + /// Returned when trying to start Mutiny while it is already running. + AlreadyRunning, + /// Returned when trying to stop Mutiny while it is not running. + NotRunning, + /// The funding transaction could not be created. + FundingTxCreationFailed, + /// A network connection has been closed. + ConnectionFailed, + /// Payment of the given invoice has already been initiated. + NonUniquePaymentHash, + /// The given invoice is invalid. + InvoiceInvalid, + /// Invoice creation failed. + InvoiceCreationFailed, + /// No route for the given target could be found. + RoutingFailed, + /// A given peer info could not be parsed. + PeerInfoParseFailed, + /// A channel could not be opened. + ChannelCreationFailed, + /// A channel could not be closed. + ChannelClosingFailed, + /// Persistence failed. + PersistenceFailed, + /// A wallet operation failed. + WalletOperationFailed, + /// A signing operation failed. + WalletSigningFailed, + /// A chain access operation failed. + ChainAccessFailed, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::AlreadyRunning => write!(f, "Mutiny is already running."), + Self::NotRunning => write!(f, "Mutiny is not running."), + Self::FundingTxCreationFailed => { + write!(f, "Funding transaction could not be created.") + } + Self::ConnectionFailed => write!(f, "Network connection closed."), + Self::NonUniquePaymentHash => write!(f, "An invoice must not get payed twice."), + Self::InvoiceInvalid => write!(f, "The given invoice is invalid."), + Self::InvoiceCreationFailed => write!(f, "Failed to create invoice."), + Self::RoutingFailed => write!(f, "Failed to find route."), + Self::PeerInfoParseFailed => write!(f, "Failed to parse the given peer information."), + Self::ChannelCreationFailed => write!(f, "Failed to create channel."), + Self::ChannelClosingFailed => write!(f, "Failed to close channel."), + Self::PersistenceFailed => write!(f, "Failed to persist data."), + Self::WalletOperationFailed => write!(f, "Failed to conduct wallet operation."), + Self::WalletSigningFailed => write!(f, "Failed to sign given transaction."), + Self::ChainAccessFailed => write!(f, "Failed to conduct chain access operation."), + } + } +} + +impl std::error::Error for Error {} + +impl From for Error { + fn from(e: bdk::Error) -> Self { + match e { + bdk::Error::Signer(_) => Self::WalletSigningFailed, + _ => Self::WalletOperationFailed, + } + } +} + +// todo uncomment when we add esplora stuff +// impl From for Error { +// fn from(_e: esplora::EsploraError) -> Self { +// Self::ChainAccessFailed +// } +// } + +impl From for Error { + fn from(_e: StorageError) -> Self { + Self::PersistenceFailed + } +} diff --git a/node-manager/src/lib.rs b/node-manager/src/lib.rs index ff736018a..779a83418 100644 --- a/node-manager/src/lib.rs +++ b/node-manager/src/lib.rs @@ -2,9 +2,10 @@ // wasm_bindgen uses improper casing and it needs to be turned off: // https://github.com/rustwasm/wasm-bindgen/issues/2882 +mod error; +mod localstorage; mod nodemanager; mod seedgen; -mod storage; mod utils; use cfg_if::cfg_if; @@ -26,3 +27,12 @@ pub async fn main_js() -> Result<(), JsValue> { debug!("Main function ends"); Ok(()) } + +#[cfg(test)] +mod test { + use gloo_storage::{LocalStorage, Storage}; + + pub(crate) fn cleanup_test() { + LocalStorage::clear(); + } +} diff --git a/node-manager/src/localstorage.rs b/node-manager/src/localstorage.rs new file mode 100644 index 000000000..24acad672 --- /dev/null +++ b/node-manager/src/localstorage.rs @@ -0,0 +1,978 @@ +use std::str::FromStr; + +use bdk::database::{BatchDatabase, BatchOperations, Database, SyncTime}; +use bdk::{KeychainKind, LocalUtxo, TransactionDetails}; +use bip39::Mnemonic; +use bitcoin::consensus::deserialize; +use bitcoin::consensus::encode::serialize; +use bitcoin::hash_types::Txid; +use bitcoin::hashes::hex::{FromHex, ToHex}; +use bitcoin::{OutPoint, Script, Transaction}; +use gloo_storage::{LocalStorage, Storage}; +use serde::{Deserialize, Serialize}; +use serde_json::{Map, Value}; + +pub struct MutinyBrowserStorage {} + +impl MutinyBrowserStorage { + pub fn new() -> Self { + MutinyBrowserStorage {} + } + + // A wrapper for LocalStorage::set that converts the error to bdk::Error + fn set(&self, key: impl AsRef, value: T) -> Result<(), bdk::Error> + where + T: Serialize, + { + LocalStorage::set(key, value).map_err(|_| bdk::Error::Generic("Storage error".to_string())) + } + + // mostly a copy of LocalStorage::get_all() + fn scan_prefix(&self, prefix: String) -> Map { + let local_storage = LocalStorage::raw(); + let length = LocalStorage::length(); + let mut map = Map::with_capacity(length as usize); + for index in 0..length { + let key_opt: Option = local_storage.key(index).unwrap(); + + if let Some(key) = key_opt { + if key.starts_with(String::as_str(&prefix)) { + let value: Value = LocalStorage::get(&key).unwrap(); + map.insert(key, value); + } + } + } + + map + } + + pub fn insert_mnemonic(mnemonic: Mnemonic) -> Mnemonic { + LocalStorage::set("mnemonic", mnemonic.to_string()).expect("Failed to write to storage"); + mnemonic + } + + pub fn get_mnemonic() -> gloo_storage::Result { + let res: gloo_storage::Result = LocalStorage::get("mnemonic"); + match res { + Ok(str) => Ok(Mnemonic::from_str(&str).expect("could not parse specified mnemonic")), + Err(e) => Err(e), + } + } + + #[allow(dead_code)] + pub fn delete_mnemonic() { + LocalStorage::delete("mnemonic"); + } +} + +// path -> script p{i,e} -> script +// script -> path s