diff --git a/Cargo.lock b/Cargo.lock index af253ea166..1f9a55ea07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7502,6 +7502,7 @@ version = "0.3.1" dependencies = [ "anyhow", "async-trait", + "futures 0.3.5", "rand 0.7.3", "rand_core 0.5.1", "serde", @@ -7538,8 +7539,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 +}