From fc339db6b4e805c599e6c251940931a5647d1355 Mon Sep 17 00:00:00 2001 From: caojiafeng Date: Wed, 29 Jul 2020 15:55:35 +0800 Subject: [PATCH 1/4] new wallet storage and impl --- Cargo.lock | 8 + storage/src/accumulator/mod.rs | 1 - storage/src/block/mod.rs | 1 - storage/src/block_info/mod.rs | 1 - storage/src/contract_event.rs | 1 - storage/src/db_storage/mod.rs | 34 ++- storage/src/state_node/mod.rs | 1 - storage/src/storage_macros.rs | 22 +- storage/src/tests/test_storage.rs | 7 +- storage/src/transaction/mod.rs | 1 - storage/src/transaction_info/mod.rs | 1 - wallet/api/Cargo.toml | 1 + wallet/api/src/lib.rs | 3 +- wallet/api/src/rich_wallet.rs | 64 +++++ wallet/service/Cargo.toml | 8 +- wallet/service/src/actor.rs | 35 ++- wallet/service/src/lib.rs | 3 +- wallet/service/src/rich_wallet/mod.rs | 337 ++++++++++++++++++++++++++ wallet/service/src/service.rs | 93 ------- wallet/service/src/wallet_manager.rs | 225 +++++++++++++++++ 20 files changed, 701 insertions(+), 146 deletions(-) create mode 100644 wallet/api/src/rich_wallet.rs create mode 100644 wallet/service/src/rich_wallet/mod.rs delete mode 100644 wallet/service/src/service.rs create mode 100644 wallet/service/src/wallet_manager.rs diff --git a/Cargo.lock b/Cargo.lock index c1be095d75..55557c188a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7525,6 +7525,7 @@ version = "0.3.1" dependencies = [ "anyhow", "async-trait", + "futures 0.3.5", "rand 0.7.3", "rand_core 0.5.1", "serde", @@ -7561,8 +7562,15 @@ dependencies = [ "anyhow", "async-trait", "futures 0.3.5", + "parking_lot 0.9.0", + "rand 0.7.3", + "serde", + "starcoin-canonical-serialization", "starcoin-config", + "starcoin-crypto", + "starcoin-decrypt", "starcoin-logger", + "starcoin-storage", "starcoin-types", "starcoin-wallet-api", "starcoin-wallet-lib", diff --git a/storage/src/accumulator/mod.rs b/storage/src/accumulator/mod.rs index 227ea38e93..43b885e5ca 100644 --- a/storage/src/accumulator/mod.rs +++ b/storage/src/accumulator/mod.rs @@ -17,7 +17,6 @@ use starcoin_accumulator::{ }; use std::io::Write; use std::mem::size_of; -use std::sync::Arc; define_storage!( AccumulatorNodeStore, diff --git a/storage/src/block/mod.rs b/storage/src/block/mod.rs index 13a818d626..e341f4a136 100644 --- a/storage/src/block/mod.rs +++ b/storage/src/block/mod.rs @@ -16,7 +16,6 @@ use serde::{Deserialize, Serialize}; use starcoin_types::block::{Block, BlockBody, BlockHeader, BlockNumber, BlockState}; use std::io::Write; use std::mem::size_of; -use std::sync::Arc; #[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] pub struct StorageBlock { diff --git a/storage/src/block_info/mod.rs b/storage/src/block_info/mod.rs index 8a15b5ffe6..3b84bc3218 100644 --- a/storage/src/block_info/mod.rs +++ b/storage/src/block_info/mod.rs @@ -9,7 +9,6 @@ use anyhow::Result; use crypto::HashValue; use scs::SCSCodec; use starcoin_types::block::BlockInfo; -use std::sync::Arc; pub trait BlockInfoStore { fn save_block_info(&self, block_info: BlockInfo) -> Result<()>; diff --git a/storage/src/contract_event.rs b/storage/src/contract_event.rs index 833dd3758e..211df75068 100644 --- a/storage/src/contract_event.rs +++ b/storage/src/contract_event.rs @@ -9,7 +9,6 @@ use anyhow::Result; use crypto::HashValue; use scs::SCSCodec; use starcoin_types::contract_event::ContractEvent; -use std::sync::Arc; define_storage!( ContractEventStorage, diff --git a/storage/src/db_storage/mod.rs b/storage/src/db_storage/mod.rs index d569a5b631..ff3872ed17 100644 --- a/storage/src/db_storage/mod.rs +++ b/storage/src/db_storage/mod.rs @@ -13,15 +13,23 @@ use std::path::Path; pub struct DBStorage { db: DB, + cfs: Vec, } impl DBStorage { pub fn new + Clone>(db_root_path: P) -> Self { let path = db_root_path.as_ref().join("starcoindb"); - Self::open(path, false).expect("Unable to open StarcoinDB") + Self::open_with_cfs(path, VEC_PREFIX_NAME.to_vec(), false) + .expect("Unable to open StarcoinDB") } - pub fn open(path: impl AsRef, readonly: bool) -> Result { - let column_families = VEC_PREFIX_NAME.to_vec(); + + pub fn open_with_cfs( + root_path: impl AsRef, + column_families: Vec, + readonly: bool, + ) -> Result { + let path = root_path.as_ref(); + let cfs_set: HashSet<_> = column_families.iter().collect(); { ensure!( @@ -29,14 +37,13 @@ impl DBStorage { "Duplicate column family name found.", ); } - if Self::db_exists(path.as_ref()) { - let cf_vec = Self::list_cf(path.as_ref())?; + if Self::db_exists(path) { + let cf_vec = Self::list_cf(path)?; for cf in cf_vec { if cf != DEFAULT_PREFIX_NAME && cfs_set.get(&cf.as_str()).is_none() { error!( "db path cf: {:?} is not equal,please clear dir: {:?}", - path.as_ref(), - cf + path, cf ); std::process::exit(100); } @@ -44,7 +51,7 @@ impl DBStorage { } let db = if readonly { - Self::open_readonly(path.as_ref(), column_families)? + Self::open_readonly(path, column_families.clone())? } else { let mut db_opts = rocksdb::Options::default(); db_opts.create_if_missing(true); @@ -52,10 +59,13 @@ impl DBStorage { // For now we set the max total WAL size to be 1G. This config can be useful when column // families are updated at non-uniform frequencies. db_opts.set_max_total_wal_size(1 << 30); - Self::open_inner(&db_opts, path.as_ref(), column_families)? + Self::open_inner(&db_opts, path, column_families.clone())? }; - Ok(DBStorage { db }) + Ok(DBStorage { + db, + cfs: column_families, + }) } fn open_inner( @@ -88,7 +98,7 @@ impl DBStorage { } pub fn drop_cf(&mut self) -> Result<(), Error> { - for cf in &VEC_PREFIX_NAME.to_vec() { + for cf in self.cfs.clone() { self.db.drop_cf(cf)?; } Ok(()) @@ -97,7 +107,7 @@ impl DBStorage { /// Flushes all memtable data. This is only used for testing `get_approximate_sizes_cf` in unit /// tests. pub fn flush_all(&self) -> Result<()> { - for cf_name in VEC_PREFIX_NAME.to_vec() { + for cf_name in &self.cfs { let cf_handle = self.get_cf_handle(cf_name)?; self.db.flush_cf(cf_handle)?; } diff --git a/storage/src/state_node/mod.rs b/storage/src/state_node/mod.rs index 66e3be85cc..bc073b69bc 100644 --- a/storage/src/state_node/mod.rs +++ b/storage/src/state_node/mod.rs @@ -10,7 +10,6 @@ use crypto::HashValue; use forkable_jellyfish_merkle::node_type::Node; use starcoin_state_store_api::{StateNode, StateNodeStore}; use std::collections::BTreeMap; -use std::sync::Arc; define_storage!(StateStorage, HashValue, StateNode, STATE_NODE_PREFIX_NAME); diff --git a/storage/src/storage_macros.rs b/storage/src/storage_macros.rs index 9cd9addc9b..119dc90841 100644 --- a/storage/src/storage_macros.rs +++ b/storage/src/storage_macros.rs @@ -11,33 +11,33 @@ macro_rules! define_storage { ($storage_type: ident, $key_type: ty, $value_type: ty, $prefix_name: expr) => { #[derive(Clone)] pub struct $storage_type { - store: CodecStorage<$key_type, $value_type>, + store: $crate::storage::CodecStorage<$key_type, $value_type>, } impl $storage_type { - const PREFIX_NAME: $crate::ColumnFamilyName = $prefix_name; - pub fn new(instance: crate::storage::StorageInstance) -> Self { - let inner_storage = crate::storage::InnerStorage::new(instance, Self::PREFIX_NAME); + const PREFIX_NAME: $crate::storage::ColumnFamilyName = $prefix_name; + pub fn new(instance: $crate::storage::StorageInstance) -> Self { + let inner_storage = $crate::storage::InnerStorage::new(instance, Self::PREFIX_NAME); Self { - store: CodecStorage::new(Arc::new(inner_storage)), + store: CodecStorage::new(std::sync::Arc::new(inner_storage)), } } - pub fn put(&self, key: $key_type, value: $value_type) -> Result<()> { + pub fn put(&self, key: $key_type, value: $value_type) -> anyhow::Result<()> { self.store.put(key, value) } - pub fn get(&self, key: $key_type) -> Result> { + pub fn get(&self, key: $key_type) -> anyhow::Result> { self.store.get(key) } - pub fn remove(&self, key: $key_type) -> Result<()> { + pub fn remove(&self, key: $key_type) -> anyhow::Result<()> { self.store.remove(key) } - pub fn write_batch(&self, batch: WriteBatch) -> Result<()> { + pub fn write_batch(&self, batch: WriteBatch) -> anyhow::Result<()> { self.store.write_batch(batch) } - pub fn get_len(&self) -> Result { + pub fn get_len(&self) -> anyhow::Result { self.store.get_len() } - pub fn keys(&self) -> Result>> { + pub fn keys(&self) -> anyhow::Result>> { self.store.keys() } } diff --git a/storage/src/tests/test_storage.rs b/storage/src/tests/test_storage.rs index 4865510e78..c6767a5f48 100644 --- a/storage/src/tests/test_storage.rs +++ b/storage/src/tests/test_storage.rs @@ -6,7 +6,10 @@ extern crate chrono; use crate::cache_storage::CacheStorage; use crate::db_storage::DBStorage; use crate::storage::{InnerStore, StorageInstance, ValueCodec, CACHE_NONE_OBJECT}; -use crate::{Storage, TransactionInfoStore, DEFAULT_PREFIX_NAME, TRANSACTION_INFO_PREFIX_NAME}; +use crate::{ + Storage, TransactionInfoStore, DEFAULT_PREFIX_NAME, TRANSACTION_INFO_PREFIX_NAME, + VEC_PREFIX_NAME, +}; use anyhow::Result; use crypto::HashValue; use starcoin_types::transaction::TransactionInfo; @@ -45,7 +48,7 @@ fn test_open_read_only() { let result = db.put(DEFAULT_PREFIX_NAME, key.to_vec(), value.to_vec()); assert!(result.is_ok()); let path = tmpdir.as_ref().join("starcoindb"); - let db = DBStorage::open(path, true).unwrap(); + let db = DBStorage::open_with_cfs(path, VEC_PREFIX_NAME.to_vec(), true).unwrap(); let result = db.put(DEFAULT_PREFIX_NAME, key.to_vec(), value.to_vec()); assert!(result.is_err()); let result = db.get(DEFAULT_PREFIX_NAME, key.to_vec()).unwrap(); diff --git a/storage/src/transaction/mod.rs b/storage/src/transaction/mod.rs index 3ccab0aa68..71a371ef4a 100644 --- a/storage/src/transaction/mod.rs +++ b/storage/src/transaction/mod.rs @@ -10,7 +10,6 @@ use anyhow::Result; use crypto::HashValue; use scs::SCSCodec; use starcoin_types::transaction::Transaction; -use std::sync::Arc; define_storage!( TransactionStorage, diff --git a/storage/src/transaction_info/mod.rs b/storage/src/transaction_info/mod.rs index 8678a6663d..64416e81eb 100644 --- a/storage/src/transaction_info/mod.rs +++ b/storage/src/transaction_info/mod.rs @@ -10,7 +10,6 @@ use anyhow::{bail, Error, Result}; use crypto::HashValue; use scs::SCSCodec; use starcoin_types::transaction::TransactionInfo; -use std::sync::Arc; define_storage!( TransactionInfoStorage, diff --git a/wallet/api/Cargo.toml b/wallet/api/Cargo.toml index 88ed498e82..0722b4d033 100644 --- a/wallet/api/Cargo.toml +++ b/wallet/api/Cargo.toml @@ -15,6 +15,7 @@ starcoin-types = { path = "../../types"} starcoin-crypto = { path = "../../commons/crypto"} rand = "0.7.3" rand_core = { version = "0.5.1", default-features = false } +futures = "0.3" [dev-dependencies] diff --git a/wallet/api/src/lib.rs b/wallet/api/src/lib.rs index d44fab29be..607efd6c11 100644 --- a/wallet/api/src/lib.rs +++ b/wallet/api/src/lib.rs @@ -2,11 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 pub mod error; +mod rich_wallet; mod service; mod store; mod types; mod wallet; - +pub use rich_wallet::*; pub use service::*; pub use store::*; pub use types::*; diff --git a/wallet/api/src/rich_wallet.rs b/wallet/api/src/rich_wallet.rs new file mode 100644 index 0000000000..968c9b4aa6 --- /dev/null +++ b/wallet/api/src/rich_wallet.rs @@ -0,0 +1,64 @@ +use anyhow::Result; +use futures::Stream; +use serde::Deserialize; +use serde::Serialize; +use starcoin_types::account_address::AccountAddress; +use starcoin_types::account_config::token_code::TokenCode; +use starcoin_types::contract_event::EventWithProof; +use starcoin_types::event::EventKey; +use starcoin_types::transaction::{RawUserTransaction, SignedUserTransaction, TransactionPayload}; +use std::time::Duration; + +pub trait ChainEventWatcher { + fn watch_event(&self, key: EventKey) -> S + where + S: Stream; +} + +pub trait TransactionSubmitter { + fn submit_transaction(&self, txn: SignedUserTransaction) -> Result<()>; +} + +pub trait RichWallet { + fn set_default_expiration_timeout(&mut self, timeout: Duration); + fn set_default_gas_price(&mut self, gas_price: u64); + fn set_default_gas_token(&mut self, token: TokenCode); + + fn get_accepted_tokns(&self) -> Result>; + + fn build_transaction( + &self, + // if not specified, use default sender. + sender: Option, + payload: TransactionPayload, + max_gas_amount: u64, + // if not specified, uses default settings. + gas_unit_price: Option, + gas_token_code: Option, + expiration_timestamp_secs: Option, + ) -> Result; + + fn sign_transaction( + &self, + raw: RawUserTransaction, + address: Option, + ) -> Result; + fn submit_txn(&mut self, txn: SignedUserTransaction) -> Result<()>; + fn get_next_available_seq_number(&self) -> Result; + + // ...other functionality of origin wallets. +} + +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +pub struct Setting { + default_expiration_timeout: u64, + default_gas_price: u64, + default_gas_token: TokenCode, +} + +pub trait WalletStorageTrait { + fn save_default_settings(&self, setting: Setting) -> Result<()>; + + fn save_accepted_token(&self, token: TokenCode) -> Result<()>; + fn contain_wallet(&self, address: AccountAddress) -> Result; +} diff --git a/wallet/service/Cargo.toml b/wallet/service/Cargo.toml index 1f18f87626..314690379e 100644 --- a/wallet/service/Cargo.toml +++ b/wallet/service/Cargo.toml @@ -12,13 +12,19 @@ futures = "0.3" actix = "0.10.0-alpha.3" actix-rt = "1.1" async-trait = "0.1" +parking_lot = "0.9" +rand = "0.7" +serde = "1" starcoin-logger = {path = "../../commons/logger"} stest = {path = "../../commons/stest"} starcoin-types = { path = "../../types"} starcoin-config = { path = "../../config"} starcoin-wallet-api = { path = "../api", features = ["mock"]} starcoin-wallet-lib = { path = "../lib"} - +starcoin-storage = {path = "../../storage"} +starcoin-crypto = {path = "../../commons/crypto"} +starcoin-decrypt = {path = "../../commons/decrypt"} +starcoin-canonical-serialization = { package="starcoin-canonical-serialization", path = "../../commons/scs"} [dev-dependencies] tempfile="3" tokio = { version = "0.2", features = ["full"] } diff --git a/wallet/service/src/actor.rs b/wallet/service/src/actor.rs index 2b94e4387b..6ef5d50460 100644 --- a/wallet/service/src/actor.rs +++ b/wallet/service/src/actor.rs @@ -2,30 +2,27 @@ // SPDX-License-Identifier: Apache-2.0 use crate::message::{WalletRequest, WalletResponse}; -use crate::service::WalletServiceImpl; +use crate::rich_wallet::WalletStorage; +use crate::wallet_manager::WalletManager; use actix::{Actor, Addr, Context, Handler}; use anyhow::Result; use starcoin_config::NodeConfig; use starcoin_types::account_address::AccountAddress; use starcoin_types::transaction::{RawUserTransaction, SignedUserTransaction}; -use starcoin_wallet_lib::{file_wallet_store::FileWalletStore, keystore_wallet::KeyStoreWallet}; - use starcoin_wallet_api::error::AccountServiceError; -use starcoin_wallet_api::{ServiceResult, Wallet, WalletAccount, WalletAsyncService, WalletResult}; +use starcoin_wallet_api::{ServiceResult, WalletAccount, WalletAsyncService, WalletResult}; use std::sync::Arc; pub struct WalletActor { - service: WalletServiceImpl>, + service: WalletManager, } impl WalletActor { pub fn launch(config: Arc) -> Result { let vault_config = &config.vault; - let file_store = FileWalletStore::new(vault_config.dir()); - let wallet = KeyStoreWallet::new(file_store)?; - let actor = WalletActor { - service: WalletServiceImpl::new(wallet), - }; + let wallet_storage = WalletStorage::create_from_path(vault_config.dir())?; + let manager = WalletManager::new(wallet_storage)?; + let actor = WalletActor { service: manager }; Ok(WalletActorRef(actor.start())) } } @@ -40,16 +37,18 @@ impl Handler for WalletActor { fn handle(&mut self, msg: WalletRequest, _ctx: &mut Self::Context) -> Self::Result { let response = match msg { WalletRequest::CreateAccount(password) => WalletResponse::WalletAccount(Box::new( - self.service.create_account(password.as_str())?, + self.service + .create_account(password.as_str())? + .wallet_info(), )), WalletRequest::GetDefaultAccount() => { - WalletResponse::WalletAccountOption(Box::new(self.service.get_default_account()?)) + WalletResponse::WalletAccountOption(Box::new(self.service.default_wallet_info()?)) } WalletRequest::GetAccounts() => { - WalletResponse::AccountList(self.service.get_accounts()?) + WalletResponse::AccountList(self.service.list_wallet_infos()?) } WalletRequest::GetAccount(address) => { - WalletResponse::WalletAccountOption(Box::new(self.service.get_account(&address)?)) + WalletResponse::WalletAccountOption(Box::new(self.service.wallet_info(address)?)) } WalletRequest::SignTxn { txn: raw_txn, @@ -57,11 +56,11 @@ impl Handler for WalletActor { } => WalletResponse::SignedTxn(Box::new(self.service.sign_txn(*raw_txn, signer)?)), WalletRequest::UnlockAccount(address, password, duration) => { self.service - .unlock_account(address, password.as_str(), duration)?; + .unlock_wallet(address, password.as_str(), duration)?; WalletResponse::UnlockAccountResponse } WalletRequest::ExportAccount { address, password } => { - let data = self.service.export_account(&address, password.as_str())?; + let data = self.service.export_account(address, password.as_str())?; WalletResponse::ExportAccountResponse(data) } WalletRequest::ImportAccount { @@ -69,10 +68,10 @@ impl Handler for WalletActor { password, private_key, } => { - let account = + let wallet = self.service .import_account(address, private_key, password.as_str())?; - WalletResponse::WalletAccount(Box::new(account)) + WalletResponse::WalletAccount(Box::new(wallet.wallet_info())) } }; Ok(response) diff --git a/wallet/service/src/lib.rs b/wallet/service/src/lib.rs index d48ef5e9b7..e646558d21 100644 --- a/wallet/service/src/lib.rs +++ b/wallet/service/src/lib.rs @@ -3,6 +3,7 @@ mod actor; mod message; -mod service; +mod rich_wallet; +mod wallet_manager; pub use actor::*; diff --git a/wallet/service/src/rich_wallet/mod.rs b/wallet/service/src/rich_wallet/mod.rs new file mode 100644 index 0000000000..f6eb7aa966 --- /dev/null +++ b/wallet/service/src/rich_wallet/mod.rs @@ -0,0 +1,337 @@ +use anyhow::{format_err, Error, Result}; +use serde::Deserialize; +use serde::Serialize; +use starcoin_canonical_serialization::SCSCodec; +use starcoin_crypto::ed25519::{Ed25519PrivateKey, Ed25519PublicKey}; +use starcoin_crypto::PrivateKey; +use starcoin_decrypt::{decrypt, encrypt}; +use starcoin_storage::cache_storage::CacheStorage; +use starcoin_storage::db_storage::DBStorage; +use starcoin_storage::storage::{KeyCodec, ValueCodec}; +use starcoin_storage::{ + batch::WriteBatch, + define_storage, + storage::{CodecStorage, ColumnFamilyName, StorageInstance}, +}; +use starcoin_types::account_address; +use starcoin_types::account_address::AccountAddress; +use starcoin_types::transaction::{RawUserTransaction, SignedUserTransaction}; +use starcoin_wallet_api::error::WalletError; +use starcoin_wallet_api::{Setting, WalletAccount}; +use std::convert::TryFrom; +use std::path::Path; +use std::sync::Arc; + +pub const SETTING_PREFIX_NAME: ColumnFamilyName = "account_settings"; +pub const ENCRYPTED_PRIVATE_KEY_PREFIX_NAME: ColumnFamilyName = "encrypted_private_key"; +pub const PUBLIC_KEY_PREFIX_NAME: ColumnFamilyName = "public_key"; +pub const GLOBAL_PREFIX_NAME: ColumnFamilyName = "global"; + +define_storage!( + WalletSettingStore, + AccountAddressWrapper, + SettingWrapper, + SETTING_PREFIX_NAME +); + +define_storage!( + PrivateKeyStore, + AccountAddressWrapper, + EncryptedPrivateKey, + ENCRYPTED_PRIVATE_KEY_PREFIX_NAME +); +define_storage!( + PublicKeyStore, + AccountAddressWrapper, + PublicKeyWrapper, + PUBLIC_KEY_PREFIX_NAME +); + +define_storage!( + GlobalSettingStore, + GlobalSettingKey, + GlobalValue, + GLOBAL_PREFIX_NAME +); + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] +pub enum GlobalSettingKey { + DefaultAddress, + /// FIXME: once db support iter, remove this. + AllAddresses, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct GlobalValue { + addresses: Vec, +} + +impl KeyCodec for GlobalSettingKey { + fn encode_key(&self) -> Result, Error> { + self.encode() + } + + fn decode_key(data: &[u8]) -> Result { + GlobalSettingKey::decode(data) + } +} + +impl ValueCodec for GlobalValue { + fn encode_value(&self) -> Result, Error> { + self.addresses.encode() + } + + fn decode_value(data: &[u8]) -> Result { + >::decode(data).map(|addresses| GlobalValue { addresses }) + } +} + +#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)] +pub struct AccountAddressWrapper(AccountAddress); +impl From for AccountAddressWrapper { + fn from(addr: AccountAddress) -> Self { + Self(addr) + } +} +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SettingWrapper(Setting); +impl From for SettingWrapper { + fn from(setting: Setting) -> Self { + Self(setting) + } +} + +impl KeyCodec for AccountAddressWrapper { + fn encode_key(&self) -> Result, Error> { + Ok(self.0.to_vec()) + } + + fn decode_key(data: &[u8]) -> Result { + AccountAddress::try_from(data).map(AccountAddressWrapper) + } +} + +impl ValueCodec for SettingWrapper { + fn encode_value(&self) -> Result, Error> { + self.0.encode() + } + + fn decode_value(data: &[u8]) -> Result { + Setting::decode(data).map(SettingWrapper) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EncryptedPrivateKey(Vec); +impl From> for EncryptedPrivateKey { + fn from(s: Vec) -> Self { + Self(s) + } +} + +impl ValueCodec for EncryptedPrivateKey { + fn encode_value(&self) -> Result, Error> { + Ok(self.0.clone()) + } + + fn decode_value(data: &[u8]) -> Result { + Ok(EncryptedPrivateKey(data.to_vec())) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PublicKeyWrapper(Ed25519PublicKey); +impl From for PublicKeyWrapper { + fn from(s: Ed25519PublicKey) -> Self { + Self(s) + } +} + +impl ValueCodec for PublicKeyWrapper { + fn encode_value(&self) -> Result, Error> { + Ok(self.0.to_bytes().to_vec()) + } + + fn decode_value(data: &[u8]) -> Result { + let key = Ed25519PublicKey::try_from(data)?; + Ok(Self(key)) + } +} + +#[derive(Clone)] +pub struct WalletStorage { + setting_store: WalletSettingStore, + private_key_store: PrivateKeyStore, + public_key_store: PublicKeyStore, + global_value_store: GlobalSettingStore, +} + +impl WalletStorage { + pub fn create_from_path(p: impl AsRef) -> Result { + let db = DBStorage::open_with_cfs( + p, + vec![ + SETTING_PREFIX_NAME, + ENCRYPTED_PRIVATE_KEY_PREFIX_NAME, + PUBLIC_KEY_PREFIX_NAME, + GLOBAL_PREFIX_NAME, + ], + false, + )?; + let storage_instance = StorageInstance::new_cache_and_db_instance( + Arc::new(CacheStorage::default()), + Arc::new(db), + ); + Ok(Self::new(storage_instance)) + } + + pub fn new(store: StorageInstance) -> Self { + Self { + setting_store: WalletSettingStore::new(store.clone()), + private_key_store: PrivateKeyStore::new(store.clone()), + public_key_store: PublicKeyStore::new(store.clone()), + global_value_store: GlobalSettingStore::new(store), + } + } +} + +impl WalletStorage { + pub fn default_address(&self) -> Result> { + let value = self + .global_value_store + .get(GlobalSettingKey::DefaultAddress)?; + Ok(value.and_then(|mut v| v.addresses.pop())) + } + + pub fn set_default_address(&self, address: AccountAddress) -> Result<()> { + self.global_value_store.put( + GlobalSettingKey::DefaultAddress, + GlobalValue { + addresses: vec![address], + }, + ) + } + pub fn contain_address(&self, address: AccountAddress) -> Result { + self.public_key_store + .get(address.into()) + .map(|w| w.is_some()) + } + + pub fn list_addresses(&self) -> Result> { + let value = self + .global_value_store + .get(GlobalSettingKey::AllAddresses)?; + Ok(value.map(|v| v.addresses).unwrap_or_default()) + } + + pub fn public_key(&self, address: AccountAddress) -> Result> { + self.public_key_store + .get(address.into()) + .map(|w| w.map(|p| p.0)) + } + + #[allow(unused)] + pub fn save_default_settings( + &self, + address: AccountAddress, + setting: Setting, + ) -> Result<(), Error> { + self.setting_store.put(address.into(), setting.into()) + } + pub fn save_encrypted_private_key( + &self, + address: AccountAddress, + encrypted: Vec, + ) -> Result<(), Error> { + self.private_key_store.put(address.into(), encrypted.into()) + } +} + +pub struct Wallet { + addr: AccountAddress, + private_key: Ed25519PrivateKey, + store: WalletStorage, +} + +pub type WalletResult = std::result::Result; + +impl Wallet { + pub fn create( + public_key: Ed25519PublicKey, + private_key: Ed25519PrivateKey, + addr: Option, + password: String, + storage: WalletStorage, + ) -> WalletResult { + let address = addr.unwrap_or_else(|| account_address::from_public_key(&public_key)); + let encrypted_prikey = encrypt(password.as_bytes(), &private_key.to_bytes()); + + storage + .public_key_store + .put(address.into(), public_key.into())?; + storage.save_encrypted_private_key(address, encrypted_prikey)?; + + Ok(Self { + addr: address, + private_key, + store: storage, + }) + } + pub fn load( + addr: AccountAddress, + password: &str, + store: WalletStorage, + ) -> WalletResult> { + let encrypted_key = store.private_key_store.get(addr.into())?; + if encrypted_key.is_none() { + return Ok(None); + } + + let encrypted_key = encrypted_key.unwrap(); + let plain_key_data = decrypt(password.as_bytes(), &encrypted_key.0) + .map_err(|_e| WalletError::InvalidPassword(addr))?; + let private_key = Ed25519PrivateKey::try_from(plain_key_data.as_slice()).map_err(|_e| { + WalletError::StoreError(format_err!("underline vault store corrupted")) + })?; + let saved_public_key = store.public_key_store.get(addr.into())?.map(|w| w.0); + let saved_public_key = saved_public_key.ok_or_else(|| { + WalletError::StoreError(format_err!("public key not found for address {}", addr)) + })?; + if saved_public_key.to_bytes() != private_key.public_key().to_bytes() { + return Err(WalletError::StoreError(format_err!( + "invalid state of public key and private key" + ))); + } + + Ok(Some(Self { + addr, + private_key, + store, + })) + } + + pub fn wallet_info(&self) -> WalletAccount { + // TODO: fix is_default + WalletAccount::new(self.addr, self.private_key.public_key(), false) + } + + pub fn sign_txn(&self, raw_txn: RawUserTransaction) -> Result { + raw_txn + .sign(&self.private_key, self.private_key.public_key()) + .map(|t| t.into_inner()) + } + + pub fn destory(self) -> Result<()> { + self.store.private_key_store.remove(self.addr.into())?; + self.store.setting_store.remove(self.addr.into())?; + Ok(()) + } + + #[allow(unused)] + pub fn address(&self) -> &AccountAddress { + &self.addr + } + pub fn private_key(&self) -> &Ed25519PrivateKey { + &self.private_key + } +} diff --git a/wallet/service/src/service.rs b/wallet/service/src/service.rs deleted file mode 100644 index 74ef52d9d7..0000000000 --- a/wallet/service/src/service.rs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) The Starcoin Core Contributors -// SPDX-License-Identifier: Apache-2.0 - -use starcoin_types::account_address::AccountAddress; -use starcoin_types::transaction::{RawUserTransaction, SignedUserTransaction}; -use starcoin_wallet_api::{Wallet, WalletAccount, WalletResult, WalletService}; -use std::time::Duration; - -pub struct WalletServiceImpl -where - W: Wallet, -{ - //TODO support multi wallet. - wallet: W, -} - -impl WalletServiceImpl -where - W: Wallet, -{ - pub fn new(wallet: W) -> Self { - Self { wallet } - } -} - -impl WalletService for WalletServiceImpl where W: Wallet {} - -impl Wallet for WalletServiceImpl -where - W: Wallet, -{ - fn create_account(&self, password: &str) -> WalletResult { - self.wallet.create_account(password) - } - - fn get_account(&self, address: &AccountAddress) -> WalletResult> { - self.wallet.get_account(address) - } - - fn import_account( - &self, - address: AccountAddress, - private_key: Vec, - password: &str, - ) -> WalletResult { - self.wallet.import_account(address, private_key, password) - } - - fn export_account(&self, address: &AccountAddress, password: &str) -> WalletResult> { - self.wallet.export_account(address, password) - } - - fn contains(&self, address: &AccountAddress) -> WalletResult { - self.wallet.contains(address) - } - - fn unlock_account( - &self, - address: AccountAddress, - password: &str, - duration: Duration, - ) -> WalletResult<()> { - self.wallet.unlock_account(address, password, duration) - } - - fn lock_account(&self, address: AccountAddress) -> WalletResult<()> { - self.wallet.lock_account(address) - } - - fn sign_txn( - &self, - raw_txn: RawUserTransaction, - signer_address: AccountAddress, - ) -> WalletResult { - self.wallet.sign_txn(raw_txn, signer_address) - } - - fn get_default_account(&self) -> WalletResult> { - self.wallet.get_default_account() - } - - fn get_accounts(&self) -> WalletResult> { - self.wallet.get_accounts() - } - - fn set_default(&self, address: &AccountAddress) -> WalletResult<()> { - self.wallet.set_default(address) - } - - fn remove_account(&self, address: &AccountAddress) -> WalletResult<()> { - self.wallet.remove_account(address) - } -} diff --git a/wallet/service/src/wallet_manager.rs b/wallet/service/src/wallet_manager.rs new file mode 100644 index 0000000000..d29a6a4048 --- /dev/null +++ b/wallet/service/src/wallet_manager.rs @@ -0,0 +1,225 @@ +// Copyright (c) The Starcoin Core Contributors +// SPDX-License-Identifier: Apache-2.0 + +use crate::rich_wallet::{Wallet, WalletStorage}; +use parking_lot::RwLock; +use rand::prelude::*; +use starcoin_crypto::ed25519::{Ed25519PrivateKey, Ed25519PublicKey}; +use starcoin_crypto::{PrivateKey, Uniform}; + +use starcoin_types::{ + account_address::{self, AccountAddress}, + transaction::{RawUserTransaction, SignedUserTransaction}, +}; +use starcoin_wallet_api::error::WalletError; +use starcoin_wallet_api::WalletAccount; +use std::collections::HashMap; +use std::convert::TryFrom; +use std::ops::Add; +use std::time::Duration; +use std::time::Instant; + +type KeyPair = starcoin_crypto::test_utils::KeyPair; +pub type Result = std::result::Result; + +/// Wallet base KeyStore +/// encrypt account's key by a password. +pub struct WalletManager { + store: WalletStorage, + key_cache: RwLock, +} + +#[derive(Default, Debug, PartialEq, Eq)] +struct KeyCache { + cache: HashMap, +} +impl KeyCache { + pub fn cache_key(&mut self, account: AccountAddress, pass: String, ttl: Instant) { + self.cache.insert(account, (ttl, pass)); + } + pub fn remove_key(&mut self, account: &AccountAddress) { + self.cache.remove(account); + } + pub fn get_key(&mut self, account: &AccountAddress) -> Option { + match self.cache.remove(account) { + None => None, + Some((ttl, kp)) => { + if Instant::now() < ttl { + self.cache.insert(*account, (ttl, kp)); + self.cache.get(account).map(|t| t.1.to_string()) + } else { + None + } + } + } + } + + #[allow(dead_code)] + pub fn clean_expired(&mut self) { + let cur_instant = Instant::now(); + self.cache.retain(|_account, (ttl, _)| &cur_instant < ttl); + } +} + +impl WalletManager { + pub fn new(storage: WalletStorage) -> Result { + let manager = Self { + store: storage, + key_cache: RwLock::new(KeyCache::default()), + }; + Ok(manager) + } + + pub fn create_account(&self, password: &str) -> Result { + let keypair = gen_keypair(); + let address = account_address::from_public_key(&keypair.public_key); + + let wallet = Wallet::create( + keypair.public_key.clone(), + keypair.private_key, + Some(address), + password.to_string(), + self.store.clone(), + )?; + + Ok(wallet) + } + + pub fn unlock_wallet( + &self, + address: AccountAddress, + password: &str, + duration: Duration, + ) -> Result<()> { + let _ = Wallet::load(address, password, self.store.clone())? + .ok_or_else(|| WalletError::AccountNotExist(address))?; + let ttl = std::time::Instant::now().add(duration); + self.key_cache + .write() + .cache_key(address, password.to_string(), ttl); + Ok(()) + } + + pub fn import_account( + &self, + address: AccountAddress, + private_key: Vec, + password: &str, + ) -> Result { + if self.contains(&address)? { + return Err(WalletError::AccountAlreadyExist(address)); + } + let private_key = Ed25519PrivateKey::try_from(private_key.as_slice()) + .map_err(|_| WalletError::InvalidPrivateKey)?; + // let key_pair = KeyPair::from(private_key); + let wallet = Wallet::create( + private_key.public_key(), + private_key, + Some(address), + password.to_string(), + self.store.clone(), + )?; + Ok(wallet) + } + + pub fn export_account(&self, address: AccountAddress, password: &str) -> Result> { + let wallet = Wallet::load(address, password, self.store.clone())? + .ok_or_else(|| WalletError::AccountNotExist(address))?; + Ok(wallet.private_key().to_bytes().to_vec()) + } + + pub fn contains(&self, address: &AccountAddress) -> Result { + self.store + .contain_address(*address) + .map_err(WalletError::StoreError) + } + + pub fn default_wallet_info(&self) -> Result> { + let default_address = self.store.default_address()?; + let wallet_info = default_address + .map(|a| self.wallet_info(a)) + .transpose()? + .and_then(|a| a); + Ok(wallet_info) + } + pub fn list_wallet_infos(&self) -> Result> { + let default_account = self.store.default_address()?; + let mut res = vec![]; + for account in self.store.list_addresses()? { + let pubkey = self.store.public_key(account)?; + match pubkey { + Some(p) => { + res.push(WalletAccount { + address: account, + is_default: default_account.filter(|a| a == &account).is_some(), + public_key: p, + }); + } + None => { + continue; + } + } + } + Ok(res) + } + + pub fn wallet_info(&self, address: AccountAddress) -> Result> { + match self.store.public_key(address)? { + Some(p) => { + let default_account = self.store.default_address()?; + Ok(Some(WalletAccount { + address, + is_default: default_account.filter(|a| a == &address).is_some(), + public_key: p, + })) + } + None => Ok(None), + } + } + + pub fn sign_txn( + &self, + raw_txn: RawUserTransaction, + signer_address: AccountAddress, + ) -> Result { + let pass = self.key_cache.write().get_key(&signer_address); + match pass { + None => Err(WalletError::AccountLocked(signer_address)), + Some(p) => { + let wallet = Wallet::load(signer_address, p.as_str(), self.store.clone())? + .ok_or_else(|| WalletError::AccountNotExist(signer_address))?; + wallet + .sign_txn(raw_txn) + .map_err(WalletError::TransactionSignError) + } + } + } + + #[allow(unused)] + pub fn set_default(&self, address: AccountAddress) -> Result<()> { + self.store + .set_default_address(address) + .map_err(WalletError::StoreError) + } + + /// remove wallet need user password. + #[allow(unused)] + pub fn remove_account(&self, address: AccountAddress, password: &str) -> Result<()> { + let wallet = Wallet::load(address, password, self.store.clone())?; + match wallet { + Some(wallet) => { + self.key_cache.write().remove_key(&address); + wallet.destory().map_err(WalletError::StoreError) + } + None => Ok(()), + } + } +} + +fn gen_keypair() -> KeyPair { + let mut seed_rng = rand::rngs::OsRng; + let seed_buf: [u8; 32] = seed_rng.gen(); + let mut rng: StdRng = SeedableRng::from_seed(seed_buf); + let key_pair: KeyPair = KeyPair::generate(&mut rng); + key_pair +} From 4fb87f4492042e86785f11da1e11a4dab46d2b5c Mon Sep 17 00:00:00 2001 From: caojiafeng Date: Thu, 30 Jul 2020 15:44:10 +0800 Subject: [PATCH 2/4] cleanup code --- Cargo.lock | 9 +- wallet/lib/Cargo.toml | 8 +- wallet/lib/src/file_wallet_store.rs | 6 +- wallet/lib/src/keystore_wallet.rs | 2 +- wallet/lib/src/lib.rs | 8 +- wallet/lib/src/wallet.rs | 86 +++++++++ wallet/{service => lib}/src/wallet_manager.rs | 27 +-- .../mod.rs => lib/src/wallet_storage.rs} | 167 +++++++----------- wallet/lib/src/wallet_test.rs | 64 +++++++ wallet/service/Cargo.toml | 6 - wallet/service/src/actor.rs | 18 +- wallet/service/src/lib.rs | 3 - 12 files changed, 260 insertions(+), 144 deletions(-) create mode 100644 wallet/lib/src/wallet.rs rename wallet/{service => lib}/src/wallet_manager.rs (90%) rename wallet/{service/src/rich_wallet/mod.rs => lib/src/wallet_storage.rs} (66%) create mode 100644 wallet/lib/src/wallet_test.rs diff --git a/Cargo.lock b/Cargo.lock index 55557c188a..226e5fb430 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7543,11 +7543,14 @@ dependencies = [ "anyhow", "async-trait", "futures 0.3.5", + "parking_lot 0.9.0", "rand 0.7.3", "rand_core 0.5.1", + "serde", "starcoin-canonical-serialization", "starcoin-crypto", "starcoin-decrypt", + "starcoin-storage", "starcoin-types", "starcoin-wallet-api", "tempfile", @@ -7562,15 +7565,9 @@ dependencies = [ "anyhow", "async-trait", "futures 0.3.5", - "parking_lot 0.9.0", - "rand 0.7.3", - "serde", - "starcoin-canonical-serialization", "starcoin-config", "starcoin-crypto", - "starcoin-decrypt", "starcoin-logger", - "starcoin-storage", "starcoin-types", "starcoin-wallet-api", "starcoin-wallet-lib", diff --git a/wallet/lib/Cargo.toml b/wallet/lib/Cargo.toml index ba9d7ebd40..3c9660f4bf 100644 --- a/wallet/lib/Cargo.toml +++ b/wallet/lib/Cargo.toml @@ -13,11 +13,15 @@ actix = "0.10.0-alpha.3" actix-rt = "1.1" async-trait = "0.1" rand = "0.7.3" +parking_lot = "0.9" +serde = "1" rand_core = { version = "0.5.1", default-features = false } -wallet-api = {path = "../api",package = "starcoin-wallet-api"} -scs ={package= "starcoin-canonical-serialization", path = "../../commons/scs"} + +starcoin-wallet-api = {path = "../api",package = "starcoin-wallet-api"} +starcoin-canonical-serialization ={package= "starcoin-canonical-serialization", path = "../../commons/scs"} starcoin-types = { path = "../../types"} starcoin-crypto = { path = "../../commons/crypto"} starcoin-decrypt = {path = "../../commons/decrypt"} +starcoin-storage = {path = "../../storage"} [dev-dependencies] tempfile="3" diff --git a/wallet/lib/src/file_wallet_store.rs b/wallet/lib/src/file_wallet_store.rs index 7880290ff6..c29b7db2a9 100644 --- a/wallet/lib/src/file_wallet_store.rs +++ b/wallet/lib/src/file_wallet_store.rs @@ -2,8 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use anyhow::{bail, Result}; -use scs::SCSCodec; +use starcoin_canonical_serialization as scs; +use starcoin_canonical_serialization::SCSCodec; use starcoin_types::account_address::AccountAddress; +use starcoin_wallet_api::{WalletAccount, WalletStore}; use std::fs::OpenOptions; use std::path::Path; use std::{ @@ -12,8 +14,6 @@ use std::{ io::{Read, Write}, path::PathBuf, }; -use wallet_api::WalletAccount; -use wallet_api::WalletStore; pub const DEFAULT_ACCOUNT_FILE_NAME: &str = "account"; diff --git a/wallet/lib/src/keystore_wallet.rs b/wallet/lib/src/keystore_wallet.rs index 2b62e5c602..a5e5344496 100644 --- a/wallet/lib/src/keystore_wallet.rs +++ b/wallet/lib/src/keystore_wallet.rs @@ -11,13 +11,13 @@ use starcoin_types::{ account_address::{self, AccountAddress}, transaction::{RawUserTransaction, SignedUserTransaction}, }; +use starcoin_wallet_api::{error::WalletError, Wallet, WalletAccount, WalletStore}; use std::collections::HashMap; use std::convert::TryFrom; use std::ops::Add; use std::sync::{Mutex, RwLock}; use std::time::Duration; use std::time::Instant; -use wallet_api::{error::WalletError, Wallet, WalletAccount, WalletStore}; type KeyPair = starcoin_crypto::test_utils::KeyPair; pub type Result = std::result::Result; diff --git a/wallet/lib/src/lib.rs b/wallet/lib/src/lib.rs index 462081b141..fe23fa8d26 100644 --- a/wallet/lib/src/lib.rs +++ b/wallet/lib/src/lib.rs @@ -3,13 +3,19 @@ pub mod file_wallet_store; pub mod keystore_wallet; +pub mod wallet; +pub mod wallet_manager; +pub mod wallet_storage; + +#[cfg(test)] +mod wallet_test; #[cfg(test)] mod test { use crate::file_wallet_store::FileWalletStore; // use starcoin_types::account_address::AccountAddress; + use starcoin_wallet_api::{WalletAccount, WalletStore}; use std::collections::HashMap; - use wallet_api::{WalletAccount, WalletStore}; #[test] fn test_file_store() { diff --git a/wallet/lib/src/wallet.rs b/wallet/lib/src/wallet.rs new file mode 100644 index 0000000000..43c12106ef --- /dev/null +++ b/wallet/lib/src/wallet.rs @@ -0,0 +1,86 @@ +use crate::wallet_storage::WalletStorage; +use anyhow::{format_err, Result}; +use starcoin_crypto::ed25519::{Ed25519PrivateKey, Ed25519PublicKey}; +use starcoin_crypto::PrivateKey; +use starcoin_types::account_address; +use starcoin_types::account_address::AccountAddress; +use starcoin_types::transaction::{RawUserTransaction, SignedUserTransaction}; +use starcoin_wallet_api::error::WalletError; +use starcoin_wallet_api::WalletAccount; + +pub struct Wallet { + addr: AccountAddress, + private_key: Ed25519PrivateKey, + store: WalletStorage, +} + +pub type WalletResult = std::result::Result; + +impl Wallet { + pub fn create( + public_key: Ed25519PublicKey, + private_key: Ed25519PrivateKey, + addr: Option, + password: String, + storage: WalletStorage, + ) -> WalletResult { + let address = addr.unwrap_or_else(|| account_address::from_public_key(&public_key)); + storage.update_key(address, public_key, &private_key, password.as_str())?; + + Ok(Self { + addr: address, + private_key, + store: storage, + }) + } + pub fn load( + addr: AccountAddress, + password: &str, + store: WalletStorage, + ) -> WalletResult> { + let decrypted_key = store.decrypt_private_key(addr, password)?; + let private_key = match decrypted_key { + None => return Ok(None), + Some(p) => p, + }; + + let saved_public_key = store.public_key(addr)?; + let saved_public_key = saved_public_key.ok_or_else(|| { + WalletError::StoreError(format_err!("public key not found for address {}", addr)) + })?; + if saved_public_key.to_bytes() != private_key.public_key().to_bytes() { + return Err(WalletError::StoreError(format_err!( + "invalid state of public key and private key" + ))); + } + + Ok(Some(Self { + addr, + private_key, + store, + })) + } + + pub fn wallet_info(&self) -> WalletAccount { + // TODO: fix is_default + WalletAccount::new(self.addr, self.private_key.public_key(), false) + } + + pub fn sign_txn(&self, raw_txn: RawUserTransaction) -> Result { + raw_txn + .sign(&self.private_key, self.private_key.public_key()) + .map(|t| t.into_inner()) + } + + pub fn destroy(self) -> Result<()> { + self.store.destroy_wallet(self.addr) + } + + #[allow(unused)] + pub fn address(&self) -> &AccountAddress { + &self.addr + } + pub fn private_key(&self) -> &Ed25519PrivateKey { + &self.private_key + } +} diff --git a/wallet/service/src/wallet_manager.rs b/wallet/lib/src/wallet_manager.rs similarity index 90% rename from wallet/service/src/wallet_manager.rs rename to wallet/lib/src/wallet_manager.rs index d29a6a4048..426f16d53a 100644 --- a/wallet/service/src/wallet_manager.rs +++ b/wallet/lib/src/wallet_manager.rs @@ -1,12 +1,13 @@ // Copyright (c) The Starcoin Core Contributors // SPDX-License-Identifier: Apache-2.0 -use crate::rich_wallet::{Wallet, WalletStorage}; +use crate::wallet::Wallet; +use crate::wallet_storage::WalletStorage; + use parking_lot::RwLock; use rand::prelude::*; use starcoin_crypto::ed25519::{Ed25519PrivateKey, Ed25519PublicKey}; use starcoin_crypto::{PrivateKey, Uniform}; - use starcoin_types::{ account_address::{self, AccountAddress}, transaction::{RawUserTransaction, SignedUserTransaction}, @@ -70,7 +71,7 @@ impl WalletManager { Ok(manager) } - pub fn create_account(&self, password: &str) -> Result { + pub fn create_wallet(&self, password: &str) -> Result { let keypair = gen_keypair(); let address = account_address::from_public_key(&keypair.public_key); @@ -81,6 +82,12 @@ impl WalletManager { password.to_string(), self.store.clone(), )?; + self.store.add_address(*wallet.address())?; + + // if it's the first address, set it default. + if self.store.list_addresses()?.len() == 1 { + self.set_default_wallet(address)?; + } Ok(wallet) } @@ -100,7 +107,7 @@ impl WalletManager { Ok(()) } - pub fn import_account( + pub fn import_wallet( &self, address: AccountAddress, private_key: Vec, @@ -122,7 +129,7 @@ impl WalletManager { Ok(wallet) } - pub fn export_account(&self, address: AccountAddress, password: &str) -> Result> { + pub fn export_wallet(&self, address: AccountAddress, password: &str) -> Result> { let wallet = Wallet::load(address, password, self.store.clone())? .ok_or_else(|| WalletError::AccountNotExist(address))?; Ok(wallet.private_key().to_bytes().to_vec()) @@ -179,8 +186,8 @@ impl WalletManager { pub fn sign_txn( &self, - raw_txn: RawUserTransaction, signer_address: AccountAddress, + raw_txn: RawUserTransaction, ) -> Result { let pass = self.key_cache.write().get_key(&signer_address); match pass { @@ -196,20 +203,20 @@ impl WalletManager { } #[allow(unused)] - pub fn set_default(&self, address: AccountAddress) -> Result<()> { + pub fn set_default_wallet(&self, address: AccountAddress) -> Result<()> { self.store - .set_default_address(address) + .set_default_address(Some(address)) .map_err(WalletError::StoreError) } /// remove wallet need user password. #[allow(unused)] - pub fn remove_account(&self, address: AccountAddress, password: &str) -> Result<()> { + pub fn delete_wallet(&self, address: AccountAddress, password: &str) -> Result<()> { let wallet = Wallet::load(address, password, self.store.clone())?; match wallet { Some(wallet) => { self.key_cache.write().remove_key(&address); - wallet.destory().map_err(WalletError::StoreError) + wallet.destroy().map_err(WalletError::StoreError) } None => Ok(()), } diff --git a/wallet/service/src/rich_wallet/mod.rs b/wallet/lib/src/wallet_storage.rs similarity index 66% rename from wallet/service/src/rich_wallet/mod.rs rename to wallet/lib/src/wallet_storage.rs index f6eb7aa966..e28982550c 100644 --- a/wallet/service/src/rich_wallet/mod.rs +++ b/wallet/lib/src/wallet_storage.rs @@ -1,9 +1,8 @@ -use anyhow::{format_err, Error, Result}; +use anyhow::{Error, Result}; use serde::Deserialize; use serde::Serialize; use starcoin_canonical_serialization::SCSCodec; use starcoin_crypto::ed25519::{Ed25519PrivateKey, Ed25519PublicKey}; -use starcoin_crypto::PrivateKey; use starcoin_decrypt::{decrypt, encrypt}; use starcoin_storage::cache_storage::CacheStorage; use starcoin_storage::db_storage::DBStorage; @@ -13,11 +12,8 @@ use starcoin_storage::{ define_storage, storage::{CodecStorage, ColumnFamilyName, StorageInstance}, }; -use starcoin_types::account_address; use starcoin_types::account_address::AccountAddress; -use starcoin_types::transaction::{RawUserTransaction, SignedUserTransaction}; -use starcoin_wallet_api::error::WalletError; -use starcoin_wallet_api::{Setting, WalletAccount}; +use starcoin_wallet_api::Setting; use std::convert::TryFrom; use std::path::Path; use std::sync::Arc; @@ -122,7 +118,7 @@ impl ValueCodec for SettingWrapper { } #[derive(Debug, Clone, PartialEq, Eq)] -pub struct EncryptedPrivateKey(Vec); +pub struct EncryptedPrivateKey(pub Vec); impl From> for EncryptedPrivateKey { fn from(s: Vec) -> Self { Self(s) @@ -203,13 +199,19 @@ impl WalletStorage { Ok(value.and_then(|mut v| v.addresses.pop())) } - pub fn set_default_address(&self, address: AccountAddress) -> Result<()> { - self.global_value_store.put( - GlobalSettingKey::DefaultAddress, - GlobalValue { - addresses: vec![address], - }, - ) + /// Update or remove default address settings + pub fn set_default_address(&self, address: Option) -> Result<()> { + match address { + Some(addr) => self.global_value_store.put( + GlobalSettingKey::DefaultAddress, + GlobalValue { + addresses: vec![addr], + }, + ), + None => self + .global_value_store + .remove(GlobalSettingKey::DefaultAddress), + } } pub fn contain_address(&self, address: AccountAddress) -> Result { self.public_key_store @@ -217,6 +219,21 @@ impl WalletStorage { .map(|w| w.is_some()) } + /// FIXME: once storage support iter, we can remove this. + pub fn add_address(&self, address: AccountAddress) -> Result<()> { + let value = self + .global_value_store + .get(GlobalSettingKey::AllAddresses)?; + let mut addrs = value.map(|v| v.addresses).unwrap_or_default(); + if !addrs.contains(&address) { + addrs.push(address); + } + self.global_value_store.put( + GlobalSettingKey::AllAddresses, + GlobalValue { addresses: addrs }, + ) + } + pub fn list_addresses(&self) -> Result> { let value = self .global_value_store @@ -230,108 +247,54 @@ impl WalletStorage { .map(|w| w.map(|p| p.0)) } - #[allow(unused)] - pub fn save_default_settings( + pub fn decrypt_private_key( &self, address: AccountAddress, - setting: Setting, - ) -> Result<(), Error> { - self.setting_store.put(address.into(), setting.into()) - } - pub fn save_encrypted_private_key( - &self, - address: AccountAddress, - encrypted: Vec, - ) -> Result<(), Error> { - self.private_key_store.put(address.into(), encrypted.into()) - } -} - -pub struct Wallet { - addr: AccountAddress, - private_key: Ed25519PrivateKey, - store: WalletStorage, -} - -pub type WalletResult = std::result::Result; - -impl Wallet { - pub fn create( - public_key: Ed25519PublicKey, - private_key: Ed25519PrivateKey, - addr: Option, - password: String, - storage: WalletStorage, - ) -> WalletResult { - let address = addr.unwrap_or_else(|| account_address::from_public_key(&public_key)); - let encrypted_prikey = encrypt(password.as_bytes(), &private_key.to_bytes()); - - storage - .public_key_store - .put(address.into(), public_key.into())?; - storage.save_encrypted_private_key(address, encrypted_prikey)?; - - Ok(Self { - addr: address, - private_key, - store: storage, - }) - } - pub fn load( - addr: AccountAddress, - password: &str, - store: WalletStorage, - ) -> WalletResult> { - let encrypted_key = store.private_key_store.get(addr.into())?; + password: impl AsRef, + ) -> Result> { + let encrypted_key = self.private_key_store.get(address.into())?; if encrypted_key.is_none() { return Ok(None); } - let encrypted_key = encrypted_key.unwrap(); - let plain_key_data = decrypt(password.as_bytes(), &encrypted_key.0) - .map_err(|_e| WalletError::InvalidPassword(addr))?; - let private_key = Ed25519PrivateKey::try_from(plain_key_data.as_slice()).map_err(|_e| { - WalletError::StoreError(format_err!("underline vault store corrupted")) - })?; - let saved_public_key = store.public_key_store.get(addr.into())?.map(|w| w.0); - let saved_public_key = saved_public_key.ok_or_else(|| { - WalletError::StoreError(format_err!("public key not found for address {}", addr)) - })?; - if saved_public_key.to_bytes() != private_key.public_key().to_bytes() { - return Err(WalletError::StoreError(format_err!( - "invalid state of public key and private key" - ))); - } - - Ok(Some(Self { - addr, - private_key, - store, - })) - } - pub fn wallet_info(&self) -> WalletAccount { - // TODO: fix is_default - WalletAccount::new(self.addr, self.private_key.public_key(), false) - } + let plain_key_data = decrypt(password.as_ref().as_bytes(), &encrypted_key.0)?; - pub fn sign_txn(&self, raw_txn: RawUserTransaction) -> Result { - raw_txn - .sign(&self.private_key, self.private_key.public_key()) - .map(|t| t.into_inner()) + let private_key = Ed25519PrivateKey::try_from(plain_key_data.as_slice())?; + Ok(Some(private_key)) } - pub fn destory(self) -> Result<()> { - self.store.private_key_store.remove(self.addr.into())?; - self.store.setting_store.remove(self.addr.into())?; + pub fn update_key( + &self, + address: AccountAddress, + public_key: Ed25519PublicKey, + private_key: &Ed25519PrivateKey, + password: impl AsRef, + ) -> Result<()> { + let encrypted_prikey = encrypt(password.as_ref().as_bytes(), &private_key.to_bytes()); + self.private_key_store + .put(address.into(), encrypted_prikey.into())?; + self.public_key_store + .put(address.into(), public_key.into())?; Ok(()) } #[allow(unused)] - pub fn address(&self) -> &AccountAddress { - &self.addr + pub fn update_default_settings( + &self, + address: AccountAddress, + setting: Setting, + ) -> Result<(), Error> { + self.setting_store.put(address.into(), setting.into()) } - pub fn private_key(&self) -> &Ed25519PrivateKey { - &self.private_key + + pub fn destroy_wallet(&self, address: AccountAddress) -> Result<()> { + if self.default_address()?.filter(|a| a == &address).is_some() { + self.set_default_address(None)?; + } + self.private_key_store.remove(address.into())?; + self.public_key_store.remove(address.into())?; + self.setting_store.remove(address.into())?; + Ok(()) } } diff --git a/wallet/lib/src/wallet_test.rs b/wallet/lib/src/wallet_test.rs new file mode 100644 index 0000000000..b7518422ad --- /dev/null +++ b/wallet/lib/src/wallet_test.rs @@ -0,0 +1,64 @@ +use crate::wallet::Wallet; +use crate::wallet_manager::WalletManager; +use crate::wallet_storage::WalletStorage; +use actix::clock::Duration; +use anyhow::Result; +use starcoin_types::chain_config::ChainId; +use starcoin_types::transaction::{RawUserTransaction, Script, TransactionPayload}; + +#[test] +pub fn test_wallet() -> Result<()> { + let tempdir = tempfile::tempdir()?; + let storage = WalletStorage::create_from_path(tempdir.path())?; + let manager = WalletManager::new(storage.clone())?; + + // should success + let wallet = manager.create_wallet("hello")?; + + let wallet_address = wallet.address(); + + // test reload + let loaded_wallet = Wallet::load(*wallet_address, "hello", storage.clone())?; + assert!(loaded_wallet.is_some()); + let reloaded_wallet = loaded_wallet.unwrap(); + assert_eq!( + reloaded_wallet.private_key().to_bytes(), + wallet.private_key().to_bytes() + ); + + // test default wallet + let default_wallet_info = manager.default_wallet_info()?; + assert!(default_wallet_info.is_some()); + let default_wallet_info = default_wallet_info.unwrap(); + assert_eq!(&default_wallet_info.address, wallet.address()); + + // test wallet destroy + + wallet.destroy()?; + + Ok(()) +} + +#[test] +pub fn test_wallet_unlock() -> Result<()> { + let tempdir = tempfile::tempdir()?; + let storage = WalletStorage::create_from_path(tempdir.path())?; + let manager = WalletManager::new(storage.clone())?; + + let wallet = manager.create_wallet("hello")?; + + let unlock_result = manager.unlock_wallet(*wallet.address(), "hell0", Duration::from_secs(1)); + assert!(unlock_result.is_err()); + manager.unlock_wallet(*wallet.address(), "hello", Duration::from_secs(1))?; + let fake_txn = RawUserTransaction::new( + *wallet.address(), + 1, + TransactionPayload::Script(Script::new(vec![], vec![], vec![])), + 1000, + 1, + 100000, + ChainId::new(1), + ); + let _signed = manager.sign_txn(*wallet.address(), fake_txn)?; + Ok(()) +} diff --git a/wallet/service/Cargo.toml b/wallet/service/Cargo.toml index 314690379e..75b05ef8ff 100644 --- a/wallet/service/Cargo.toml +++ b/wallet/service/Cargo.toml @@ -12,19 +12,13 @@ futures = "0.3" actix = "0.10.0-alpha.3" actix-rt = "1.1" async-trait = "0.1" -parking_lot = "0.9" -rand = "0.7" -serde = "1" starcoin-logger = {path = "../../commons/logger"} stest = {path = "../../commons/stest"} starcoin-types = { path = "../../types"} starcoin-config = { path = "../../config"} starcoin-wallet-api = { path = "../api", features = ["mock"]} starcoin-wallet-lib = { path = "../lib"} -starcoin-storage = {path = "../../storage"} starcoin-crypto = {path = "../../commons/crypto"} -starcoin-decrypt = {path = "../../commons/decrypt"} -starcoin-canonical-serialization = { package="starcoin-canonical-serialization", path = "../../commons/scs"} [dev-dependencies] tempfile="3" tokio = { version = "0.2", features = ["full"] } diff --git a/wallet/service/src/actor.rs b/wallet/service/src/actor.rs index 6ef5d50460..b8f7355ee6 100644 --- a/wallet/service/src/actor.rs +++ b/wallet/service/src/actor.rs @@ -2,8 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 use crate::message::{WalletRequest, WalletResponse}; -use crate::rich_wallet::WalletStorage; -use crate::wallet_manager::WalletManager; use actix::{Actor, Addr, Context, Handler}; use anyhow::Result; use starcoin_config::NodeConfig; @@ -11,6 +9,8 @@ use starcoin_types::account_address::AccountAddress; use starcoin_types::transaction::{RawUserTransaction, SignedUserTransaction}; use starcoin_wallet_api::error::AccountServiceError; use starcoin_wallet_api::{ServiceResult, WalletAccount, WalletAsyncService, WalletResult}; +use starcoin_wallet_lib::wallet_manager::WalletManager; +use starcoin_wallet_lib::wallet_storage::WalletStorage; use std::sync::Arc; pub struct WalletActor { @@ -37,9 +37,7 @@ impl Handler for WalletActor { fn handle(&mut self, msg: WalletRequest, _ctx: &mut Self::Context) -> Self::Result { let response = match msg { WalletRequest::CreateAccount(password) => WalletResponse::WalletAccount(Box::new( - self.service - .create_account(password.as_str())? - .wallet_info(), + self.service.create_wallet(password.as_str())?.wallet_info(), )), WalletRequest::GetDefaultAccount() => { WalletResponse::WalletAccountOption(Box::new(self.service.default_wallet_info()?)) @@ -53,14 +51,14 @@ impl Handler for WalletActor { WalletRequest::SignTxn { txn: raw_txn, signer, - } => WalletResponse::SignedTxn(Box::new(self.service.sign_txn(*raw_txn, signer)?)), + } => WalletResponse::SignedTxn(Box::new(self.service.sign_txn(signer, *raw_txn)?)), WalletRequest::UnlockAccount(address, password, duration) => { self.service .unlock_wallet(address, password.as_str(), duration)?; WalletResponse::UnlockAccountResponse } WalletRequest::ExportAccount { address, password } => { - let data = self.service.export_account(address, password.as_str())?; + let data = self.service.export_wallet(address, password.as_str())?; WalletResponse::ExportAccountResponse(data) } WalletRequest::ImportAccount { @@ -68,9 +66,9 @@ impl Handler for WalletActor { password, private_key, } => { - let wallet = - self.service - .import_account(address, private_key, password.as_str())?; + let wallet = self + .service + .import_wallet(address, private_key, password.as_str())?; WalletResponse::WalletAccount(Box::new(wallet.wallet_info())) } }; diff --git a/wallet/service/src/lib.rs b/wallet/service/src/lib.rs index e646558d21..f838d9ce7e 100644 --- a/wallet/service/src/lib.rs +++ b/wallet/service/src/lib.rs @@ -3,7 +3,4 @@ mod actor; mod message; -mod rich_wallet; -mod wallet_manager; - pub use actor::*; From dafd2e0fd1eb6b3775342bf2c7469f13b97375c8 Mon Sep 17 00:00:00 2001 From: caojiafeng Date: Thu, 30 Jul 2020 15:54:55 +0800 Subject: [PATCH 3/4] chore: fix clippy --- wallet/lib/src/wallet_test.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wallet/lib/src/wallet_test.rs b/wallet/lib/src/wallet_test.rs index b7518422ad..eb70b43ab8 100644 --- a/wallet/lib/src/wallet_test.rs +++ b/wallet/lib/src/wallet_test.rs @@ -18,7 +18,7 @@ pub fn test_wallet() -> Result<()> { let wallet_address = wallet.address(); // test reload - let loaded_wallet = Wallet::load(*wallet_address, "hello", storage.clone())?; + let loaded_wallet = Wallet::load(*wallet_address, "hello", storage)?; assert!(loaded_wallet.is_some()); let reloaded_wallet = loaded_wallet.unwrap(); assert_eq!( @@ -43,7 +43,7 @@ pub fn test_wallet() -> Result<()> { pub fn test_wallet_unlock() -> Result<()> { let tempdir = tempfile::tempdir()?; let storage = WalletStorage::create_from_path(tempdir.path())?; - let manager = WalletManager::new(storage.clone())?; + let manager = WalletManager::new(storage)?; let wallet = manager.create_wallet("hello")?; From e11c061c001f6c53b298f095562439e026947944 Mon Sep 17 00:00:00 2001 From: caojiafeng Date: Thu, 30 Jul 2020 16:45:25 +0800 Subject: [PATCH 4/4] feature: lock account --- wallet/api/src/mock/mock_wallet_service.rs | 3 +++ wallet/api/src/service.rs | 1 + wallet/lib/src/wallet_manager.rs | 25 +++++++++++++--------- wallet/service/src/actor.rs | 17 +++++++++++++++ wallet/service/src/message.rs | 1 + 5 files changed, 37 insertions(+), 10 deletions(-) diff --git a/wallet/api/src/mock/mock_wallet_service.rs b/wallet/api/src/mock/mock_wallet_service.rs index 518b0a5851..fac274cc74 100644 --- a/wallet/api/src/mock/mock_wallet_service.rs +++ b/wallet/api/src/mock/mock_wallet_service.rs @@ -58,6 +58,9 @@ impl WalletAsyncService for MockWalletService { .wallet .unlock_account(address, password.as_str(), duration)?) } + async fn lock_account(self, address: AccountAddress) -> ServiceResult<()> { + Ok(self.wallet.lock_account(address)?) + } async fn import_account( self, diff --git a/wallet/api/src/service.rs b/wallet/api/src/service.rs index bbe148ff6c..06d98627cf 100644 --- a/wallet/api/src/service.rs +++ b/wallet/api/src/service.rs @@ -31,6 +31,7 @@ pub trait WalletAsyncService: Clone + std::marker::Unpin + Send + Sync { password: String, duration: std::time::Duration, ) -> ServiceResult<()>; + async fn lock_account(self, address: AccountAddress) -> ServiceResult<()>; async fn import_account( self, address: AccountAddress, diff --git a/wallet/lib/src/wallet_manager.rs b/wallet/lib/src/wallet_manager.rs index 426f16d53a..05106e0fae 100644 --- a/wallet/lib/src/wallet_manager.rs +++ b/wallet/lib/src/wallet_manager.rs @@ -27,21 +27,21 @@ pub type Result = std::result::Result; /// encrypt account's key by a password. pub struct WalletManager { store: WalletStorage, - key_cache: RwLock, + key_cache: RwLock, } #[derive(Default, Debug, PartialEq, Eq)] -struct KeyCache { +struct PasswordCache { cache: HashMap, } -impl KeyCache { - pub fn cache_key(&mut self, account: AccountAddress, pass: String, ttl: Instant) { +impl PasswordCache { + pub fn cache_pass(&mut self, account: AccountAddress, pass: String, ttl: Instant) { self.cache.insert(account, (ttl, pass)); } - pub fn remove_key(&mut self, account: &AccountAddress) { + pub fn remove_pass(&mut self, account: &AccountAddress) { self.cache.remove(account); } - pub fn get_key(&mut self, account: &AccountAddress) -> Option { + pub fn get_pass(&mut self, account: &AccountAddress) -> Option { match self.cache.remove(account) { None => None, Some((ttl, kp)) => { @@ -66,7 +66,7 @@ impl WalletManager { pub fn new(storage: WalletStorage) -> Result { let manager = Self { store: storage, - key_cache: RwLock::new(KeyCache::default()), + key_cache: RwLock::new(PasswordCache::default()), }; Ok(manager) } @@ -103,7 +103,12 @@ impl WalletManager { let ttl = std::time::Instant::now().add(duration); self.key_cache .write() - .cache_key(address, password.to_string(), ttl); + .cache_pass(address, password.to_string(), ttl); + Ok(()) + } + + pub fn lock_wallet(&self, address: AccountAddress) -> Result<()> { + self.key_cache.write().remove_pass(&address); Ok(()) } @@ -189,7 +194,7 @@ impl WalletManager { signer_address: AccountAddress, raw_txn: RawUserTransaction, ) -> Result { - let pass = self.key_cache.write().get_key(&signer_address); + let pass = self.key_cache.write().get_pass(&signer_address); match pass { None => Err(WalletError::AccountLocked(signer_address)), Some(p) => { @@ -215,7 +220,7 @@ impl WalletManager { let wallet = Wallet::load(address, password, self.store.clone())?; match wallet { Some(wallet) => { - self.key_cache.write().remove_key(&address); + self.key_cache.write().remove_pass(&address); wallet.destroy().map_err(WalletError::StoreError) } None => Ok(()), diff --git a/wallet/service/src/actor.rs b/wallet/service/src/actor.rs index b8f7355ee6..ef4912065d 100644 --- a/wallet/service/src/actor.rs +++ b/wallet/service/src/actor.rs @@ -57,6 +57,10 @@ impl Handler for WalletActor { .unlock_wallet(address, password.as_str(), duration)?; WalletResponse::UnlockAccountResponse } + WalletRequest::LockAccount(address) => { + self.service.lock_wallet(address)?; + WalletResponse::None + } WalletRequest::ExportAccount { address, password } => { let data = self.service.export_wallet(address, password.as_str())?; WalletResponse::ExportAccountResponse(data) @@ -183,6 +187,19 @@ impl WalletAsyncService for WalletActorRef { } } + async fn lock_account(self, address: AccountAddress) -> ServiceResult<()> { + let response = self + .0 + .send(WalletRequest::LockAccount(address)) + .await + .map_err(|e| AccountServiceError::OtherError(Box::new(e)))??; + if let WalletResponse::None = response { + Ok(()) + } else { + panic!("Unexpect response type.") + } + } + async fn import_account( self, address: AccountAddress, diff --git a/wallet/service/src/message.rs b/wallet/service/src/message.rs index 7237ca893e..c35addc6d9 100644 --- a/wallet/service/src/message.rs +++ b/wallet/service/src/message.rs @@ -18,6 +18,7 @@ pub enum WalletRequest { signer: AccountAddress, }, UnlockAccount(AccountAddress, String, Duration), + LockAccount(AccountAddress), ImportAccount { address: AccountAddress, private_key: Vec,