Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Windowless sleep #757

Merged
merged 5 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading