Skip to content

Commit

Permalink
implement WASM persistent storage
Browse files Browse the repository at this point in the history
  • Loading branch information
borngraced committed Sep 26, 2024
1 parent 6453a33 commit 9f87a9e
Show file tree
Hide file tree
Showing 14 changed files with 4,446 additions and 63 deletions.
6 changes: 6 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions mm2src/kdf_walletconnect/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ async-trait = "0.1.52"
base64 = "0.21.2"
chrono = { version = "0.4.23", "features" = ["serde"] }
common = { path = "../common" }
cfg-if = "1.0"
db_common = { path = "../db_common" }
derive_more = "0.99"
enum_derives = { path = "../derives/enum_derives" }
Expand All @@ -34,3 +35,14 @@ x25519-dalek = { version = "2.0", features = ["static_secrets"] }

[target.'cfg(target_arch = "wasm32")'.dependencies]
mm2_db = { path = "../mm2_db" }
mm2_test_helpers = { path = "../mm2_test_helpers" }
wasm-bindgen = "0.2.86"
wasm-bindgen-test = { version = "0.3.2" }
wasm-bindgen-futures = "0.4.21"
web-sys = { version = "0.3.55", features = ["console", "CloseEvent", "DomException", "ErrorEvent", "IdbDatabase",
"IdbCursor", "IdbCursorWithValue", "IdbFactory", "IdbIndex", "IdbIndexParameters", "IdbObjectStore",
"IdbObjectStoreParameters", "IdbOpenDbRequest", "IdbKeyRange", "IdbTransaction", "IdbTransactionMode",
"IdbVersionChangeEvent", "MessageEvent", "MessagePort", "ReadableStreamDefaultReader", "ReadableStream"]}

[dev-dependencies]
mm2_test_helpers = { path = "../mm2_test_helpers" }
2 changes: 1 addition & 1 deletion mm2src/kdf_walletconnect/src/chain/cosmos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use super::WcRequestMethods;

#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum CosmosAccountAlgo {
#[serde(rename = "secp256k")]
#[serde(rename = "secp256k1")]
Secp256k1,
#[serde(rename = "tendermint/PubKeySecp256k1")]
TendermintSecp256k1,
Expand Down
82 changes: 82 additions & 0 deletions mm2src/kdf_walletconnect/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use enum_derives::EnumFromStringify;
#[cfg(target_arch = "wasm32")]
use mm2_db::indexed_db::cursor_prelude::*;
#[cfg(target_arch = "wasm32")]
use mm2_db::indexed_db::{DbTransactionError, InitDbError};
use pairing_api::PairingClientError;
use relay_client::error::{ClientError, Error};
use relay_rpc::rpc::{PublishError, SubscriptionError};
Expand Down Expand Up @@ -74,6 +78,8 @@ pub enum WalletConnectCtxError {
EmptyAccount(String),
#[error("WalletConnect is not initaliazed yet!")]
NotInitialized,
#[error("Storage Error: {0}")]
StorageError(String),
}

impl From<Error<PublishError>> for WalletConnectCtxError {
Expand All @@ -90,3 +96,79 @@ pub enum SessionError {
#[error("Failed to generate symmetric session key: {0}")]
SymKeyGeneration(String),
}

#[cfg(target_arch = "wasm32")]
#[derive(Debug, Clone, thiserror::Error)]
pub enum WcIndexedDbError {
#[error("Internal Error: {0}")]
InternalError(String),
#[error("Not supported: {0}")]
NotSupported(String),
#[error("Delete Error: {0}")]
DeletionError(String),
#[error("Insert Error: {0}")]
AddToStorageErr(String),
#[error("GetFromStorage Error: {0}")]
GetFromStorageError(String),
#[error("Decoding Error: {0}")]
DecodingError(String),
}

#[cfg(target_arch = "wasm32")]
impl From<InitDbError> for WcIndexedDbError {
fn from(e: InitDbError) -> Self {
match &e {
InitDbError::NotSupported(_) => WcIndexedDbError::NotSupported(e.to_string()),
InitDbError::EmptyTableList
| InitDbError::DbIsOpenAlready { .. }
| InitDbError::InvalidVersion(_)
| InitDbError::OpeningError(_)
| InitDbError::TypeMismatch { .. }
| InitDbError::UnexpectedState(_)
| InitDbError::UpgradingError { .. } => WcIndexedDbError::InternalError(e.to_string()),
}
}
}

#[cfg(target_arch = "wasm32")]
impl From<DbTransactionError> for WcIndexedDbError {
fn from(e: DbTransactionError) -> Self {
match e {
DbTransactionError::ErrorSerializingItem(_) | DbTransactionError::ErrorDeserializingItem(_) => {
WcIndexedDbError::DecodingError(e.to_string())
},
DbTransactionError::ErrorUploadingItem(_) => WcIndexedDbError::AddToStorageErr(e.to_string()),
DbTransactionError::ErrorGettingItems(_) | DbTransactionError::ErrorCountingItems(_) => {
WcIndexedDbError::GetFromStorageError(e.to_string())
},
DbTransactionError::ErrorDeletingItems(_) => WcIndexedDbError::DeletionError(e.to_string()),
DbTransactionError::NoSuchTable { .. }
| DbTransactionError::ErrorCreatingTransaction(_)
| DbTransactionError::ErrorOpeningTable { .. }
| DbTransactionError::ErrorSerializingIndex { .. }
| DbTransactionError::UnexpectedState(_)
| DbTransactionError::TransactionAborted
| DbTransactionError::MultipleItemsByUniqueIndex { .. }
| DbTransactionError::NoSuchIndex { .. }
| DbTransactionError::InvalidIndex { .. } => WcIndexedDbError::InternalError(e.to_string()),
}
}
}

#[cfg(target_arch = "wasm32")]
impl From<CursorError> for WcIndexedDbError {
fn from(value: CursorError) -> Self {
match value {
CursorError::ErrorSerializingIndexFieldValue { .. }
| CursorError::ErrorDeserializingIndexValue { .. }
| CursorError::ErrorDeserializingItem(_) => Self::DecodingError(value.to_string()),
CursorError::ErrorOpeningCursor { .. }
| CursorError::AdvanceError { .. }
| CursorError::InvalidKeyRange { .. }
| CursorError::IncorrectNumberOfKeysPerIndex { .. }
| CursorError::UnexpectedState(_)
| CursorError::IncorrectUsage { .. }
| CursorError::TypeMismatch { .. } => Self::InternalError(value.to_string()),
}
}
}
19 changes: 13 additions & 6 deletions mm2src/kdf_walletconnect/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ use relay_rpc::{auth::{ed25519_dalek::SigningKey, AuthToken},
use serde_json::Value;
use session::{propose::send_proposal, Session, SymKeyPair};
use std::{sync::Arc, time::Duration};
use storage::SessionStorageDb;
use wc_common::{decode_and_decrypt_type0, encrypt_and_encode, EnvelopeType};

pub(crate) const SUPPORTED_PROTOCOL: &str = "irn";
Expand All @@ -46,22 +47,22 @@ pub struct WalletConnectCtx {
pub pairing: PairingClient,
pub session: Arc<Mutex<Option<Session>>>,
pub active_chain_id: Arc<Mutex<String>>,

pub(crate) key_pair: SymKeyPair,
pub(crate) storage: SessionStorageDb,

relay: Relay,
metadata: Metadata,
namespaces: ProposeNamespaces,
subscriptions: Arc<Mutex<Vec<Topic>>>,

inbound_message_handler: Arc<Mutex<UnboundedReceiver<PublishedMessage>>>,
connection_live_handler: Arc<Mutex<UnboundedReceiver<()>>>,

session_request_sender: Arc<Mutex<UnboundedSender<SessionEventMessage>>>,
session_request_handler: Arc<Mutex<UnboundedReceiver<SessionEventMessage>>>,
}

impl WalletConnectCtx {
pub fn init() -> Self {
pub fn try_init(ctx: &MmArc) -> MmResult<Self, WalletConnectCtxError> {
let (msg_sender, msg_receiver) = unbounded();
let (conn_live_sender, conn_live_receiver) = unbounded();
let (session_request_sender, session_request_receiver) = unbounded();
Expand All @@ -76,7 +77,9 @@ impl WalletConnectCtx {
data: None,
};

Self {
let storage = SessionStorageDb::init(ctx)?;

Ok(Self {
client,
pairing,
session: Default::default(),
Expand All @@ -85,16 +88,20 @@ impl WalletConnectCtx {
namespaces: required,
metadata: generate_metadata(),
key_pair: SymKeyPair::new(),
storage,
inbound_message_handler: Arc::new(Mutex::new(msg_receiver)),
connection_live_handler: Arc::new(Mutex::new(conn_live_receiver)),
session_request_handler: Arc::new(Mutex::new(session_request_receiver)),
session_request_sender: Arc::new(Mutex::new(session_request_sender)),
subscriptions: Default::default(),
}
})
}

pub fn from_ctx(ctx: &MmArc) -> MmResult<Arc<WalletConnectCtx>, WalletConnectCtxError> {
from_ctx(&ctx.wallet_connect, move || Ok(Self::init())).map_to_mm(WalletConnectCtxError::InternalError)
from_ctx(&ctx.wallet_connect, move || {
Self::try_init(ctx).map_err(|err| err.to_string())
})
.map_to_mm(WalletConnectCtxError::InternalError)
}

pub async fn connect_client(&self) -> MmResult<(), WalletConnectCtxError> {
Expand Down
10 changes: 5 additions & 5 deletions mm2src/kdf_walletconnect/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ pub(crate) const THIRTY_DAYS: u64 = 60 * 60 * 30;

pub(crate) type WcRequestResponseResult = MmResult<(Value, IrnMetadata), WalletConnectCtxError>;

#[derive(Clone, Serialize, Deserialize)]
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SessionKey {
sym_key: [u8; 32],
public_key: [u8; 32],
pub(crate) sym_key: [u8; 32],
pub(crate) public_key: [u8; 32],
}

impl std::fmt::Debug for SessionKey {
Expand Down Expand Up @@ -111,7 +111,7 @@ impl SessionKey {
/// In the WalletConnect protocol, a session involves two parties: a controller
/// (typically a wallet) and a proposer (typically a dApp). This enum is used
/// to distinguish between these two roles.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum SessionType {
/// Represents the controlling party in a session, typically a wallet.
Controller,
Expand All @@ -131,7 +131,7 @@ impl ToString for SessionType {
/// This struct is typically used in the core session management logic of a WalletConnect
/// implementation. It's used to store, retrieve, and update session information throughout
/// the lifecycle of a WalletConnect connection.
#[derive(Debug, Clone, Deserialize, Serialize)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct Session {
/// Session topic
pub topic: Topic,
Expand Down
2 changes: 2 additions & 0 deletions mm2src/kdf_walletconnect/src/session/propose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ pub async fn process_proposal_request(
}

{
println!("{:?}", session);
send_session_settle_request(ctx, &session).await?;
};

Expand Down Expand Up @@ -124,6 +125,7 @@ pub(crate) async fn process_session_propose_response(
session.controller.public_key = response.responder_public_key;

{
println!("{:?}", session);
let mut old_session = ctx.session.lock().await;
*old_session = Some(session);
let mut subs = ctx.subscriptions.lock().await;
Expand Down
106 changes: 106 additions & 0 deletions mm2src/kdf_walletconnect/src/storage/indexed_db.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use super::WalletConnectStorageOps;
use crate::error::WcIndexedDbError;
use crate::session::Session;
use async_trait::async_trait;
use mm2_core::mm_ctx::MmArc;
use mm2_db::indexed_db::{ConstructibleDb, DbIdentifier, DbInstance, DbLocked, DbUpgrader, IndexedDb, IndexedDbBuilder,
InitDbResult, OnUpgradeResult, SharedDb, TableSignature};
use mm2_err_handle::prelude::MmResult;
use mm2_err_handle::prelude::*;
use relay_rpc::domain::Topic;

const DB_VERSION: u32 = 1;

pub type IDBSessionStorageLocked<'a> = DbLocked<'a, IDBSessionStorageInner>;

impl TableSignature for Session {
const TABLE_NAME: &'static str = "sessions";

fn on_upgrade_needed(upgrader: &DbUpgrader, old_version: u32, new_version: u32) -> OnUpgradeResult<()> {
if let (0, 1) = (old_version, new_version) {
let table = upgrader.create_table(Self::TABLE_NAME)?;
table.create_index("topic", false)?;
}
Ok(())
}
}

pub struct IDBSessionStorageInner(IndexedDb);

#[async_trait]
impl DbInstance for IDBSessionStorageInner {
const DB_NAME: &'static str = "wc_session_storage";

async fn init(db_id: DbIdentifier) -> InitDbResult<Self> {
let inner = IndexedDbBuilder::new(db_id)
.with_version(DB_VERSION)
.with_table::<Session>()
.build()
.await?;

Ok(Self(inner))
}
}

impl IDBSessionStorageInner {
pub(crate) fn get_inner(&self) -> &IndexedDb { &self.0 }
}

#[derive(Clone)]
pub struct IDBSessionStorage(SharedDb<IDBSessionStorageInner>);

impl IDBSessionStorage {
pub(crate) fn new(ctx: &MmArc) -> MmResult<Self, WcIndexedDbError> {
Ok(Self(ConstructibleDb::new(ctx).into_shared()))
}

async fn lock_db(&self) -> MmResult<IDBSessionStorageLocked<'_>, WcIndexedDbError> {
Ok(self
.0
.get_or_initialize()
.await
.mm_err(|err| WcIndexedDbError::InternalError(err.to_string()))?)
}
}

#[async_trait::async_trait]
impl WalletConnectStorageOps for IDBSessionStorage {
type Error = WcIndexedDbError;

async fn init(&self) -> MmResult<(), Self::Error> { Ok(()) }

async fn is_initialized(&self) -> MmResult<bool, Self::Error> { Ok(true) }

async fn save_session(&self, session: Session) -> MmResult<(), Self::Error> {
let lock_db = self.lock_db().await?;
let transaction = lock_db.get_inner().transaction().await?;
let session_table = transaction.table::<Session>().await?;
session_table
.replace_item_by_unique_index("topic", session.topic.clone(), &session)
.await?;

Ok(())
}

async fn get_session(&self, topic: &Topic) -> MmResult<Option<Session>, Self::Error> {
let lock_db = self.lock_db().await?;
let transaction = lock_db.get_inner().transaction().await?;
let session_table = transaction.table::<Session>().await?;

Ok(session_table
.get_item_by_unique_index("topic", topic)
.await?
.map(|s| s.1))
}

async fn delete_session(&self, topic: &Topic) -> MmResult<(), Self::Error> {
let lock_db = self.lock_db().await?;
let transaction = lock_db.get_inner().transaction().await?;
let session_table = transaction.table::<Session>().await?;

session_table.delete_item_by_unique_index("topic", topic).await?;
Ok(())
}

async fn update_session(&self, session: Session) -> MmResult<(), Self::Error> { self.save_session(session).await }
}
Loading

0 comments on commit 9f87a9e

Please sign in to comment.