diff --git a/src/cli/account.rs b/src/cli/account.rs index 9626abc8b..426db0925 100644 --- a/src/cli/account.rs +++ b/src/cli/account.rs @@ -32,6 +32,13 @@ pub enum AccountCmd { #[clap(short, long, default_value_t = false)] deploy: bool, }, + + /// Remove existing account from the local store + #[clap(short_flag = 'r')] + Remove { + #[clap()] + id: String, + }, } #[derive(Debug, Parser, Clone)] @@ -64,6 +71,7 @@ impl AccountCmd { new_account(template, *deploy)?; } AccountCmd::View { id: _ } => todo!(), + AccountCmd::Remove { id } => remove_account(id)?, } Ok(()) } @@ -153,10 +161,10 @@ fn new_account(template: &Option, deploy: bool) -> Result<(), S // TODO: Make these inserts atomic through a single transaction client .store() - .insert_account(&account) - .and_then(|_| client.store().insert_account_code(account.code())) + .insert_account_code(account.code()) .and_then(|_| client.store().insert_account_storage(account.storage())) .and_then(|_| client.store().insert_account_vault(account.vault())) + .and_then(|_| client.store().insert_account(&account)) .map(|_| { println!( "Succesfully created and stored Account ID: {}", @@ -167,3 +175,20 @@ fn new_account(template: &Option, deploy: bool) -> Result<(), S Ok(()) } + +// ACCOUNT REMOVE +// ================================================================================================ + +fn remove_account(id: &str) -> Result<(), String> { + let mut client = Client::new(ClientConfig::default()).map_err(|err| err.to_string())?; + + let without_prefix = id.trim_start_matches("0x"); + let account_id: u64 = u64::from_str_radix(without_prefix, 16).map_err(|err| err.to_string())?; + client + .store_mut() + .remove_account(account_id) + .map_err(|err| err.to_string())?; + + println!("Succesfully removed Account ID: {}", id); + Ok(()) +} diff --git a/src/errors.rs b/src/errors.rs index b52fbf800..3b0b02710 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -38,6 +38,7 @@ pub enum StoreError { QueryError(rusqlite::Error), InputSerializationError(serde_json::Error), DataDeserializationError(serde_json::Error), + NotFound, } impl fmt::Display for StoreError { @@ -53,6 +54,7 @@ impl fmt::Display for StoreError { DataDeserializationError(err) => { write!(f, "error deserializing data from the store: {err}") } + NotFound => write!(f, "requested data was not found in the store"), } } } diff --git a/src/lib.rs b/src/lib.rs index a888acb35..31d0b1e04 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,6 +47,11 @@ impl Client { &self.store } + /// Returns a mutable reference to the store + pub fn store_mut(&mut self) -> &mut Store { + &mut self.store + } + // DATA RETRIEVAL // -------------------------------------------------------------------------------------------- diff --git a/src/store/mod.rs b/src/store/mod.rs index 9f3fe8b06..3b6f2f044 100644 --- a/src/store/mod.rs +++ b/src/store/mod.rs @@ -128,12 +128,85 @@ impl Store { self.db .execute( - "INSERT INTO account_vault (root, assets) VALUES (?, ?)", + "INSERT INTO account_vaults (root, assets) VALUES (?, ?)", params![vault_root, assets], ) .map(|_| ()) .map_err(StoreError::QueryError) } + + pub fn remove_account(&mut self, account_id: u64) -> Result<(), StoreError> { + let tx = self.db.transaction().map_err(StoreError::ConnectionError)?; + + let Ok((code_root, storage_root, vault_root)) = tx.query_row( + "SELECT code_root, storage_root, vault_root FROM accounts WHERE id = ? ", + params![account_id as i64], + |row| { + let code_root: String = row.get(0)?; + let storage_root: String = row.get(1)?; + let vault_root: String = row.get(2)?; + + Ok((code_root, storage_root, vault_root)) + }, + ) else { + return Err(StoreError::NotFound); + }; + + // Remove from accounts table + if tx + .execute( + "DELETE FROM accounts WHERE id = ?", + params![account_id as i64], + ) + .map_err(StoreError::QueryError)? + == 0 + { + return Err(StoreError::NotFound); + } + + // Remove from account_keys table + // Currently not stored + + // Remove from account_code table + // should only be removed if it is not used by any other account + if tx + .execute( + "DELETE FROM account_code WHERE root = ?", + params![code_root], + ) + .map_err(StoreError::QueryError)? + == 0 + { + return Err(StoreError::NotFound); + } + + // Remove from account_storage table + if tx + .execute( + "DELETE FROM account_storage WHERE root = ?", + params![storage_root], + ) + .map_err(StoreError::QueryError)? + == 0 + { + return Err(StoreError::NotFound); + } + + // Remove from account_vaults table + if tx + .execute( + "DELETE FROM account_vaults WHERE root = ?", + params![vault_root], + ) + .map_err(StoreError::QueryError)? + == 0 + { + return Err(StoreError::NotFound); + } + + tx.commit().map_err(StoreError::ConnectionError)?; + Ok(()) + } } // STORE CONFIG diff --git a/src/store/store.sql b/src/store/store.sql index ac38d4836..f28c916f3 100644 --- a/src/store/store.sql +++ b/src/store/store.sql @@ -1,28 +1,43 @@ -- Create account_code table CREATE TABLE account_code ( - root BLOB NOT NULL, -- root of the Merkle tree for all exported procedures in account module. - procedures BLOB NOT NULL, -- serialized procedure digests for the account code. - module BLOB NOT NULL -- serialized ModuleAst for the account code. + root BLOB NOT NULL, -- root of the Merkle tree for all exported procedures in account module. + procedures BLOB NOT NULL, -- serialized procedure digests for the account code. + module BLOB NOT NULL, -- serialized ModuleAst for the account code. + PRIMARY KEY (root) ); -- Create account_storage table CREATE TABLE account_storage ( - root BLOB NOT NULL, -- root of the account storage Merkle tree. - slots BLOB NOT NULL -- serialized key-value pair of non-empty account slots. + root BLOB NOT NULL, -- root of the account storage Merkle tree. + slots BLOB NOT NULL, -- serialized key-value pair of non-empty account slots. + PRIMARY KEY (root) ); --- Create account_vault table -CREATE TABLE account_vault ( - root BLOB NOT NULL, -- root of the Merkle tree for the account vault. - assets BLOB NOT NULL -- serialized account vault assets. +-- Create account_vaults table +CREATE TABLE account_vaults ( + root BLOB NOT NULL, -- root of the Merkle tree for the account vault. + assets BLOB NOT NULL, -- serialized account vault assets. + PRIMARY KEY (root) ); --- Update accounts table +-- Create account_keys table +CREATE TABLE account_keys ( + account_id UNSIGNED BIG INT NOT NULL, -- ID of the account + key_pair BLOB NOT NULL, -- key pair + PRIMARY KEY (account_id), + FOREIGN KEY (account_id) REFERENCES accounts(id) +); + +-- Create accounts table CREATE TABLE accounts ( id UNSIGNED BIG INT NOT NULL, -- account ID. - code_root BLOB NOT NULL, -- root of the account_code Merkle tree. - storage_root BLOB NOT NULL, -- root of the account_storage Merkle tree. - vault_root BLOB NOT NULL, -- root of the account_vault Merkle tree. - nonce BIGINT NOT NULL, -- account nonce. - committed BOOLEAN NOT NULL -- true if recorded, false if not. + code_root BLOB NOT NULL, -- root of the account_code Merkle tree. + storage_root BLOB NOT NULL, -- root of the account_storage Merkle tree. + vault_root BLOB NOT NULL, -- root of the account_vault Merkle tree. + nonce BIGINT NOT NULL, -- account nonce. + committed BOOLEAN NOT NULL, -- true if recorded, false if not. + PRIMARY KEY (id), + FOREIGN KEY (code_root) REFERENCES account_code(root), + FOREIGN KEY (storage_root) REFERENCES account_storage(root), + FOREIGN KEY (vault_root) REFERENCES account_vaults(root) );