Skip to content
This repository has been archived by the owner on Feb 3, 2025. It is now read-only.

Commit

Permalink
Windowless sleep (#757)
Browse files Browse the repository at this point in the history
* windowless sleep

* web-sys is just for dev now

* Remove local storage

* Use crates release

* Use utils from mutiny-core

---------

Co-authored-by: benthecarman <[email protected]>
  • Loading branch information
futurepaul and benthecarman authored Nov 8, 2023
1 parent 71fbc97 commit 46b0194
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 384 deletions.
231 changes: 114 additions & 117 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion mutiny-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,14 @@ test-utils = []
[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = "0.2.84"
wasm-bindgen-futures = { version = "0.4.33" }
web-sys = { version = "0.3.60", features = ["console"] }
js-sys = { version = "0.3.60" }
gloo-net = { version = "0.2.4" }
instant = { version = "0.1", features = ["wasm-bindgen"] }
getrandom = { version = "0.2", features = ["js"] }
windowless_sleep = "0.1.1"

[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
web-sys = { version = "0.3.60", features = ["console"] }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { version = "1", features = ["rt"] }
Expand Down
10 changes: 1 addition & 9 deletions mutiny-core/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,13 @@ pub(crate) fn min_lightning_amount(network: Network) -> u64 {
pub async fn sleep(millis: i32) {
#[cfg(target_arch = "wasm32")]
{
let mut cb = |resolve: js_sys::Function, _reject: js_sys::Function| {
web_sys::window()
.unwrap()
.set_timeout_with_callback_and_timeout_and_arguments_0(&resolve, millis)
.unwrap();
};
let p = js_sys::Promise::new(&mut cb);
wasm_bindgen_futures::JsFuture::from(p).await.unwrap();
windowless_sleep::sleep(millis).await;
}
#[cfg(not(target_arch = "wasm32"))]
{
tokio::time::sleep(Duration::from_millis(millis.try_into().unwrap())).await;
}
}

pub fn now() -> Duration {
#[cfg(target_arch = "wasm32")]
return instant::SystemTime::now()
Expand Down
6 changes: 3 additions & 3 deletions mutiny-wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,13 @@ wasm-logger = "0.2.0"
log = "0.4.17"
rexie = "0.4"
js-sys = "0.3.60"
gloo-storage = "0.2.2"
gloo-utils = { version = "0.1.6", features = ["serde"] }
web-sys = { version = "0.3.60", features = ["console"] }
bip39 = { version = "2.0.0" }
getrandom = { version = "0.2", features = ["js"] }
futures = "0.3.25"
urlencoding = "2.1.2"
once_cell = "1.18.0"
windowless_sleep = "0.1.1"

# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
Expand All @@ -52,9 +51,10 @@ console_error_panic_hook = { version = "0.1.6", optional = true }
[dev-dependencies]
mutiny-core = { path = "../mutiny-core", features = ["test-utils"] }
wasm-bindgen-test = "0.3.33"
web-sys = { version = "0.3.60", features = ["console"] }

[features]
default = [ ]
default = []

[package.metadata.wasm-pack.profile.release]
wasm-opt = true
233 changes: 8 additions & 225 deletions mutiny-wasm/src/indexed_db.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use anyhow::anyhow;
use bip39::Mnemonic;
use gloo_storage::{LocalStorage, Storage};
use gloo_utils::format::JsValueSerdeExt;
use lightning::util::logger::Logger;
use lightning::{log_debug, log_error};
Expand Down Expand Up @@ -265,26 +264,6 @@ impl IndexedDbStorage {
map.set(key, json)?;
}

// get the local storage data, this should take priority if it is being used
log_debug!(logger, "Reading from local storage");
let local_storage = LocalStorage::raw();
let length = LocalStorage::length();
for index in 0..length {
let key_opt: Option<String> = local_storage.key(index).unwrap();

if let Some(key) = key_opt {
// only add to the map if it is a key we expect
// this is to prevent any unexpected data from being added to the map
// from either malicious 3rd party or a previous version of the wallet
if write_to_local_storage(&key) {
// compare versions between local storage and indexed db storage
if let Some((key, value)) = Self::handle_local_storage_key(key, &map, logger)? {
map.set_data(key, value, None)?;
}
}
}
}

match vss {
None => {
let final_map = map.memory.read().unwrap();
Expand All @@ -309,76 +288,6 @@ impl IndexedDbStorage {
}
}

fn handle_local_storage_key(
key: String,
current: &MemoryStorage,
logger: &MutinyLogger,
) -> Result<Option<(String, Value)>, MutinyError> {
if key.starts_with(MONITORS_PREFIX_KEY) {
// we can get versions from monitors, so we should compare
match current.get::<Vec<u8>>(&key)? {
Some(bytes) => {
// check first byte is 1, then take u64 from next 8 bytes
let current_version = utils::get_monitor_version(&bytes);

let obj: Value = LocalStorage::get(&key).unwrap();
let value = decrypt_value(&key, obj, current.password())?;
if let Ok(local_bytes) = serde_json::from_value::<Vec<u8>>(value.clone()) {
let local_version = utils::get_monitor_version(&local_bytes);

// if the current version is less than the version from local storage
// then we want to use the local storage version
if current_version < local_version {
log_debug!(
logger,
"Using local storage key {key} with version {}",
local_version
);
return Ok(Some((key, value)));
}
}
}
None => {
let value: Value = LocalStorage::get(&key).unwrap();
return Ok(Some((key, value)));
}
}
} else if key.starts_with(CHANNEL_MANAGER_KEY) {
// we can get versions from channel manager, so we should compare
match current.get_data::<VersionedValue>(&key) {
Ok(Some(local)) => {
let obj: Value = LocalStorage::get(&key).unwrap();
let value = decrypt_value(&key, obj, current.password())?;

// if the current version is less than the version from local storage
// then we want to use the local storage version
if let Ok(v) = serde_json::from_value::<VersionedValue>(value.clone()) {
if v.version > local.version {
log_debug!(
logger,
"Using local storage key {key} with version {}",
v.version
);
return Ok(Some((key, value)));
}
}
}
Ok(None) => {
let obj: Value = LocalStorage::get(&key).unwrap();
let value = decrypt_value(&key, obj, current.password())?;
if serde_json::from_value::<VersionedValue>(value.clone()).is_ok() {
return Ok(Some((key, value)));
}
}
Err(_) => return Err(MutinyError::IncorrectPassword),
}
}

log_debug!(logger, "Skipping local storage key {key}");

Ok(None)
}

async fn handle_vss_key(
kv: KeyVersion,
vss: &MutinyVssClient,
Expand Down Expand Up @@ -544,18 +453,6 @@ fn used_once(key: &str) -> bool {
}
}

/// To help prevent force closes we save to local storage as well as indexed db.
/// This is because indexed db is not always reliable.
///
/// We need to do this for the channel manager and channel monitors.
fn write_to_local_storage(key: &str) -> bool {
match key {
str if str.starts_with(CHANNEL_MANAGER_KEY) => true,
str if str.starts_with(MONITORS_PREFIX_KEY) => true,
_ => false,
}
}

impl MutinyStorage for IndexedDbStorage {
fn password(&self) -> Option<&str> {
self.password.as_deref()
Expand Down Expand Up @@ -588,15 +485,6 @@ impl MutinyStorage for IndexedDbStorage {
};
});

// Some values we want to write to local storage as well as indexed db
if write_to_local_storage(&key) {
LocalStorage::set(&key, &data).map_err(|e| {
MutinyError::write_err(MutinyStorageError::Other(anyhow!(
"Failed to write to local storage: {e}"
)))
})?;
}

// some values only are read once, so we don't need to write them to memory,
// just need them in indexed db for next time
if !used_once(key.as_ref()) {
Expand All @@ -621,15 +509,6 @@ impl MutinyStorage for IndexedDbStorage {

Self::save_to_indexed_db(&self.indexed_db, &key, &data).await?;

// Some values we want to write to local storage as well as indexed db
if write_to_local_storage(&key) {
LocalStorage::set(&key, &data).map_err(|e| {
MutinyError::write_err(MutinyStorageError::Other(anyhow!(
"Failed to write to local storage: {e}"
)))
})?;
}

// some values only are read once, so we don't need to write them to memory,
// just need them in indexed db for next time
if !used_once(key.as_ref()) {
Expand Down Expand Up @@ -695,11 +574,6 @@ impl MutinyStorage for IndexedDbStorage {
.map_err(|e| MutinyError::write_err(e.into()))?;

for key in keys {
// Some values we want to write to local storage as well as indexed db
// we should delete them from local storage as well
if write_to_local_storage(&key) {
LocalStorage::delete(&key)
}
map.remove(&key);
}

Expand Down Expand Up @@ -818,9 +692,6 @@ impl MutinyStorage for IndexedDbStorage {
.await
.map_err(|e| MutinyError::write_err(anyhow!("Failed clear indexed db: {e}").into()))?;

// We use some localstorage right now for ensuring channel data
LocalStorage::clear();

Ok(())
}

Expand All @@ -839,20 +710,15 @@ impl MutinyStorage for IndexedDbStorage {
#[cfg(test)]
mod tests {
use super::*;
use crate::indexed_db::{IndexedDbStorage, WALLET_OBJECT_STORE_NAME};
use crate::utils::sleep;
use crate::indexed_db::IndexedDbStorage;
use crate::utils::test::log;
use bip39::Mnemonic;
use bitcoin::hashes::hex::ToHex;
use gloo_storage::{LocalStorage, Storage};
use mutiny_core::storage::MutinyStorage;
use mutiny_core::test_utils::{MANAGER_BYTES, MONITOR_VERSION_HIGHER, MONITOR_VERSION_LOWER};
use mutiny_core::utils::sleep;
use mutiny_core::{encrypt::encryption_key_from_pass, logging::MutinyLogger};
use rexie::TransactionMode;
use serde_json::json;
use std::str::FromStr;
use std::sync::Arc;
use wasm_bindgen::JsValue;
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};

wasm_bindgen_test_configure!(run_in_browser);
Expand Down Expand Up @@ -1018,102 +884,17 @@ mod tests {
IndexedDbStorage::clear().await.unwrap();
}

async fn compare_local_storage_versions(
test_name: &str,
local_storage: Vec<u8>,
indexed_db: Vec<u8>,
) -> Vec<u8> {
let key = format!("{MONITORS_PREFIX_KEY}test_{test_name}");
// set in local storage
LocalStorage::set(&key, local_storage).unwrap();
// set in indexed db
let rexie = IndexedDbStorage::build_indexed_db_database().await.unwrap();
let tx = rexie
.transaction(&[WALLET_OBJECT_STORE_NAME], TransactionMode::ReadWrite)
.unwrap();
let store = tx.store(WALLET_OBJECT_STORE_NAME).unwrap();
store
.put(
&JsValue::from_serde(&indexed_db).unwrap(),
Some(&JsValue::from(&key)),
)
.await
.unwrap();

tx.done().await.unwrap();

let logger = Arc::new(MutinyLogger::default());
let storage = IndexedDbStorage::new(None, None, None, logger)
.await
.unwrap();

let bytes: Vec<u8> = storage.get(&key).unwrap().unwrap();

// clear the storage to clean up
IndexedDbStorage::clear().await.unwrap();

bytes
}

#[test]
async fn test_local_storage_version_0_indexed_db_version_max() {
let test_name = "test_local_storage_version_0_indexed_db_version_max";
log!("{test_name}");

let bytes = compare_local_storage_versions(
test_name,
MONITOR_VERSION_LOWER.to_vec(),
MONITOR_VERSION_HIGHER.to_vec(),
)
.await;
assert_eq!(bytes, MONITOR_VERSION_HIGHER);
}

#[test]
async fn test_local_storage_version_max_indexed_db_version_0() {
let test_name = "test_local_storage_version_max_indexed_db_version_0";
log!("{test_name}");

let bytes = compare_local_storage_versions(
test_name,
MONITOR_VERSION_HIGHER.to_vec(),
MONITOR_VERSION_LOWER.to_vec(),
)
.await;
assert_eq!(bytes, MONITOR_VERSION_HIGHER);
}

#[test]
async fn test_local_storage_version_max_indexed_db_version_max() {
let test_name = "test_local_storage_version_max_indexed_db_version_max";
log!("{test_name}");

let bytes = compare_local_storage_versions(
test_name,
MONITOR_VERSION_HIGHER.to_vec(),
MONITOR_VERSION_HIGHER.to_vec(),
)
.await;
assert_eq!(bytes, MONITOR_VERSION_HIGHER);
}

#[test]
async fn test_correct_incorrect_password_error() {
let test_name = "test_correct_incorrect_password_error";
log!("{test_name}");
let logger = Arc::new(MutinyLogger::default());

let key = format!("{CHANNEL_MANAGER_KEY}_test_{test_name}");
let data = VersionedValue {
version: 69,
// just use this as dummy data
value: Value::String(MANAGER_BYTES.to_hex()),
};
let storage = IndexedDbStorage::new(None, None, None, logger.clone())
.await
.unwrap();

storage.set_data(&key, data, None).unwrap();
let seed = generate_seed(12).unwrap();
storage.set_data(MNEMONIC_KEY, seed, None).unwrap();
// wait for the storage to be persisted
utils::sleep(1_000).await;

Expand All @@ -1125,9 +906,11 @@ mod tests {
.transpose()
.unwrap();

let result = IndexedDbStorage::new(password, cipher, None, logger).await;
let storage = IndexedDbStorage::new(password, cipher, None, logger)
.await
.unwrap();

match result {
match storage.get_mnemonic() {
Err(MutinyError::IncorrectPassword) => (),
Ok(_) => panic!("Expected IncorrectPassword error, got Ok"),
Err(e) => panic!("Expected IncorrectPassword error, got {:?}", e),
Expand Down
Loading

0 comments on commit 46b0194

Please sign in to comment.