diff --git a/src/Cargo.toml b/src/Cargo.toml index 9037e62e2..389b7ff66 100644 --- a/src/Cargo.toml +++ b/src/Cargo.toml @@ -28,6 +28,8 @@ members = [ "./component/cyfs-chunk-lib", "./component/cyfs-mobile-stack", "./component/cyfs-bdt-ext", + "./component/cyfs-group-lib", + "./component/cyfs-group", "./service/ood-control", "./service/ood-daemon", @@ -74,6 +76,7 @@ members = [ "./tests/cyfs-stack-test", "./tests/cyfs-bench-mark", "./tests/cyfs-stack-bench", + "./tests/group-example", ] [profile.release] diff --git a/src/component/cyfs-backup/src/state_backup/roots.rs b/src/component/cyfs-backup/src/state_backup/roots.rs index a14fd766f..061034244 100644 --- a/src/component/cyfs-backup/src/state_backup/roots.rs +++ b/src/component/cyfs-backup/src/state_backup/roots.rs @@ -37,7 +37,7 @@ impl RootObjectCategoryManager { list.push(item); let item = RootObjectCategory { - object_type: ObjectTypeCode::SimpleGroup.to_u16(), + object_type: ObjectTypeCode::Group.to_u16(), ref_depth: 0, }; list.push(item); diff --git a/src/component/cyfs-base-meta/Cargo.toml b/src/component/cyfs-base-meta/Cargo.toml index 7f1bfa8da..14a3dd3f3 100644 --- a/src/component/cyfs-base-meta/Cargo.toml +++ b/src/component/cyfs-base-meta/Cargo.toml @@ -20,5 +20,6 @@ serde_json = '1.0' hex = '0.4.2' async-trait = '0.1.53' async-std = '1.11' +futures = '0.3.25' primitive-types = { version = '0.9' } codec = { package = 'parity-scale-codec', version = '2.0', default-features = false, features = ['derive', 'full'], optional = true } diff --git a/src/component/cyfs-base-meta/src/group.rs b/src/component/cyfs-base-meta/src/group.rs new file mode 100644 index 000000000..358c9141f --- /dev/null +++ b/src/component/cyfs-base-meta/src/group.rs @@ -0,0 +1,238 @@ +use std::collections::HashSet; + +use cyfs_base::{ + BuckyError, BuckyErrorCode, BuckyResult, Group, NamedObject, ObjectDesc, ObjectId, + ObjectTypeCode, People, PeopleId, RawEncode, RsaCPUObjectVerifier, Signature, + SingleKeyObjectDesc, Verifier, +}; +use cyfs_core::ToGroupShell; + +async fn verify_signature( + signs: Option<&Vec>, + data_buf: &[u8], + verifier: &RsaCPUObjectVerifier, + signer_id: &PeopleId, +) -> BuckyResult<()> { + let signs = signs.map_or([].as_slice(), |s| s.as_slice()); + let sign = signs.iter().find(|s| match s.sign_source() { + cyfs_base::SignatureSource::Object(signer) => &signer.obj_id == signer_id.object_id(), + _ => false, + }); + + match sign { + Some(sign) => { + if verifier.verify(data_buf, sign).await { + Ok(()) + } else { + let msg = format!("Invalid signature from {}", signer_id); + log::warn!("{}", msg); + Err(BuckyError::new(BuckyErrorCode::InvalidSignature, msg)) + } + } + None => { + let msg = format!("Not found signature from {}", signer_id); + log::warn!("{}", msg); + Err(BuckyError::new(BuckyErrorCode::NotFound, msg)) + } + } +} + +async fn verify_group_signature( + group: &Group, + people_id: &PeopleId, + member_querier: &impl MemberQuerier, +) -> BuckyResult<()> { + let people = member_querier.get_people(people_id).await?; + let verifier = RsaCPUObjectVerifier::new(people.desc().public_key().clone()); + let desc_buf = group.desc().raw_hash_value()?; + verify_signature( + group.signs().desc_signs(), + desc_buf.as_slice(), + &verifier, + people_id, + ) + .await?; + let body_buf = group.body().as_ref().unwrap().raw_hash_value()?; + verify_signature( + group.signs().body_signs(), + body_buf.as_slice(), + &verifier, + people_id, + ) + .await +} + +#[async_trait::async_trait] +pub trait MemberQuerier: Send + Sync { + async fn get_people(&self, people_id: &PeopleId) -> BuckyResult; +} + +#[async_trait::async_trait] +pub trait GroupVerifier { + // Check the update is allowed + async fn is_update_valid( + &self, + latest_group: Option<&Group>, + member_querier: &impl MemberQuerier, + ) -> BuckyResult<()>; +} + +#[async_trait::async_trait] +impl GroupVerifier for Group { + async fn is_update_valid( + &self, + latest_group: Option<&Group>, + member_querier: &impl MemberQuerier, + ) -> BuckyResult<()> { + let group_id = self.desc().object_id(); + + if self.admins().len() == 0 { + let msg = format!("Update group({}) with no admins.", group_id); + log::warn!("{}", msg); + return Err(BuckyError::new(BuckyErrorCode::Failed, msg)); + } + + let (last_admins, last_members) = match latest_group { + Some(latest_group) => { + let latest_group_id = latest_group.desc().object_id(); + if group_id != latest_group_id { + let msg = format!( + "The new group({}) is unmatch with the latest group({}).", + group_id, latest_group_id + ); + log::warn!("{}", msg); + return Err(BuckyError::new(BuckyErrorCode::Unmatch, msg)); + } + + let latest_group_shell = latest_group.to_shell(); + let latest_group_shell_id = latest_group_shell.shell_id(); + if self.version() != latest_group.version() + 1 + || self.prev_shell_id() != &Some(latest_group_shell_id) + { + let msg = format!("Attempt to update group({}) from unknown version({}-1/{:?}), latest version: {}/{}.", group_id, self.version(), self.prev_shell_id(), latest_group.version(), latest_group_shell_id); + log::warn!("{}", msg); + return Err(BuckyError::new(BuckyErrorCode::Unmatch, msg)); + } + + ( + HashSet::::from_iter( + latest_group + .admins() + .keys() + .filter(|m| m.obj_type_code() == ObjectTypeCode::People) + .map(|m| *m), + ), + HashSet::::from_iter( + latest_group + .members() + .keys() + .filter(|m| m.obj_type_code() == ObjectTypeCode::People) + .map(|m| *m), + ), + ) + } + None => match self.prev_shell_id() { + Some(prev_shell_id) => { + let msg = format!( + "The latest group({}) is necessary for update. prev_shell_id: {}", + group_id, prev_shell_id + ); + log::warn!("{}", msg); + return Err(BuckyError::new(BuckyErrorCode::Unmatch, msg)); + } + None => { + if let Some(founder) = self.founder_id() { + if self.admins().values().find(|m| &m.id == founder).is_none() { + let msg = format!( + "Update group({}) the founder({}) must be an administrator.", + group_id, founder + ); + log::warn!("{}", msg); + return Err(BuckyError::new(BuckyErrorCode::Failed, msg)); + } + } + (HashSet::new(), HashSet::new()) + } + }, + }; + + // admins: > 1/2 + // new members: all + + let add_admins = HashSet::::from_iter( + self.admins() + .keys() + .filter(|m| { + m.obj_type_code() == ObjectTypeCode::People && !last_admins.contains(*m) + }) + .map(|m| *m), + ); + let add_members = HashSet::::from_iter( + self.members() + .keys() + .filter(|m| { + m.obj_type_code() == ObjectTypeCode::People && !last_members.contains(*m) + }) + .map(|m| *m), + ); + + let additionals = add_admins + .union(&add_members) + .map(|id| *id) + .collect::>(); + if additionals.len() != add_admins.len() + add_members.len() { + let msg = format!( + "Update group({}) with admins is not necessary in members.", + group_id + ); + log::warn!("{}", msg); + return Err(BuckyError::new(BuckyErrorCode::Failed, msg)); + } + + let check_peoples = + futures::future::join_all(last_admins.iter().chain(additionals.iter()).map( + |people_id| async move { + let people_id = PeopleId::try_from(people_id).unwrap(); + verify_group_signature(self, &people_id, member_querier).await + }, + )) + .await; + + let (last_admin_signs, add_member_signs) = check_peoples.split_at(last_admins.len()); + let last_admin_sign_count = last_admin_signs.iter().filter(|s| s.is_ok()).count(); + if last_admins.len() > 0 && last_admin_sign_count <= last_admins.len() / 2 { + let msg = format!( + "Update group({}) failed for signatures from admins in latest version is not enough: expected {}, got {}.", + group_id, + last_admins.len() / 2 + 1, + last_admin_sign_count + ); + log::warn!("{}", msg); + return Err(BuckyError::new(BuckyErrorCode::InvalidSignature, msg)); + } + + let failed_add_member_sign_pos = add_member_signs.iter().position(|s| s.is_err()); + match failed_add_member_sign_pos { + Some(pos) => { + let msg = format!( + "Update group({}) failed for signatures from additional member({:?}) is invalid.", + group_id, + additionals.get(pos) + ); + log::warn!("{}", msg); + return Err(BuckyError::new(BuckyErrorCode::InvalidSignature, msg)); + } + None => { + log::info!( + "Update group({}) verify ok, from {:?}/{:?}, to {}/{}", + group_id, + latest_group.map(|group| group.version()), + self.prev_shell_id(), + self.version(), + self.to_shell().shell_id() + ); + Ok(()) + } + } + } +} diff --git a/src/component/cyfs-base-meta/src/lib.rs b/src/component/cyfs-base-meta/src/lib.rs index 09481a16d..2beadb599 100644 --- a/src/component/cyfs-base-meta/src/lib.rs +++ b/src/component/cyfs-base-meta/src/lib.rs @@ -1,28 +1,30 @@ extern crate alloc; pub use block::*; +pub use code::*; pub use config::*; +pub use contract::*; pub use event::*; pub use extension::*; +pub use group::*; +pub use nft::*; pub use sn_service::*; pub use spv::*; pub use tx::*; pub use types::*; pub use view::*; -pub use code::*; -pub use contract::*; -pub use nft::*; -mod types; -mod config; mod block; -mod view; -mod event; -mod tx; -mod sn_service; -mod spv; -mod extension; mod code; +mod config; mod contract; -mod nft; +mod event; pub mod evm_def; +mod extension; +mod group; +mod nft; +mod sn_service; +mod spv; +mod tx; +mod types; +mod view; diff --git a/src/component/cyfs-base-meta/src/tx.rs b/src/component/cyfs-base-meta/src/tx.rs index addd0eb11..b40fc43a2 100644 --- a/src/component/cyfs-base-meta/src/tx.rs +++ b/src/component/cyfs-base-meta/src/tx.rs @@ -1,12 +1,16 @@ -use cyfs_base::*; -use sha2::{Digest, Sha256}; use crate::contract::*; -use crate::{MetaExtensionTx, NFTAgreeApplyTx, NFTApplyBuyTx, NFTAuctionTx, NFTBidTx, NFTBuyTx, NFTCancelApplyBuyTx, NFTCancelSellTx, NFTCreateTx, NFTCreateTx2, NFTLikeTx, NFTSellTx, NFTSellTx2, NFTSetNameTx, NFTTransTx, SNService, SPVTx}; +use crate::{ + MetaExtensionTx, NFTAgreeApplyTx, NFTApplyBuyTx, NFTAuctionTx, NFTBidTx, NFTBuyTx, + NFTCancelApplyBuyTx, NFTCancelSellTx, NFTCreateTx, NFTCreateTx2, NFTLikeTx, NFTSellTx, + NFTSellTx2, NFTSetNameTx, NFTTransTx, SNService, SPVTx, +}; use async_trait::async_trait; +use cyfs_base::*; use cyfs_core::CoreObjectType; use generic_array::typenum::U32; use generic_array::GenericArray; use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; use std::convert::TryFrom; #[derive(Clone, Debug, RawEncode, RawDecode)] @@ -317,13 +321,14 @@ pub enum SavedMetaObject { Device(Device), //BDT中定义 People(People), //BDT中定义 UnionAccount(UnionAccount), //两人公有账号,用于闪电网络 - Group(SimpleGroup), //M*N Group,最常见的Group BDT中定义 + SimpleGroup, //M*N Group,最常见的Group BDT中定义 File(File), Data(Data), - Org(Org), + Org, MinerGroup(MinerGroup), SNService(SNService), Contract(Contract), + Group(Group), } impl SavedMetaObject { @@ -336,16 +341,17 @@ impl SavedMetaObject { pub fn id(&self) -> ObjectId { match self { - SavedMetaObject::Device(o) => {o.desc().calculate_id()} - SavedMetaObject::People(o) => {o.desc().calculate_id()} - SavedMetaObject::UnionAccount(o) => {o.desc().calculate_id()} - SavedMetaObject::Group(o) => {o.desc().calculate_id()} - SavedMetaObject::File(o) => {o.desc().calculate_id()} - SavedMetaObject::Data(o) => {o.id.clone()} - SavedMetaObject::Org(o) => {o.desc().calculate_id()} - SavedMetaObject::MinerGroup(o) => {o.desc().calculate_id()} - SavedMetaObject::SNService(o) => {o.desc().calculate_id()} - SavedMetaObject::Contract(o) => {o.desc().calculate_id()} + SavedMetaObject::Device(o) => o.desc().calculate_id(), + SavedMetaObject::People(o) => o.desc().calculate_id(), + SavedMetaObject::UnionAccount(o) => o.desc().calculate_id(), + SavedMetaObject::SimpleGroup => panic!("SimpleGroup is deprecated, you can use the Group."), + SavedMetaObject::File(o) => o.desc().calculate_id(), + SavedMetaObject::Data(o) => o.id.clone(), + SavedMetaObject::Org => panic!("Org is deprecated, you can use the Group."), + SavedMetaObject::MinerGroup(o) => o.desc().calculate_id(), + SavedMetaObject::SNService(o) => o.desc().calculate_id(), + SavedMetaObject::Contract(o) => o.desc().calculate_id(), + SavedMetaObject::Group(o) => o.desc().calculate_id(), } } } @@ -370,9 +376,8 @@ impl TryFrom for StandardObject { SavedMetaObject::Device(v) => Ok(Self::Device(v)), SavedMetaObject::People(v) => Ok(Self::People(v)), SavedMetaObject::UnionAccount(v) => Ok(Self::UnionAccount(v)), - SavedMetaObject::Group(v) => Ok(Self::SimpleGroup(v)), + SavedMetaObject::Group(v) => Ok(Self::Group(v)), SavedMetaObject::File(v) => Ok(Self::File(v)), - SavedMetaObject::Org(v) => Ok(Self::Org(v)), SavedMetaObject::Contract(v) => Ok(Self::Contract(v)), _ => Err(BuckyError::from(BuckyErrorCode::NotSupport)), } @@ -387,9 +392,8 @@ impl TryFrom for SavedMetaObject { StandardObject::Device(v) => Self::Device(v), StandardObject::People(v) => Self::People(v), StandardObject::UnionAccount(v) => Self::UnionAccount(v), - StandardObject::SimpleGroup(v) => SavedMetaObject::Group(v), + StandardObject::Group(v) => SavedMetaObject::Group(v), StandardObject::File(v) => Self::File(v), - StandardObject::Org(v) => Self::Org(v), StandardObject::Contract(v) => Self::Contract(v), _ => { return Err(BuckyError::from(BuckyErrorCode::NotSupport)); @@ -939,7 +943,6 @@ mod tx_test { #[test] fn test_people_codec() { - for code in OLD_LIST { let code = hex::decode(code).unwrap(); let ret = SavedMetaObject::clone_from_slice(code.as_slice()).unwrap(); diff --git a/src/component/cyfs-base/protos/standard_objects.proto b/src/component/cyfs-base/protos/standard_objects.proto index 7e804ccb9..99e1e22e6 100644 --- a/src/component/cyfs-base/protos/standard_objects.proto +++ b/src/component/cyfs-base/protos/standard_objects.proto @@ -52,24 +52,6 @@ message FileBodyContent { ChunkList chunk_list = 1; } -// org -message Director { - bytes id = 1; - uint32 right = 2; -} - -message OrgMember { - bytes id = 1; - uint32 right = 2; - uint64 shares = 3; -} - -message OrgBodyContent { - repeated OrgMember members = 1; - repeated Director directors = 2; - uint64 total_equity = 3; -} - // people message PeopleBodyContent { repeated bytes ood_list = 1; @@ -78,12 +60,44 @@ message PeopleBodyContent { optional string ood_work_mode = 4; } +message GroupMember { + bytes id = 1; + string title = 2; +} + +message CommonGroupBodyContent { + optional string name = 1; + optional string icon = 2; + optional string description = 3; + + repeated GroupMember members = 4; // sort by id deduplicated ascending order + + repeated bytes ood_list = 5; // sort by id deduplicated ascending order + + optional bytes prev_shell_id = 6; + uint64 version = 7; +} // simple_group +message SimpleGroupDescContent { + bytes unique_id = 1; + optional bytes founder_id = 2; + repeated GroupMember admins = 3; // sort by id deduplicated ascending order +} + message SimpleGroupBodyContent { - repeated bytes members = 1; - repeated bytes ood_list = 2; - optional string ood_work_mode = 3; + CommonGroupBodyContent common = 1; +} + +// org +message OrgDescContent { + bytes unique_id = 1; + optional bytes founder_id = 2; +} + +message OrgBodyContent { + repeated GroupMember admins = 1; // sort by id deduplicated ascending order + CommonGroupBodyContent common = 2; } // tx diff --git a/src/component/cyfs-base/src/base/error.rs b/src/component/cyfs-base/src/base/error.rs index ce77b2104..23f4ca079 100644 --- a/src/component/cyfs-base/src/base/error.rs +++ b/src/component/cyfs-base/src/base/error.rs @@ -125,6 +125,10 @@ pub enum BuckySystemErrorCode { ParseError = 261, NotHandled = 262, + InvalidTarget = 263, + ErrorTimestamp = 264, + DecNotRunning = 265, + // 在system error code里面,meta_error默认值都取值5000 MetaError = 5000, @@ -240,6 +244,10 @@ pub enum BuckyErrorCode { ParseError, NotHandled, + InvalidTarget, + ErrorTimestamp, + DecNotRunning, + // meta chain的error段,取值范围是[0, BUCKY_META_ERROR_CODE_MAX) MetaError(u16), @@ -330,6 +338,10 @@ impl Into for BuckyErrorCode { Self::ParseError => BuckySystemErrorCode::ParseError, Self::NotHandled => BuckySystemErrorCode::NotHandled, + Self::InvalidTarget => BuckySystemErrorCode::InvalidTarget, + Self::ErrorTimestamp => BuckySystemErrorCode::ErrorTimestamp, + Self::DecNotRunning => BuckySystemErrorCode::DecNotRunning, + Self::MetaError(_) => BuckySystemErrorCode::MetaError, Self::DecError(_) => BuckySystemErrorCode::DecError, } @@ -391,7 +403,7 @@ impl Into for BuckySystemErrorCode { Self::OutofSessionLimit => BuckyErrorCode::OutofSessionLimit, Self::Redirect => BuckyErrorCode::Redirect, - + Self::MongoDBError => BuckyErrorCode::MongoDBError, Self::SqliteError => BuckyErrorCode::SqliteError, Self::UrlError => BuckyErrorCode::UrlError, @@ -421,6 +433,10 @@ impl Into for BuckySystemErrorCode { Self::ParseError => BuckyErrorCode::ParseError, Self::NotHandled => BuckyErrorCode::NotHandled, + Self::InvalidTarget => BuckyErrorCode::InvalidTarget, + Self::ErrorTimestamp => BuckyErrorCode::ErrorTimestamp, + Self::DecNotRunning => BuckyErrorCode::DecNotRunning, + Self::MetaError => BuckyErrorCode::MetaError(0), Self::DecError => BuckyErrorCode::DecError(0), } diff --git a/src/component/cyfs-base/src/codec/format.rs b/src/component/cyfs-base/src/codec/format.rs index e3964852c..4afc0874d 100644 --- a/src/component/cyfs-base/src/codec/format.rs +++ b/src/component/cyfs-base/src/codec/format.rs @@ -1,5 +1,6 @@ use crate::*; +use itertools::Itertools; use serde::Serialize; use serde_json::{Map, Value}; @@ -481,8 +482,7 @@ impl ObjectFormat for StandardObject { match self { StandardObject::Device(o) => o.format_json(), StandardObject::People(o) => o.format_json(), - StandardObject::SimpleGroup(o) => o.format_json(), - StandardObject::Org(o) => o.format_json(), + StandardObject::Group(o) => o.format_json(), StandardObject::AppGroup(o) => o.format_json(), StandardObject::UnionAccount(o) => o.format_json(), StandardObject::ChunkId(chunk_id) => chunk_id.format_json(), @@ -560,38 +560,75 @@ impl ObjectFormat for PeopleBodyContent { } } -// simple group -impl ObjectFormat for SimpleGroupDescContent { +// group +impl ObjectFormat for GroupDescContent { fn format_json(&self) -> serde_json::Value { - let map = serde_json::Map::new(); + let mut map = serde_json::Map::new(); + + JsonCodecHelper::encode_option_string_field( + &mut map, + "founder", + self.founder_id().as_ref(), + ); + if let GroupDescContent::SimpleGroup(simple_group) = self { + JsonCodecHelper::encode_str_array_field( + &mut map, + "admins", + &simple_group + .admins() + .values() + .sorted_by(|l, r| l.id.cmp(&r.id)) + .collect::>(), + ); + } map.into() } } -impl ObjectFormat for SimpleGroupBodyContent { +impl ObjectFormat for GroupBodyContent { fn format_json(&self) -> serde_json::Value { let mut map = serde_json::Map::new(); - JsonCodecHelper::encode_str_array_field(&mut map, "members", self.members()); - JsonCodecHelper::encode_str_array_field(&mut map, "ood_list", &self.ood_list()); - JsonCodecHelper::encode_string_field(&mut map, "ood_work_mode", &self.ood_work_mode()); - - map.into() - } -} + JsonCodecHelper::encode_option_string_field(&mut map, "name", self.name().as_ref()); + JsonCodecHelper::encode_option_string_field(&mut map, "icon", self.icon().as_ref()); + JsonCodecHelper::encode_option_string_field( + &mut map, + "description", + self.description().as_ref(), + ); -// org -impl ObjectFormat for OrgDescContent { - fn format_json(&self) -> serde_json::Value { - let map = serde_json::Map::new(); + if let GroupBodyContent::Org(org) = self { + JsonCodecHelper::encode_str_array_field( + &mut map, + "admins", + &org.admins() + .values() + .sorted_by(|l, r| l.id.cmp(&r.id)) + .collect::>(), + ); + } + JsonCodecHelper::encode_str_array_field( + &mut map, + "members", + &self + .members() + .values() + .sorted_by(|l, r| l.id.cmp(&r.id)) + .collect::>(), + ); + JsonCodecHelper::encode_str_array_field(&mut map, "ood_list", self.ood_list()); + JsonCodecHelper::encode_option_string_field( + &mut map, + "prev_shell_id", + self.prev_shell_id().as_ref(), + ); + JsonCodecHelper::encode_string_field(&mut map, "version", &self.version()); map.into() } } -impl ObjectFormatAutoWithSerde for OrgBodyContent {} - // appgroup impl ObjectFormat for AppGroupDescContent { fn format_json(&self) -> serde_json::Value { @@ -907,19 +944,31 @@ fn test() { let secret = PrivateKey::generate_rsa(1024).unwrap(); let public = secret.public(); - let mut device = Device::new(Some(owner), UniqueId::default(), - vec![], vec![], vec![], - public, Area::new(1,2,3,4), DeviceCategory::OOD).build(); + let mut device = Device::new( + Some(owner), + UniqueId::default(), + vec![], + vec![], + vec![], + public, + Area::new(1, 2, 3, 4), + DeviceCategory::OOD, + ) + .build(); device.set_bdt_version(Some(2)); println!("new device obj: {}", device.format_json().to_string()); - let (mut old_device, _) = Device::decode_from_file("c:\\cyfs\\etc\\desc\\device.desc".as_ref(), &mut vec![]).unwrap(); + let (mut old_device, _) = + Device::decode_from_file("c:\\cyfs\\etc\\desc\\device.desc".as_ref(), &mut vec![]).unwrap(); println!("old device obj: {}", old_device.format_json().to_string()); old_device.set_bdt_version(Some(5)); - println!("old device set bdt ver obj: {}", old_device.format_json().to_string()); + println!( + "old device set bdt ver obj: {}", + old_device.format_json().to_string() + ); } use std::collections::{hash_map::Entry, HashMap}; @@ -961,7 +1010,10 @@ impl FormatFactory { v.insert(f); } Entry::Occupied(mut o) => { - warn!("register ext object format but already exists! obj_type={}", obj_type); + warn!( + "register ext object format but already exists! obj_type={}", + obj_type + ); o.insert(f); } } diff --git a/src/component/cyfs-base/src/objects/any.rs b/src/component/cyfs-base/src/objects/any.rs index d837e8945..169d00249 100644 --- a/src/component/cyfs-base/src/objects/any.rs +++ b/src/component/cyfs-base/src/objects/any.rs @@ -14,8 +14,7 @@ macro_rules! match_any_obj { AnyNamedObject::Standard(o) => match o { StandardObject::Device($o) => $body, StandardObject::People($o) => $body, - StandardObject::SimpleGroup($o) => $body, - StandardObject::Org($o) => $body, + StandardObject::Group($o) => $body, StandardObject::AppGroup($o) => $body, StandardObject::UnionAccount($o) => $body, StandardObject::ChunkId($chunk_id) => $chunk_body, @@ -378,25 +377,17 @@ impl AnyNamedObject { )) } - fn raw_decode_simple_group<'de>(buf: &'de [u8]) -> Result<(Self, &'de [u8]), BuckyError> { - let (simple_group, buf) = SimpleGroup::raw_decode(buf).map_err(|e| { - log::error!("AnyNamedObject::raw_decode/simple_group error:{}", e); + fn raw_decode_group<'de>(buf: &'de [u8]) -> Result<(Self, &'de [u8]), BuckyError> { + let (simple_group, buf) = Group::raw_decode(buf).map_err(|e| { + log::error!("AnyNamedObject::raw_decode/group error:{}", e); e })?; return Ok(( - AnyNamedObject::Standard(StandardObject::SimpleGroup(simple_group)), + AnyNamedObject::Standard(StandardObject::Group(simple_group)), buf, )); } - fn raw_decode_org<'de>(buf: &'de [u8]) -> Result<(Self, &'de [u8]), BuckyError> { - let (org, buf) = Org::raw_decode(buf).map_err(|e| { - log::error!("AnyNamedObject::raw_decode/org error:{}", e); - e - })?; - return Ok((AnyNamedObject::Standard(StandardObject::Org(org)), buf)); - } - fn raw_decode_union_account<'de>(buf: &'de [u8]) -> Result<(Self, &'de [u8]), BuckyError> { let (ua, buf) = UnionAccount::raw_decode(buf).map_err(|e| { log::error!("AnyNamedObject::raw_decode/ua error:{}", e); @@ -550,8 +541,7 @@ impl<'de> RawDecode<'de> for AnyNamedObject { ObjectTypeCode::Custom => { Self::raw_decode_custom(buf, obj_type_info.is_decapp_object()) } - ObjectTypeCode::SimpleGroup => Self::raw_decode_simple_group(buf), - ObjectTypeCode::Org => Self::raw_decode_org(buf), + ObjectTypeCode::Group => Self::raw_decode_group(buf), } } } @@ -698,5 +688,5 @@ any_for_standard_target!(as_file, into_file, File); any_for_standard_target!(as_dir, into_dir, Dir); any_for_standard_target!(as_people, into_people, People); any_for_standard_target!(as_device, into_device, Device); -any_for_standard_target!(as_simple_group, into_simple_group, SimpleGroup); +any_for_standard_target!(as_group, into_group, Group); any_for_standard_target!(as_object_map, into_object_map, ObjectMap); diff --git a/src/component/cyfs-base/src/objects/group.rs b/src/component/cyfs-base/src/objects/group.rs new file mode 100644 index 000000000..2e5649025 --- /dev/null +++ b/src/component/cyfs-base/src/objects/group.rs @@ -0,0 +1,805 @@ +use itertools::Itertools; + +use crate::codec as cyfs_base; +use crate::protos::standard_objects; +use crate::*; + +use std::collections::{HashMap, HashSet}; +use std::convert::TryFrom; +use std::str::FromStr; + +pub enum GroupMemberScope { + Admin, + Member, + All, +} + +#[derive(Clone, Debug, RawEncode, RawDecode)] +pub enum GroupDescContent { + SimpleGroup(SimpleGroupDescContent), + Org(OrgDescContent), +} + +impl GroupDescContent { + pub fn founder_id(&self) -> &Option { + match self { + GroupDescContent::SimpleGroup(desc) => &desc.founder_id, + GroupDescContent::Org(desc) => &desc.founder_id, + } + } +} + +#[derive(Clone, Debug, RawEncode, RawDecode)] +pub enum GroupBodyContent { + SimpleGroup(SimpleGroupBodyContent), + Org(OrgBodyContent), +} + +impl DescContent for GroupDescContent { + fn obj_type() -> u16 { + ObjectTypeCode::Group.into() + } + + type OwnerType = SubDescNone; + type AreaType = Option; + type AuthorType = SubDescNone; + type PublicKeyType = SubDescNone; +} + +impl BodyContent for GroupBodyContent { + fn format(&self) -> u8 { + OBJECT_CONTENT_CODEC_FORMAT_RAW + } +} + +impl GroupBodyContent { + pub fn name(&self) -> &Option { + &self.common().name + } + + pub fn icon(&self) -> &Option { + &self.common().icon + } + + pub fn description(&self) -> &Option { + &self.common().description + } + + pub fn members(&self) -> &HashMap { + &self.common().members + } + + pub fn members_mut(&mut self) -> &mut HashMap { + &mut self.common_mut().members + } + + pub fn ood_list(&self) -> &Vec { + &self.common().ood_list + } + + pub fn ood_list_mut(&mut self) -> &mut Vec { + &mut self.common_mut().ood_list + } + + pub fn version(&self) -> u64 { + self.common().version + } + + pub fn prev_shell_id(&self) -> &Option { + &self.common().prev_shell_id + } + + fn common(&self) -> &CommonGroupBodyContent { + match self { + GroupBodyContent::Org(body) => &body.common, + GroupBodyContent::SimpleGroup(body) => &body.common, + } + } + + fn common_mut(&mut self) -> &mut CommonGroupBodyContent { + match self { + GroupBodyContent::Org(body) => &mut body.common, + GroupBodyContent::SimpleGroup(body) => &mut body.common, + } + } +} + +pub type GroupType = NamedObjType; +pub type GroupBuilder = NamedObjectBuilder; + +pub type GroupDesc = NamedObjectDesc; +pub type GroupId = NamedObjectId; +pub type Group = NamedObjectBase; + +impl GroupDesc { + pub fn group_id(&self) -> GroupId { + GroupId::try_from(self.calculate_id()).unwrap() + } +} + +impl Group { + pub fn new_simple_group( + founder_id: Option, + admins: Vec, + area: Area, + ) -> GroupBuilder { + let desc_content = SimpleGroupDescContent { + unique_id: UniqueId::create_with_random(), + admins: HashMap::from_iter(admins.into_iter().map(|m| (m.id, m))), + founder_id, + }; + + let body_content = SimpleGroupBodyContent::default(); + + GroupBuilder::new( + GroupDescContent::SimpleGroup(desc_content), + GroupBodyContent::SimpleGroup(body_content), + ) + .area(area) + } + + pub fn new_org(founder_id: Option, area: Area) -> GroupBuilder { + let desc_content = OrgDescContent { + founder_id, + unique_id: UniqueId::create_with_random(), + }; + + let body_content = OrgBodyContent::default(); + + GroupBuilder::new( + GroupDescContent::Org(desc_content), + GroupBodyContent::Org(body_content), + ) + .area(area) + } + + pub fn founder_id(&self) -> &Option { + match self.desc().content() { + GroupDescContent::SimpleGroup(s) => &s.founder_id, + GroupDescContent::Org(o) => &o.founder_id, + } + } + + pub fn name(&self) -> &Option { + &self.common().name + } + + pub fn set_name(&mut self, name: Option) { + self.common_mut().name = name; + } + + pub fn icon(&self) -> &Option { + &self.common().icon + } + + pub fn set_icon(&mut self, icon: Option) { + self.common_mut().icon = icon; + } + + pub fn description(&self) -> &Option { + &self.common().description + } + + pub fn set_description(&mut self, description: Option) { + self.common_mut().description = description; + } + + pub fn admins(&self) -> &HashMap { + if self.is_org() { + &self.check_org_body_content().admins + } else { + &self.check_simple_group_desc_content().admins + } + } + + pub fn members(&self) -> &HashMap { + &self.common().members + } + + pub fn set_members(&mut self, members: Vec) { + self.common_mut().members = HashMap::from_iter(members.into_iter().map(|m| (m.id, m))); + } + + pub fn ood_list(&self) -> &Vec { + &self.common().ood_list + } + + pub fn set_ood_list(&mut self, oods: Vec) { + self.common_mut().ood_list = HashSet::::from_iter(oods.into_iter()) + .into_iter() + .sorted() + .collect(); + } + + pub fn contain_ood(&self, ood_id: &ObjectId) -> bool { + match DeviceId::try_from(ood_id) { + Ok(device_id) => self.ood_list().contains(&device_id), + Err(_) => false, + } + } + + pub fn is_same_ood_list(&self, other: &Group) -> bool { + let my_oods = self.ood_list(); + let other_oods = other.ood_list(); + + if my_oods.len() != other_oods.len() { + return false; + } + + for id in my_oods { + if !other_oods.contains(id) { + return false; + } + } + + true + } + + pub fn version(&self) -> u64 { + self.common().version + } + + pub fn set_version(&mut self, version: u64) { + self.common_mut().version = version; + } + + pub fn prev_shell_id(&self) -> &Option { + &self.common().prev_shell_id + } + + pub fn set_prev_shell_id(&mut self, prev_shell_id: Option) { + self.common_mut().prev_shell_id = prev_shell_id; + } + + pub fn is_simple_group(&self) -> bool { + match self.desc().content() { + GroupDescContent::SimpleGroup(_) => true, + _ => false, + } + } + + pub fn is_org(&self) -> bool { + match self.desc().content() { + GroupDescContent::Org(_) => true, + _ => false, + } + } + + pub fn check_simple_group_desc_content(&self) -> &SimpleGroupDescContent { + match self.desc().content() { + GroupDescContent::SimpleGroup(desc) => desc, + _ => panic!("group type not match, expect: simple"), + } + } + + pub fn check_org_desc_content(&self) -> &OrgDescContent { + match self.desc().content() { + GroupDescContent::Org(desc) => desc, + _ => panic!("group type not match, expect: org"), + } + } + + pub fn check_simple_group_body_content(&self) -> &SimpleGroupBodyContent { + match self.body().as_ref().unwrap().content() { + GroupBodyContent::SimpleGroup(body) => body, + _ => panic!("group type not match, expect: simple"), + } + } + + pub fn check_org_body_content(&self) -> &OrgBodyContent { + match self.body().as_ref().unwrap().content() { + GroupBodyContent::Org(body) => body, + _ => panic!("group type not match, expect: org"), + } + } + + pub fn check_simple_group_body_content_mut(&mut self) -> &mut SimpleGroupBodyContent { + match self.body_mut().as_mut().unwrap().content_mut() { + GroupBodyContent::SimpleGroup(body) => body, + _ => panic!("group type not match, expect: simple"), + } + } + + pub fn check_org_body_content_mut(&mut self) -> &mut OrgBodyContent { + match self.body_mut().as_mut().unwrap().content_mut() { + GroupBodyContent::Org(body) => body, + _ => panic!("group type not match, expect: org"), + } + } + + pub fn select_members_with_distance( + &self, + target: &ObjectId, + scope: GroupMemberScope, + ) -> Vec<&ObjectId> { + let mut members = match scope { + GroupMemberScope::Admin => self.admins().keys().collect::>(), + GroupMemberScope::Member => self.members().keys().collect::>(), + GroupMemberScope::All => [ + self.admins().keys().collect::>(), + self.members().keys().collect::>(), + ] + .concat(), + }; + + members.sort_unstable_by(|l, r| { + let dl = l.distance_of(target); + let dr = r.distance_of(target); + dl.cmp(&dr) + }); + members + } + + pub fn ood_list_with_distance(&self, target: &ObjectId) -> Vec<&ObjectId> { + let oods = self + .ood_list() + .iter() + .map(|id| id.object_id()) + .sorted_unstable_by(|l, r| { + let dl = l.distance_of(target); + let dr = r.distance_of(target); + dl.cmp(&dr) + }) + .collect::>(); + oods + } + + fn common(&self) -> &CommonGroupBodyContent { + self.body().as_ref().unwrap().content().common() + } + + fn common_mut(&mut self) -> &mut CommonGroupBodyContent { + self.body_mut().as_mut().unwrap().content_mut().common_mut() + } +} + +#[derive(Clone, Debug)] +pub struct GroupMember { + pub id: ObjectId, + pub title: String, +} + +impl GroupMember { + pub fn new(id: ObjectId, title: String) -> Self { + GroupMember { id, title } + } + pub fn from_member_id(id: ObjectId) -> GroupMember { + GroupMember { + id, + title: "".to_string(), + } + } +} + +impl TryFrom for GroupMember { + type Error = BuckyError; + + fn try_from(value: protos::GroupMember) -> BuckyResult { + let ret = Self { + id: ProtobufCodecHelper::decode_buf(value.id)?, + title: value.title, + }; + + Ok(ret) + } +} + +impl TryFrom<&GroupMember> for protos::GroupMember { + type Error = BuckyError; + + fn try_from(value: &GroupMember) -> BuckyResult { + let mut ret = Self::new(); + + ret.id = value.id.to_vec()?; + ret.title = value.title.clone(); + + Ok(ret) + } +} + +impl FromStr for GroupMember { + type Err = BuckyError; + + fn from_str(s: &str) -> Result { + let mut fields = s.split(":"); + + let id = if let Some(id) = fields.next() { + PeopleId::from_str(id)? + } else { + return Err(BuckyError::new( + BuckyErrorCode::InvalidFormat, + "need peopleid of member.", + )); + }; + + let title = fields.next().unwrap_or(""); + + Ok(Self { + id: id.object_id().clone(), + title: title.to_string(), + }) + } +} + +impl ToString for &GroupMember { + fn to_string(&self) -> String { + format!("{}:{}", self.id, self.title) + } +} + +#[derive(Clone, Debug, Default)] +struct CommonGroupBodyContent { + name: Option, + icon: Option, + description: Option, + + members: HashMap, + + ood_list: Vec, + + version: u64, + prev_shell_id: Option, +} + +impl CommonGroupBodyContent { + fn new( + name: Option, + icon: Option, + description: Option, + members: Vec, + ood_list: Vec, + ) -> Self { + Self { + name, + icon, + description, + members: HashMap::from_iter(members.into_iter().map(|m| (m.id, m))), + ood_list: HashSet::::from_iter(ood_list.into_iter()) + .into_iter() + .sorted() + .collect::>(), + version: 0, + prev_shell_id: None, + } + } +} + +impl TryFrom for CommonGroupBodyContent { + type Error = BuckyError; + + fn try_from(mut value: protos::CommonGroupBodyContent) -> BuckyResult { + let mut ood_list = ProtobufCodecHelper::decode_buf_list(value.take_ood_list())?; + ood_list.sort(); + + let ret = Self { + name: if value.has_name() { + Some(value.take_name()) + } else { + None + }, + icon: if value.has_icon() { + Some(value.take_icon()) + } else { + None + }, + description: if value.has_description() { + Some(value.take_description()) + } else { + None + }, + members: + HashMap::from_iter( + ProtobufCodecHelper::decode_value_list::< + GroupMember, + standard_objects::GroupMember, + >(value.take_members())? + .into_iter() + .map(|m| (m.id, m)), + ), + ood_list, + version: value.version, + prev_shell_id: if value.has_prev_shell_id() { + Some(ProtobufCodecHelper::decode_buf(value.take_prev_shell_id())?) + } else { + None + }, + }; + + Ok(ret) + } +} + +impl TryFrom<&CommonGroupBodyContent> for protos::CommonGroupBodyContent { + type Error = BuckyError; + + fn try_from(value: &CommonGroupBodyContent) -> BuckyResult { + let mut ret = Self::new(); + + if let Some(name) = value.name.as_ref() { + ret.set_name(name.clone()); + } + if let Some(icon) = value.icon.as_ref() { + ret.set_icon(icon.clone()); + } + if let Some(description) = value.description.as_ref() { + ret.set_description(description.clone()); + } + + let members = value + .members + .values() + .sorted_by(|l, r| l.id.cmp(&r.id)) + .map(|m| m.clone()) + .collect::>(); + ret.set_members(ProtobufCodecHelper::encode_nested_list(&members)?); + + let oods = value + .ood_list + .iter() + .sorted() + .map(|id| id.clone()) + .collect::>(); + ret.set_ood_list(ProtobufCodecHelper::encode_buf_list(oods.as_slice())?); + + ret.version = value.version; + if let Some(prev_shell_id) = &value.prev_shell_id { + ret.set_prev_shell_id(prev_shell_id.to_vec()?); + } + + Ok(ret) + } +} + +#[derive(Clone, Debug)] +pub struct SimpleGroupDescContent { + unique_id: UniqueId, + founder_id: Option, + admins: HashMap, +} + +impl SimpleGroupDescContent { + pub fn admins(&self) -> &HashMap { + &self.admins + } +} + +impl TryFrom for SimpleGroupDescContent { + type Error = BuckyError; + + fn try_from(mut value: protos::SimpleGroupDescContent) -> BuckyResult { + let ret = Self { + founder_id: if value.has_founder_id() { + ProtobufCodecHelper::decode_buf(value.take_founder_id())? + } else { + None + }, + unique_id: ProtobufCodecHelper::decode_buf(value.unique_id)?, + admins: + HashMap::from_iter( + ProtobufCodecHelper::decode_value_list::< + GroupMember, + standard_objects::GroupMember, + >(value.admins)? + .into_iter() + .map(|m| (m.id, m)), + ), + }; + + Ok(ret) + } +} + +impl TryFrom<&SimpleGroupDescContent> for protos::SimpleGroupDescContent { + type Error = BuckyError; + + fn try_from(value: &SimpleGroupDescContent) -> BuckyResult { + let mut ret = Self::new(); + + ret.unique_id = value.unique_id.to_vec()?; + if let Some(founder_id) = value.founder_id.as_ref() { + ret.set_founder_id(founder_id.to_vec()?); + } + + let admins = value + .admins + .values() + .sorted_by(|l, r| l.id.cmp(&r.id)) + .map(|m| m.clone()) + .collect::>(); + ret.set_admins(ProtobufCodecHelper::encode_nested_list(&admins)?); + + Ok(ret) + } +} + +#[derive(Clone, Debug, Default)] +pub struct SimpleGroupBodyContent { + common: CommonGroupBodyContent, +} + +impl SimpleGroupBodyContent { + fn new( + name: Option, + icon: Option, + description: Option, + members: Vec, + ood_list: Vec, + ) -> Self { + Self { + common: CommonGroupBodyContent::new(name, icon, description, members, ood_list), + } + } +} + +impl TryFrom for SimpleGroupBodyContent { + type Error = BuckyError; + + fn try_from(mut value: protos::SimpleGroupBodyContent) -> BuckyResult { + let ret = Self { + common: ProtobufCodecHelper::decode_value(value.take_common())?, + }; + + Ok(ret) + } +} + +impl TryFrom<&SimpleGroupBodyContent> for protos::SimpleGroupBodyContent { + type Error = BuckyError; + + fn try_from(value: &SimpleGroupBodyContent) -> BuckyResult { + let mut ret = Self::new(); + + ret.set_common(ProtobufCodecHelper::encode_nested_item(&value.common)?); + + Ok(ret) + } +} + +#[derive(Clone, Debug)] +pub struct OrgDescContent { + unique_id: UniqueId, + founder_id: Option, +} + +impl TryFrom for OrgDescContent { + type Error = BuckyError; + + fn try_from(mut value: protos::OrgDescContent) -> BuckyResult { + let ret = Self { + founder_id: if value.has_founder_id() { + Some(ProtobufCodecHelper::decode_buf(value.take_founder_id())?) + } else { + None + }, + unique_id: ProtobufCodecHelper::decode_buf(value.unique_id)?, + }; + + Ok(ret) + } +} + +impl TryFrom<&OrgDescContent> for protos::OrgDescContent { + type Error = BuckyError; + + fn try_from(value: &OrgDescContent) -> BuckyResult { + let mut ret = Self::new(); + + ret.unique_id = value.unique_id.to_vec()?; + if let Some(founder_id) = value.founder_id.as_ref() { + ret.set_founder_id(founder_id.to_vec()?); + } + + Ok(ret) + } +} + +#[derive(Clone, Debug, Default)] +pub struct OrgBodyContent { + admins: HashMap, + common: CommonGroupBodyContent, +} + +impl OrgBodyContent { + fn new( + name: Option, + icon: Option, + description: Option, + admins: Vec, + members: Vec, + ood_list: Vec, + ) -> Self { + Self { + common: CommonGroupBodyContent::new(name, icon, description, members, ood_list), + admins: HashMap::from_iter(admins.into_iter().map(|m| (m.id, m))), + } + } + + pub fn admins(&self) -> &HashMap { + &self.admins + } + + pub fn set_admins(&mut self, admins: Vec) { + self.admins = HashMap::from_iter(admins.into_iter().map(|m| (m.id, m))); + } +} + +impl TryFrom for OrgBodyContent { + type Error = BuckyError; + + fn try_from(mut value: protos::OrgBodyContent) -> BuckyResult { + let ret = Self { + admins: + HashMap::from_iter( + ProtobufCodecHelper::decode_value_list::< + GroupMember, + standard_objects::GroupMember, + >(value.take_admins())? + .into_iter() + .map(|m| (m.id, m)), + ), + common: ProtobufCodecHelper::decode_value(value.take_common())?, + }; + + Ok(ret) + } +} + +impl TryFrom<&OrgBodyContent> for protos::OrgBodyContent { + type Error = BuckyError; + + fn try_from(value: &OrgBodyContent) -> BuckyResult { + let mut ret = Self::new(); + + let admins = value + .admins + .values() + .sorted_by(|l, r| l.id.cmp(&r.id)) + .map(|m| m.clone()) + .collect::>(); + + ret.set_admins(ProtobufCodecHelper::encode_nested_list(&admins)?); + ret.set_common(ProtobufCodecHelper::encode_nested_item(&value.common)?); + + Ok(ret) + } +} + +crate::inner_impl_default_protobuf_raw_codec!(SimpleGroupDescContent); +crate::inner_impl_default_protobuf_raw_codec!(SimpleGroupBodyContent); + +crate::inner_impl_default_protobuf_raw_codec!(OrgDescContent); +crate::inner_impl_default_protobuf_raw_codec!(OrgBodyContent); + +#[cfg(test)] +mod test { + use crate::*; + + #[test] + fn simple_group() { + // let threshold = 0; + + // let members = vec![ObjectId::default()]; + + // let ood_list = vec![DeviceId::default()]; + + // let obj = SimpleGroup::new( + // threshold, + // vec![], + // members, + // OODWorkMode::Standalone, + // ood_list, + // Area::default(), + // ) + // .build(); + // // let p = Path::new("f:\\temp\\simple_group.obj"); + // // if p.parent().unwrap().exists() { + // // obj.clone().encode_to_file(p, false); + // // } + + // let buf = obj.to_vec().unwrap(); + + // let decode_obj = SimpleGroup::clone_from_slice(&buf).unwrap(); + + // assert!(obj.desc().simple_group_id() == decode_obj.desc().simple_group_id()); + } +} diff --git a/src/component/cyfs-base/src/objects/mod.rs b/src/component/cyfs-base/src/objects/mod.rs index 448318736..4e4e76ec9 100644 --- a/src/component/cyfs-base/src/objects/mod.rs +++ b/src/component/cyfs-base/src/objects/mod.rs @@ -9,6 +9,7 @@ mod diff; mod dir; mod empty; mod file; +mod group; mod named_object_id; mod ndn; mod object; @@ -19,11 +20,9 @@ mod object_map; mod object_signs; mod object_type; mod object_typeless; -mod org; mod people; mod proof_of_service; mod raw_diff; -mod simple_group; mod standard; mod tx; mod union_account; @@ -40,6 +39,7 @@ pub use device::*; pub use dir::*; pub use empty::*; pub use file::*; +pub use group::*; pub use named_object_id::*; pub use ndn::*; pub use object::*; @@ -51,11 +51,9 @@ pub use object_map::*; pub use object_signs::*; pub use object_type::*; pub use object_typeless::*; -pub use org::*; pub use people::*; pub use proof_of_service::*; pub use raw_diff::*; -pub use simple_group::*; pub use standard::*; pub use tx::*; pub use union_account::*; diff --git a/src/component/cyfs-base/src/objects/object_id.rs b/src/component/cyfs-base/src/objects/object_id.rs index 1978866d8..ae5cfc2dc 100644 --- a/src/component/cyfs-base/src/objects/object_id.rs +++ b/src/component/cyfs-base/src/objects/object_id.rs @@ -12,6 +12,8 @@ use std::fmt; use std::hash::{Hash, Hasher}; use std::str::FromStr; +pub const OBJECT_ID_LEN: usize = 32; + #[derive(Debug, Clone, Eq, PartialEq)] pub enum ObjectCategory { Standard, diff --git a/src/component/cyfs-base/src/objects/object_map/mod.rs b/src/component/cyfs-base/src/objects/object_map/mod.rs index be52c2ea5..75589834b 100644 --- a/src/component/cyfs-base/src/objects/object_map/mod.rs +++ b/src/component/cyfs-base/src/objects/object_map/mod.rs @@ -2,6 +2,7 @@ mod access; mod cache; mod check; mod diff; +mod isolate_path_env; mod iterator; mod lock; mod object_map; @@ -13,11 +14,11 @@ mod path_iterator; mod root; mod single_env; mod visitor; -mod isolate_path_env; pub use access::*; pub use cache::*; pub use diff::*; +pub use isolate_path_env::*; pub use iterator::*; pub use object_map::*; pub use op_env::*; @@ -25,4 +26,5 @@ pub use path::*; pub use path_env::*; pub use path_iterator::*; pub use root::*; +pub use single_env::*; pub use visitor::*; diff --git a/src/component/cyfs-base/src/objects/object_type.rs b/src/component/cyfs-base/src/objects/object_type.rs index d4993ce40..cae745311 100644 --- a/src/component/cyfs-base/src/objects/object_type.rs +++ b/src/component/cyfs-base/src/objects/object_type.rs @@ -7,8 +7,7 @@ use std::str::FromStr; pub enum ObjectTypeCode { Device = 1, People = 2, - SimpleGroup = 3, - Org = 4, + Group = 3, AppGroup = 5, UnionAccount = 6, Chunk = 7, @@ -28,8 +27,7 @@ impl From for ObjectTypeCode { match v { 1u16 => ObjectTypeCode::Device, 2u16 => ObjectTypeCode::People, - 3u16 => ObjectTypeCode::SimpleGroup, - 4u16 => ObjectTypeCode::Org, + 3u16 => ObjectTypeCode::Group, 5u16 => ObjectTypeCode::AppGroup, 6u16 => ObjectTypeCode::UnionAccount, 7u16 => ObjectTypeCode::Chunk, @@ -52,8 +50,7 @@ impl From<&ObjectTypeCode> for u16 { match v { ObjectTypeCode::Device => 1u16, ObjectTypeCode::People => 2u16, - ObjectTypeCode::SimpleGroup => 3u16, - ObjectTypeCode::Org => 4u16, + ObjectTypeCode::Group => 3u16, ObjectTypeCode::AppGroup => 5u16, ObjectTypeCode::UnionAccount => 6u16, ObjectTypeCode::Chunk => 7u16, diff --git a/src/component/cyfs-base/src/objects/object_typeless.rs b/src/component/cyfs-base/src/objects/object_typeless.rs index 351417e2e..e94c99310 100644 --- a/src/component/cyfs-base/src/objects/object_typeless.rs +++ b/src/component/cyfs-base/src/objects/object_typeless.rs @@ -1297,8 +1297,8 @@ impl TryFrom for AnyNamedObject { e })?, ))), - ObjectTypeCode::Org => Ok(AnyNamedObject::Standard(StandardObject::Org( - Org::try_from(value).map_err(|e| { + ObjectTypeCode::Group => Ok(AnyNamedObject::Standard(StandardObject::Group( + Group::try_from(value).map_err(|e| { log::error!("AnyNamedObject::try_from/Org error:{}", e); e })?, @@ -1309,12 +1309,6 @@ impl TryFrom for AnyNamedObject { e })?, ))), - ObjectTypeCode::SimpleGroup => Ok(AnyNamedObject::Standard( - StandardObject::SimpleGroup(SimpleGroup::try_from(value).map_err(|e| { - log::error!("AnyNamedObject::try_from/SimpleGroup error:{}", e); - e - })?), - )), ObjectTypeCode::UnionAccount => Ok(AnyNamedObject::Standard( StandardObject::UnionAccount(UnionAccount::try_from(value).map_err(|e| { log::error!("AnyNamedObject::try_from/UnionAccount error:{}", e); diff --git a/src/component/cyfs-base/src/objects/org.rs b/src/component/cyfs-base/src/objects/org.rs deleted file mode 100644 index e9e6af335..000000000 --- a/src/component/cyfs-base/src/objects/org.rs +++ /dev/null @@ -1,178 +0,0 @@ -use crate::*; - -use std::convert::TryFrom; -use serde::Serialize; - -#[derive(Clone, Debug, RawEncode, RawDecode)] -pub struct OrgDescContent {} - -impl DescContent for OrgDescContent { - fn obj_type() -> u16 { - ObjectTypeCode::Org.into() - } - - type OwnerType = Option; - type AreaType = SubDescNone; - type AuthorType = SubDescNone; - type PublicKeyType = SubDescNone; -} - -#[derive(Clone, Debug, Serialize)] -pub struct Director { - pub id: ObjectId, - pub right: u8, -} - -pub struct BoardOfDirector {} - -pub enum DepartmentMember {} - -pub struct Department {} - -#[derive(Clone, Debug, Serialize)] -pub struct OrgMember { - pub id: ObjectId, - pub right: u8, - pub shares: u64, -} - -#[derive(Clone, Debug, Serialize)] -pub struct OrgBodyContent { - pub members: Vec, - pub directors: Vec, - pub total_equity: u64, -} - -impl BodyContent for OrgBodyContent { - fn format(&self) -> u8 { - OBJECT_CONTENT_CODEC_FORMAT_PROTOBUF - } -} - -// body使用protobuf编解码 -impl TryFrom for Director { - type Error = BuckyError; - - fn try_from(mut value: protos::Director) -> BuckyResult { - Ok(Self { - id: ProtobufCodecHelper::decode_buf(value.take_id())?, - right: value.get_right() as u8, - }) - } -} - -impl TryFrom<&Director> for protos::Director { - type Error = BuckyError; - - fn try_from(value: &Director) -> BuckyResult { - let mut ret = Self::new(); - ret.set_id(value.id.to_vec()?); - ret.set_right(value.right as u32); - - Ok(ret) - } -} - -impl TryFrom for OrgMember { - type Error = BuckyError; - - fn try_from(mut value: protos::OrgMember) -> BuckyResult { - Ok(Self { - id: ProtobufCodecHelper::decode_buf(value.take_id())?, - right: value.get_right() as u8, - shares: value.get_shares(), - }) - } -} - -impl TryFrom<&OrgMember> for protos::OrgMember { - type Error = BuckyError; - - fn try_from(value: &OrgMember) -> BuckyResult { - let mut ret = Self::new(); - ret.set_id(value.id.to_vec()?); - ret.set_right(value.right as u32); - ret.set_shares(value.shares); - - Ok(ret) - } -} - -impl TryFrom for OrgBodyContent { - type Error = BuckyError; - - fn try_from(mut value: protos::OrgBodyContent) -> BuckyResult { - Ok(Self { - members: ProtobufCodecHelper::decode_nested_list(value.take_members())?, - directors: ProtobufCodecHelper::decode_nested_list(value.take_directors())?, - total_equity: value.total_equity, - }) - } -} - -impl TryFrom<&OrgBodyContent> for protos::OrgBodyContent { - type Error = BuckyError; - - fn try_from(value: &OrgBodyContent) -> BuckyResult { - let mut ret = Self::new(); - ret.set_members(ProtobufCodecHelper::encode_nested_list(&value.members)?); - ret.set_directors(ProtobufCodecHelper::encode_nested_list(&value.directors)?); - ret.set_total_equity(value.total_equity); - - Ok(ret) - } -} - -crate::inner_impl_default_protobuf_raw_codec!(OrgBodyContent); - -pub type OrgType = NamedObjType; -pub type OrgBuilder = NamedObjectBuilder; - -pub type OrgDesc = NamedObjectDesc; -pub type OrgId = NamedObjectId; -pub type Org = NamedObjectBase; - -impl OrgDesc { - pub fn action_id(&self) -> OrgId { - OrgId::try_from(self.calculate_id()).unwrap() - } -} - -impl NamedObjectBase { - pub fn new(members: Vec, directors: Vec) -> OrgBuilder { - let desc_content = OrgDescContent {}; - let body_content = OrgBodyContent { - members, - directors, - total_equity: 0, - }; - OrgBuilder::new(desc_content, body_content) - } - - pub fn members(&self) -> &Vec { - &self.body().as_ref().unwrap().content().members - } - - pub fn members_mut(&mut self) -> &mut Vec { - &mut self.body_mut().as_mut().unwrap().content_mut().members - } -} - -#[cfg(test)] -mod test { - use crate::*; - //use std::path::Path; - - #[test] - fn org() { - let action = Org::new(vec![], vec![]).build(); - - // let p = Path::new("f:\\temp\\org.obj"); - // if p.parent().unwrap().exists() { - // action.clone().encode_to_file(p, false); - // } - - let buf = action.to_vec().unwrap(); - let _obj = Org::clone_from_slice(&buf).unwrap(); - } -} diff --git a/src/component/cyfs-base/src/objects/simple_group.rs b/src/component/cyfs-base/src/objects/simple_group.rs deleted file mode 100644 index 9bbdf5e4f..000000000 --- a/src/component/cyfs-base/src/objects/simple_group.rs +++ /dev/null @@ -1,175 +0,0 @@ -use crate::*; - -use std::convert::TryFrom; -use std::str::FromStr; - -#[derive(Clone, Debug, RawEncode, RawDecode)] -pub struct SimpleGroupDescContent {} - -impl DescContent for SimpleGroupDescContent { - fn obj_type() -> u16 { - ObjectTypeCode::SimpleGroup.into() - } - - type OwnerType = SubDescNone; - type AreaType = Option; - type AuthorType = SubDescNone; - type PublicKeyType = MNPublicKey; -} - -#[derive(Clone, Debug)] -pub struct SimpleGroupBodyContent { - members: Vec, - ood_list: Vec, - ood_work_mode: Option, -} - -impl BodyContent for SimpleGroupBodyContent { - fn format(&self) -> u8 { - OBJECT_CONTENT_CODEC_FORMAT_PROTOBUF - } -} - -impl SimpleGroupBodyContent { - pub fn new( - members: Vec, - ood_work_mode: OODWorkMode, - ood_list: Vec, - ) -> Self { - Self { - members, - ood_work_mode: Some(ood_work_mode), - ood_list, - } - } - - pub fn members(&self) -> &Vec { - &self.members - } - - pub fn members_mut(&mut self) -> &mut Vec { - &mut self.members - } - - pub fn ood_list(&self) -> &Vec { - &self.ood_list - } - - pub fn ood_list_mut(&mut self) -> &mut Vec { - &mut self.ood_list - } - - pub fn ood_work_mode(&self) -> OODWorkMode { - self.ood_work_mode - .clone() - .unwrap_or(OODWorkMode::Standalone) - } - - pub fn set_ood_work_mode(&mut self, ood_work_mode: OODWorkMode) { - self.ood_work_mode = Some(ood_work_mode); - } -} - -// body使用protobuf编解码 -impl TryFrom for SimpleGroupBodyContent { - type Error = BuckyError; - - fn try_from(mut value: protos::SimpleGroupBodyContent) -> BuckyResult { - let mut ret = Self { - members: ProtobufCodecHelper::decode_buf_list(value.take_members())?, - ood_list: ProtobufCodecHelper::decode_buf_list(value.take_ood_list())?, - ood_work_mode: None, - }; - - if value.has_ood_work_mode() { - ret.ood_work_mode = Some(OODWorkMode::from_str(value.get_ood_work_mode())?); - } - - Ok(ret) - } -} - -impl TryFrom<&SimpleGroupBodyContent> for protos::SimpleGroupBodyContent { - type Error = BuckyError; - - fn try_from(value: &SimpleGroupBodyContent) -> BuckyResult { - let mut ret = Self::new(); - - ret.set_members(ProtobufCodecHelper::encode_buf_list(&value.members)?); - ret.set_ood_list(ProtobufCodecHelper::encode_buf_list(&value.ood_list)?); - - if let Some(ood_work_mode) = &value.ood_work_mode { - ret.set_ood_work_mode(ood_work_mode.to_string()); - } - - Ok(ret) - } -} - -crate::inner_impl_default_protobuf_raw_codec!(SimpleGroupBodyContent); - -pub type SimpleGroupType = NamedObjType; -pub type SimpleGroupBuilder = NamedObjectBuilder; - -pub type SimpleGroupDesc = NamedObjectDesc; -pub type SimpleGroupId = NamedObjectId; -pub type SimpleGroup = NamedObjectBase; - -impl SimpleGroupDesc { - pub fn simple_group_id(&self) -> SimpleGroupId { - SimpleGroupId::try_from(self.calculate_id()).unwrap() - } -} - -impl SimpleGroup { - pub fn new( - threshold: u8, - owners: Vec, - members: Vec, - ood_work_mode: OODWorkMode, - ood_list: Vec, - area: Area, - ) -> SimpleGroupBuilder { - let desc_content = SimpleGroupDescContent {}; - - let body_content = SimpleGroupBodyContent::new(members, ood_work_mode, ood_list); - - SimpleGroupBuilder::new(desc_content, body_content) - .area(area) - .public_key((threshold, owners)) - } -} - -#[cfg(test)] -mod test { - use crate::*; - - #[test] - fn simple_group() { - let threshold = 0; - - let members = vec![ObjectId::default()]; - - let ood_list = vec![DeviceId::default()]; - - let obj = SimpleGroup::new( - threshold, - vec![], - members, - OODWorkMode::Standalone, - ood_list, - Area::default(), - ) - .build(); - // let p = Path::new("f:\\temp\\simple_group.obj"); - // if p.parent().unwrap().exists() { - // obj.clone().encode_to_file(p, false); - // } - - let buf = obj.to_vec().unwrap(); - - let decode_obj = SimpleGroup::clone_from_slice(&buf).unwrap(); - - assert!(obj.desc().simple_group_id() == decode_obj.desc().simple_group_id()); - } -} diff --git a/src/component/cyfs-base/src/objects/standard.rs b/src/component/cyfs-base/src/objects/standard.rs index cae3bd847..de2d5fffc 100644 --- a/src/component/cyfs-base/src/objects/standard.rs +++ b/src/component/cyfs-base/src/objects/standard.rs @@ -4,8 +4,6 @@ use crate::*; pub enum StandardObject { Device(Device), People(People), - SimpleGroup(SimpleGroup), - Org(Org), AppGroup(AppGroup), UnionAccount(UnionAccount), ChunkId(ChunkId), @@ -17,6 +15,7 @@ pub enum StandardObject { Action(Action), ObjectMap(ObjectMap), Contract(Contract), + Group(Group), } #[macro_export] @@ -25,8 +24,7 @@ macro_rules! match_standard_obj { match $on { StandardObject::Device($o) => $body, StandardObject::People($o) => $body, - StandardObject::SimpleGroup($o) => $body, - StandardObject::Org($o) => $body, + StandardObject::Group($o) => $body, StandardObject::AppGroup($o) => $body, StandardObject::UnionAccount($o) => $body, StandardObject::ChunkId($chunk_id) => $chunk_body, @@ -64,7 +62,7 @@ macro_rules! match_standard_pubkey_obj { match $on { StandardObject::Device($o) => $body, StandardObject::People($o) => $body, - StandardObject::SimpleGroup($o) => $body, + // StandardObject::Group($o) => $body, _ => $other_body, } }; @@ -83,7 +81,16 @@ macro_rules! match_standard_author_obj { macro_rules! match_standard_ood_list_obj { ($on:ident, $o:ident, $body:tt, $other_body:tt) => { match $on { - StandardObject::SimpleGroup($o) => $body, + StandardObject::Group($o) => $body, + StandardObject::People($o) => $body, + _ => $other_body, + } + }; +} + +macro_rules! match_standard_ood_work_mode_obj { + ($on:ident, $o:ident, $body:tt, $other_body:tt) => { + match $on { StandardObject::People($o) => $body, _ => $other_body, } @@ -152,7 +159,7 @@ impl StandardObject { } pub fn ood_work_mode(&self) -> BuckyResult { - match_standard_ood_list_obj!( + match_standard_ood_work_mode_obj!( self, o, { @@ -187,14 +194,8 @@ impl StandardObject { } _ => unreachable!(), }, - Self::SimpleGroup(o) => match other { - Self::SimpleGroup(other) => { - *o.body_mut() = other.body().clone(); - } - _ => unreachable!(), - }, - Self::Org(o) => match other { - Self::Org(other) => { + Self::Group(o) => match other { + Self::Group(other) => { *o.body_mut() = other.body().clone(); } _ => unreachable!(), @@ -271,8 +272,7 @@ impl RawEncode for StandardObject { match self { StandardObject::Device(o) => o.raw_measure(purpose), StandardObject::People(o) => o.raw_measure(purpose), - StandardObject::SimpleGroup(o) => o.raw_measure(purpose), - StandardObject::Org(o) => o.raw_measure(purpose), + StandardObject::Group(o) => o.raw_measure(purpose), StandardObject::AppGroup(o) => o.raw_measure(purpose), StandardObject::UnionAccount(o) => o.raw_measure(purpose), StandardObject::ChunkId(o) => o.raw_measure(purpose), @@ -295,8 +295,7 @@ impl RawEncode for StandardObject { match self { StandardObject::Device(o) => o.raw_encode(buf, purpose), StandardObject::People(o) => o.raw_encode(buf, purpose), - StandardObject::SimpleGroup(o) => o.raw_encode(buf, purpose), - StandardObject::Org(o) => o.raw_encode(buf, purpose), + StandardObject::Group(o) => o.raw_encode(buf, purpose), StandardObject::AppGroup(o) => o.raw_encode(buf, purpose), StandardObject::UnionAccount(o) => o.raw_encode(buf, purpose), StandardObject::ChunkId(o) => o.raw_encode(buf, purpose), @@ -327,10 +326,8 @@ impl<'de> RawDecode<'de> for StandardObject { ObjectTypeCode::People => { People::raw_decode(buf).map(|(obj, buf)| (StandardObject::People(obj), buf)) } - ObjectTypeCode::SimpleGroup => SimpleGroup::raw_decode(buf) - .map(|(obj, buf)| (StandardObject::SimpleGroup(obj), buf)), - ObjectTypeCode::Org => { - Org::raw_decode(buf).map(|(obj, buf)| (StandardObject::Org(obj), buf)) + ObjectTypeCode::Group => { + Group::raw_decode(buf).map(|(obj, buf)| (StandardObject::Group(obj), buf)) } ObjectTypeCode::AppGroup => { AppGroup::raw_decode(buf).map(|(obj, buf)| (StandardObject::AppGroup(obj), buf)) diff --git a/src/component/cyfs-base/src/objects/tx.rs b/src/component/cyfs-base/src/objects/tx.rs index 33f8906ec..ebf1692ee 100644 --- a/src/component/cyfs-base/src/objects/tx.rs +++ b/src/component/cyfs-base/src/objects/tx.rs @@ -7,7 +7,7 @@ use std::convert::TryFrom; #[derive(Clone, Debug, RawEncode, RawDecode)] pub enum TxCondition { //时间 -//BTC 交易确认 + //BTC 交易确认 } #[derive(Copy, Clone, Debug, Serialize, Deserialize, RawEncode, RawDecode, Eq, PartialEq)] @@ -38,7 +38,7 @@ impl ProtobufTransform> for CoinTokenId { pub enum TxCaller { People(PeopleDesc), Device(DeviceDesc), - Group(SimpleGroupDesc), + Group(GroupDesc), Union(UnionAccountDesc), Miner(ObjectId), Id(ObjectId), @@ -50,7 +50,7 @@ impl TryFrom<&StandardObject> for TxCaller { match obj { StandardObject::People(desc) => Ok(Self::People(desc.desc().clone())), StandardObject::Device(desc) => Ok(Self::Device(desc.desc().clone())), - StandardObject::SimpleGroup(desc) => Ok(Self::Group(desc.desc().clone())), + StandardObject::Group(desc) => Ok(Self::Group(desc.desc().clone())), StandardObject::UnionAccount(desc) => Ok(Self::Union(desc.desc().clone())), _ => Err(BuckyError::new(BuckyErrorCode::Failed, "Failed")), } diff --git a/src/component/cyfs-base/src/objects/unique_id.rs b/src/component/cyfs-base/src/objects/unique_id.rs index 1d28aa561..afe8b72b9 100644 --- a/src/component/cyfs-base/src/objects/unique_id.rs +++ b/src/component/cyfs-base/src/objects/unique_id.rs @@ -1,9 +1,9 @@ use crate::*; +use base58::ToBase58; use generic_array::typenum::{marker_traits::Unsigned, U16}; use generic_array::GenericArray; use std::fmt; -use base58::ToBase58; // unique id in const info #[derive(Clone, Eq, PartialEq)] @@ -80,6 +80,14 @@ impl UniqueId { sha256.input(src); Self::create(&sha256.result()) } + + pub fn create_with_random() -> Self { + let mut id = Self::default(); + let (l, h) = id.as_mut_slice().split_at_mut(8); + l.copy_from_slice(&rand::random::().to_be_bytes()); + h.copy_from_slice(&rand::random::().to_be_bytes()); + id + } } impl RawFixedBytes for UniqueId { @@ -181,4 +189,4 @@ mod test { let hash = id.raw_hash_value().unwrap(); println!("hash: {}", hash); } -} \ No newline at end of file +} diff --git a/src/component/cyfs-chunk-lib/src/chunk.rs b/src/component/cyfs-chunk-lib/src/chunk.rs index 24482633f..054cb052b 100644 --- a/src/component/cyfs-chunk-lib/src/chunk.rs +++ b/src/component/cyfs-chunk-lib/src/chunk.rs @@ -2,8 +2,8 @@ use crate::SharedMemChunk; use crate::{MMapChunk, MemChunk}; use cyfs_base::*; -use cyfs_util::AsyncReadWithSeek; use cyfs_debug::Mutex; +use cyfs_util::AsyncReadWithSeek; use std::future::Future; use std::io::SeekFrom; @@ -134,7 +134,7 @@ impl async_std::io::Seek for ChunkRead { } } -use async_std::io::{Seek, Read}; +use async_std::io::{Read, Seek}; use std::ops::Range; impl AsyncReadWithSeek for ChunkRead {} diff --git a/src/component/cyfs-core/Cargo.toml b/src/component/cyfs-core/Cargo.toml index 72db63ed2..26833b7e8 100644 --- a/src/component/cyfs-core/Cargo.toml +++ b/src/component/cyfs-core/Cargo.toml @@ -30,3 +30,5 @@ hex = '0.4' chrono = '0.4' protobuf = { version = '2', features = ['with-bytes'] } semver = '1.0' +sha2 = { version = '0.8' } +generic-array = { version = '0.12', default-features = false, features = ['serde'] } diff --git a/src/component/cyfs-core/protos/core_objects.proto b/src/component/cyfs-core/protos/core_objects.proto index 8e941363c..3fbf3b7e9 100644 --- a/src/component/cyfs-core/protos/core_objects.proto +++ b/src/component/cyfs-core/protos/core_objects.proto @@ -10,6 +10,49 @@ message StorageBodyContent { bytes value = 1; } +// ObjectShellObject +message ObjectShellDescContent { + // true: storage `ObjectId` in `desc` only, without `ObjectDesc`; + // false: encode the `ObjectDesc` in `desc`. + bool is_object_id_only = 1; + + // true: calculate the `fix_content_hash` include `desc_sign`; + // false: calculate the `fix_content_hash` without `desc_sign`. + bool is_desc_sign_fix = 2; + + // true: calculate the `fix_content_hash` include `body_sign`; + // false: calculate the `fix_content_hash` without `body_sign`. + bool is_body_sign_fix = 3; + + // true: calculate the `fix_content_hash` include `nonce`; + // false: calculate the `fix_content_hash` without `nonce`. + bool is_nonce_fix = 4; + + // hash of fixed fields in `ObjectShellBodyContent`. + // hash_buf = desc + body + [desc_signatures] + [body_signatures] + [nonce] + // * any field with a value of `None` should be occupied with 1 byte with a value of 0. + // fix_content_hash = sha256(hash_buf) + bytes fix_content_hash = 5; +} + +message ObjectShellBodyContent { + // if is_object_id_only is true, `desc` is the encoded buffer of `ObjectId` of the original object. + // otherwise, `desc` is the encoded buffer of the full `Desc` of the original object. + bytes desc = 1; + + // `body` is the encoded buffer of the `Body` of the original object. + optional bytes body = 2; + + // `desc_signatures` is the encoded buffer of the `Object.signs().desc_signs()` of the original object. + optional bytes desc_signatures = 3; + + // `body_signatures` is the encoded buffer of the `Object.signs().body_signs()` of the original object. + optional bytes body_signatures = 4; + + // `nonce` is the encoded buffer of the `nonce` of the original object. + optional bytes nonce = 5; +} + // TextObject message TextDescContent { string id = 1; @@ -328,4 +371,123 @@ message SyncResponseObjectMetaInfo { optional string context = 3; optional string last_access_rpath = 4; optional uint32 access_string = 5; -} \ No newline at end of file +} + +message GroupRPath { + bytes group_id = 1; + bytes dec_id = 2; + string rpath = 3; +} + +message GroupProposalDescContent { + // target + GroupRPath rpath = 1; + + // for app + string method = 2; + optional bytes params = 3; // blob, app define, it canbe: blob/hash/ObjectId/any other + + // time + optional bytes meta_block_id = 4; + optional uint64 effective_begining = 5; + optional uint64 effective_ending = 6; +} + +message GroupProposalBodyContent { + optional bytes payload = 1; +/* + message Signature { + bytes signature = 1; // sign(hash(ProposalId, proponent_id, decide)) + bytes proponent_id = 2; + bytes decide = 3; + } + + repeated Signature decide_signatures = 2; +*/ +} + +/* +message GroupUpdateGroupPropsalParam { + repeated bytes target_dec_id = 1; // the proccesor decs + optional bytes from_chunk_id = 2; // Chunk(Encode(Group)) + bytes to_chunk_id = 3; // Chunk(Encode(Group)) +} + +message GroupPropsalDecideParam { + bytes signature = 1; // sign(hash(ProposalId, owner, decide)) + bytes proposal_id = 2; + bytes decide = 3; +} +*/ + +message HotstuffBlockQc { + bytes block_id = 1; + optional bytes prev_block_id = 2; // + uint64 round = 3; + + message VoteSignature { + bytes voter = 1; + bytes signature = 2; + } + + repeated VoteSignature votes = 4; +} + +message HotstuffTimeout { + uint64 round = 1; + + message VoteSignature { + bytes voter = 1; + uint64 high_qc_round = 2; + bytes signature = 3; + } + + repeated VoteSignature votes = 2; + optional bytes group_shell_id = 3; // None if it's same as the block +} + +message GroupConsensusBlockDescContent { + // target + GroupRPath rpath = 1; + + // input + bytes body_hash = 2; // hash(Encode(proposals, proposal_result_states, proposal_receiptes)) + + // result + optional bytes result_state_id = 3; + uint64 height = 4; + + // time + bytes meta_block_id = 5; + + uint64 round = 7; + bytes group_shell_id = 8; +} + +message GroupConsensusBlockBodyContent { + message Proposal { + bytes proposal_id = 1; + optional bytes proposal_result_state = 2; + optional bytes proposal_receipt = 3; + optional bytes context = 4; + } + + repeated Proposal proposals = 1; + + optional HotstuffBlockQc qc = 2; + optional HotstuffTimeout tc = 3; +} + +/* +message GroupActionDescContent { + // target + GroupRPath rpath = 1; + + // for app + string method = 2; + optional bytes params = 3; // blob, app define, it canbe: blob/hash/ObjectId/any other + + optional uint64 value = 4; + optional bytes conclusion = 5; +} +*/ diff --git a/src/component/cyfs-core/src/coreobj.rs b/src/component/cyfs-core/src/coreobj.rs index 3e135e9a4..a19e3a775 100644 --- a/src/component/cyfs-core/src/coreobj.rs +++ b/src/component/cyfs-core/src/coreobj.rs @@ -15,6 +15,9 @@ pub enum CoreObjectType { // 文本对象 Text = 41, + // A shell of an mutable `Object`, we can use it to storage `Object` with different `Body` and same `Desc` in `NOC`. + ObjectShell = 42, + // 通讯录 // FriendList = 130, FriendOption = 131, @@ -60,6 +63,14 @@ pub enum CoreObjectType { // Perf PerfOperation = 600, + // Group + GroupProposal = 700, + GroupUpdateGroup = 701, + GroupConsensusBlock = 702, + GroupAction = 704, + GroupQuorumCertificate = 705, + GroupCommand = 706, + // IM通用对象 AddFriend = 1001, Msg = 1003, diff --git a/src/component/cyfs-core/src/group/group_consensus_block.rs b/src/component/cyfs-core/src/group/group_consensus_block.rs new file mode 100644 index 000000000..c967dcb41 --- /dev/null +++ b/src/component/cyfs-core/src/group/group_consensus_block.rs @@ -0,0 +1,460 @@ +use std::sync::{ + atomic::{AtomicU8, Ordering}, + Arc, +}; + +use crate::{CoreObjectType, GroupRPath}; +use cyfs_base::*; +use sha2::Digest; + +#[derive(Clone, ProtobufEncode, ProtobufDecode, ProtobufTransform)] +#[cyfs_protobuf_type(crate::codec::protos::GroupConsensusBlockDescContent)] +pub struct GroupConsensusBlockDescContent { + rpath: GroupRPath, + body_hash: HashValue, + result_state_id: Option, + height: u64, + meta_block_id: ObjectId, + round: u64, + group_shell_id: ObjectId, +} + +impl DescContent for GroupConsensusBlockDescContent { + fn obj_type() -> u16 { + CoreObjectType::GroupConsensusBlock as u16 + } + + fn format(&self) -> u8 { + OBJECT_CONTENT_CODEC_FORMAT_PROTOBUF + } + + fn debug_info() -> String { + String::from("GroupConsensusBlockDescContent") + } + + type OwnerType = Option; + type AreaType = SubDescNone; + type AuthorType = SubDescNone; + type PublicKeyType = SubDescNone; +} + +impl GroupConsensusBlockDescContent { + pub fn rpath(&self) -> &GroupRPath { + &self.rpath + } + + pub fn result_state_id(&self) -> &Option { + &self.result_state_id + } + + pub fn height(&self) -> u64 { + self.height + } + + pub fn round(&self) -> u64 { + self.round + } + + pub fn group_shell_id(&self) -> &ObjectId { + &self.group_shell_id + } +} + +#[derive(Clone, ProtobufTransformType)] +#[cyfs_protobuf_type(crate::codec::protos::hotstuff_block_qc::VoteSignature)] +pub struct HotstuffBlockQCSign { + pub voter: ObjectId, + pub signature: Signature, +} + +impl ProtobufTransform + for HotstuffBlockQCSign +{ + fn transform( + value: crate::codec::protos::hotstuff_block_qc::VoteSignature, + ) -> BuckyResult { + Ok(Self { + voter: ObjectId::raw_decode(value.voter.as_slice())?.0, + signature: Signature::raw_decode(value.signature.as_slice())?.0, + }) + } +} + +impl ProtobufTransform<&HotstuffBlockQCSign> + for crate::codec::protos::hotstuff_block_qc::VoteSignature +{ + fn transform(value: &HotstuffBlockQCSign) -> BuckyResult { + Ok(Self { + voter: value.voter.to_vec()?, + signature: value.signature.to_vec()?, + }) + } +} + +#[derive(Default, Clone, ProtobufEncode, ProtobufDecode, ProtobufTransform)] +#[cyfs_protobuf_type(crate::codec::protos::HotstuffBlockQc)] +pub struct HotstuffBlockQC { + pub block_id: ObjectId, + pub prev_block_id: Option, + pub round: u64, + pub votes: Vec, +} + +#[derive(Clone, ProtobufTransformType)] +#[cyfs_protobuf_type(crate::codec::protos::hotstuff_timeout::VoteSignature)] +pub struct HotstuffTimeoutSign { + pub voter: ObjectId, + pub high_qc_round: u64, + pub signature: Signature, +} + +impl ProtobufTransform + for HotstuffTimeoutSign +{ + fn transform( + value: crate::codec::protos::hotstuff_timeout::VoteSignature, + ) -> BuckyResult { + Ok(Self { + voter: ObjectId::raw_decode(value.voter.as_slice())?.0, + signature: Signature::raw_decode(value.signature.as_slice())?.0, + high_qc_round: value.high_qc_round, + }) + } +} + +impl ProtobufTransform<&HotstuffTimeoutSign> + for crate::codec::protos::hotstuff_timeout::VoteSignature +{ + fn transform(value: &HotstuffTimeoutSign) -> BuckyResult { + Ok(Self { + voter: value.voter.to_vec()?, + signature: value.signature.to_vec()?, + high_qc_round: value.high_qc_round, + }) + } +} + +#[derive(Clone, ProtobufEncode, ProtobufDecode, ProtobufTransform)] +#[cyfs_protobuf_type(crate::codec::protos::HotstuffTimeout)] +pub struct HotstuffTimeout { + pub round: u64, + pub votes: Vec, + pub group_shell_id: Option, +} + +#[derive(Clone, ProtobufTransformType)] +#[cyfs_protobuf_type(crate::codec::protos::group_consensus_block_body_content::Proposal)] +pub struct GroupConsensusBlockProposal { + pub proposal: ObjectId, + pub result_state: Option, + pub receipt: Option>, + pub context: Option>, +} + +impl ProtobufTransform + for GroupConsensusBlockProposal +{ + fn transform( + mut value: crate::codec::protos::group_consensus_block_body_content::Proposal, + ) -> BuckyResult { + let result_state = match value.proposal_result_state { + Some(state_id) => Some(ObjectId::raw_decode(state_id.as_slice())?.0), + None => None, + }; + + Ok(Self { + proposal: ObjectId::raw_decode(value.proposal_id.as_slice())?.0, + result_state, + receipt: value.proposal_receipt.take(), + context: value.context.take(), + }) + } +} + +impl ProtobufTransform<&GroupConsensusBlockProposal> + for crate::codec::protos::group_consensus_block_body_content::Proposal +{ + fn transform(value: &GroupConsensusBlockProposal) -> BuckyResult { + Ok(Self { + proposal_id: value.proposal.to_vec()?, + proposal_result_state: value.result_state.map(|id| id.to_vec().unwrap()), + proposal_receipt: value.receipt.clone(), + context: value.context.clone(), + }) + } +} + +#[derive(Clone, ProtobufEncode, ProtobufDecode, ProtobufTransform)] +#[cyfs_protobuf_type(crate::codec::protos::GroupConsensusBlockBodyContent)] +pub struct GroupConsensusBlockBodyContent { + proposals: Vec, + qc: Option, + tc: Option, +} + +impl BodyContent for GroupConsensusBlockBodyContent { + fn format(&self) -> u8 { + OBJECT_CONTENT_CODEC_FORMAT_PROTOBUF + } +} + +pub type GroupConsensusBlockDesc = NamedObjectDesc; +type GroupConsensusBlockType = + NamedObjType; +type GroupConsensusBlockBuilder = + NamedObjectBuilder; + +pub type GroupConsensusBlockId = NamedObjectId; +#[derive(Clone)] +pub struct GroupConsensusBlock( + NamedObjectBase, + Arc<(AtomicU8, GroupConsensusBlockId)>, +); + +const BLOCK_CHECK_STATE_NONE: u8 = 0; +const BLOCK_CHECK_STATE_SUCC: u8 = 1; +const BLOCK_CHECK_STATE_FAIL: u8 = 2; + +impl GroupConsensusBlockDescContent { + fn hash_object_vec(object_ids: &[ObjectId]) -> HashValue { + let mut sha256 = sha2::Sha256::new(); + for id in object_ids { + sha256.input(id.as_slice()); + } + + sha256.result().into() + } +} + +impl GroupConsensusBlockBodyContent { + fn hash(&self) -> HashValue { + let buf = self.to_vec().unwrap(); + let mut sha256 = sha2::Sha256::new(); + sha256.input(buf.as_slice()); + sha256.result().into() + } +} + +pub trait GroupConsensusBlockObject { + fn create( + rpath: GroupRPath, + proposals: Vec, + result_state_id: Option, + height: u64, + meta_block_id: ObjectId, + round: u64, + group_shell_id: ObjectId, + qc: Option, + tc: Option, + owner: ObjectId, + ) -> Self; + fn check(&self) -> bool; + fn rpath(&self) -> &GroupRPath; + fn proposals(&self) -> &Vec; + fn result_state_id(&self) -> &Option; + fn height(&self) -> u64; + fn block_id(&self) -> &GroupConsensusBlockId; + fn meta_block_id(&self) -> &ObjectId; + fn prev_block_id(&self) -> Option<&ObjectId>; + fn owner(&self) -> &ObjectId; + fn named_object(&self) -> &NamedObjectBase; + fn named_object_mut(&mut self) -> &mut NamedObjectBase; + fn round(&self) -> u64; + fn group_shell_id(&self) -> &ObjectId; + fn qc(&self) -> &Option; + fn tc(&self) -> &Option; +} + +impl GroupConsensusBlockObject for GroupConsensusBlock { + fn create( + rpath: GroupRPath, + proposals: Vec, + result_state_id: Option, + height: u64, + meta_block_id: ObjectId, + round: u64, + group_shell_id: ObjectId, + qc: Option, + mut tc: Option, + owner: ObjectId, + ) -> Self { + if let Some(tc) = tc.as_mut() { + if tc.group_shell_id.as_ref() == Some(&group_shell_id) { + tc.group_shell_id = None; + } + } + + let body = GroupConsensusBlockBodyContent { proposals, qc, tc }; + + let desc = GroupConsensusBlockDescContent { + rpath, + result_state_id, + + height, + meta_block_id, + body_hash: body.hash(), + round, + group_shell_id, + }; + + let block = GroupConsensusBlockBuilder::new(desc, body) + .owner(owner) + .create_time(bucky_time_now()) + .build(); + + let block_id = GroupConsensusBlockId::try_from(block.desc().object_id()).unwrap(); + Self( + block, + Arc::new((AtomicU8::new(BLOCK_CHECK_STATE_SUCC), block_id)), + ) + } + + fn check(&self) -> bool { + let state = self.1 .0.load(Ordering::SeqCst); + if state == BLOCK_CHECK_STATE_NONE { + let desc = self.0.desc().content(); + let body = self.0.body().as_ref().unwrap().content(); + + let is_result_state_match = body + .proposals + .last() + .map_or(true, |p| p.result_state == desc.result_state_id); + + if is_result_state_match + && self.0.desc().owner().is_some() + && body.hash() == desc.body_hash + { + self.1 .0.store(BLOCK_CHECK_STATE_SUCC, Ordering::SeqCst); + true + } else { + self.1 .0.store(BLOCK_CHECK_STATE_FAIL, Ordering::SeqCst); + false + } + } else { + state == BLOCK_CHECK_STATE_SUCC + } + } + + fn rpath(&self) -> &GroupRPath { + let desc = self.0.desc().content(); + &desc.rpath + } + + fn proposals(&self) -> &Vec { + let body = self.0.body().as_ref().unwrap().content(); + &body.proposals + } + + fn result_state_id(&self) -> &Option { + let desc = self.0.desc().content(); + &desc.result_state_id + } + + fn height(&self) -> u64 { + let desc = self.0.desc().content(); + desc.height + } + + fn block_id(&self) -> &GroupConsensusBlockId { + &self.1 .1 + } + + fn meta_block_id(&self) -> &ObjectId { + let desc = self.0.desc().content(); + &desc.meta_block_id + } + + fn prev_block_id(&self) -> Option<&ObjectId> { + let body = self.0.body().as_ref().unwrap().content(); + body.qc.as_ref().map(|qc| &qc.block_id) + } + + fn owner(&self) -> &ObjectId { + let desc = self.0.desc(); + desc.owner().as_ref().unwrap() + } + + fn named_object(&self) -> &NamedObjectBase { + &self.0 + } + + fn named_object_mut(&mut self) -> &mut NamedObjectBase { + &mut self.0 + } + + fn round(&self) -> u64 { + let desc = self.0.desc().content(); + desc.round + } + + fn group_shell_id(&self) -> &ObjectId { + let desc = self.0.desc().content(); + &desc.group_shell_id + } + + fn qc(&self) -> &Option { + let body = self.0.body().as_ref().unwrap().content(); + &body.qc + } + + fn tc(&self) -> &Option { + let body = self.0.body().as_ref().unwrap().content(); + &body.tc + } +} + +impl RawEncode for GroupConsensusBlock { + fn raw_measure(&self, purpose: &Option) -> BuckyResult { + self.0.raw_measure(purpose) + } + + fn raw_encode<'a>( + &self, + buf: &'a mut [u8], + purpose: &Option, + ) -> BuckyResult<&'a mut [u8]> { + self.0.raw_encode(buf, purpose) + } +} + +impl<'de> RawDecode<'de> for GroupConsensusBlock { + fn raw_decode(buf: &'de [u8]) -> BuckyResult<(Self, &'de [u8])> { + let (obj, remain) = NamedObjectBase::::raw_decode(buf)?; + let block_id = GroupConsensusBlockId::try_from(obj.desc().object_id()).unwrap(); + Ok(( + Self( + obj, + Arc::new((AtomicU8::new(BLOCK_CHECK_STATE_NONE), block_id)), + ), + remain, + )) + } +} + +#[cfg(test)] +mod test { + use super::{GroupConsensusBlock, GroupConsensusBlockObject}; + use cyfs_base::*; + + #[async_std::test] + async fn create_group_rpath() { + // let secret1 = PrivateKey::generate_rsa(1024).unwrap(); + // let secret2 = PrivateKey::generate_rsa(1024).unwrap(); + // let people1 = People::new(None, vec![], secret1.public(), None, None, None).build(); + // let people1_id = people1.desc().people_id(); + // let people2 = People::new(None, vec![], secret2.public(), None, None, None).build(); + // let _people2_id = people2.desc().people_id(); + + // let g1 = GroupConsensusBlock::create( + // people1_id.object_id().to_owned(), + // people1_id.object_id().to_owned(), + // people1_id.to_string(), + // ); + + // let buf = g1.to_vec().unwrap(); + // let add2 = GroupConsensusBlock::clone_from_slice(&buf).unwrap(); + // let any = AnyNamedObject::clone_from_slice(&buf).unwrap(); + // assert_eq!(g1.desc().calculate_id(), add2.desc().calculate_id()); + // assert_eq!(g1.desc().calculate_id(), any.calculate_id()); + } +} diff --git a/src/component/cyfs-core/src/group/group_proposal.rs b/src/component/cyfs-core/src/group/group_proposal.rs new file mode 100644 index 000000000..e011afdf7 --- /dev/null +++ b/src/component/cyfs-core/src/group/group_proposal.rs @@ -0,0 +1,449 @@ +use crate::{CoreObjectType, GroupRPath}; +use async_trait::async_trait; +use cyfs_base::*; + +#[derive(Clone, ProtobufEncode, ProtobufDecode, ProtobufTransform)] +#[cyfs_protobuf_type(crate::codec::protos::GroupProposalDescContent)] +pub struct GroupProposalDescContent { + rpath: GroupRPath, + method: String, + params: Option>, + + meta_block_id: Option, + effective_begining: Option, + effective_ending: Option, +} + +impl DescContent for GroupProposalDescContent { + fn obj_type() -> u16 { + CoreObjectType::GroupProposal as u16 + } + + fn format(&self) -> u8 { + OBJECT_CONTENT_CODEC_FORMAT_PROTOBUF + } + + fn debug_info() -> String { + String::from("GroupProposalDescContent") + } + + type OwnerType = Option; + type AreaType = Option; + type AuthorType = Option; + type PublicKeyType = SubDescNone; +} + +// #[derive(Clone)] +// pub struct GroupProposalSignature { +// signature: Signature, +// proponent_id: ObjectId, +// decide: Vec, +// } + +// impl ProtobufTransform<&crate::codec::protos::group_proposal_body_content::Signature> +// for GroupProposalSignature +// { +// fn transform( +// value: &crate::codec::protos::group_proposal_body_content::Signature, +// ) -> BuckyResult { +// Ok(Self { +// signature: Signature::raw_decode(value.signature.as_slice())?.0, +// proponent_id: ObjectId::raw_decode(value.proponent_id.as_slice())?.0, +// decide: value.decide.clone(), +// }) +// } +// } + +// impl ProtobufTransform<&GroupProposalSignature> +// for crate::codec::protos::group_proposal_body_content::Signature +// { +// fn transform(value: &GroupProposalSignature) -> BuckyResult { +// Ok(Self { +// signature: value.signature.to_vec()?, +// proponent_id: value.proponent_id.to_vec()?, +// decide: value.decide.clone(), +// }) +// } +// } + +#[derive(Clone, ProtobufEncode, ProtobufDecode, ProtobufTransformType, Default)] +#[cyfs_protobuf_type(crate::codec::protos::GroupProposalBodyContent)] +pub struct GroupProposalBodyContent { + payload: Option>, + // decide_signatures: Vec, +} + +impl BodyContent for GroupProposalBodyContent { + fn format(&self) -> u8 { + OBJECT_CONTENT_CODEC_FORMAT_PROTOBUF + } +} + +impl ProtobufTransform + for GroupProposalBodyContent +{ + fn transform(value: crate::codec::protos::GroupProposalBodyContent) -> BuckyResult { + // let mut decide_signatures = vec![]; + // for sign in value.decide_signatures.as_slice() { + // decide_signatures.push(GroupProposalSignature::transform(sign)?); + // } + + Ok(Self { + payload: value.payload, + // decide_signatures, + }) + } +} + +impl ProtobufTransform<&GroupProposalBodyContent> + for crate::codec::protos::GroupProposalBodyContent +{ + fn transform(value: &GroupProposalBodyContent) -> BuckyResult { + // let mut decide_signatures = vec![]; + // for sign in value.decide_signatures.as_slice() { + // decide_signatures.push( + // crate::codec::protos::group_proposal_body_content::Signature::transform(sign)?, + // ); + // } + + Ok(Self { + payload: value.payload.clone(), + // decide_signatures, + }) + } +} + +pub type GroupProposalType = NamedObjType; +pub type GroupProposalBuilder = + NamedObjectBuilder; + +pub type GroupProposalId = NamedObjectId; +pub type GroupProposal = NamedObjectBase; + +impl GroupProposalDescContent { + pub fn new( + rpath: GroupRPath, + method: String, + params: Option>, + meta_block_id: Option, + effective_begining: Option, + effective_ending: Option, + ) -> GroupProposalDescContent { + Self { + rpath, + method, + params, + meta_block_id, + effective_begining, + effective_ending, + } + } +} + +impl GroupProposalBodyContent { + // fn hash_decide_signature( + // proposal_id: &ObjectId, + // proponent_id: &ObjectId, + // decide: &[u8], + // ) -> HashValue { + // let mut sha256 = sha2::Sha256::new(); + // sha256.input(proposal_id.as_slice()); + // sha256.input(proponent_id.as_slice()); + // sha256.input(decide); + // sha256.result().into() + // } +} + +#[async_trait] +pub trait GroupProposalObject { + fn create( + rpath: GroupRPath, + method: String, + params: Option>, + payload: Option>, + timestamp: Option, + owner: ObjectId, + meta_block_id: Option, + effective_begining: Option, + effective_ending: Option, + ) -> GroupProposalBuilder; + + fn rpath(&self) -> &GroupRPath; + fn method(&self) -> &str; + fn params(&self) -> &Option>; + fn params_hash(&self) -> BuckyResult>; + fn params_object_id(&self) -> BuckyResult>; + fn meta_block_id(&self) -> &Option; + fn effective_begining(&self) -> Option; + fn effective_ending(&self) -> Option; + + fn payload(&self) -> &Option>; + fn set_payload(&mut self, payload: Option>); + + // async fn verify_member_decide( + // &self, + // member_id: &ObjectId, + // public_key: &PublicKey, + // ) -> BuckyResult>; + + // fn decided_members_no_verify(&self) -> &Vec; + + // async fn decide( + // &self, + // member_id: ObjectId, + // decide: Vec, + // private_key: &PrivateKey, + // ) -> BuckyResult; + + // async fn verify_and_merge_decide( + // &mut self, + // decide: &GroupPropsalDecideParam, + // member_id: ObjectId, + // public_key: &PublicKey, + // ) -> BuckyResult<()>; +} + +#[async_trait] +impl GroupProposalObject for GroupProposal { + fn create( + rpath: GroupRPath, + method: String, + params: Option>, + payload: Option>, + timestamp: Option, + owner: ObjectId, + meta_block_id: Option, + effective_begining: Option, + effective_ending: Option, + ) -> GroupProposalBuilder { + let desc = GroupProposalDescContent { + rpath, + method, + params, + meta_block_id, + effective_begining, + effective_ending, + }; + + GroupProposalBuilder::new( + desc, + GroupProposalBodyContent { + payload, + // decide_signatures: vec![], + }, + ) + .create_time(timestamp.map_or(bucky_time_now(), |t| t)) + .owner(owner) + } + + fn rpath(&self) -> &GroupRPath { + &self.desc().content().rpath + } + + fn method(&self) -> &str { + self.desc().content().method.as_str() + } + + fn params(&self) -> &Option> { + &self.desc().content().params + } + + fn params_hash(&self) -> BuckyResult> { + match &self.desc().content().params { + Some(params) => HashValue::try_from(params.as_slice()).map(|h| Some(h)), + None => Ok(None), + } + } + + fn params_object_id(&self) -> BuckyResult> { + match &self.desc().content().params { + Some(params) => { + if params.len() != OBJECT_ID_LEN { + Err(BuckyError::new( + BuckyErrorCode::Unmatch, + format!( + "try parse GroupProposal.param as ObjectId with error length: ${}", + params.len() + ), + )) + } else { + Ok(Some(ObjectId::raw_decode(params.as_slice())?.0)) + } + } + None => Ok(None), + } + } + + fn meta_block_id(&self) -> &Option { + &self.desc().content().meta_block_id + } + + fn effective_begining(&self) -> Option { + self.desc().content().effective_begining + } + + fn effective_ending(&self) -> Option { + self.desc().content().effective_ending + } + + fn payload(&self) -> &Option> { + &self.body().as_ref().unwrap().content().payload + } + + fn set_payload(&mut self, payload: Option>) { + self.body_mut().as_mut().unwrap().content_mut().payload = payload; + } + + // async fn verify_member_decide( + // &self, + // member_id: &ObjectId, + // public_key: &PublicKey, + // ) -> BuckyResult> { + // let signs = self + // .body() + // .as_ref() + // .unwrap() + // .content() + // .decide_signatures + // .as_slice(); + + // let proposal_id = self.desc().object_id(); + // let verifier = RsaCPUObjectVerifier::new(public_key.clone()); + + // let mut decides = vec![]; + + // for sign in signs { + // if &sign.proponent_id == member_id { + // let hash = GroupProposalBodyContent::hash_decide_signature( + // &proposal_id, + // &sign.proponent_id, + // sign.decide.as_slice(), + // ); + + // if verifier.verify(hash.as_slice(), &sign.signature).await { + // decides.push(sign.decide.as_slice()); + // } else { + // return Err(BuckyError::new( + // BuckyErrorCode::InvalidSignature, + // "invalid signature", + // )); + // } + // } + // } + + // Ok(decides) + // } + + // async fn decide( + // &self, + // member_id: ObjectId, + // decide: Vec, + // private_key: &PrivateKey, + // ) -> BuckyResult { + // let signs = &self.body().as_ref().unwrap().content().decide_signatures; + + // if signs.iter().find(|s| s.proponent_id == member_id).is_some() { + // return Err(BuckyError::new( + // BuckyErrorCode::AlreadyExists, + // "duplicated decide", + // )); + // } + + // let proposal_id = self.desc().object_id(); + + // let hash = GroupProposalBodyContent::hash_decide_signature( + // &proposal_id, + // &member_id, + // decide.as_slice(), + // ); + + // let signer = RsaCPUObjectSigner::new(private_key.public(), private_key.clone()); + // let sign = signer + // .sign(hash.as_slice(), &SignatureSource::RefIndex(0)) + // .await?; + + // Ok(GroupPropsalDecideParam::new(sign, proposal_id, decide)) + // } + + // async fn verify_and_merge_decide( + // &mut self, + // decide: &GroupPropsalDecideParam, + // member_id: ObjectId, + // public_key: &PublicKey, + // ) -> BuckyResult<()> { + // let proposal_id = self.desc().object_id(); + + // if decide.proposal_id() != &proposal_id { + // return Err(BuckyError::new( + // BuckyErrorCode::NotMatch, + // format!( + // "proposal id not match for decide signature: {}/{}", + // proposal_id, + // decide.proposal_id() + // ), + // )); + // } + + // let hash = GroupProposalBodyContent::hash_decide_signature( + // &proposal_id, + // &member_id, + // decide.decide(), + // ); + + // let verifier = RsaCPUObjectVerifier::new(public_key.clone()); + // if verifier.verify(hash.as_slice(), decide.signature()).await { + // let signs = &mut self + // .body_mut() + // .as_mut() + // .unwrap() + // .content_mut() + // .decide_signatures; + // for exist in signs.iter() { + // if &exist.proponent_id == &member_id && exist.decide == decide.decide() { + // return Ok(()); + // } + // } + + // signs.push(GroupProposalSignature { + // signature: decide.signature().clone(), + // proponent_id: member_id, + // decide: Vec::from(decide.decide()), + // }); + + // Ok(()) + // } else { + // Err(BuckyError::new( + // BuckyErrorCode::InvalidSignature, + // "invalid signature", + // )) + // } + // } +} + +#[cfg(test)] +mod test { + use super::{GroupProposal, GroupProposalObject}; + use cyfs_base::*; + + #[async_std::test] + async fn create_group_proposal() { + // let secret1 = PrivateKey::generate_rsa(1024).unwrap(); + // let secret2 = PrivateKey::generate_rsa(1024).unwrap(); + // let people1 = People::new(None, vec![], secret1.public(), None, None, None).build(); + // let people1_id = people1.desc().people_id(); + // let people2 = People::new(None, vec![], secret2.public(), None, None, None).build(); + // let _people2_id = people2.desc().people_id(); + + // let g1 = GroupRPath::create( + // people1_id.object_id().to_owned(), + // people1_id.object_id().to_owned(), + // people1_id.to_string(), + // ); + + // let buf = g1.to_vec().unwrap(); + // let add2 = GroupRPath::clone_from_slice(&buf).unwrap(); + // let any = AnyNamedObject::clone_from_slice(&buf).unwrap(); + // assert_eq!(g1.desc().calculate_id(), add2.desc().calculate_id()); + // assert_eq!(g1.desc().calculate_id(), any.calculate_id()); + } +} diff --git a/src/component/cyfs-core/src/group/group_proposal_decide_param.rs b/src/component/cyfs-core/src/group/group_proposal_decide_param.rs new file mode 100644 index 000000000..918e804ff --- /dev/null +++ b/src/component/cyfs-core/src/group/group_proposal_decide_param.rs @@ -0,0 +1,51 @@ +use cyfs_base::*; + +#[derive(Clone, ProtobufEncode, ProtobufDecode, ProtobufTransformType)] +#[cyfs_protobuf_type(crate::codec::protos::GroupPropsalDecideParam)] +pub struct GroupPropsalDecideParam { + signature: Signature, + proposal_id: ObjectId, + decide: Vec, +} + +impl GroupPropsalDecideParam { + pub fn new(signature: Signature, proposal_id: ObjectId, decide: Vec) -> Self { + Self { + signature, + proposal_id, + decide, + } + } + + pub fn signature(&self) -> &Signature { + &self.signature + } + + pub fn proposal_id(&self) -> &ObjectId { + &self.proposal_id + } + + pub fn decide(&self) -> &[u8] { + &self.decide + } +} + +impl ProtobufTransform for GroupPropsalDecideParam { + fn transform(value: crate::codec::protos::GroupPropsalDecideParam) -> BuckyResult { + Ok(Self { + signature: Signature::raw_decode(value.signature.as_slice())?.0, + proposal_id: ObjectId::transform(value.proposal_id)?, + decide: value.decide, + }) + } +} + +impl ProtobufTransform<&GroupPropsalDecideParam> for crate::codec::protos::GroupPropsalDecideParam { + fn transform(value: &GroupPropsalDecideParam) -> BuckyResult { + Ok(Self { + signature: value.signature.to_vec()?, + proposal_id: value.proposal_id.to_vec()?, + decide: value.decide.clone(), + }) + } +} diff --git a/src/component/cyfs-core/src/group/group_quorum_certificate.rs b/src/component/cyfs-core/src/group/group_quorum_certificate.rs new file mode 100644 index 000000000..df65c6c45 --- /dev/null +++ b/src/component/cyfs-core/src/group/group_quorum_certificate.rs @@ -0,0 +1,145 @@ +use cyfs_base::{ + BuckyError, BuckyErrorCode, BuckyResult, DescContent, EmptyBodyContent, NamedObjType, + NamedObject, NamedObjectBase, NamedObjectBuilder, NamedObjectId, RawDecode, RawEncode, + SubDescNone, OBJECT_CONTENT_CODEC_FORMAT_RAW, +}; + +use crate::{CoreObjectType, HotstuffBlockQC, HotstuffTimeout}; + +#[derive(Clone)] +pub enum GroupQuorumCertificateDescContent { + QC(HotstuffBlockQC), + TC(HotstuffTimeout), +} + +impl DescContent for GroupQuorumCertificateDescContent { + fn obj_type() -> u16 { + CoreObjectType::GroupQuorumCertificate as u16 + } + + fn format(&self) -> u8 { + OBJECT_CONTENT_CODEC_FORMAT_RAW + } + + fn debug_info() -> String { + String::from("GroupQuorumCertificateDescContent") + } + + type OwnerType = SubDescNone; + type AreaType = SubDescNone; + type AuthorType = SubDescNone; + type PublicKeyType = SubDescNone; +} + +pub type GroupQuorumCertificateType = + NamedObjType; +pub type GroupQuorumCertificateBuilder = + NamedObjectBuilder; + +pub type GroupQuorumCertificateId = NamedObjectId; +pub type GroupQuorumCertificate = NamedObjectBase; + +pub trait GroupQuorumCertificateObject { + fn quorum_round(&self) -> u64; +} + +impl GroupQuorumCertificateObject for GroupQuorumCertificate { + fn quorum_round(&self) -> u64 { + match self.desc().content() { + GroupQuorumCertificateDescContent::QC(qc) => qc.round, + GroupQuorumCertificateDescContent::TC(tc) => tc.round, + } + } +} + +impl RawEncode for GroupQuorumCertificateDescContent { + fn raw_measure( + &self, + purpose: &Option, + ) -> cyfs_base::BuckyResult { + let len = match self { + GroupQuorumCertificateDescContent::QC(qc) => qc.raw_measure(purpose)?, + GroupQuorumCertificateDescContent::TC(tc) => tc.raw_measure(purpose)?, + }; + + Ok(len + 1) + } + + fn raw_encode<'a>( + &self, + buf: &'a mut [u8], + purpose: &Option, + ) -> cyfs_base::BuckyResult<&'a mut [u8]> { + match self { + GroupQuorumCertificateDescContent::QC(qc) => { + buf[0] = 0; + let buf = &mut buf[1..]; + qc.raw_encode(buf, purpose) + } + GroupQuorumCertificateDescContent::TC(tc) => { + buf[0] = 1; + let buf = &mut buf[1..]; + tc.raw_encode(buf, purpose) + } + } + } +} + +impl<'de> RawDecode<'de> for GroupQuorumCertificateDescContent { + fn raw_decode(buf: &'de [u8]) -> BuckyResult<(Self, &'de [u8])> { + let obj_type = buf[0]; + let buf = &buf[1..]; + match obj_type { + 0 => { + let (qc, remain) = HotstuffBlockQC::raw_decode(buf)?; + Ok((GroupQuorumCertificateDescContent::QC(qc), remain)) + } + 1 => { + let (qc, remain) = HotstuffTimeout::raw_decode(buf)?; + Ok((GroupQuorumCertificateDescContent::TC(qc), remain)) + } + _ => Err(BuckyError::new(BuckyErrorCode::Unknown, "unknown qc")), + } + } +} + +impl From for GroupQuorumCertificate { + fn from(qc: HotstuffBlockQC) -> Self { + let desc = GroupQuorumCertificateDescContent::QC(qc); + GroupQuorumCertificateBuilder::new(desc, EmptyBodyContent).build() + } +} + +impl From for GroupQuorumCertificate { + fn from(tc: HotstuffTimeout) -> Self { + assert!(tc.group_shell_id.is_some()); + let desc = GroupQuorumCertificateDescContent::TC(tc); + GroupQuorumCertificateBuilder::new(desc, EmptyBodyContent).build() + } +} + +impl TryInto for GroupQuorumCertificate { + type Error = BuckyError; + + fn try_into(self) -> Result { + match self.into_desc().into_content() { + GroupQuorumCertificateDescContent::QC(qc) => Ok(qc), + GroupQuorumCertificateDescContent::TC(_) => { + Err(BuckyError::new(BuckyErrorCode::Unmatch, "is tc, expect qc")) + } + } + } +} + +impl TryInto for GroupQuorumCertificate { + type Error = BuckyError; + + fn try_into(self) -> Result { + match self.into_desc().into_content() { + GroupQuorumCertificateDescContent::TC(tc) => Ok(tc), + GroupQuorumCertificateDescContent::QC(_) => { + Err(BuckyError::new(BuckyErrorCode::Unmatch, "is qc, expect tc")) + } + } + } +} diff --git a/src/component/cyfs-core/src/group/group_rpath.rs b/src/component/cyfs-core/src/group/group_rpath.rs new file mode 100644 index 000000000..f177015bf --- /dev/null +++ b/src/component/cyfs-core/src/group/group_rpath.rs @@ -0,0 +1,37 @@ +use cyfs_base::*; + +#[derive(Clone, ProtobufEncode, ProtobufDecode, ProtobufTransform, PartialEq, Hash)] +#[cyfs_protobuf_type(crate::codec::protos::GroupRPath)] +pub struct GroupRPath { + group_id: ObjectId, + dec_id: ObjectId, + rpath: String, +} + +impl std::fmt::Debug for GroupRPath { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}-{:?}-{:?}", self.group_id, self.dec_id, self.rpath) + } +} + +impl GroupRPath { + pub fn new(group_id: ObjectId, dec_id: ObjectId, rpath: String) -> Self { + Self { + group_id, + dec_id, + rpath, + } + } + + pub fn group_id(&self) -> &ObjectId { + &self.group_id + } + + pub fn dec_id(&self) -> &ObjectId { + &self.dec_id + } + + pub fn rpath(&self) -> &str { + self.rpath.as_str() + } +} diff --git a/src/component/cyfs-core/src/group/group_shell.rs b/src/component/cyfs-core/src/group/group_shell.rs new file mode 100644 index 000000000..bd871f170 --- /dev/null +++ b/src/component/cyfs-core/src/group/group_shell.rs @@ -0,0 +1,15 @@ +use cyfs_base::{Group, GroupType}; + +use crate::{ObjectShell, OBJECT_SHELL_ALL_FREEDOM_WITH_FULL_DESC}; + +pub type GroupShell = ObjectShell; + +pub trait ToGroupShell: Sized { + fn to_shell(&self) -> GroupShell; +} + +impl ToGroupShell for Group { + fn to_shell(&self) -> GroupShell { + GroupShell::from_object(self, OBJECT_SHELL_ALL_FREEDOM_WITH_FULL_DESC) + } +} diff --git a/src/component/cyfs-core/src/group/group_update_group_proposal_param.rs b/src/component/cyfs-core/src/group/group_update_group_proposal_param.rs new file mode 100644 index 000000000..986b00e14 --- /dev/null +++ b/src/component/cyfs-core/src/group/group_update_group_proposal_param.rs @@ -0,0 +1,36 @@ +use cyfs_base::*; +use serde::Serialize; + +#[derive(Clone, ProtobufEncode, ProtobufDecode, ProtobufTransform, Serialize)] +#[cyfs_protobuf_type(crate::codec::protos::GroupUpdateGroupPropsalParam)] +pub struct GroupUpdateGroupPropsalParam { + target_dec_id: Vec, + from_chunk_id: Option, + to_chunk_id: ObjectId, +} + +impl GroupUpdateGroupPropsalParam { + pub fn new( + target_dec_id: Vec, + from_chunk_id: Option, + to_chunk_id: ObjectId, + ) -> Self { + Self { + target_dec_id, + from_chunk_id, + to_chunk_id, + } + } + + pub fn target_dec_id(&self) -> &[ObjectId] { + self.target_dec_id.as_slice() + } + + pub fn from_chunk_id(&self) -> &Option { + &self.from_chunk_id + } + + pub fn to_chunk_id(&self) -> &ObjectId { + &self.to_chunk_id + } +} diff --git a/src/component/cyfs-core/src/group/mod.rs b/src/component/cyfs-core/src/group/mod.rs new file mode 100644 index 000000000..1f94656e9 --- /dev/null +++ b/src/component/cyfs-core/src/group/mod.rs @@ -0,0 +1,15 @@ +mod group_consensus_block; +mod group_proposal; +// mod group_proposal_decide_param; +mod group_quorum_certificate; +mod group_rpath; +// mod group_update_group_proposal_param; +mod group_shell; + +pub use group_consensus_block::*; +pub use group_proposal::*; +// pub use group_proposal_decide_param::*; +pub use group_quorum_certificate::*; +pub use group_rpath::*; +// pub use group_update_group_proposal_param::*; +pub use group_shell::*; diff --git a/src/component/cyfs-core/src/lib.rs b/src/component/cyfs-core/src/lib.rs index e77fa3731..fa0143477 100644 --- a/src/component/cyfs-core/src/lib.rs +++ b/src/component/cyfs-core/src/lib.rs @@ -2,20 +2,22 @@ extern crate log; pub use app::*; +pub use codec::*; pub use common::*; pub use coreobj::*; +pub use group::*; +pub use nft::*; pub use storage::*; -pub use zone::*; pub use trans::*; -pub use nft::*; -pub use codec::*; +pub use zone::*; +mod app; pub mod codec; +mod common; mod coreobj; -mod zone; +mod group; +pub mod im; +mod nft; mod storage; -mod app; -mod common; mod trans; -mod nft; -pub mod im; \ No newline at end of file +mod zone; diff --git a/src/component/cyfs-core/src/storage/mod.rs b/src/component/cyfs-core/src/storage/mod.rs index 649a8946f..d4b260682 100644 --- a/src/component/cyfs-core/src/storage/mod.rs +++ b/src/component/cyfs-core/src/storage/mod.rs @@ -1,3 +1,5 @@ +mod object_shell; mod storage; -pub use storage::*; \ No newline at end of file +pub use object_shell::*; +pub use storage::*; diff --git a/src/component/cyfs-core/src/storage/object_shell.rs b/src/component/cyfs-core/src/storage/object_shell.rs new file mode 100644 index 000000000..0e467714b --- /dev/null +++ b/src/component/cyfs-core/src/storage/object_shell.rs @@ -0,0 +1,576 @@ +use cyfs_base::{ + BodyContent, BuckyError, BuckyErrorCode, BuckyResult, DescContent, HashValue, NamedObjType, + NamedObject, NamedObjectBase, NamedObjectBaseBuilder, NamedObjectBodyContext, + NamedObjectBuilder, NamedObjectDesc, NamedObjectId, ObjectDesc, ObjectId, ObjectMutBody, + ObjectSigns, ObjectType, ProtobufDecode, ProtobufEncode, ProtobufTransform, RawConvertTo, + RawDecode, RawEncode, RawEncodePurpose, RawEncodeWithContext, Signature, SubDescNone, + OBJECT_CONTENT_CODEC_FORMAT_PROTOBUF, +}; +use serde::Serialize; +use sha2::Digest; + +use crate::CoreObjectType; + +#[derive(Debug, Clone, ProtobufEncode, ProtobufDecode, ProtobufTransform, Serialize)] +#[cyfs_protobuf_type(crate::codec::protos::ObjectShellDescContent)] +struct ObjectShellDescContent { + is_object_id_only: bool, + is_desc_sign_fix: bool, + is_body_sign_fix: bool, + is_nonce_fix: bool, + fix_content_hash: HashValue, +} + +impl DescContent for ObjectShellDescContent { + fn obj_type() -> u16 { + CoreObjectType::ObjectShell as u16 + } + + fn format(&self) -> u8 { + OBJECT_CONTENT_CODEC_FORMAT_PROTOBUF + } + + type OwnerType = SubDescNone; + type AreaType = SubDescNone; + type AuthorType = SubDescNone; + type PublicKeyType = SubDescNone; +} + +#[derive(Clone, Debug, ProtobufEncode, ProtobufDecode, ProtobufTransform)] +#[cyfs_protobuf_type(crate::codec::protos::ObjectShellBodyContent)] +struct ObjectShellBodyContent { + desc: Vec, + body: Option>, + desc_signatures: Option>, + body_signatures: Option>, + nonce: Option>, +} + +impl BodyContent for ObjectShellBodyContent { + fn format(&self) -> u8 { + OBJECT_CONTENT_CODEC_FORMAT_PROTOBUF + } +} + +impl ObjectShellBodyContent { + fn hash(&self, flags: &ObjectShellFlags) -> HashValue { + let occupy = [0u8; 0]; + let occupy = occupy.raw_encode_to_buffer().unwrap(); + + let mut sha256 = sha2::Sha256::new(); + sha256.input(self.desc.as_slice()); + sha256.input( + self.body + .as_ref() + .map_or(occupy.as_slice(), |v| v.as_slice()), + ); + + if flags.is_desc_sign_fix { + sha256.input( + self.desc_signatures + .as_ref() + .map_or(occupy.as_slice(), |v| v.as_slice()), + ); + } + if flags.is_body_sign_fix { + sha256.input( + self.body_signatures + .as_ref() + .map_or(occupy.as_slice(), |v| v.as_slice()), + ); + } + if flags.is_nonce_fix { + sha256.input( + self.nonce + .as_ref() + .map_or(occupy.as_slice(), |v| v.as_slice()), + ); + } + HashValue::from(sha256.result()) + } +} + +type ObjectShellType = NamedObjType; +type ObjectShellBuilder = NamedObjectBuilder; +type ObjectShellDesc = NamedObjectDesc; + +type ObjectShellId = NamedObjectId; +type ObjectShellStorage = NamedObjectBase; + +trait ObjectShellStorageObject { + fn shell_id(&self) -> ObjectId; + fn check_hash(&self) -> bool; + fn hash(&self) -> Option; + fn flags(&self) -> ObjectShellFlags; +} +impl ObjectShellStorageObject for ObjectShellStorage { + fn shell_id(&self) -> ObjectId { + self.desc().calculate_id() + } + + fn check_hash(&self) -> bool { + self.hash().as_ref().map_or(false, |hash| { + &self.desc().content().fix_content_hash == hash + }) + } + + fn hash(&self) -> Option { + self.body() + .as_ref() + .map(|body| body.content().hash(&self.flags())) + } + + fn flags(&self) -> ObjectShellFlags { + let desc = self.desc().content(); + ObjectShellFlags { + is_object_id_only: desc.is_object_id_only, + is_desc_sign_fix: desc.is_desc_sign_fix, + is_body_sign_fix: desc.is_body_sign_fix, + is_nonce_fix: desc.is_nonce_fix, + } + } +} + +#[derive(Copy, Clone)] +pub struct ObjectShellFlags { + is_object_id_only: bool, + is_desc_sign_fix: bool, + is_body_sign_fix: bool, + is_nonce_fix: bool, +} + +impl ObjectShellFlags { + pub fn object_id_only(&self) -> bool { + self.is_object_id_only + } + + pub fn desc_sign_fix(&self) -> bool { + self.is_desc_sign_fix + } + + pub fn body_sign_fix(&self) -> bool { + self.is_body_sign_fix + } + + pub fn nonce_fix(&self) -> bool { + self.is_nonce_fix + } +} + +pub const OBJECT_SHELL_ALL_FREEDOM_WITH_FULL_DESC: ObjectShellFlags = ObjectShellFlags { + is_object_id_only: false, + is_desc_sign_fix: false, + is_body_sign_fix: false, + is_nonce_fix: false, +}; + +pub struct ObjectShellFlagsBuilder { + flags: ObjectShellFlags, +} + +impl ObjectShellFlagsBuilder { + pub fn new() -> Self { + Self { + flags: OBJECT_SHELL_ALL_FREEDOM_WITH_FULL_DESC, + } + } + + pub fn build(&self) -> ObjectShellFlags { + self.flags + } + + pub fn object_id_only(&mut self, is_only: bool) -> &mut Self { + self.flags.is_object_id_only = is_only; + self + } + + pub fn desc_sign_fix(&mut self, is_fix: bool) -> &mut Self { + self.flags.is_desc_sign_fix = is_fix; + self + } + + pub fn body_sign_fix(&mut self, is_fix: bool) -> &mut Self { + self.flags.is_body_sign_fix = is_fix; + self + } + + pub fn nonce_fix(&mut self, is_fix: bool) -> &mut Self { + self.flags.is_nonce_fix = is_fix; + self + } +} + +#[derive(Clone)] +enum ShelledDesc { + ObjectId(ObjectId), + Desc(D), +} + +#[derive(Clone)] +pub struct ObjectShell +where + O: ObjectType, + O::ContentType: BodyContent, + O::DescType: Clone, +{ + desc: ShelledDesc, + body: Option>, + signs: ObjectSigns, + nonce: Option, + flags: ObjectShellFlags, +} + +impl ObjectShell +where + O: ObjectType, + O::ContentType: BodyContent + RawEncode + for<'de> RawDecode<'de> + Clone, + O::DescType: RawEncode + for<'de> RawDecode<'de> + Clone, +{ + pub fn from_object(raw: &NO, flags: ObjectShellFlags) -> Self + where + NO: NamedObject + RawEncode + for<'local> RawDecode<'local> + Clone, + { + Self { + flags, + desc: ShelledDesc::Desc(raw.desc().clone()), + body: raw.body().clone(), + signs: raw.signs().clone(), + nonce: raw.nonce().clone(), + } + } + + pub fn shell_id(&self) -> ObjectId { + self.to_storage().shell_id() + } + + pub fn flags(&self) -> &ObjectShellFlags { + &self.flags + } + + pub fn with_full_desc(&self) -> bool { + match self.desc { + ShelledDesc::ObjectId(_) => false, + ShelledDesc::Desc(_) => true, + } + } + + pub fn body(&self) -> &Option> { + &self.body + } + // update the raw object + pub fn body_mut(&mut self) -> &mut Option> { + &mut self.body + } + + pub fn signs(&self) -> &ObjectSigns { + &self.signs + } + + // update the signatures + pub fn signs_mut(&mut self) -> &mut ObjectSigns { + &mut self.signs + } + + pub fn nonce(&self) -> &Option { + &self.nonce + } + + // update the nonce + pub fn nonce_mut(&mut self) -> &mut Option { + &mut self.nonce + } + + pub fn try_into_object( + mut self, + desc: Option<&O::DescType>, + ) -> BuckyResult> { + let body = self.body.take(); + let mut signs = ObjectSigns::default(); + std::mem::swap(&mut signs, &mut self.signs); + let nonce = self.nonce.take(); + + let desc = match self.desc { + ShelledDesc::Desc(desc_inner) => { + let id = desc_inner.object_id(); + if let Some(desc) = desc { + let obj_id_param = desc.object_id(); + if obj_id_param != id { + let msg = format!( + "parameter desc({}) is not match with object-id({}) from desc.", + obj_id_param, id + ); + log::error!("{}", msg); + return Err(BuckyError::new(BuckyErrorCode::NotMatch, msg)); + } + } + desc_inner + } + ShelledDesc::ObjectId(id) => match desc { + Some(desc) => { + let obj_id_param = desc.object_id(); + if obj_id_param == id { + desc.clone() + } else { + let msg = format!( + "parameter desc({}) is not match with object-id({}).", + obj_id_param, id + ); + log::error!("{}", msg); + return Err(BuckyError::new(BuckyErrorCode::NotMatch, msg)); + } + } + None => { + let msg = format!( + "no desc stored in the shell, you should input it from parameters. object-id is {}", + id + ); + log::error!("{}", msg); + return Err(BuckyError::new(BuckyErrorCode::InvalidParam, msg)); + } + }, + }; + + let builder = NamedObjectBaseBuilder::::new(desc); + let builder = if let Some(body) = body { + builder.body(body) + } else { + builder + }; + let builder = builder.signs(signs); + let obj = if let Some(nonce) = nonce { + builder.nonce(nonce).build() + } else { + builder.build() + }; + + Ok(obj) + } + + fn from_storage(storage: &ObjectShellStorage) -> BuckyResult { + if !storage.check_hash() { + return Err(BuckyError::new( + BuckyErrorCode::NotMatch, + "Hash does not match with raw object", + )); + } + + let flags = storage.flags(); + + let storage_body = storage.body().as_ref().unwrap().content(); + + let nonce = match storage_body.nonce.as_ref() { + Some(nonce_buf) => { + if nonce_buf.len() != 16 { + return Err(BuckyError::new( + BuckyErrorCode::OutOfLimit, + "nonce should be a u128.", + )); + } + let mut nonce = [0; 16]; + nonce.clone_from_slice(nonce_buf.as_slice()); + Some(u128::from_be_bytes(nonce)) + } + None => None, + }; + + let desc = if flags.is_object_id_only { + let (id, remain) = ObjectId::raw_decode(storage_body.desc.as_slice())?; + assert_eq!(remain.len(), 0); + ShelledDesc::ObjectId(id) + } else { + let (desc, remain) = O::DescType::raw_decode(storage_body.desc.as_slice())?; + assert_eq!(remain.len(), 0); + ShelledDesc::Desc(desc) + }; + + let body = match storage_body.body.as_ref() { + Some(body) => { + let (body, remain) = + ObjectMutBody::::raw_decode(body.as_slice())?; + assert_eq!(remain.len(), 0); + Some(body) + } + None => None, + }; + + let mut signs = ObjectSigns::default(); + if let Some(desc_signatures) = storage_body.desc_signatures.as_ref() { + let (desc_signatures, remain) = + Vec::::raw_decode(desc_signatures.as_slice())?; + assert_eq!(remain.len(), 0); + for sign in desc_signatures { + signs.push_desc_sign(sign); + } + }; + + if let Some(body_signatures) = storage_body.body_signatures.as_ref() { + let (body_signatures, remain) = + Vec::::raw_decode(body_signatures.as_slice())?; + assert_eq!(remain.len(), 0); + for sign in body_signatures { + signs.push_body_sign(sign); + } + }; + + Ok(Self { + desc, + body, + signs, + nonce, + flags, + }) + } + + fn to_storage(&self) -> ObjectShellStorage { + let desc = match &self.desc { + ShelledDesc::ObjectId(obj_id) => { + assert!(self.flags.is_object_id_only); + obj_id.to_vec().expect("encode desc as object-id failed.") + } + ShelledDesc::Desc(desc) => { + if self.flags.is_object_id_only { + desc.object_id() + .to_vec() + .expect("encode desc as object-id from desc failed.") + } else { + desc.to_vec().expect("encode desc as desc failed.") + } + } + }; + + let body = match self.body.as_ref() { + Some(body) => { + let mut ctx = NamedObjectBodyContext::new(); + let size = body + .raw_measure_with_context(&mut ctx, &None) + .expect("measure body failed."); + let mut body_buf = vec![]; + body_buf.resize(size, 0u8); + let remain = body + .raw_encode_with_context(body_buf.as_mut(), &mut ctx, &None) + .expect("encode body failed."); + assert_eq!(remain.len(), 0); + Some(body_buf) + } + None => None, + }; + + let desc_signatures = match self.signs.desc_signs().as_ref() { + Some(desc_signatures) => Some( + desc_signatures + .to_vec() + .expect("encode desc-signatures failed."), + ), + None => None, + }; + + let body_signatures = match self.signs.body_signs().as_ref() { + Some(body_signatures) => Some( + body_signatures + .to_vec() + .expect("encode body-signatures failed."), + ), + None => None, + }; + + let nonce = self.nonce.clone(); + + let storage_body = ObjectShellBodyContent { + desc, + body, + desc_signatures, + body_signatures, + nonce: nonce.map(|n| Vec::from(n.to_be_bytes())), + }; + + let hash = storage_body.hash(&self.flags); + + let storage_desc = ObjectShellDescContent { + is_object_id_only: self.flags.is_object_id_only, + is_desc_sign_fix: self.flags.is_desc_sign_fix, + is_body_sign_fix: self.flags.is_body_sign_fix, + is_nonce_fix: self.flags.is_nonce_fix, + fix_content_hash: hash, + }; + + let shell = ObjectShellBuilder::new(storage_desc, storage_body) + .no_create_time() + .build(); + shell + } +} + +impl RawEncode for ObjectShell +where + O: ObjectType, + O::ContentType: BodyContent + RawEncode + for<'de> RawDecode<'de> + Clone, + O::DescType: RawEncode + for<'de> RawDecode<'de> + Clone, +{ + fn raw_measure(&self, purpose: &Option) -> BuckyResult { + self.to_storage().raw_measure(purpose) + } + + fn raw_encode<'a>( + &self, + buf: &'a mut [u8], + purpose: &Option, + ) -> BuckyResult<&'a mut [u8]> { + self.to_storage().raw_encode(buf, purpose) + } +} + +impl<'de, O> RawDecode<'de> for ObjectShell +where + O: ObjectType, + O::ContentType: BodyContent + RawEncode + for<'de1> RawDecode<'de1> + Clone, + O::DescType: RawEncode + for<'de1> RawDecode<'de1> + Clone, +{ + fn raw_decode(buf: &'de [u8]) -> BuckyResult<(Self, &'de [u8])> { + let (storage, remain) = ObjectShellStorage::raw_decode(buf)?; + Self::from_storage(&storage).map(|o| (o, remain)) + } +} + +#[cfg(test)] +mod object_shell_test { + use crate::{ObjectShell, Text, TextObj, OBJECT_SHELL_ALL_FREEDOM_WITH_FULL_DESC}; + + #[test] + fn test() { + let txt_v1 = Text::create("txt-storage", "header", "v1"); + // shell with full-desc, desc-signatures freedom, body-signatures freedom, nonce freedom. + let txt_shell_v1 = + ObjectShell::from_object::(&txt_v1, OBJECT_SHELL_ALL_FREEDOM_WITH_FULL_DESC); + let txt_v1_from_shell = txt_shell_v1 + .try_into_object(None) + .expect("recover text from shell failed."); + let shell_id_v1 = txt_shell_v1.shell_id(); + assert_eq!(txt_v1_from_shell.id(), txt_v1.id()); + assert_eq!(txt_v1_from_shell.header(), txt_v1.header()); + assert_eq!(txt_v1_from_shell.value(), txt_v1.value()); + + // shell-id changed when the body updated. + let mut txt_shell_v2 = txt_shell_v1.clone(); + *txt_shell_v2.body_mut().unwrap().content().value_mut() = "v2".to_string(); + let txt_v2_from_shell = txt_shell_v2 + .try_into_object(None) + .expect("recover text from shell failed."); + let shell_id_v2 = txt_shell_v2.shell_id(); + assert_eq!(txt_v2_from_shell.id(), txt_v1.id()); + assert_eq!(txt_v2_from_shell.header(), txt_v1.header()); + assert_eq!(txt_v2_from_shell.value(), "v2"); + assert_ne!(shell_id_v1, shell_id_v2); + + // shell-id not changed when the nonce updated. + let mut txt_shell_v2_nonce = txt_shell_v2.clone(); + *txt_shell_v2_nonce.nonce_mut() = Some(1); + let txt_v2_nonce_from_shell = txt_shell_v2_nonce + .try_into_object(None) + .expect("recover text from shell failed."); + let shell_id_v2_nonce = txt_shell_v2_nonce.shell_id(); + assert_eq!(txt_v2_nonce_from_shell.id(), txt_v1.id()); + assert_eq!(txt_v2_nonce_from_shell.header(), txt_v1.header()); + assert_eq!(txt_v2_nonce_from_shell.value(), txt_v2_from_shell.value()); + assert_ne!(shell_id_v2_nonce, shell_id_v2); + } +} diff --git a/src/component/cyfs-core/src/storage/storage.rs b/src/component/cyfs-core/src/storage/storage.rs index aab24b403..3f25ad0ac 100644 --- a/src/component/cyfs-core/src/storage/storage.rs +++ b/src/component/cyfs-core/src/storage/storage.rs @@ -57,6 +57,8 @@ pub trait StorageObj { fn into_value(self) -> Vec; fn storage_id(&self) -> StorageId; + + fn check_hash(&self) -> Option; } impl StorageObj for Storage { @@ -131,6 +133,12 @@ impl StorageObj for Storage { fn storage_id(&self) -> StorageId { self.desc().calculate_id().try_into().unwrap() } + + fn check_hash(&self) -> Option { + self.desc().content().hash.as_ref().map(|hash| { + hash == &hash_data(self.body().as_ref().unwrap().content().value.as_slice()) + }) + } } #[cfg(test)] diff --git a/src/component/cyfs-group-lib/Cargo.toml b/src/component/cyfs-group-lib/Cargo.toml new file mode 100644 index 000000000..68931728c --- /dev/null +++ b/src/component/cyfs-group-lib/Cargo.toml @@ -0,0 +1,35 @@ + +[package] +name = 'cyfs-group-lib' +version = '0.1.1' +authors = ['zhangzhen '] +edition = '2021' +license = 'BSD-2-Clause' +description = 'Rust cyfs-group package' + +[build-dependencies] +prost-build = { version = '0.9.0' } +protoc-rust = '2' +chrono = '0.4' +protoc-bin-vendored = '3' + +[dependencies] +async-trait = "0.1.53" +async-std = '1.11' +log = '0.4' +serde_json = '1.0' +futures = '0.3.25' +serde = { version = '1.0', features = ['derive'] } +prost = { version = '0.11.5' } +protobuf = { version = '2', features = ['with-bytes'] } +lazy_static = '1.4' +sha2 = { version = '0.8' } +async-recursion = '1.0' +rand = '0.8.5' +itertools = "0.10.3" +http-types = '2.12' +cyfs-base = { path = '../../component/cyfs-base', version = '0.6' } +cyfs-core = { path = '../../component/cyfs-core', version = '0.6' } +cyfs-debug = { path = "../../component/cyfs-debug" } +cyfs-lib = { path = '../../component/cyfs-lib' } +cyfs-util = { path = '../../component/cyfs-util' } \ No newline at end of file diff --git a/src/component/cyfs-group-lib/build.rs b/src/component/cyfs-group-lib/build.rs new file mode 100644 index 000000000..206d6bc6f --- /dev/null +++ b/src/component/cyfs-group-lib/build.rs @@ -0,0 +1,49 @@ +extern crate chrono; +extern crate protoc_rust; + +use std::io::Write; + +static MOD_PROTOS_RS: &str = r#" +pub(crate) mod group_bft_protocol; +mod group_bft_protocol_with_macro; + +pub use group_bft_protocol_with_macro::*; +"#; + +#[allow(dead_code)] +fn gen_protos() { + let out_dir = std::env::var("OUT_DIR").unwrap(); + let mut gen = protoc_rust::Codegen::new(); + gen.input("protos/group_bft_protocol.proto") + .protoc_path(protoc_bin_vendored::protoc_bin_path().unwrap()) + .out_dir(&out_dir) + .customize(protoc_rust::Customize { + expose_fields: Some(true), + ..Default::default() + }); + + gen.run().expect("protoc error!"); + + let mut config = prost_build::Config::new(); + config.default_package_filename("group_bft_protocol_with_macro"); + config + .compile_protos(&["protos/group_bft_protocol.proto"], &["protos"]) + .unwrap(); + + std::fs::File::create(out_dir + "/mod.rs") + .expect("write protos mod error") + .write_all(MOD_PROTOS_RS.as_ref()) + .expect("write protos mod error"); +} + +fn main() { + println!("cargo:rerun-if-changed=protos"); + println!( + "cargo:warning={}", + format!( + "cyfs-core run build script, OUT_DIR={}", + std::env::var("OUT_DIR").unwrap() + ) + ); + gen_protos(); +} diff --git a/src/component/cyfs-group-lib/protos/group_bft_protocol.proto b/src/component/cyfs-group-lib/protos/group_bft_protocol.proto new file mode 100644 index 000000000..d04576c0b --- /dev/null +++ b/src/component/cyfs-group-lib/protos/group_bft_protocol.proto @@ -0,0 +1,60 @@ + +syntax = "proto3"; + +message HotstuffBlockQCVote { + bytes block_id = 1; + optional bytes prev_block_id = 2; + uint64 round = 3; + bytes voter = 4; + bytes signature = 5; +} + +message HotstuffTimeoutVote { + optional bytes high_qc = 1; // encode(core:HotstuffBlockQc) + uint64 round = 2; + bytes voter = 3; + bytes signature = 4; + bytes group_shell_id = 5; +} + +message GroupRPathStatus { + bytes block_desc = 1; // GroupConsensusBlockDescContent + bytes certificate = 2; // HotstuffBlockQC for block + repeated bytes status_list = 4; // Array> +} + +// GroupCommand + +message GroupCommandDescContent { + +} + +message GroupCommandNewRPath { + bytes group_id = 1; + string rpath = 2; + optional bytes with_block = 3; // Block.to_vec() +} + +message GroupCommandExecute { + bytes proposal = 1; // Proposal.to_vec() + optional bytes prev_state_id = 2; // ObjectId +} + +message GroupCommandExecuteResult { + optional bytes result_state_id = 1; // ObjectId + optional bytes receipt = 2; // NONObjectInfo.to_vec() + optional bytes context = 3; // Vec +} + +message GroupCommandVerify { + bytes proposal = 1; // Proposal.to_vec() + optional bytes prev_state_id = 2; // ObjectId + optional bytes result_state_id = 3; // ObjectId + optional bytes receipt = 4; // NONObjectInfo.to_vec() + optional bytes context = 5; // Vec +} + +message GroupCommandCommited { + optional bytes prev_state_id = 1; // ObjectId + bytes block = 2; // Block.to_vec() +} diff --git a/src/component/cyfs-group-lib/src/delegate.rs b/src/component/cyfs-group-lib/src/delegate.rs new file mode 100644 index 000000000..14adb523c --- /dev/null +++ b/src/component/cyfs-group-lib/src/delegate.rs @@ -0,0 +1,57 @@ +use cyfs_base::{BuckyResult, ObjectId}; +use cyfs_core::{GroupConsensusBlock, GroupProposal}; +use cyfs_lib::{IsolatePathOpEnvStub, NONObjectInfo, RootStateOpEnvAccess, SingleOpEnvStub}; + +#[async_trait::async_trait] +pub trait DelegateFactory: Send + Sync { + async fn create_rpath_delegate( + &self, + group_id: &ObjectId, + rpath: &str, + with_block: Option<&GroupConsensusBlock>, + is_new: bool, + ) -> BuckyResult>; +} + +pub struct ExecuteResult { + pub result_state_id: Option, // pack block + pub receipt: Option, // to client + pub context: Option>, // timestamp etc. +} + +#[async_trait::async_trait] +pub trait RPathDelegate: Sync + Send { + async fn on_execute( + &self, + proposal: &GroupProposal, + prev_state_id: &Option, + object_map_processor: &dyn GroupObjectMapProcessor, + ) -> BuckyResult; + + async fn on_verify( + &self, + proposal: &GroupProposal, + prev_state_id: &Option, + execute_result: &ExecuteResult, + object_map_processor: &dyn GroupObjectMapProcessor, + ) -> BuckyResult<()>; + + async fn on_commited( + &self, + prev_state_id: &Option, + block: &GroupConsensusBlock, + object_map_processor: &dyn GroupObjectMapProcessor, + ); +} + +#[async_trait::async_trait] +pub trait GroupObjectMapProcessor: Send + Sync { + async fn create_single_op_env( + &self, + access: Option, + ) -> BuckyResult; + async fn create_sub_tree_op_env( + &self, + access: Option, + ) -> BuckyResult; +} diff --git a/src/component/cyfs-group-lib/src/group_manager.rs b/src/component/cyfs-group-lib/src/group_manager.rs new file mode 100644 index 000000000..f48af84ea --- /dev/null +++ b/src/component/cyfs-group-lib/src/group_manager.rs @@ -0,0 +1,532 @@ +use std::{collections::HashMap, sync::Arc}; + +use async_std::sync::RwLock; +use cyfs_base::{ + BuckyError, BuckyErrorCode, BuckyResult, NamedObject, ObjectDesc, ObjectId, OwnerObjectDesc, + RawConvertTo, RawDecode, +}; +use cyfs_core::{ + CoreObjectType, DecAppId, GroupConsensusBlock, GroupConsensusBlockObject, GroupProposalObject, + GroupRPath, +}; +use cyfs_lib::{ + CyfsStackRequestorType, DeviceZoneCategory, HttpRequestorRef, NONObjectInfo, + NONPostObjectInputResponse, RequestGlobalStatePath, RouterHandlerAction, RouterHandlerChain, + RouterHandlerManagerProcessor, RouterHandlerPostObjectRequest, RouterHandlerPostObjectResult, + SharedCyfsStack, +}; +use cyfs_util::EventListenerAsyncRoutine; + +use crate::{ + DelegateFactory, ExecuteResult, GroupCommand, GroupCommandCommited, GroupCommandExecute, + GroupCommandExecuteResult, GroupCommandNewRPath, GroupCommandObject, GroupCommandType, + GroupCommandVerify, RPathClient, RPathDelegate, RPathService, +}; + +type ServiceByRPath = HashMap; +type ServiceByDec = HashMap; +type ServiceByGroup = HashMap; + +type ClientByRPath = HashMap; +type ClientByDec = HashMap; +type ClientByGroup = HashMap; + +struct GroupManagerRaw { + stack: SharedCyfsStack, + requestor: HttpRequestorRef, + delegate_factory: Option>, + clients: RwLock, + services: RwLock, + local_zone: Option, +} + +#[derive(Clone)] +pub struct GroupManager(Arc); + +impl GroupManager { + pub async fn open( + stack: SharedCyfsStack, + delegate_factory: Box, + requestor_type: &CyfsStackRequestorType, + ) -> BuckyResult { + if stack.dec_id().is_none() { + let msg = "the stack should be opened with dec-id"; + log::warn!("{}", msg); + return Err(BuckyError::new(BuckyErrorCode::InvalidParam, msg)); + } + + let dec_id = stack.dec_id().unwrap().clone(); + let requestor = stack.select_requestor(requestor_type); + let local_zone = stack.local_device().desc().owner().clone(); + let router_handler_manager = stack.router_handlers().clone(); + + let mgr = Self(Arc::new(GroupManagerRaw { + stack, + requestor, + delegate_factory: Some(delegate_factory), + clients: RwLock::new(HashMap::new()), + services: RwLock::new(HashMap::new()), + local_zone, + })); + + // TODO: other filters? only local zone + let filter = format!( + "obj_type == {} && source.dec_id == {} && source.zone_category == {}", + CoreObjectType::GroupCommand as u16, + dec_id, + DeviceZoneCategory::CurrentZone.to_string(), + ); + + // let filter = "*".to_string(); + let req_path = RequestGlobalStatePath::new(Some(dec_id.clone()), Some("group/inner-cmd")); + + router_handler_manager + .post_object() + .add_handler( + RouterHandlerChain::Handler, + format!("group-cmd-{}", dec_id).as_str(), + 0, + Some(filter), + Some(req_path.format_string()), + RouterHandlerAction::Pass, + Some(Box::new(mgr.clone())), + ) + .await?; + + Ok(mgr) + } + + pub async fn open_as_client( + stack: SharedCyfsStack, + requestor_type: &CyfsStackRequestorType, + ) -> BuckyResult { + let requestor = stack.select_requestor(requestor_type); + let local_zone = stack.local_device().desc().owner().clone(); + + Ok(Self(Arc::new(GroupManagerRaw { + stack, + requestor, + delegate_factory: None, + clients: RwLock::new(HashMap::new()), + services: RwLock::new(HashMap::new()), + local_zone, + }))) + } + + pub async fn stop(&self) { + unimplemented!() + } + + pub fn stack(&self) -> &SharedCyfsStack { + &self.0.stack + } + + pub async fn start_rpath_service( + &self, + group_id: ObjectId, + rpath: String, + delegate: Box, + ) -> BuckyResult { + let dec_id = self.0.stack.dec_id().unwrap().clone(); + + { + let services = self.0.services.read().await; + let found = services + .get(&group_id) + .and_then(|by_dec| by_dec.get(&dec_id)) + .and_then(|by_rpath| by_rpath.get(rpath.as_str())); + + if let Some(found) = found { + return Ok(found.clone()); + } + } + + { + let mut services = self.0.services.write().await; + let service = services + .entry(group_id.clone()) + .or_insert_with(HashMap::new) + .entry(dec_id.into()) + .or_insert_with(HashMap::new) + .entry(rpath.to_string()) + .or_insert_with(|| { + RPathService::new( + GroupRPath::new(group_id.clone(), dec_id.clone(), rpath.to_string()), + self.0.requestor.clone(), + delegate, + self.0.stack.clone(), + ) + }); + Ok(service.clone()) + } + } + + pub async fn find_rpath_service( + &self, + group_id: &ObjectId, + rpath: &str, + ) -> BuckyResult { + let dec_id = self.0.stack.dec_id().unwrap(); + let services = self.0.services.read().await; + let found = services + .get(&group_id) + .and_then(|by_dec| by_dec.get(dec_id)) + .and_then(|by_rpath| by_rpath.get(rpath)); + + found.map_or( + Err(BuckyError::new( + BuckyErrorCode::NotFound, + "please start the service first", + )), + |service| Ok(service.clone()), + ) + } + + pub async fn rpath_client( + &self, + group_id: ObjectId, + dec_id: DecAppId, + rpath: &str, + ) -> RPathClient { + { + let clients = self.0.clients.read().await; + let found = clients + .get(&group_id) + .and_then(|by_dec| by_dec.get(dec_id.object_id())) + .and_then(|by_rpath| by_rpath.get(rpath)); + + if let Some(found) = found { + return found.clone(); + } + } + + { + let client = RPathClient::new( + GroupRPath::new(group_id, dec_id.object_id().clone(), rpath.to_string()), + self.0.stack.dec_id().cloned(), + self.0.stack.non_service().clone(), + ); + + let mut clients = self.0.clients.write().await; + let client = clients + .entry(group_id) + .or_insert_with(HashMap::new) + .entry(dec_id.into()) + .or_insert_with(HashMap::new) + .entry(rpath.to_string()) + .or_insert(client); + client.clone() + } + } + + async fn on_command( + &self, + cmd: GroupCommand, + ) -> BuckyResult> { + match cmd.into_cmd() { + crate::GroupCommandBodyContent::NewRPath(cmd) => { + self.on_new_rpath(cmd).await.map(|_| None) + } + crate::GroupCommandBodyContent::Execute(cmd) => { + self.on_execute(cmd).await.map(|r| Some(r)) + } + crate::GroupCommandBodyContent::ExecuteResult(_) => { + let msg = format!( + "should not get the cmd({:?}) in sdk", + GroupCommandType::ExecuteResult + ); + log::warn!("{}", msg); + Err(BuckyError::new(BuckyErrorCode::InvalidParam, msg)) + } + crate::GroupCommandBodyContent::Verify(cmd) => self.on_verify(cmd).await.map(|_| None), + crate::GroupCommandBodyContent::Commited(cmd) => { + self.on_commited(cmd).await.map(|_| None) + } + } + } + + async fn on_new_rpath(&self, cmd: GroupCommandNewRPath) -> BuckyResult<()> { + self.find_or_restart_service( + &cmd.group_id, + self.0.stack.dec_id().unwrap(), + cmd.rpath.as_str(), + &cmd.with_block, + true, + ) + .await + .map(|_| ()) + .map_err(|err| { + log::warn!( + "group on_new_rpath {}-{:?}-{} failed, err: {:?}", + cmd.group_id, + self.0.stack.dec_id(), + cmd.rpath, + err + ); + err + }) + } + + async fn on_execute(&self, cmd: GroupCommandExecute) -> BuckyResult { + let rpath = cmd.proposal.rpath(); + let service = self + .find_or_restart_service( + rpath.group_id(), + rpath.dec_id(), + rpath.rpath(), + &None, + false, + ) + .await + .map_err(|err| { + log::warn!( + "group on_execute find service {:?} failed, err: {:?}", + cmd.proposal.rpath(), + err + ); + err + })?; + + let mut result = service + .on_execute(&cmd.proposal, &cmd.prev_state_id) + .await + .map_err(|err| { + log::warn!( + "group on_execute {:?} failed, err: {:?}", + cmd.proposal.rpath(), + err + ); + err + })?; + + Ok(GroupCommandExecuteResult { + result_state_id: result.result_state_id.take(), + receipt: result.receipt.take(), + context: result.context.take(), + }) + } + + async fn on_verify(&self, mut cmd: GroupCommandVerify) -> BuckyResult<()> { + let rpath = cmd.proposal.rpath(); + let service = self + .find_or_restart_service( + rpath.group_id(), + rpath.dec_id(), + rpath.rpath(), + &None, + false, + ) + .await + .map_err(|err| { + log::warn!( + "group on_verify find service {:?} failed, err: {:?}", + cmd.proposal.rpath(), + err + ); + err + })?; + + let result = ExecuteResult { + result_state_id: cmd.result_state_id.take(), + receipt: cmd.receipt.take(), + context: cmd.context.take(), + }; + + service + .on_verify(&cmd.proposal, &cmd.prev_state_id, &result) + .await + .map_err(|err| { + log::warn!( + "group on_verify {:?} failed, err: {:?}", + cmd.proposal.rpath(), + err + ); + err + }) + } + + async fn on_commited(&self, cmd: GroupCommandCommited) -> BuckyResult<()> { + let rpath = cmd.block.rpath(); + let service = self + .find_or_restart_service( + rpath.group_id(), + rpath.dec_id(), + rpath.rpath(), + &None, + false, + ) + .await + .map_err(|err| { + log::warn!( + "group on_commited find service {:?} failed, err: {:?}", + cmd.block.rpath(), + err + ); + err + })?; + + service.on_commited(&cmd.prev_state_id, &cmd.block).await; + Ok(()) + } + + async fn find_or_restart_service( + &self, + group_id: &ObjectId, + dec_id: &ObjectId, + rpath: &str, + with_block: &Option, + is_new: bool, + ) -> BuckyResult { + if dec_id != self.0.stack.dec_id().unwrap() { + let msg = format!( + "try find proposal in different dec {:?}, expected: {:?}", + dec_id, + self.0.stack.dec_id().unwrap() + ); + log::warn!("{}", msg); + return Err(BuckyError::new(BuckyErrorCode::InvalidParam, msg)); + } + + { + let services = self.0.services.read().await; + let found = services + .get(group_id) + .and_then(|by_dec| by_dec.get(dec_id)) + .and_then(|by_rpath| by_rpath.get(rpath)); + + if let Some(found) = found { + return Ok(found.clone()); + } + } + + match self.0.delegate_factory.as_ref() { + Some(factory) => { + let delegate = factory + .create_rpath_delegate(group_id, rpath, with_block.as_ref(), is_new) + .await?; + + let new_service = { + let mut is_new = false; + + let mut services = self.0.services.write().await; + let service = services + .entry(group_id.clone()) + .or_insert_with(HashMap::new) + .entry(dec_id.clone()) + .or_insert_with(HashMap::new) + .entry(rpath.to_string()) + .or_insert_with(|| { + is_new = true; + + RPathService::new( + GroupRPath::new( + group_id.clone(), + dec_id.clone(), + rpath.to_string(), + ), + self.0.requestor.clone(), + delegate, + self.0.stack.clone(), + ) + }); + + if is_new { + service.clone() + } else { + return Ok(service.clone()); + } + }; + + new_service.start().await; + Ok(new_service.clone()) + } + None => Err(BuckyError::new(BuckyErrorCode::Reject, "is not service")), + } + } +} + +#[async_trait::async_trait] +impl EventListenerAsyncRoutine + for GroupManager +{ + async fn call( + &self, + param: &RouterHandlerPostObjectRequest, + ) -> BuckyResult { + let req_common = ¶m.request.common; + let obj = ¶m.request.object; + + log::debug!( + "group-command handle, level = {:?}, zone = {:?}, local-zone = {:?}, dec-id = {:?}, obj_type = {:?}", + req_common.level, + req_common.source.zone, + self.0.local_zone, + self.0.stack.dec_id(), + obj.object.as_ref().map(|o| o.obj_type()) + ); + + if !req_common.source.zone.is_current_zone() + || self.0.local_zone.is_none() + // || req_common.source.zone.zone != self.0.local_zone + || self.0.stack.dec_id().is_none() + { + log::warn!( + "there should no group-command from other zone, level = {:?}, zone = {:?}, local-zone = {:?}, dec-id = {:?}, obj_type = {:?}", + req_common.level, + req_common.source.zone, + self.0.local_zone, + self.0.stack.dec_id(), + obj.object.as_ref().map(|o| o.obj_type()) + ); + + return Ok(RouterHandlerPostObjectResult { + action: RouterHandlerAction::Pass, + request: None, + response: None, + }); + } + + match obj.object.as_ref() { + None => { + return Ok(RouterHandlerPostObjectResult { + action: RouterHandlerAction::Reject, + request: None, + response: None, + }) + } + Some(any_obj) => { + assert_eq!(any_obj.obj_type(), CoreObjectType::GroupCommand as u16); + if any_obj.obj_type() != CoreObjectType::GroupCommand as u16 { + return Ok(RouterHandlerPostObjectResult { + action: RouterHandlerAction::Reject, + request: None, + response: None, + }); + } + + let (cmd, remain) = GroupCommand::raw_decode(obj.object_raw.as_slice())?; + assert_eq!(remain.len(), 0); + + let resp_obj = self.on_command(cmd).await; + + let resp_cmd = resp_obj.map_or_else( + |err| Err(err), + |resp_obj| { + resp_obj.map_or(Ok(None), |resp_cmd| { + let resp_cmd = GroupCommand::from(resp_cmd); + resp_cmd.to_vec().map(|buf| { + Some(NONObjectInfo::new(resp_cmd.desc().object_id(), buf, None)) + }) + }) + }, + ); + + Ok(RouterHandlerPostObjectResult { + action: RouterHandlerAction::Response, + request: None, + response: Some(resp_cmd.map(|cmd| NONPostObjectInputResponse { object: cmd })), + }) + } + } + } +} diff --git a/src/component/cyfs-group-lib/src/input_request.rs b/src/component/cyfs-group-lib/src/input_request.rs new file mode 100644 index 000000000..def36929a --- /dev/null +++ b/src/component/cyfs-group-lib/src/input_request.rs @@ -0,0 +1,36 @@ +use std::fmt; + +use cyfs_base::ObjectId; +use cyfs_core::GroupProposal; +use cyfs_lib::{NONObjectInfo, RequestSourceInfo}; + +#[derive(Clone, Debug)] +pub struct GroupInputRequestCommon { + // the request source info in bundle + pub source: RequestSourceInfo, +} + +impl fmt::Display for GroupInputRequestCommon { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, ", {}", self.source)?; + + Ok(()) + } +} + +pub struct GroupStartServiceInputRequest { + pub common: GroupInputRequestCommon, + pub group_id: ObjectId, + pub rpath: String, +} + +pub struct GroupStartServiceInputResponse {} + +pub struct GroupPushProposalInputRequest { + pub common: GroupInputRequestCommon, + pub proposal: GroupProposal, +} + +pub struct GroupPushProposalInputResponse { + pub object: Option, +} diff --git a/src/component/cyfs-group-lib/src/lib.rs b/src/component/cyfs-group-lib/src/lib.rs new file mode 100644 index 000000000..1b130ece2 --- /dev/null +++ b/src/component/cyfs-group-lib/src/lib.rs @@ -0,0 +1,22 @@ +mod delegate; +mod group_manager; +mod input_request; +mod objects; +mod output_request; +mod processor; +mod request; +mod request_codec; +mod requestor; +mod rpath_client; +mod rpath_service; + +pub use delegate::*; +pub use group_manager::*; +pub use input_request::*; +pub use objects::*; +pub use output_request::*; +pub use processor::*; +pub use request_codec::*; +pub use requestor::*; +pub use rpath_client::*; +pub use rpath_service::*; diff --git a/src/component/cyfs-group-lib/src/objects/certificate.rs b/src/component/cyfs-group-lib/src/objects/certificate.rs new file mode 100644 index 000000000..8614d1c8a --- /dev/null +++ b/src/component/cyfs-group-lib/src/objects/certificate.rs @@ -0,0 +1,205 @@ +use cyfs_base::{ + BuckyError, BuckyErrorCode, BuckyResult, HashValue, ObjectId, ObjectLink, ProtobufDecode, + ProtobufEncode, ProtobufTransform, ProtobufTransformType, RawConvertTo, RawDecode, + RawEncodePurpose, RsaCPUObjectSigner, Signature, SignatureSource, Signer, +}; +use cyfs_core::{GroupConsensusBlock, GroupConsensusBlockObject, HotstuffBlockQC}; +use sha2::Digest; + +#[derive(Clone, ProtobufEncode, ProtobufDecode, ProtobufTransformType)] +#[cyfs_protobuf_type(super::codec::protos::HotstuffBlockQcVote)] +pub struct HotstuffBlockQCVote { + pub block_id: ObjectId, + pub prev_block_id: Option, + pub round: u64, + pub voter: ObjectId, + pub signature: Signature, +} + +impl HotstuffBlockQCVote { + pub async fn new( + block: &GroupConsensusBlock, + local_device_id: ObjectId, + signer: &RsaCPUObjectSigner, + ) -> BuckyResult { + let block_id = block.block_id().object_id(); + let round = block.round(); + + log::debug!( + "[block vote] local: {:?}, vote hash {}, round: {}", + local_device_id, + block.block_id(), + block.round() + ); + + let hash = Self::hash_content(block_id, block.prev_block_id(), round); + + log::debug!( + "[block vote] local: {:?}, vote sign {}, round: {}", + local_device_id, + block.block_id(), + block.round() + ); + + let signature = signer + .sign( + hash.as_slice(), + &SignatureSource::Object(ObjectLink { + obj_id: local_device_id, + obj_owner: None, + }), + ) + .await?; + + Ok(Self { + block_id: block_id.clone(), + round, + voter: local_device_id, + signature, + prev_block_id: block.prev_block_id().map(|id| id.clone()), + }) + } + + pub fn hash(&self) -> HashValue { + Self::hash_content(&self.block_id, self.prev_block_id.as_ref(), self.round) + } + + fn hash_content( + block_id: &ObjectId, + prev_block_id: Option<&ObjectId>, + round: u64, + ) -> HashValue { + let mut sha256 = sha2::Sha256::new(); + sha256.input(block_id.as_slice()); + sha256.input(round.to_le_bytes()); + if let Some(prev_block_id) = prev_block_id { + sha256.input(prev_block_id.as_slice()); + } + sha256.result().into() + } +} + +impl ProtobufTransform for HotstuffBlockQCVote { + fn transform(value: super::codec::protos::HotstuffBlockQcVote) -> BuckyResult { + Ok(Self { + voter: ObjectId::raw_decode(value.voter.as_slice())?.0, + signature: Signature::raw_decode(value.signature.as_slice())?.0, + block_id: ObjectId::raw_decode(value.block_id.as_slice())?.0, + round: value.round, + prev_block_id: match value.prev_block_id.as_ref() { + Some(id) => Some(ObjectId::raw_decode(id.as_slice())?.0), + None => None, + }, + }) + } +} + +impl ProtobufTransform<&HotstuffBlockQCVote> for super::codec::protos::HotstuffBlockQcVote { + fn transform(value: &HotstuffBlockQCVote) -> BuckyResult { + let ret = super::codec::protos::HotstuffBlockQcVote { + block_id: value.block_id.to_vec()?, + round: value.round, + voter: value.voter.to_vec()?, + signature: value.signature.to_vec()?, + prev_block_id: match value.prev_block_id.as_ref() { + Some(id) => Some(id.to_vec()?), + None => None, + }, + }; + + Ok(ret) + } +} + +#[derive(Clone, ProtobufEncode, ProtobufDecode, ProtobufTransformType)] +#[cyfs_protobuf_type(super::codec::protos::HotstuffTimeoutVote)] +pub struct HotstuffTimeoutVote { + pub high_qc: Option, + pub round: u64, + pub voter: ObjectId, + pub signature: Signature, + pub group_shell_id: ObjectId, +} + +impl HotstuffTimeoutVote { + pub async fn new( + group_shell_id: ObjectId, + high_qc: Option, + round: u64, + local_device_id: ObjectId, + signer: &RsaCPUObjectSigner, + ) -> BuckyResult { + let signature = signer + .sign( + Self::hash_content( + high_qc.as_ref().map_or(0, |qc| qc.round), + round, + &group_shell_id, + ) + .as_slice(), + &SignatureSource::Object(ObjectLink { + obj_id: local_device_id, + obj_owner: None, + }), + ) + .await?; + + Ok(Self { + high_qc, + round, + voter: local_device_id, + signature, + group_shell_id, + }) + } + + pub fn hash(&self) -> HashValue { + Self::hash_content( + self.high_qc.as_ref().map_or(0, |qc| qc.round), + self.round, + &self.group_shell_id, + ) + } + + pub fn hash_content(high_qc_round: u64, round: u64, group_shell_id: &ObjectId) -> HashValue { + let mut sha256 = sha2::Sha256::new(); + sha256.input(high_qc_round.to_le_bytes()); + sha256.input(round.to_le_bytes()); + sha256.input(group_shell_id.as_slice()); + sha256.result().into() + } +} + +impl ProtobufTransform for HotstuffTimeoutVote { + fn transform(value: super::codec::protos::HotstuffTimeoutVote) -> BuckyResult { + let high_qc = if value.high_qc().len() == 0 { + None + } else { + Some(HotstuffBlockQC::raw_decode(value.high_qc())?.0) + }; + Ok(Self { + voter: ObjectId::raw_decode(value.voter.as_slice())?.0, + signature: Signature::raw_decode(value.signature.as_slice())?.0, + round: value.round, + high_qc, + group_shell_id: ObjectId::raw_decode(value.group_shell_id.as_slice())?.0, + }) + } +} + +impl ProtobufTransform<&HotstuffTimeoutVote> for super::codec::protos::HotstuffTimeoutVote { + fn transform(value: &HotstuffTimeoutVote) -> BuckyResult { + let ret = super::codec::protos::HotstuffTimeoutVote { + high_qc: match value.high_qc.as_ref() { + Some(qc) => Some(qc.to_vec()?), + None => None, + }, + round: value.round, + voter: value.voter.to_vec()?, + signature: value.signature.to_vec()?, + group_shell_id: value.group_shell_id.to_vec()?, + }; + + Ok(ret) + } +} diff --git a/src/component/cyfs-group-lib/src/objects/codec/mod.rs b/src/component/cyfs-group-lib/src/objects/codec/mod.rs new file mode 100644 index 000000000..66df9053f --- /dev/null +++ b/src/component/cyfs-group-lib/src/objects/codec/mod.rs @@ -0,0 +1,3 @@ +pub mod protos { + include!(concat!(env!("OUT_DIR"), "/mod.rs")); +} diff --git a/src/component/cyfs-group-lib/src/objects/group_command.rs b/src/component/cyfs-group-lib/src/objects/group_command.rs new file mode 100644 index 000000000..0e441d98f --- /dev/null +++ b/src/component/cyfs-group-lib/src/objects/group_command.rs @@ -0,0 +1,404 @@ +use cyfs_base::{ + BodyContent, BuckyError, BuckyErrorCode, BuckyResult, DescContent, NamedObjType, NamedObject, + NamedObjectBase, NamedObjectBuilder, NamedObjectId, ObjectId, ProtobufDecode, ProtobufEncode, + ProtobufTransform, ProtobufTransformType, RawConvertTo, RawDecode, RawEncode, RawEncodePurpose, + SubDescNone, OBJECT_CONTENT_CODEC_FORMAT_PROTOBUF, +}; + +use cyfs_core::{CoreObjectType, GroupConsensusBlock, GroupProposal}; +use cyfs_lib::NONObjectInfo; + +#[derive(Clone, ProtobufEncode, ProtobufDecode, ProtobufTransform)] +#[cyfs_protobuf_type(super::codec::protos::GroupCommandDescContent)] +pub struct GroupCommandDescContent {} + +impl DescContent for GroupCommandDescContent { + fn obj_type() -> u16 { + CoreObjectType::GroupCommand as u16 + } + + fn format(&self) -> u8 { + OBJECT_CONTENT_CODEC_FORMAT_PROTOBUF + } + + fn debug_info() -> String { + String::from("GroupCommandDescContent") + } + + type OwnerType = SubDescNone; + type AreaType = SubDescNone; + type AuthorType = SubDescNone; + type PublicKeyType = SubDescNone; +} + +#[derive(Debug)] +pub enum GroupCommandType { + NewRPath, + Execute, + ExecuteResult, + Verify, + Commited, +} + +#[derive(Clone, RawEncode, RawDecode)] +pub enum GroupCommandBodyContent { + NewRPath(GroupCommandNewRPath), + Execute(GroupCommandExecute), + ExecuteResult(GroupCommandExecuteResult), + Verify(GroupCommandVerify), + Commited(GroupCommandCommited), +} + +pub type GroupCommandObjectType = NamedObjType; +pub type GroupCommandBuilder = NamedObjectBuilder; + +pub type GroupCommandId = NamedObjectId; +pub type GroupCommand = NamedObjectBase; + +impl GroupCommandBodyContent { + pub fn cmd_type(&self) -> GroupCommandType { + match self { + GroupCommandBodyContent::NewRPath(_) => GroupCommandType::NewRPath, + GroupCommandBodyContent::Execute(_) => GroupCommandType::Execute, + GroupCommandBodyContent::ExecuteResult(_) => GroupCommandType::ExecuteResult, + GroupCommandBodyContent::Verify(_) => GroupCommandType::Verify, + GroupCommandBodyContent::Commited(_) => GroupCommandType::Commited, + } + } +} + +pub trait GroupCommandObject { + fn cmd_type(&self) -> GroupCommandType; + fn into_cmd(self) -> GroupCommandBodyContent; +} + +impl GroupCommandObject for GroupCommand { + fn cmd_type(&self) -> GroupCommandType { + self.body().as_ref().unwrap().content().cmd_type() + } + + fn into_cmd(self) -> GroupCommandBodyContent { + self.into_body().unwrap().into_content() + } +} + +impl BodyContent for GroupCommandBodyContent { + fn version(&self) -> u8 { + 0 + } + + fn format(&self) -> u8 { + cyfs_base::OBJECT_CONTENT_CODEC_FORMAT_RAW + } + + fn debug_info() -> String { + String::from("GroupCommandBodyContent") + } +} + +#[derive(Clone, ProtobufEncode, ProtobufDecode, ProtobufTransformType)] +#[cyfs_protobuf_type(super::codec::protos::GroupCommandNewRPath)] +pub struct GroupCommandNewRPath { + pub group_id: ObjectId, + pub rpath: String, + pub with_block: Option, +} + +impl ProtobufTransform for GroupCommandNewRPath { + fn transform(value: super::codec::protos::GroupCommandNewRPath) -> BuckyResult { + Ok(Self { + group_id: ObjectId::raw_decode(value.group_id.as_slice())?.0, + with_block: match value.with_block.as_ref() { + Some(buf) => Some(GroupConsensusBlock::raw_decode(buf.as_slice())?.0), + None => None, + }, + rpath: value.rpath, + }) + } +} + +impl ProtobufTransform<&GroupCommandNewRPath> for super::codec::protos::GroupCommandNewRPath { + fn transform(value: &GroupCommandNewRPath) -> BuckyResult { + Ok(Self { + group_id: value.group_id.to_vec()?, + rpath: value.rpath.clone(), + with_block: match value.with_block.as_ref() { + Some(block) => Some(block.to_vec()?), + None => None, + }, + }) + } +} + +#[derive(Clone, ProtobufEncode, ProtobufDecode, ProtobufTransformType)] +#[cyfs_protobuf_type(super::codec::protos::GroupCommandExecute)] +pub struct GroupCommandExecute { + pub proposal: GroupProposal, + pub prev_state_id: Option, +} + +impl ProtobufTransform for GroupCommandExecute { + fn transform(value: super::codec::protos::GroupCommandExecute) -> BuckyResult { + Ok(Self { + prev_state_id: match value.prev_state_id.as_ref() { + Some(buf) => Some(ObjectId::raw_decode(buf.as_slice())?.0), + None => None, + }, + proposal: GroupProposal::raw_decode(value.proposal.as_slice())?.0, + }) + } +} + +impl ProtobufTransform<&GroupCommandExecute> for super::codec::protos::GroupCommandExecute { + fn transform(value: &GroupCommandExecute) -> BuckyResult { + Ok(Self { + proposal: value.proposal.to_vec()?, + prev_state_id: match value.prev_state_id.as_ref() { + Some(prev_state_id) => Some(prev_state_id.to_vec()?), + None => None, + }, + }) + } +} + +#[derive(Clone, ProtobufEncode, ProtobufDecode, ProtobufTransformType)] +#[cyfs_protobuf_type(super::codec::protos::GroupCommandExecuteResult)] +pub struct GroupCommandExecuteResult { + pub result_state_id: Option, + pub receipt: Option, + pub context: Option>, +} + +impl ProtobufTransform + for GroupCommandExecuteResult +{ + fn transform(value: super::codec::protos::GroupCommandExecuteResult) -> BuckyResult { + Ok(Self { + result_state_id: match value.result_state_id.as_ref() { + Some(result_state_id) => Some(ObjectId::raw_decode(result_state_id.as_slice())?.0), + None => None, + }, + receipt: match value.receipt.as_ref() { + Some(buf) => Some(NONObjectInfo::raw_decode(buf.as_slice())?.0), + None => None, + }, + context: value.context, + }) + } +} + +impl ProtobufTransform<&GroupCommandExecuteResult> + for super::codec::protos::GroupCommandExecuteResult +{ + fn transform(value: &GroupCommandExecuteResult) -> BuckyResult { + Ok(Self { + result_state_id: match value.result_state_id.as_ref() { + Some(result_state_id) => Some(result_state_id.to_vec()?), + None => None, + }, + receipt: match value.receipt.as_ref() { + Some(receipt) => Some(receipt.to_vec()?), + None => None, + }, + context: value.context.clone(), + }) + } +} + +#[derive(Clone, ProtobufEncode, ProtobufDecode, ProtobufTransformType)] +#[cyfs_protobuf_type(super::codec::protos::GroupCommandVerify)] +pub struct GroupCommandVerify { + pub proposal: GroupProposal, + pub prev_state_id: Option, + pub result_state_id: Option, + pub receipt: Option, + pub context: Option>, +} + +impl ProtobufTransform for GroupCommandVerify { + fn transform(value: super::codec::protos::GroupCommandVerify) -> BuckyResult { + Ok(Self { + prev_state_id: match value.prev_state_id { + Some(buf) => Some(ObjectId::raw_decode(buf.as_slice())?.0), + None => None, + }, + proposal: GroupProposal::raw_decode(value.proposal.as_slice())?.0, + result_state_id: match value.result_state_id { + Some(buf) => Some(ObjectId::raw_decode(buf.as_slice())?.0), + None => None, + }, + receipt: match value.receipt { + Some(buf) => Some(NONObjectInfo::raw_decode(buf.as_slice())?.0), + None => None, + }, + context: value.context, + }) + } +} + +impl ProtobufTransform<&GroupCommandVerify> for super::codec::protos::GroupCommandVerify { + fn transform(value: &GroupCommandVerify) -> BuckyResult { + Ok(Self { + proposal: value.proposal.to_vec()?, + prev_state_id: match value.prev_state_id.as_ref() { + Some(prev_state_id) => Some(prev_state_id.to_vec()?), + None => None, + }, + result_state_id: match value.result_state_id.as_ref() { + Some(result_state_id) => Some(result_state_id.to_vec()?), + None => None, + }, + receipt: match value.receipt.as_ref() { + Some(receipt) => Some(receipt.to_vec()?), + None => None, + }, + context: value.context.clone(), + }) + } +} + +#[derive(Clone, ProtobufEncode, ProtobufDecode, ProtobufTransformType)] +#[cyfs_protobuf_type(super::codec::protos::GroupCommandCommited)] +pub struct GroupCommandCommited { + pub prev_state_id: Option, + pub block: GroupConsensusBlock, +} + +impl ProtobufTransform for GroupCommandCommited { + fn transform(value: super::codec::protos::GroupCommandCommited) -> BuckyResult { + Ok(Self { + prev_state_id: match value.prev_state_id.as_ref() { + Some(prev_state_id) => Some(ObjectId::raw_decode(prev_state_id.as_slice())?.0), + None => None, + }, + block: GroupConsensusBlock::raw_decode(&value.block.as_slice())?.0, + }) + } +} + +impl ProtobufTransform<&GroupCommandCommited> for super::codec::protos::GroupCommandCommited { + fn transform(value: &GroupCommandCommited) -> BuckyResult { + Ok(Self { + prev_state_id: match value.prev_state_id.as_ref() { + Some(prev_state_id) => Some(prev_state_id.to_vec()?), + None => None, + }, + block: value.block.to_vec()?, + }) + } +} + +impl From for GroupCommand { + fn from(cmd: GroupCommandNewRPath) -> Self { + let desc = GroupCommandDescContent {}; + let body = GroupCommandBodyContent::NewRPath(cmd); + GroupCommandBuilder::new(desc, body).build() + } +} + +impl From for GroupCommand { + fn from(cmd: GroupCommandExecute) -> Self { + let desc = GroupCommandDescContent {}; + let body = GroupCommandBodyContent::Execute(cmd); + GroupCommandBuilder::new(desc, body).build() + } +} + +impl From for GroupCommand { + fn from(cmd: GroupCommandExecuteResult) -> Self { + let desc = GroupCommandDescContent {}; + let body = GroupCommandBodyContent::ExecuteResult(cmd); + GroupCommandBuilder::new(desc, body).build() + } +} + +impl From for GroupCommand { + fn from(cmd: GroupCommandVerify) -> Self { + let desc = GroupCommandDescContent {}; + let body = GroupCommandBodyContent::Verify(cmd); + GroupCommandBuilder::new(desc, body).build() + } +} + +impl From for GroupCommand { + fn from(cmd: GroupCommandCommited) -> Self { + let desc = GroupCommandDescContent {}; + let body = GroupCommandBodyContent::Commited(cmd); + GroupCommandBuilder::new(desc, body).build() + } +} + +impl TryInto for GroupCommand { + type Error = BuckyError; + + fn try_into(self) -> Result { + let cmd_type = self.cmd_type(); + match self.into_cmd() { + GroupCommandBodyContent::NewRPath(cmd) => Ok(cmd), + _ => Err(BuckyError::new( + BuckyErrorCode::Unmatch, + format!("is {:?}, expect NewRPath", cmd_type), + )), + } + } +} + +impl TryInto for GroupCommand { + type Error = BuckyError; + + fn try_into(self) -> Result { + let cmd_type = self.cmd_type(); + match self.into_cmd() { + GroupCommandBodyContent::Execute(cmd) => Ok(cmd), + _ => Err(BuckyError::new( + BuckyErrorCode::Unmatch, + format!("is {:?}, expect Execute", cmd_type), + )), + } + } +} + +impl TryInto for GroupCommand { + type Error = BuckyError; + + fn try_into(self) -> Result { + let cmd_type = self.cmd_type(); + match self.into_cmd() { + GroupCommandBodyContent::ExecuteResult(cmd) => Ok(cmd), + _ => Err(BuckyError::new( + BuckyErrorCode::Unmatch, + format!("is {:?}, expect ExecuteResult", cmd_type), + )), + } + } +} + +impl TryInto for GroupCommand { + type Error = BuckyError; + + fn try_into(self) -> Result { + let cmd_type = self.cmd_type(); + match self.into_cmd() { + GroupCommandBodyContent::Verify(cmd) => Ok(cmd), + _ => Err(BuckyError::new( + BuckyErrorCode::Unmatch, + format!("is {:?}, expect Verify", cmd_type), + )), + } + } +} + +impl TryInto for GroupCommand { + type Error = BuckyError; + + fn try_into(self) -> Result { + let cmd_type = self.cmd_type(); + match self.into_cmd() { + GroupCommandBodyContent::Commited(cmd) => Ok(cmd), + _ => Err(BuckyError::new( + BuckyErrorCode::Unmatch, + format!("is {:?}, expect Commited", cmd_type), + )), + } + } +} diff --git a/src/component/cyfs-group-lib/src/objects/group_decide_proposal.rs b/src/component/cyfs-group-lib/src/objects/group_decide_proposal.rs new file mode 100644 index 000000000..6eb9b9027 --- /dev/null +++ b/src/component/cyfs-group-lib/src/objects/group_decide_proposal.rs @@ -0,0 +1,105 @@ +use std::mem; + +use cyfs_base::*; +use cyfs_core::{ + GroupProposal, GroupProposalBuilder, GroupProposalDescContent, GroupProposalObject, + GroupPropsalDecideParam, GroupRPath, +}; + +use crate::GROUP_METHOD_DECIDE; + +pub struct GroupDecideProposal { + proposal: GroupProposal, + decides: Vec, +} + +impl GroupDecideProposal { + pub fn create( + rpath: GroupRPath, + decides: Vec, + owner_id: ObjectId, + ) -> GroupDecideProposalBuilder { + GroupDecideProposalBuilder::create(rpath, decides, owner_id) + } + + pub fn base(&self) -> &GroupProposal { + &self.proposal + } + + pub fn decides(&self) -> &[GroupPropsalDecideParam] { + self.decides.as_slice() + } +} + +impl TryFrom for GroupDecideProposal { + type Error = BuckyError; + + fn try_from(value: GroupProposal) -> Result { + if value.params().is_none() { + return Err(BuckyError::new( + BuckyErrorCode::InvalidFormat, + "param for GroupDecideProposal shoud not None", + )); + } + + let (decides, remain) = Vec::::raw_decode( + value.params().as_ref().unwrap().as_slice(), + )?; + assert_eq!(remain.len(), 0); + + let ret = GroupDecideProposal { + proposal: value, + decides, + }; + Ok(ret) + } +} + +pub struct GroupDecideProposalBuilder { + proposal: GroupProposalBuilder, + decides: Vec, +} + +impl GroupDecideProposalBuilder { + pub fn create( + rpath: GroupRPath, + decides: Vec, + owner: ObjectId, + ) -> Self { + let param_vec = { + let len = decides.raw_measure(&None).unwrap(); + let mut buf = vec![0u8; len]; + let remain = decides.raw_encode(buf.as_mut_slice(), &None).unwrap(); + assert_eq!(remain.len(), 0); + buf + }; + + let proposal = GroupProposal::create( + rpath, + GROUP_METHOD_DECIDE.to_string(), + Some(param_vec), + None, + None, + owner, + None, + None, + None, + ); + + Self { proposal, decides } + } + + pub fn desc_builder(&mut self) -> &mut NamedObjectDescBuilder { + self.proposal.mut_desc_builder() + } + + pub fn build(mut self) -> GroupDecideProposal { + let mut decides = vec![]; + mem::swap(&mut decides, &mut self.decides); + + GroupDecideProposal { + proposal: self.proposal.build(), + decides, + } + } +} diff --git a/src/component/cyfs-group-lib/src/objects/group_rpath_status.rs b/src/component/cyfs-group-lib/src/objects/group_rpath_status.rs new file mode 100644 index 000000000..ae656b5ad --- /dev/null +++ b/src/component/cyfs-group-lib/src/objects/group_rpath_status.rs @@ -0,0 +1,106 @@ +use std::collections::HashMap; + +use cyfs_base::*; +use cyfs_core::{GroupConsensusBlockDesc, HotstuffBlockQC}; +use cyfs_lib::NONObjectInfo; +use prost::Message; + +#[derive(Clone)] +pub struct GroupRPathStatus { + pub block_desc: GroupConsensusBlockDesc, + pub certificate: HotstuffBlockQC, + pub status_map: HashMap, +} + +impl RawEncode for GroupRPathStatus { + fn raw_measure(&self, _purpose: &Option) -> BuckyResult { + let block_desc = self.block_desc.to_vec()?; + let certificate = self.certificate.to_vec()?; + let mut status_list = vec![]; + for (_, obj) in self.status_map.iter() { + status_list.push(obj.to_vec()?); + } + + let proto = super::codec::protos::GroupRPathStatus { + block_desc, + certificate, + status_list, + }; + + Ok(proto.encoded_len()) + } + + fn raw_encode<'a>( + &self, + mut buf: &'a mut [u8], + _purpose: &Option, + ) -> BuckyResult<&'a mut [u8]> { + let block_desc = self.block_desc.to_vec()?; + let certificate = self.certificate.to_vec()?; + let mut status_list = vec![]; + for (_, obj) in self.status_map.iter() { + status_list.push(obj.to_vec()?); + } + + let proto = super::codec::protos::GroupRPathStatus { + block_desc, + certificate, + status_list, + }; + + proto.encode_raw(&mut buf); + + Ok(buf) + } +} + +impl<'de> RawDecode<'de> for GroupRPathStatus { + fn raw_decode(mut buf: &'de [u8]) -> BuckyResult<(Self, &'de [u8])> { + let proto = super::codec::protos::GroupRPathStatus::decode(&mut buf).map_err(|err| { + let msg = format!("decode proto-buf for GroupRPathStatus failed {:?}", err); + log::error!("{}", msg); + BuckyError::new(BuckyErrorCode::Failed, msg) + })?; + + let (block_desc, remain) = + GroupConsensusBlockDesc::raw_decode(proto.block_desc.as_slice())?; + assert_eq!(remain.len(), 0); + let (certificate, remain) = HotstuffBlockQC::raw_decode(proto.certificate.as_slice())?; + assert_eq!(remain.len(), 0); + let mut status_map = HashMap::new(); + for obj_buf in proto.status_list.iter() { + log::debug!("will decode len: {}", obj_buf.len()); + // size + object_id + let status = if obj_buf.len() > OBJECT_ID_LEN + 1 { + let (status, remain) = NONObjectInfo::raw_decode(obj_buf.as_slice())?; + assert_eq!(remain.len(), 0); + status + } else if obj_buf.len() == OBJECT_ID_LEN + 1 { + NONObjectInfo::new( + ObjectId::clone_from_slice(&obj_buf.as_slice()[1..])?, + obj_buf.clone(), + None, + ) + } else { + return Err(BuckyError::new( + BuckyErrorCode::InvalidData, + "expect NONObjectInfo", + )); + }; + + status_map.insert(status.object_id, status); + } + + Ok(( + Self { + block_desc, + certificate, + status_map, + }, + buf, + )) + } +} + +#[cfg(test)] +mod test {} diff --git a/src/component/cyfs-group-lib/src/objects/group_update_proposal.rs b/src/component/cyfs-group-lib/src/objects/group_update_proposal.rs new file mode 100644 index 000000000..9a90478b1 --- /dev/null +++ b/src/component/cyfs-group-lib/src/objects/group_update_proposal.rs @@ -0,0 +1,251 @@ +use std::mem; + +use async_std::task; +use cyfs_base::*; +use cyfs_core::{ + GroupProposal, GroupProposalBuilder, GroupProposalDescContent, GroupProposalObject, + GroupPropsalDecideParam, GroupRPath, GroupUpdateGroupPropsalParam, +}; + +use crate::{GROUP_METHOD_UPDATE, STATEPATH_GROUP_DEC_ID, STATEPATH_GROUP_DEC_RPATH}; + +pub struct GroupUpdateProposal { + proposal: GroupProposal, + target_dec_id: Vec, + from_shell_id: Option, + to_group: Group, +} + +impl GroupUpdateProposal { + pub fn create( + from_group_shell_id: Option, + to_group: Group, + owner: ObjectId, + target_dec_id: Vec, + meta_block_id: Option, + effective_begining: Option, + effective_ending: Option, + ) -> GroupUpdateProposalBuilder { + GroupUpdateProposalBuilder::create( + from_group_shell_id, + to_group, + owner, + target_dec_id, + meta_block_id, + effective_begining, + effective_ending, + ) + } + + pub fn create_new_group( + to_group: Group, + owner: ObjectId, + target_dec_id: Vec, + meta_block_id: Option, + effective_begining: Option, + effective_ending: Option, + ) -> GroupUpdateProposalBuilder { + GroupUpdateProposalBuilder::create( + None, + to_group, + owner, + target_dec_id, + meta_block_id, + effective_begining, + effective_ending, + ) + } + + pub fn base(&self) -> &GroupProposal { + &self.proposal + } + + pub fn target_dec_id(&self) -> &[ObjectId] { + self.target_dec_id.as_slice() + } + + pub fn from_shell_id(&self) -> &Option { + &self.from_shell_id + } + + pub fn to_group(&self) -> &Group { + &self.to_group + } + + pub async fn decide( + &mut self, + member_id: ObjectId, + decide: Vec, + private_key: &PrivateKey, + ) -> BuckyResult { + self.proposal.decide(member_id, decide, private_key).await + } + + async fn verify_and_merge_decide( + &mut self, + decide: &GroupPropsalDecideParam, + member_id: ObjectId, + public_key: &PublicKey, + ) -> BuckyResult<()> { + self.proposal + .verify_and_merge_decide(decide, member_id, public_key) + .await + } + + pub fn group_update_rpath(group_id: ObjectId) -> GroupRPath { + GroupRPath::new( + group_id, + *STATEPATH_GROUP_DEC_ID, + STATEPATH_GROUP_DEC_RPATH.to_string(), + ) + } +} + +impl TryFrom for GroupUpdateProposal { + type Error = BuckyError; + + fn try_from(value: GroupProposal) -> Result { + if value.params().is_none() { + return Err(BuckyError::new( + BuckyErrorCode::InvalidFormat, + "param for GroupUpdateProposal shoud not None", + )); + } + + let (param, remain) = + GroupUpdateGroupPropsalParam::raw_decode(value.params().as_ref().unwrap().as_slice())?; + assert_eq!(remain.len(), 0); + + if value.payload().is_none() { + return Err(BuckyError::new( + BuckyErrorCode::InvalidFormat, + "payload for GroupUpdateProposal shoud not None", + )); + } + + let payload = value.payload().as_ref().unwrap(); + let (group_shell, remain) = ChunkMeta::raw_decode(payload.as_slice()).unwrap(); + assert_eq!(remain.len(), 0); + + let to_group = Group::try_from(&group_shell)?; + + let to_shell_id = + task::block_on(async { group_shell.to_chunk().await.unwrap().calculate_id() }); + if &to_shell_id.object_id() != param.to_shell_id() { + return Err(BuckyError::new( + BuckyErrorCode::InvalidFormat, + "the chunk in GroupUpdateProposal.body is not match with the to_shell_id in desc.param", + )); + } + + let ret = Self { + proposal: value, + target_dec_id: Vec::from(param.target_dec_id()), + from_shell_id: param.from_shell_id().clone(), + to_group, + }; + + Ok(ret) + } +} + +pub struct GroupUpdateProposalBuilder { + proposal: GroupProposalBuilder, + target_dec_id: Vec, + from_shell_id: Option, + to_group: Option, +} + +impl GroupUpdateProposalBuilder { + pub fn create( + from_group_shell_id: Option, + to_group: Group, + owner: ObjectId, + target_dec_id: Vec, + meta_block_id: Option, + effective_begining: Option, + effective_ending: Option, + ) -> Self { + let group_shell = ChunkMeta::from(&to_group); + let group_shell_vec = { + let len = group_shell.raw_measure(&None).unwrap(); + let mut buf = vec![0u8; len]; + let remain = group_shell.raw_encode(buf.as_mut_slice(), &None).unwrap(); + assert_eq!(remain.len(), 0); + buf + }; + + let to_shell_id = + task::block_on(async { group_shell.to_chunk().await.unwrap().calculate_id() }); + + let param = + GroupUpdateGroupPropsalParam::new(target_dec_id.clone(), None, to_shell_id.object_id()); + let param_vec = { + let len = param.raw_measure(&None).unwrap(); + let mut buf = vec![0u8; len]; + let remain = param.raw_encode(buf.as_mut_slice(), &None).unwrap(); + assert_eq!(remain.len(), 0); + buf + }; + + let group_id = to_group.desc().group_id(); + let update_rpath = GroupUpdateProposal::group_update_rpath(group_id.object_id().clone()); + + let proposal = GroupProposal::create( + update_rpath, + GROUP_METHOD_UPDATE.to_string(), + Some(param_vec), + Some(group_shell_vec), + None, + owner, + meta_block_id, + effective_begining, + effective_ending, + ); + + Self { + proposal, + target_dec_id, + from_shell_id: from_group_shell_id, + to_group: Some(to_group), + } + } + + pub fn create_new_group( + to_group: Group, + owner: ObjectId, + target_dec_id: Vec, + meta_block_id: Option, + effective_begining: Option, + effective_ending: Option, + ) -> Self { + Self::create( + None, + to_group, + owner, + target_dec_id, + meta_block_id, + effective_begining, + effective_ending, + ) + } + + pub fn desc_builder(&mut self) -> &mut NamedObjectDescBuilder { + self.proposal.mut_desc_builder() + } + + pub fn build(mut self) -> GroupUpdateProposal { + let mut target_dec_id: Vec = vec![]; + let mut from_shell_id: Option = None; + let to_group = self.to_group.take().unwrap(); + mem::swap(&mut target_dec_id, &mut self.target_dec_id); + mem::swap(&mut from_shell_id, &mut self.from_shell_id); + + GroupUpdateProposal { + proposal: self.proposal.build(), + target_dec_id, + from_shell_id, + to_group, + } + } +} diff --git a/src/component/cyfs-group-lib/src/objects/mod.rs b/src/component/cyfs-group-lib/src/objects/mod.rs new file mode 100644 index 000000000..668afe8cf --- /dev/null +++ b/src/component/cyfs-group-lib/src/objects/mod.rs @@ -0,0 +1,12 @@ +mod group_command; +// mod group_decide_proposal; +mod group_rpath_status; +// mod group_update_proposal; +mod certificate; +mod codec; + +pub use group_command::*; +// pub use group_decide_proposal::*; +pub use group_rpath_status::*; +// pub use group_update_proposal::*; +pub use certificate::*; diff --git a/src/component/cyfs-group-lib/src/output_request.rs b/src/component/cyfs-group-lib/src/output_request.rs new file mode 100644 index 000000000..348d32934 --- /dev/null +++ b/src/component/cyfs-group-lib/src/output_request.rs @@ -0,0 +1,54 @@ +use std::fmt::{self, Debug}; + +use cyfs_base::{NamedObject, ObjectDesc, ObjectId}; +use cyfs_core::GroupProposal; +use cyfs_lib::NONObjectInfo; + +#[derive(Clone, Debug)] +pub struct GroupOutputRequestCommon { + // source dec-id + pub dec_id: Option, +} + +impl GroupOutputRequestCommon { + pub fn new() -> Self { + Self { dec_id: None } + } +} + +impl fmt::Display for GroupOutputRequestCommon { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(dec_id) = &self.dec_id { + write!(f, ", dec_id: {}", dec_id)?; + } + + Ok(()) + } +} + +#[derive(Debug)] +pub struct GroupStartServiceOutputRequest { + pub common: GroupOutputRequestCommon, + pub group_id: ObjectId, + pub rpath: String, +} + +pub struct GroupStartServiceOutputResponse {} + +pub struct GroupPushProposalOutputRequest { + pub common: GroupOutputRequestCommon, + pub proposal: GroupProposal, +} + +impl Debug for GroupPushProposalOutputRequest { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("GroupPushProposalOutputRequest") + .field("common", &self.common) + .field("proposal", &self.proposal.desc().object_id()) + .finish() + } +} + +pub struct GroupPushProposalOutputResponse { + pub object: Option, +} diff --git a/src/component/cyfs-group-lib/src/processor.rs b/src/component/cyfs-group-lib/src/processor.rs new file mode 100644 index 000000000..dc7962e28 --- /dev/null +++ b/src/component/cyfs-group-lib/src/processor.rs @@ -0,0 +1,23 @@ +use std::sync::Arc; + +use cyfs_base::BuckyResult; +use cyfs_core::GroupProposal; + +use crate::{ + GroupOutputRequestCommon, GroupPushProposalOutputRequest, GroupPushProposalOutputResponse, + GroupStartServiceOutputRequest, GroupStartServiceOutputResponse, +}; + +#[async_trait::async_trait] +pub trait GroupOutputProcessor: Send + Sync { + async fn start_service( + &self, + req: GroupStartServiceOutputRequest, + ) -> BuckyResult; + async fn push_proposal( + &self, + req: GroupPushProposalOutputRequest, + ) -> BuckyResult; +} + +pub type GroupOutputProcessorRef = Arc>; diff --git a/src/component/cyfs-group-lib/src/request.rs b/src/component/cyfs-group-lib/src/request.rs new file mode 100644 index 000000000..e69de29bb diff --git a/src/component/cyfs-group-lib/src/request_codec.rs b/src/component/cyfs-group-lib/src/request_codec.rs new file mode 100644 index 000000000..bc8203626 --- /dev/null +++ b/src/component/cyfs-group-lib/src/request_codec.rs @@ -0,0 +1,75 @@ +use cyfs_base::{BuckyResult, JsonCodec, JsonCodecHelper}; +use serde_json::{Map, Value}; + +use crate::{ + output_request::GroupStartServiceOutputRequest, GroupInputRequestCommon, + GroupOutputRequestCommon, GroupStartServiceInputRequest, +}; + +impl JsonCodec for GroupOutputRequestCommon { + fn encode_json(&self) -> Map { + let mut obj = Map::new(); + JsonCodecHelper::encode_option_string_field(&mut obj, "dec-id", self.dec_id.as_ref()); + + obj + } + + fn decode_json(obj: &Map) -> BuckyResult { + Ok(Self { + dec_id: JsonCodecHelper::decode_option_string_field(obj, "dec-id")?, + }) + } +} + +impl JsonCodec for GroupInputRequestCommon { + fn encode_json(&self) -> Map { + let mut obj = Map::new(); + JsonCodecHelper::encode_field(&mut obj, "source", &self.source); + + obj + } + + fn decode_json(obj: &Map) -> BuckyResult { + Ok(Self { + source: JsonCodecHelper::decode_field(obj, "source")?, + }) + } +} + +impl JsonCodec for GroupStartServiceOutputRequest { + fn encode_json(&self) -> Map { + let mut obj = Map::new(); + JsonCodecHelper::encode_string_field(&mut obj, "group-id", &self.group_id); + JsonCodecHelper::encode_string_field(&mut obj, "rpath", self.rpath.as_str()); + JsonCodecHelper::encode_field(&mut obj, "common", &self.common); + + obj + } + + fn decode_json(obj: &Map) -> BuckyResult { + Ok(Self { + group_id: JsonCodecHelper::decode_string_field(obj, "group-id")?, + rpath: JsonCodecHelper::decode_string_field(obj, "rpath")?, + common: JsonCodecHelper::decode_field(obj, "common")?, + }) + } +} + +impl JsonCodec for GroupStartServiceInputRequest { + fn encode_json(&self) -> Map { + let mut obj = Map::new(); + JsonCodecHelper::encode_string_field(&mut obj, "group-id", &self.group_id); + JsonCodecHelper::encode_string_field(&mut obj, "rpath", self.rpath.as_str()); + JsonCodecHelper::encode_field(&mut obj, "common", &self.common); + + obj + } + + fn decode_json(obj: &Map) -> BuckyResult { + Ok(Self { + group_id: JsonCodecHelper::decode_string_field(obj, "group-id")?, + rpath: JsonCodecHelper::decode_string_field(obj, "rpath")?, + common: JsonCodecHelper::decode_field(obj, "common")?, + }) + } +} diff --git a/src/component/cyfs-group-lib/src/requestor.rs b/src/component/cyfs-group-lib/src/requestor.rs new file mode 100644 index 000000000..1c9068da4 --- /dev/null +++ b/src/component/cyfs-group-lib/src/requestor.rs @@ -0,0 +1,176 @@ +use std::sync::Arc; + +use cyfs_base::{ + BuckyError, BuckyResult, JsonCodec, NamedObject, ObjectDesc, ObjectId, RawConvertTo, +}; +use cyfs_core::{GroupProposal, GroupProposalObject}; +use cyfs_lib::{HttpRequestorRef, NONObjectInfo, NONRequestorHelper, RequestorHelper}; +use http_types::{Method, Request, Url}; + +use crate::{ + output_request::GroupStartServiceOutputRequest, + processor::{GroupOutputProcessor, GroupOutputProcessorRef}, + GroupOutputRequestCommon, GroupPushProposalOutputRequest, GroupPushProposalOutputResponse, + GroupStartServiceOutputResponse, +}; + +#[derive(Clone)] +pub struct GroupRequestor { + dec_id: ObjectId, + requestor: HttpRequestorRef, + service_url: Url, +} + +impl GroupRequestor { + pub fn new(dec_id: ObjectId, requestor: HttpRequestorRef) -> Self { + let addr = requestor.remote_addr(); + + let url = format!("http://{}/group/", addr); + let url = Url::parse(&url).unwrap(); + + Self { + dec_id, + requestor, + service_url: url, + } + } + + pub fn clone_processor(&self) -> GroupOutputProcessorRef { + Arc::new(Box::new(self.clone())) + } + + fn encode_common_headers(&self, com_req: &GroupOutputRequestCommon, http_req: &mut Request) { + let dec_id = com_req.dec_id.as_ref().unwrap_or(&self.dec_id); + http_req.insert_header(cyfs_base::CYFS_DEC_ID, dec_id.to_string()); + } + + pub(crate) fn make_default_common(dec_id: ObjectId) -> GroupOutputRequestCommon { + GroupOutputRequestCommon { + dec_id: Some(dec_id), + } + } + + pub async fn start_service( + &self, + req: GroupStartServiceOutputRequest, + ) -> BuckyResult { + log::info!("will start group service: {:?}", req.rpath); + + let url = self.service_url.join("service").unwrap(); + let mut http_req = Request::new(Method::Put, url); + self.encode_common_headers(&req.common, &mut http_req); + + let req = GroupStartServiceOutputRequest { + group_id: req.group_id.clone(), + rpath: req.rpath.to_string(), + common: req.common, + }; + + let body = req.encode_string(); + http_req.set_body(body); + + let mut resp = self.requestor.request(http_req).await?; + + match resp.status() { + code if code.is_success() => { + let body = resp.body_string().await.map_err(|e| { + let msg = format!( + "group start service failed, read body string error! req={:?} {}", + req, e + ); + log::error!("{}", msg); + + BuckyError::from(msg) + })?; + + // let resp = GroupStartServiceOutputResponse::decode_string(&body).map_err(|e| { + // error!( + // "decode group start service resp from body string error: body={} {}", + // body, e, + // ); + // e + // })?; + + log::debug!("group start service success"); + + Ok(GroupStartServiceOutputResponse {}) + } + code @ _ => { + let e = RequestorHelper::error_from_resp(&mut resp).await; + log::error!( + "group start service failed: rpath={:?}, status={}, {}", + req.rpath, + code, + e + ); + Err(e) + } + } + } + + pub async fn push_proposal( + &self, + req: GroupPushProposalOutputRequest, + ) -> BuckyResult { + let proposal_id = req.proposal.desc().object_id(); + log::info!( + "will push proposal: {:?}, {}", + req.proposal.rpath(), + proposal_id + ); + + let url = self.service_url.join("proposal").unwrap(); + let mut http_req = Request::new(Method::Put, url); + + self.encode_common_headers(&req.common, &mut http_req); + + NONRequestorHelper::encode_object_info( + &mut http_req, + NONObjectInfo::new(proposal_id, req.proposal.to_vec()?, None), + ); + + let mut resp = self.requestor.request(http_req).await?; + + let status = resp.status(); + if status.is_success() { + match status { + http_types::StatusCode::NoContent => { + let e = RequestorHelper::error_from_resp(&mut resp).await; + log::info!( + "push proposal but empty response! obj={}, {}", + proposal_id, + e + ); + Err(e) + } + _ => { + log::info!("push proposal success: {}", proposal_id); + let object = NONRequestorHelper::decode_option_object_info(&mut resp).await?; + let ret = GroupPushProposalOutputResponse { object }; + Ok(ret) + } + } + } else { + let e = RequestorHelper::error_from_resp(&mut resp).await; + log::error!("push proposal error! object={}, {}", proposal_id, e); + Err(e) + } + } +} + +#[async_trait::async_trait] +impl GroupOutputProcessor for GroupRequestor { + async fn start_service( + &self, + req: GroupStartServiceOutputRequest, + ) -> BuckyResult { + GroupRequestor::start_service(self, req).await + } + + async fn push_proposal( + &self, + req: GroupPushProposalOutputRequest, + ) -> BuckyResult { + GroupRequestor::push_proposal(self, req).await + } +} diff --git a/src/component/cyfs-group-lib/src/rpath_client.rs b/src/component/cyfs-group-lib/src/rpath_client.rs new file mode 100644 index 000000000..2eaf71315 --- /dev/null +++ b/src/component/cyfs-group-lib/src/rpath_client.rs @@ -0,0 +1,62 @@ +use std::sync::Arc; + +use cyfs_base::{BuckyResult, NamedObject, ObjectDesc, ObjectId, RawConvertTo}; +use cyfs_core::{GroupProposal, GroupProposalObject, GroupRPath}; +use cyfs_lib::{ + NONAPILevel, NONObjectInfo, NONOutputRequestCommon, NONPostObjectOutputRequest, NONRequestor, + RequestGlobalStatePath, +}; + +struct RPathClientRaw { + rpath: GroupRPath, + local_dec_id: Option, + requestor: NONRequestor, +} + +#[derive(Clone)] +pub struct RPathClient(Arc); + +impl RPathClient { + pub(crate) fn new( + rpath: GroupRPath, + local_dec_id: Option, + requestor: NONRequestor, + ) -> Self { + Self(Arc::new(RPathClientRaw { + requestor, + rpath, + local_dec_id, + })) + } + + pub fn rpath(&self) -> &GroupRPath { + &self.0.rpath + } + + // post proposal to the admins, it's same as calling to non.post_object with default parameters; + // and you can call the non.post_object with more parameters. + pub async fn post_proposal( + &self, + proposal: &GroupProposal, + ) -> BuckyResult> { + let req_path = RequestGlobalStatePath::new( + Some(proposal.rpath().dec_id().clone()), + Some("group/proposal"), + ); + self.0 + .requestor + .post_object(NONPostObjectOutputRequest { + common: NONOutputRequestCommon { + req_path: Some(req_path.format_string()), + source: None, + dec_id: self.0.local_dec_id.clone(), + level: NONAPILevel::Router, + target: Some(proposal.rpath().group_id().clone()), + flags: 0, + }, + object: NONObjectInfo::new(proposal.desc().object_id(), proposal.to_vec()?, None), + }) + .await + .map(|resp| resp.object) + } +} diff --git a/src/component/cyfs-group-lib/src/rpath_service.rs b/src/component/cyfs-group-lib/src/rpath_service.rs new file mode 100644 index 000000000..03825d416 --- /dev/null +++ b/src/component/cyfs-group-lib/src/rpath_service.rs @@ -0,0 +1,151 @@ +use std::sync::Arc; + +use cyfs_base::{BuckyResult, ObjectId}; +use cyfs_core::{GroupConsensusBlock, GroupProposal, GroupProposalObject, GroupRPath}; +use cyfs_lib::{ + HttpRequestorRef, IsolatePathOpEnvStub, NONObjectInfo, RootStateOpEnvAccess, SharedCyfsStack, + SingleOpEnvStub, +}; + +use crate::{ + ExecuteResult, GroupObjectMapProcessor, GroupPushProposalOutputRequest, GroupRequestor, + GroupStartServiceOutputRequest, RPathDelegate, +}; + +struct RPathServiceRaw { + rpath: GroupRPath, + requestor: GroupRequestor, + delegate: Box, + stack: SharedCyfsStack, +} + +#[derive(Clone)] +pub struct RPathService(Arc); + +impl RPathService { + pub fn rpath(&self) -> &GroupRPath { + &self.0.rpath + } + + pub async fn push_proposal( + &self, + proposal: &GroupProposal, + ) -> BuckyResult> { + // post http + self.0 + .requestor + .push_proposal(GroupPushProposalOutputRequest { + common: GroupRequestor::make_default_common(proposal.rpath().dec_id().clone()), + proposal: proposal.clone(), + }) + .await + .map(|resp| resp.object) + } + + pub(crate) fn new( + rpath: GroupRPath, + requestor: HttpRequestorRef, + delegate: Box, + stack: SharedCyfsStack, + ) -> Self { + Self(Arc::new(RPathServiceRaw { + requestor: GroupRequestor::new(rpath.dec_id().clone(), requestor), + rpath, + delegate, + stack, + })) + } + + pub(crate) async fn start(&self) -> BuckyResult<()> { + // post create command + self.0 + .requestor + .start_service(GroupStartServiceOutputRequest { + common: GroupRequestor::make_default_common(self.0.rpath.dec_id().clone()), + group_id: self.rpath().group_id().clone(), + rpath: self.rpath().rpath().to_string(), + }) + .await + .map(|_| {}) + } + + pub(crate) async fn on_execute( + &self, + proposal: &GroupProposal, + prev_state_id: &Option, + ) -> BuckyResult { + self.0 + .delegate + .on_execute( + proposal, + prev_state_id, + &GroupObjectMapProcessorImpl { + stack: self.0.stack.clone(), + }, + ) + .await + } + + pub(crate) async fn on_verify( + &self, + proposal: &GroupProposal, + prev_state_id: &Option, + execute_result: &ExecuteResult, + ) -> BuckyResult<()> { + self.0 + .delegate + .on_verify( + proposal, + prev_state_id, + execute_result, + &GroupObjectMapProcessorImpl { + stack: self.0.stack.clone(), + }, + ) + .await + } + + pub(crate) async fn on_commited( + &self, + prev_state_id: &Option, + block: &GroupConsensusBlock, + ) { + self.0 + .delegate + .on_commited( + prev_state_id, + block, + &GroupObjectMapProcessorImpl { + stack: self.0.stack.clone(), + }, + ) + .await + } +} + +struct GroupObjectMapProcessorImpl { + stack: SharedCyfsStack, +} + +#[async_trait::async_trait] +impl GroupObjectMapProcessor for GroupObjectMapProcessorImpl { + async fn create_single_op_env( + &self, + access: Option, + ) -> BuckyResult { + self.stack + .root_state_stub(None, None) + .create_single_op_env_with_access(access) + .await + } + + async fn create_sub_tree_op_env( + &self, + access: Option, + ) -> BuckyResult { + self.stack + .root_state_stub(None, None) + .create_isolate_path_op_env_with_access(access) + .await + } +} diff --git a/src/component/cyfs-group/Cargo.toml b/src/component/cyfs-group/Cargo.toml new file mode 100644 index 000000000..f31e91017 --- /dev/null +++ b/src/component/cyfs-group/Cargo.toml @@ -0,0 +1,37 @@ + +[package] +name = 'cyfs-group' +version = '0.1.1' +authors = ['zhangzhen '] +edition = '2021' +license = 'BSD-2-Clause' +description = 'Rust cyfs-group package' + +[build-dependencies] +prost-build = { version = '0.9.0' } +protoc-rust = '2' +chrono = '0.4' +protoc-bin-vendored = '3' + +[dependencies] +async-trait = "0.1.53" +async-std = '1.11' +log = '0.4' +serde_json = '1.0' +futures = '0.3.25' +serde = { version = '1.0', features = ['derive'] } +prost = { version = '0.11.5' } +protobuf = { version = '2', features = ['with-bytes'] } +lazy_static = '1.4' +sha2 = { version = '0.8' } +async-recursion = '1.0' +rand = '0.8.5' +itertools = "0.10.3" +cyfs-base = { path = '../../component/cyfs-base', version = '0.6' } +cyfs-core = { path = '../../component/cyfs-core', version = '0.6' } +cyfs-bdt = { path = '../../component/cyfs-bdt', version = '0.7' } +cyfs-debug = { path = "../../component/cyfs-debug" } +cyfs-lib = { path = '../../component/cyfs-lib' } +cyfs-group-lib = { path = '../../component/cyfs-group-lib' } +cyfs-meta-lib = { path = "../../component/cyfs-meta-lib" } +cyfs-base-meta = { path = "../../component/cyfs-base-meta" } \ No newline at end of file diff --git a/src/component/cyfs-group/readme.md b/src/component/cyfs-group/readme.md new file mode 100644 index 000000000..fd211a02c --- /dev/null +++ b/src/component/cyfs-group/readme.md @@ -0,0 +1,14 @@ +# 模块说明 + +支持群组产权数据相关需求 + +# 方案简介 + +1. 抛弃目前的 SimpleGroup 标准对象,提供一个可以包含多个 People 对象的 Group 标准对象,Group 对象可以支持动态配置相关属性(成员、权力等) +2. 提供 GroupState 结构,在 Group 的各成员 OOD 上保存其所属 Group 的 r-path 状态信息,类似个人产权的 RootState 设计,但更新机制不同,需要 Group 成员之间通过共识协议保持一致 +3. 共识协议目前采用 BFT(HotStuff?) +4. 向 Group 发起的请求(Post/Get),能自动寻址到其成员,并投递 +5. 对从 Group 获取到的信息,Group 提供方法验证(主要是验证签名的过程) +6. 支持 cyfs://r/${groupid}/${decid}/${r-path} +7. 提供对 GroupState 的访问权限控制 ACL +8. 提供操作 Group 的权限配置 Group-ACL diff --git a/src/component/cyfs-group/src/consensus/hotstuff/hotstuff.rs b/src/component/cyfs-group/src/consensus/hotstuff/hotstuff.rs new file mode 100644 index 000000000..6ec5909f2 --- /dev/null +++ b/src/component/cyfs-group/src/consensus/hotstuff/hotstuff.rs @@ -0,0 +1,2881 @@ +use std::{ + collections::HashMap, + sync::Arc, + time::{Duration, SystemTime}, +}; + +use async_std::channel::{Receiver, Sender}; +use cyfs_base::{ + bucky_time_to_system_time, BuckyError, BuckyErrorCode, BuckyResult, Group, NamedObject, + ObjectDesc, ObjectId, ObjectLink, ObjectTypeCode, OwnerObjectDesc, RawConvertTo, RawDecode, + RawEncode, RsaCPUObjectSigner, SignatureSource, Signer, +}; +use cyfs_core::{ + GroupConsensusBlock, GroupConsensusBlockObject, GroupConsensusBlockProposal, GroupProposal, + GroupProposalObject, GroupRPath, HotstuffBlockQC, HotstuffTimeout, ToGroupShell, +}; +use cyfs_group_lib::{ExecuteResult, HotstuffBlockQCVote, HotstuffTimeoutVote}; +use cyfs_lib::NONObjectInfo; +use futures::FutureExt; +use itertools::Itertools; + +use crate::{ + consensus::{proposal, synchronizer::Synchronizer}, + dec_state::{CallReplyNotifier, CallReplyWaiter, StatePusher}, + helper::Timer, + storage::GroupShellManager, + Committee, GroupObjectMapProcessor, GroupStorage, HotstuffMessage, PendingProposalConsumer, + RPathEventNotifier, SyncBound, VoteMgr, VoteThresholded, BLOCK_COUNT_REST_TO_SYNC, + CHANNEL_CAPACITY, GROUP_DEFAULT_CONSENSUS_INTERVAL, HOTSTUFF_TIMEOUT_DEFAULT, + PROPOSAL_MAX_TIMEOUT, TIME_PRECISION, +}; + +/** + * TODO: generate empty block when the 'Node' is synchronizing + * + * How to distinguish synchronizing: max_quorum_round - round > BLOCK_COUNT_REST_TO_SYNC +*/ + +pub(crate) struct Hotstuff { + rpath: GroupRPath, + local_device_id: ObjectId, + tx_message: Sender<(HotstuffMessage, ObjectId)>, + state_pusher: StatePusher, + proposal_result_notifier: CallReplyNotifier>>, +} + +impl std::fmt::Debug for Hotstuff { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}-{:?}", self.rpath, self.local_device_id) + } +} + +impl Hotstuff { + pub fn new( + local_id: ObjectId, + local_device_id: ObjectId, + committee: Committee, + store: GroupStorage, + signer: Arc, + network_sender: crate::network::Sender, + non_driver: crate::network::NONDriverHelper, + shell_mgr: GroupShellManager, + proposal_consumer: PendingProposalConsumer, + event_notifier: RPathEventNotifier, + rpath: GroupRPath, + ) -> Self { + let (tx_message, rx_message) = async_std::channel::bounded(CHANNEL_CAPACITY); + let proposal_result_notifier = CallReplyNotifier::new(); + + let state_pusher = StatePusher::new( + local_id, + network_sender.clone(), + rpath.clone(), + non_driver.clone(), + shell_mgr.clone(), + ); + + let mut runner = HotstuffRunner::new( + local_id, + local_device_id, + committee, + store, + signer, + network_sender, + non_driver, + shell_mgr, + tx_message.clone(), + rx_message, + proposal_consumer, + state_pusher.clone(), + event_notifier, + rpath.clone(), + proposal_result_notifier.clone(), + ); + + async_std::task::spawn(async move { runner.run().await }); + + Self { + local_device_id, + tx_message, + state_pusher, + rpath, + proposal_result_notifier, + } + } + + pub async fn wait_proposal_result( + &self, + proposal_id: ObjectId, + ) -> CallReplyWaiter>> { + self.proposal_result_notifier.prepare(proposal_id).await + } + + pub async fn on_block(&self, block: cyfs_core::GroupConsensusBlock, remote: ObjectId) { + log::debug!("[hotstuff] local: {:?}, on_block: {:?}/{:?}/{:?}, prev: {:?}/{:?}, owner: {:?}, remote: {:?},", + self, + block.block_id(), block.round(), block.height(), + block.prev_block_id(), block.qc().as_ref().map_or(0, |qc| qc.round), + block.owner(), remote); + + self.tx_message + .send((HotstuffMessage::Block(block), remote)) + .await; + } + + pub async fn on_block_vote(&self, vote: HotstuffBlockQCVote, remote: ObjectId) { + log::debug!("[hotstuff] local: {:?}, on_block_vote: {:?}/{:?}, prev: {:?}, voter: {:?}, remote: {:?},", + self, + vote.block_id, vote.round, + vote.prev_block_id, + vote.voter, remote); + + self.tx_message + .send((HotstuffMessage::BlockVote(vote), remote)) + .await; + } + + pub async fn on_timeout_vote(&self, vote: HotstuffTimeoutVote, remote: ObjectId) { + log::debug!( + "[hotstuff] local: {:?}, on_timeout_vote: {:?}, qc: {:?}, voter: {:?}, remote: {:?},", + self, + vote.round, + vote.high_qc.as_ref().map(|qc| format!( + "{:?}/{:?}/{:?}/{:?}", + qc.block_id, + qc.round, + qc.prev_block_id, + qc.votes + .iter() + .map(|v| v.voter.to_string()) + .collect::>() + )), + vote.voter, + remote + ); + + self.tx_message + .send((HotstuffMessage::TimeoutVote(vote), remote)) + .await; + } + + pub async fn on_timeout(&self, tc: HotstuffTimeout, remote: ObjectId) { + log::debug!( + "[hotstuff] local: {:?}, on_timeout: {:?}, voter: {:?}, remote: {:?},", + self, + tc.round, + tc.votes + .iter() + .map(|vote| format!("{:?}/{:?}", vote.high_qc_round, vote.voter,)) + .collect::>(), + remote + ); + + self.tx_message + .send((HotstuffMessage::Timeout(tc), remote)) + .await; + } + + pub async fn on_sync_request( + &self, + min_bound: SyncBound, + max_bound: SyncBound, + remote: ObjectId, + ) { + log::debug!( + "[hotstuff] local: {:?}, on_sync_request: min: {:?}, max: {:?}, remote: {:?},", + self, + min_bound, + max_bound, + remote + ); + + self.tx_message + .send((HotstuffMessage::SyncRequest(min_bound, max_bound), remote)) + .await; + } + + pub async fn request_last_state(&self, remote: ObjectId) { + log::debug!( + "[hotstuff] local: {:?}, on_sync_request: remote: {:?},", + self, + remote + ); + + self.state_pusher.request_last_state(remote).await; + } + + pub async fn on_query_state(&self, sub_path: String, remote: ObjectId) { + log::debug!( + "[hotstuff] local: {:?}, on_query_state: sub_path: {}, remote: {:?},", + self, + sub_path, + remote + ); + + self.tx_message + .send((HotstuffMessage::QueryState(sub_path), remote)) + .await; + } +} + +struct HotstuffRunner { + local_id: ObjectId, + local_device_id: ObjectId, + committee: Committee, + store: GroupStorage, + signer: Arc, + round: u64, // 当前轮次 + high_qc: Option, // 最后一次通过投票的确认信息 + tc: Option, + max_quorum_round: u64, + max_quorum_height: u64, + timer: Timer, // 定时器 + vote_mgr: VoteMgr, + network_sender: crate::network::Sender, + non_driver: crate::network::NONDriverHelper, + shell_mgr: GroupShellManager, + tx_message: Sender<(HotstuffMessage, ObjectId)>, + rx_message: Receiver<(HotstuffMessage, ObjectId)>, + tx_message_inner: Sender<(GroupConsensusBlock, ObjectId)>, + rx_message_inner: Receiver<(GroupConsensusBlock, ObjectId)>, + tx_block_gen: Sender<(GroupConsensusBlock, HashMap)>, + rx_block_gen: Receiver<(GroupConsensusBlock, HashMap)>, + proposal_consumer: PendingProposalConsumer, + event_notifier: RPathEventNotifier, + synchronizer: Synchronizer, + rpath: GroupRPath, + rx_proposal_waiter: Option<(Receiver<()>, u64)>, + state_pusher: StatePusher, + proposal_result_notifier: CallReplyNotifier>>, +} + +impl std::fmt::Debug for HotstuffRunner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.debug_identify()) + } +} + +impl HotstuffRunner { + #[allow(clippy::too_many_arguments)] + pub fn new( + local_id: ObjectId, + local_device_id: ObjectId, + committee: Committee, + store: GroupStorage, + signer: Arc, + network_sender: crate::network::Sender, + non_driver: crate::network::NONDriverHelper, + shell_mgr: GroupShellManager, + tx_message: Sender<(HotstuffMessage, ObjectId)>, + rx_message: Receiver<(HotstuffMessage, ObjectId)>, + proposal_consumer: PendingProposalConsumer, + state_pusher: StatePusher, + event_notifier: RPathEventNotifier, + rpath: GroupRPath, + proposal_result_notifier: CallReplyNotifier>>, + ) -> Self { + let max_round_block = store.block_with_max_round(); + let last_qc = store.last_qc(); + let last_tc = store.last_tc(); + + let last_vote_round = store.last_vote_round(); + let block_quorum_round = last_qc.as_ref().map_or(0, |qc| qc.round); + let timeout_quorum_round = last_tc.as_ref().map_or(0, |tc| tc.round); + let quorum_round = block_quorum_round.max(timeout_quorum_round); + let (max_round_block_round, max_round_qc_round) = + max_round_block.as_ref().map_or((0, 0), |block| { + let qc_round = block.qc().as_ref().map_or(0, |qc| qc.round); + (block.round(), qc_round) + }); + let round = last_vote_round + .max(quorum_round + 1) + .max(max_round_block_round); + + log::debug!("[hotstuff] local: {:?}-{:?}-{}, startup with last_vote_round = {}, quorum_round = {}/{}, max_round_block_round = {}/{}" + , rpath, local_device_id, round, last_vote_round, block_quorum_round, timeout_quorum_round, max_round_block_round, max_round_qc_round); + + let high_qc = if max_round_qc_round >= block_quorum_round { + max_round_block.map_or(None, |b| b.qc().clone()) + } else { + last_qc.clone() + }; + + let tc = last_tc.clone(); + + let vote_mgr = VoteMgr::new(committee.clone(), round); + let init_timer_interval = GROUP_DEFAULT_CONSENSUS_INTERVAL; + let max_quorum_round = round - 1; + let header_height = store.header_height(); + let max_quorum_height = if header_height == 0 { + 0 + } else { + header_height + 1 + }; + + let (tx_block_gen, rx_block_gen) = async_std::channel::bounded(1); + let (tx_message_inner, rx_message_inner) = async_std::channel::bounded(CHANNEL_CAPACITY); + + let synchronizer = Synchronizer::new( + local_device_id, + network_sender.clone(), + rpath.clone(), + store.max_height(), + store.max_round(), + tx_message_inner.clone(), + ); + + Self { + local_id, + local_device_id, + committee, + store, + signer, + round, + high_qc, + timer: Timer::new(init_timer_interval), + vote_mgr, + network_sender, + tx_message, + rx_message, + event_notifier, + synchronizer, + non_driver, + rpath, + proposal_consumer, + rx_proposal_waiter: None, + tc, + max_quorum_round, + max_quorum_height, + state_pusher, + tx_block_gen, + rx_block_gen, + proposal_result_notifier, + tx_message_inner, + rx_message_inner, + shell_mgr, + } + } + + async fn handle_block( + &mut self, + block: &GroupConsensusBlock, + remote: ObjectId, + ) -> BuckyResult<()> { + log::debug!("[hotstuff] local: {:?}, handle_block: {:?}/{:?}/{:?}, prev: {:?}/{:?}, owner: {:?}, remote: {:?},", + self, + block.block_id(), block.round(), block.height(), + block.prev_block_id(), block.qc().as_ref().map_or(0, |qc| qc.round), + block.owner(), remote); + + if block.height() <= self.store.header_height() { + log::warn!( + "[hotstuff] local: {:?}, handle_block: {:?}/{:?}/{:?} ignored for expired.", + self, + block.block_id(), + block.round(), + block.height(), + ); + return Err(BuckyError::new(BuckyErrorCode::Expired, "block expired")); + } + + let latest_group = self + .shell_mgr + .get_group(self.rpath.group_id(), None, Some(&remote)) + .await?; + if !latest_group.contain_ood(&remote) { + log::warn!( + "[hotstuff] local: {:?}, receive block({}) from unknown({})", + self, + block.block_id(), + remote + ); + return Ok(()); + } + + /** + * 1. 验证block投票签名 + * 2. 验证出块节点 + * 3. 同步块 + * 4. 验证各个proposal执行结果 + */ + Self::check_block_result_state(block)?; + + log::debug!( + "[hotstuff] local: {:?}, handle_block-step2: {:?}", + self, + block.block_id() + ); + + { + // check leader + let leader_owner = self + .get_leader_owner(Some(block.group_shell_id()), block.round(), Some(&remote)) + .await?; + + if &leader_owner != block.owner() { + log::warn!("[hotstuff] local: {:?}, receive block({:?}) from invalid leader({}), expected {:?}", + self, + block.block_id(), + block.owner(), + leader_owner + ); + return Err(BuckyError::new(BuckyErrorCode::Ignored, "invalid leader")); + } + } + + self.committee + .verify_block(block, remote) + .await + .map_err(|err| { + log::warn!( + "[hotstuff] local: {:?}, verify block {:?} failed, {:?}.", + self, + block.block_id(), + err + ); + err + })?; + + let quorum_round = block.qc().as_ref().map_or(0, |qc| qc.round); + self.update_max_quorum_round(quorum_round); + self.update_max_quorum_height(block.height() - 1); + + log::debug!( + "[hotstuff] local: {:?}, handle_block-step3: {:?}", + self, + block.block_id() + ); + + let prev_block = match self.check_block_linked(&block, remote).await { + Ok(link) => link, + Err(err) => return err, + }; + + log::debug!( + "[hotstuff] local: {:?}, handle_block-step4: {:?}", + self, + block.block_id() + ); + + { + // 1. verify the results for proposals by DecApp + // 2. rebuild the result-state in on_verify by DecApp + // TODO: + // We can accept the block only for signatures enough without verify from `DecApp`. + // The `QC` is valuable for confirm the previous block. + // But we should not make a vote to the block not verified by `DecApp`. + // So we should only verify the block from `DecApp` before it will be voted, not here. + // But I cannot download the result-state with downloading from remote, + // so I need to verify it here to generate the result-state one by one in the `DecApp.on_verify`. + // If we can download the result-state tree to rebuild it, we can remove the code in this scope. + let mut proposals = HashMap::default(); + self + .non_driver + .load_all_proposals_for_block(block, &mut proposals) + .await.map_err(|err| { + log::warn!("[hotstuff] local: {:?}, make vote to block {} ignore for load proposals failed {:?}", + self, + block.block_id(), + err + ); + err + })?; + + self.check_block_proposal_result_state_by_app(block, &proposals, &prev_block, &remote) + .await + .map_err(|err| { + log::warn!( + "[hotstuff] local: {:?}, make vote to block {} ignore for app verify failed {:?}", + self, + block.block_id(), + err); + err + })?; + } + + self.synchronizer.pop_link_from(block); + + self.process_qc(block.qc()).await; + + if let Some(tc) = block.tc() { + self.advance_round(tc.round).await; + } + + self.process_block(block, remote, &HashMap::new()).await + } + + fn check_block_result_state(block: &GroupConsensusBlock) -> BuckyResult<()> { + if let Some(last_proposal) = block.proposals().last() { + if &last_proposal.result_state != block.result_state_id() { + log::warn!("the result-state({:?}) in last-proposal is unmatch with block.result_state_id({:?})" + , last_proposal.result_state, block.result_state_id()); + return Err(BuckyError::new( + BuckyErrorCode::Unmatch, + "result-state unmatch", + )); + } + } + Ok(()) + } + + fn check_empty_block_result_state_with_prev( + block: &GroupConsensusBlock, + prev_block: &Option, + ) -> BuckyResult<()> { + if block.proposals().is_empty() { + match prev_block.as_ref() { + Some(prev_block) => { + if block.result_state_id() != prev_block.result_state_id() { + log::warn!("block.result_state_id({:?}) is unmatch with prev_block.result_state_id({:?}) with no proposal." + , block.result_state_id(), prev_block.result_state_id()); + + return Err(BuckyError::new( + BuckyErrorCode::Unmatch, + "result-state unmatch", + )); + } + } + None => { + log::warn!("the first block is empty, ignore."); + return Err(BuckyError::new( + BuckyErrorCode::Ignored, + "empty first block", + )); + } + } + } + + Ok(()) + } + + #[async_recursion::async_recursion] + async fn check_block_proposal_result_state_by_app( + &self, + block: &GroupConsensusBlock, + proposals: &HashMap, + prev_block: &Option, + remote: &ObjectId, + ) -> BuckyResult<()> { + let mut prev_state_id = match prev_block.as_ref() { + Some(prev_block) => { + let result_state_id = prev_block.result_state_id(); + if let Some(result_state_id) = result_state_id { + if let Err(err) = self + .make_sure_result_state(result_state_id, &[prev_block.owner(), remote]) + .await + { + // TODO + // try rebuild the result-state for prev-block, it'll unreachable usually except something broken. + // if we can successfully rebuild the result-state in the future, we can remove the code in this part. + match prev_block.prev_block_id() { + Some(prev_prev_block_id) => { + let prev_prev_block = + self.non_driver.get_block(prev_prev_block_id, None).await.map_err(|err| { + log::warn!("[hotstuff] local: {:?}, rebuild of prev-prev-block({:?}) failed, get prev-prev-block failed {:?}." + , self, prev_prev_block_id, err); + err + })?; + + let mut prev_proposals = HashMap::new(); + for proposal_ex_info in prev_block.proposals() { + // TODO: Need a method to allow access in `Group`, Now only get from the owner(AccessString::full()) + let proposal = self + .non_driver + .get_proposal(&proposal_ex_info.proposal, Some(prev_block.owner())) + .await.map_err(|err| { + log::warn!("[hotstuff] local: {:?}, rebuild of prev-prev-block({:?}) failed, get proposal({}) from {} failed {:?}." + , self, prev_prev_block_id, proposal_ex_info.proposal, remote, err); + err + })?; + prev_proposals.insert(proposal_ex_info.proposal, proposal); + } + + self.check_block_proposal_result_state_by_app( + &prev_block, + &prev_proposals, + &Some(prev_prev_block), + remote, + ) + .await.map_err(|err| { + log::warn!("[hotstuff] local: {:?}, rebuild of prev-prev-block({:?}) failed, {:?}." + , self, prev_prev_block_id, err); + err + })?; + } + None => { + log::warn!("[hotstuff] local: {:?}, rebuild result-state-id({:?}) of prev-block({:?}) failed, {:?}." + , self, result_state_id, prev_block.block_id(), err); + return Err(err); + } + } + } + } + result_state_id.clone() + } + None => None, + }; + + for proposal_exe_info in block.proposals() { + // 去重 + if let Some(prev_block_id) = block.prev_block_id() { + let is_already_finished = self.store + .is_proposal_finished(&proposal_exe_info.proposal, prev_block_id) + .await.map_err(|err| { + log::warn!("[hotstuff] local: {:?}, check proposal {:?} in block {:?} with prev-block {:?} duplicate failed, {:?}." + , self, proposal_exe_info.proposal, block.block_id(), prev_block_id, err); + err + })?; + + if is_already_finished { + log::warn!("[hotstuff] local: {:?}, proposal {:?} in block {:?} with prev-block {:?} has finished before." + , self, proposal_exe_info.proposal, block.block_id(), prev_block_id); + + return Err(BuckyError::new( + BuckyErrorCode::ErrorState, + "duplicate proposal", + )); + } + } + + let proposal = proposals.get(&proposal_exe_info.proposal).unwrap(); + let receipt = match proposal_exe_info.receipt.as_ref() { + Some(receipt) => { + let (receipt, _) = NONObjectInfo::raw_decode(receipt.as_slice()).map_err(|err| { + log::warn!("[hotstuff] local: {:?}, proposal {:?} in block {:?} decode receipt failed {:?}." + , self, proposal_exe_info.proposal, block.block_id(), err); + + err + })?; + + Some(receipt) + } + None => None, + }; + + let exe_result = ExecuteResult { + result_state_id: proposal_exe_info.result_state, + receipt, + context: proposal_exe_info.context.clone(), + }; + + self + .event_notifier + .on_verify(proposal.clone(), prev_state_id, &exe_result) + .await.map_err(|err| { + log::warn!("[hotstuff] local: {:?}, proposal {:?} in block {:?} verify by app failed {:?}." + , self, proposal_exe_info.proposal, block.block_id(), err); + err + })?; + + log::debug!( + "[hotstuff] local: {:?}, block verify ok by app, proposal: {}, prev_state: {:?}/{:?}, expect-result: {:?}/{:?}", + self, + proposal_exe_info.proposal, + prev_state_id, prev_block.as_ref().map(|b| b.block_id()), + proposal_exe_info.result_state, + block.block_id() + ); + + prev_state_id = proposal_exe_info.result_state; + } + + assert_eq!( + &prev_state_id, + block.result_state_id(), + "the result state is unmatched" + ); + + Ok(()) + } + + async fn get_leader_owner( + &self, + group_shell_id: Option<&ObjectId>, + round: u64, + remote: Option<&ObjectId>, + ) -> BuckyResult { + let leader = self + .committee + .get_leader(group_shell_id, round, remote) + .await.map_err(|err| { + log::warn!( + "[hotstuff] local: {:?}, get leader from group {:?} with round {} failed, {:?}.", + self, + group_shell_id, round, + err + ); + + err + })?; + + let leader_owner = self + .non_driver + .get_device(&leader) + .await + .map_err(|err| { + log::warn!( + "[hotstuff] local: {:?}, get leader by id {:?} failed, {:?}.", + self, + leader, + err + ); + + err + })? + .desc() + .owner() + .clone(); + + match leader_owner { + Some(owner) => Ok(owner), + None => { + log::warn!( + "[hotstuff] local: {:?}, a owner must be set to the device {}", + self, + leader + ); + Err(BuckyError::new( + BuckyErrorCode::InvalidTarget, + "no owner for device", + )) + } + } + } + + async fn check_block_linked( + &mut self, + block: &GroupConsensusBlock, + remote: ObjectId, + ) -> Result, BuckyResult<()>> { + match self + .store + .block_linked(block) + .await + .map_err(|err| Err(err))? + { + crate::storage::BlockLinkState::Expired => { + log::warn!( + "[hotstuff] local: {:?}, receive block({}) expired.", + self, + block.block_id() + ); + Err(Err(BuckyError::new(BuckyErrorCode::Ignored, "expired"))) + } + crate::storage::BlockLinkState::Duplicate => { + log::warn!( + "[hotstuff] local: {:?}, receive duplicate block({}).", + self, + block.block_id() + ); + Err(Err(BuckyError::new( + BuckyErrorCode::AlreadyExists, + "duplicate block", + ))) + } + crate::storage::BlockLinkState::Link(prev_block) => { + log::debug!( + "[hotstuff] local: {:?}, receive in-order block({}), height: {}.", + self, + block.block_id(), + block.height() + ); + + // 顺序连接状态 + Self::check_empty_block_result_state_with_prev(block, &prev_block) + .map_err(|err| Err(err))?; + Ok(prev_block) + } + crate::storage::BlockLinkState::Pending => { + log::warn!( + "[hotstuff] local: {:?}, receive out-order block({}), expect height: {}, get height: {}.", + self, + block.block_id(), + self.store.header_height() + 3, + block.height() + ); + + self.sync_to_block(block, remote).await; + + Err(Ok(())) + } + crate::storage::BlockLinkState::InvalidBranch => { + log::warn!( + "[hotstuff] local: {:?}, receive block({}) in invalid branch.", + self, + block.block_id() + ); + Err(Err(BuckyError::new( + BuckyErrorCode::Conflict, + "conflict branch", + ))) + } + } + } + + async fn process_block( + &mut self, + block: &GroupConsensusBlock, + remote: ObjectId, + proposals: &HashMap, + ) -> BuckyResult<()> { + log::debug!("[hotstuff] local: {:?}, process_block: {:?}/{:?}/{:?}, prev: {:?}/{:?}, owner: {:?}, remote: {:?},", + self, + block.block_id(), block.round(), block.height(), + block.prev_block_id(), block.qc().as_ref().map_or(0, |qc| qc.round), + block.owner(), remote); + + if block.height() <= self.store.header_height() { + log::warn!( + "[hotstuff] local: {:?}, handle_block: {:?}/{:?}/{:?} ignored for expired.", + self, + block.block_id(), + block.round(), + block.height(), + ); + + return Err(BuckyError::new(BuckyErrorCode::Expired, "block expired")); + } + + /** + * 验证过的块执行这个函数 + */ + if let Err(err) = self.non_driver.put_block(block).await { + if err.code() != BuckyErrorCode::AlreadyExists + && err.code() != BuckyErrorCode::NotChange + { + log::warn!( + "[hotstuff] local: {:?}, put new block {:?}/{}/{} to noc", + self, + block.block_id(), + block.height(), + block.round() + ); + + return Err(err); + } + } + + log::info!( + "[hotstuff] local: {:?}, will push new block {:?}/{}/{} to storage", + self, + block.block_id(), + block.height(), + block.round() + ); + + let debug_identify = self.debug_identify(); + let new_header_block = self.store.push_block(block.clone()).await.map_err(|err| { + log::warn!( + "[hotstuff] local: {:?}, push verified block {:?} to storage failed {:?}", + debug_identify, + block.block_id(), + err + ); + + err + })?; + + if let Some((header_block, old_header_block, _discard_blocks)) = new_header_block { + let header_block = header_block.clone(); + self.on_new_block_commit(&header_block, &old_header_block, block) + .await; + } + + match self.vote_mgr.add_voting_block(block).await { + VoteThresholded::QC(qc) => { + log::debug!( + "[hotstuff] local: {:?}, the qc of block {:?} has received before", + self, + block.block_id() + ); + return self.process_block_qc(qc, block, &remote).await; + } + VoteThresholded::None => {} + } + + log::debug!( + "[hotstuff] local: {:?}, process_block-step4 {:?}", + self, + block.block_id() + ); + + if block.round() != self.round { + log::debug!( + "[hotstuff] local: {:?}, not my round {}, expect {}", + self, + block.round(), + self.round + ); + // 不是我的投票round + return Ok(()); + } + + if let Some(vote) = self.make_vote(block, proposals, &remote).await { + log::info!( + "[hotstuff] local: {:?}, vote to block {}, round: {}", + self, + block.block_id(), + block.round() + ); + + let next_leader = self + .committee + .get_leader(None, self.round + 1, None) + .await + .map_err(|err| { + log::warn!( + "[hotstuff] local: {:?}, get next leader in round {} failed {:?}", + self, + self.round + 1, + err + ); + + err + })?; + + if self.local_device_id == next_leader { + self.handle_vote(&vote, Some(block), self.local_device_id) + .await?; + } else { + self.network_sender + .post_message( + HotstuffMessage::BlockVote(vote), + self.rpath.clone(), + &next_leader, + ) + .await; + } + } + + Ok(()) + } + + async fn on_new_block_commit( + &mut self, + new_header_block: &GroupConsensusBlock, + old_header_block: &Option, + qc_qc_block: &GroupConsensusBlock, + ) { + log::info!( + "[hotstuff] local: {:?}, new header-block {:?} committed, old: {:?}, qc-qc: {}", + self, + new_header_block.block_id(), + old_header_block.as_ref().map(|b| b.block_id()), + qc_qc_block.block_id() + ); + + if new_header_block.height() <= self.max_quorum_height - 2 { + log::info!( + "[hotstuff] local: {:?}, new header-block {:?} committed, old: {:?}, qc-qc: {}, ignore notify history block({}/{})", + self, new_header_block.block_id(), old_header_block.as_ref().map(|b| b.block_id()), qc_qc_block.block_id(), new_header_block.height(), self.max_quorum_height + ); + return; + } + + /** + * 这里只清理已经提交的block包含的proposal + * 已经执行过的待提交block包含的proposal在下次打包时候去重 + * */ + self.cleanup_proposal(new_header_block).await; + + log::debug!( + "[hotstuff] local: {:?}, on_new_block_commit-step1 {:?}", + self, + qc_qc_block.block_id() + ); + + let (_, qc_block) = self + .store + .pre_commits() + .iter() + .next() + .expect("the pre-commit block must exist."); + + self.notify_block_committed(new_header_block, old_header_block, qc_block) + .await; + + log::debug!( + "[hotstuff] local: {:?}, on_new_block_commit-step2 {:?}", + self, + qc_qc_block.block_id() + ); + + // notify by the block generator + if &self.local_id == new_header_block.owner() { + // push to member + self.state_pusher + .notify_block_commit(new_header_block.clone(), qc_block.clone()) + .await; + + // reply + let futs = new_header_block.proposals().iter().map(|proposal_info| { + let receipt = match proposal_info.receipt.as_ref() { + Some(receipt) => { + NONObjectInfo::raw_decode(receipt.as_slice()).map(|(receipt, remain)| { + assert_eq!(remain.len(), 0); + Some(receipt) + }) + } + None => Ok(None), + }; + self.proposal_result_notifier + .reply(&proposal_info.proposal, receipt) + }); + + futures::future::join_all(futs).await; + } + + log::debug!( + "[hotstuff] local: {:?}, on_new_block_commit-step3 {:?}", + self, + qc_qc_block.block_id() + ); + } + + async fn notify_block_committed( + &self, + new_header: &GroupConsensusBlock, + old_header_block: &Option, + qc_block: &GroupConsensusBlock, + ) -> BuckyResult<()> { + assert_eq!( + new_header.prev_block_id(), + old_header_block.as_ref().map(|b| b.block_id().object_id()) + ); + + if let Some(result_state_id) = new_header.result_state_id() { + self.make_sure_result_state(result_state_id, &[new_header.owner()]) + .await?; + } + + let prev_state_id = match old_header_block.as_ref() { + Some(old_header_block) => { + let result_state_id = old_header_block.result_state_id(); + if let Some(result_state_id) = result_state_id { + self.make_sure_result_state(result_state_id, &[old_header_block.owner()]) + .await?; + } + result_state_id.clone() + } + None => None, + }; + + self.event_notifier + .on_commited(prev_state_id, new_header.clone()) + .await; + + Ok(()) + } + + async fn process_qc(&mut self, qc: &Option) { + let qc_round = qc.as_ref().map_or(0, |qc| qc.round); + + log::debug!( + "[hotstuff] local: {:?}, process_qc round {}", + self, + qc_round + ); + + self.update_max_quorum_round(qc_round); + self.advance_round(qc_round).await; + self.update_high_qc(qc); + } + + async fn advance_round(&mut self, round: u64) { + if round < self.round { + log::debug!( + "[hotstuff] local: {:?}, round {} timeout expect {}", + self, + round, + self.round + ); + return; + } + + log::info!( + "[hotstuff] local: {:?}, update round from {} to {}", + self, + self.round, + round + 1 + ); + + self.timer.reset(GROUP_DEFAULT_CONSENSUS_INTERVAL); + self.round = round + 1; + self.vote_mgr.cleanup(self.round); + self.tc = None; + } + + fn update_high_qc(&mut self, qc: &Option) { + let to_high_round = qc.as_ref().map_or(0, |qc| qc.round); + let cur_high_round = self.high_qc.as_ref().map_or(0, |qc| qc.round); + if to_high_round > cur_high_round { + self.high_qc = qc.clone(); + + log::info!( + "[hotstuff] local: {:?}, update high-qc from {} to {}", + self, + cur_high_round, + to_high_round + ); + } + } + + fn update_max_quorum_round(&mut self, quorum_round: u64) { + if quorum_round > self.max_quorum_round { + self.max_quorum_round = quorum_round; + } + } + + fn update_max_quorum_height(&mut self, quorum_height: u64) { + if quorum_height > self.max_quorum_height { + self.max_quorum_height = quorum_height; + } + } + + async fn cleanup_proposal(&mut self, commited_block: &GroupConsensusBlock) -> BuckyResult<()> { + let proposals = commited_block + .proposals() + .iter() + .map(|proposal| proposal.proposal) + .collect::>(); + + log::debug!( + "[hotstuff] local: {:?}, remove proposals: {:?}", + self, + proposals.len() + ); + + self.proposal_consumer.remove_proposals(proposals).await + } + + async fn notify_proposal_err(&self, proposal: &GroupProposal, err: BuckyError) { + log::debug!( + "[hotstuff] local: {:?}, proposal {} failed {:?}", + self, + proposal.desc().object_id(), + err + ); + + self.proposal_result_notifier + .reply(&proposal.desc().object_id(), Err(err.clone())) + .await; + + self.state_pusher + .notify_proposal_err(proposal.clone(), err) + .await; + } + + async fn make_vote( + &mut self, + block: &GroupConsensusBlock, + mut proposals: &HashMap, + remote: &ObjectId, + ) -> Option { + log::debug!( + "[hotstuff] local: {:?} make vote {} step 0", + self, + block.block_id() + ); + + if block.round() <= self.store.last_vote_round() { + log::debug!("[hotstuff] local: {:?}, make vote ignore for timeouted block {}/{}, last vote roud: {}", + self, block.block_id(), block.round(), self.store.last_vote_round()); + + return None; + } + + let mut only_rebuild_result_state = false; + if self.max_quorum_height >= self.store.max_height() { + if let Some(result_state_id) = block.result_state_id() { + // `make_sure_result_state` will rebuild result-state by downloading from remote. + if self + .make_sure_result_state(result_state_id, &[block.owner(), remote]) + .await + .is_err() + { + // download from remote failed, we need to calcute the result-state by the DEC.on_verify + only_rebuild_result_state = true; + } + } + + if !only_rebuild_result_state { + log::debug!("[hotstuff] local: {:?}, make vote ignore for the block {}/{} has enough votes {}/{}.", + self, block.block_id(), block.round(), self.max_quorum_round, self.round); + + return None; + } + } + + let prev_block = match block.prev_block_id() { + Some(prev_block_id) => match self.store.find_block_in_cache(prev_block_id) { + Ok(block) => Some(block), + Err(_) => { + log::warn!("[hotstuff] local: {:?}, make vote to block {} ignore for prev-block {:?} is invalid", + self, + block.block_id(), + block.prev_block_id() + ); + + return None; + } + }, + None => None, + }; + + // select the highest branch to vote. + if block.height() != self.store.max_height() { + log::warn!( + "[hotstuff] local: {:?}, make vote to block {} ignore for higher({}!={}) branch.", + self, + block.block_id(), + block.height(), + self.store.max_height(), + ); + + return None; + } + + // `round` must be increased one by one + let qc_round = block.qc().as_ref().map_or(0, |qc| qc.round); + if qc_round != prev_block.as_ref().map_or(0, |p| p.round()) { + log::warn!("[hotstuff] local: {:?}, make vote to block {} ignore for qc-round({}) is unmatch with prev-block({}/{:?})", + self, + block.block_id(), + qc_round, + prev_block.as_ref().map_or(0, |p| p.round()), + block.prev_block_id() + ); + return None; + } + + let is_valid_round = if block.round() == qc_round + 1 { + true + } else if let Some(tc) = block.tc() { + block.round() == tc.round + 1 + // && qc_round + // >= tc.votes.iter().map(|v| v.high_qc_round).max().unwrap() + // maybe some block timeout happened, the leaders has the larger round QC, but not broadcast to others + } else { + false + }; + + if !is_valid_round { + log::warn!("[hotstuff] local: {:?}, make vote to block {} ignore for invalid round {}, qc-round {}, tc-round {:?}", + self, + block.block_id(), + block.round(), qc_round, + block.tc().as_ref().map_or((0, 0), |tc| { + let qc_round = tc.votes.iter().map(|v| v.high_qc_round).max().unwrap(); + (tc.round, qc_round) + })); + + return None; + } + + log::debug!( + "[hotstuff] local: {:?} make vote {} step 1", + self, + block.block_id() + ); + + if !only_rebuild_result_state { + match self.check_group_is_latest(block.group_shell_id()).await { + Ok(is_latest) if is_latest => {} + _ => { + log::warn!("[hotstuff] local: {:?}, make vote to block {} ignore for the group is not latest", + self, + block.block_id()); + + return None; + } + } + } + + log::debug!( + "[hotstuff] local: {:?} make vote {} step 2", + self, + block.block_id() + ); + + let mut proposal_temp: HashMap = HashMap::new(); + if proposals.len() == 0 && block.proposals().len() > 0 { + match self + .non_driver + .load_all_proposals_for_block(block, &mut proposal_temp) + .await + { + Ok(_) => proposals = &proposal_temp, + Err(err) => { + log::warn!("[hotstuff] local: {:?}, make vote to block {} ignore for load proposals failed {:?}", + self, + block.block_id(), + err + ); + return None; + } + } + } else { + assert_eq!(proposals.len(), block.proposals().len()); + } + + log::debug!( + "[hotstuff] local: {:?} make vote {} step 3", + self, + block.block_id() + ); + + // 时间和本地误差太大,不签名,打包的proposal时间和block时间差距太大,也不签名 + if !only_rebuild_result_state + && !Self::check_timestamp_precision(block, prev_block.as_ref(), proposals) + { + log::warn!( + "[hotstuff] local: {:?}, make vote to block {} ignore for timestamp mismatch", + self, + block.block_id(), + ); + return None; + } + + if !only_rebuild_result_state && proposals.len() != block.proposals().len() { + let mut dup_proposals = block.proposals().clone(); + dup_proposals.sort_unstable_by_key(|p| p.proposal); + log::warn!( + "[hotstuff] local: {:?}, make vote to block {} ignore for proposals {:?} duplicate", + self, + block.block_id(), + dup_proposals + .iter() + .group_by(|p| p.proposal) + .into_iter() + .map(|g| (g.0, g.1.count())) + .filter(|g| g.1 > 1) + .map(|g| g.0) + .collect_vec() + ); + return None; + } + + log::debug!( + "[hotstuff] local: {:?} make vote {} step 4", + self, + block.block_id() + ); + + // 1. verify the results for proposals by DecApp + // 2. rebuild the result-state in on_verify by DecApp + if let Err(err) = self + .check_block_proposal_result_state_by_app(block, &proposals, &prev_block, remote) + .await + { + log::warn!( + "[hotstuff] local: {:?}, make vote to block {} ignore for app verify failed {:?}", + self, + block.block_id(), + err + ); + return None; + } + + if only_rebuild_result_state { + log::debug!("[hotstuff] local: {:?}, make vote ignore for the block {}/{} has enough votes {}/{} rebuild only.", + self, block.block_id(), block.round(), self.max_quorum_round, self.round); + return None; + } + + log::debug!( + "[hotstuff] local: {:?}, make-vote before sign {}, round: {}", + self, + block.block_id(), + block.round() + ); + + let vote = match HotstuffBlockQCVote::new(block, self.local_device_id, &self.signer).await { + Ok(vote) => { + log::debug!( + "[hotstuff] local: {:?}, make-vote after sign {}, round: {}", + self, + block.block_id(), + block.round() + ); + + vote + } + Err(e) => { + log::warn!( + "[hotstuff] local: {:?}, signature for block-vote failed, block: {}, err: {}", + self, + block.block_id(), + e + ); + return None; + } + }; + + if let Err(err) = self.store.set_last_vote_round(block.round()).await { + log::warn!("[hotstuff] local: {:?}, make vote to block {} ignore for update last-vote-round failed {:?}", + self, + block.block_id(), + err + ); + return None; + } + + log::debug!( + "[hotstuff] local: {:?} make vote {} step 5", + self, + block.block_id() + ); + + Some(vote) + } + + fn check_timestamp_precision( + block: &GroupConsensusBlock, + prev_block: Option<&GroupConsensusBlock>, + proposals: &HashMap, + ) -> bool { + let now = SystemTime::now(); + let block_timestamp = bucky_time_to_system_time(block.named_object().desc().create_time()); + if Self::calc_time_delta(now, block_timestamp) > TIME_PRECISION { + log::warn!( + "[hotstuff] block {} check timestamp {:?} failed with now {:?}", + block.block_id(), + block_timestamp, + now + ); + + false + } else { + if let Some(prev_block) = prev_block { + let prev_block_time = + bucky_time_to_system_time(prev_block.named_object().desc().create_time()); + if let Ok(duration) = prev_block_time.duration_since(block_timestamp) { + if duration > TIME_PRECISION { + log::warn!( + "[hotstuff] block {} check timestamp {:?} failed with prev-block {:?}", + block.block_id(), + block_timestamp, + prev_block_time + ); + return false; + } + } + } + + for proposal in block.proposals() { + let proposal_id = proposal.proposal; + let proposal = proposals + .get(&proposal_id) + .expect("should load all proposals"); + let proposal_timestamp = bucky_time_to_system_time(proposal.desc().create_time()); + if Self::calc_time_delta(block_timestamp, proposal_timestamp) > TIME_PRECISION { + log::warn!( + "[hotstuff] block {} check timestamp {:?} failed with proposal({:?}) {:?}", + block.block_id(), + block_timestamp, + proposal_id, + proposal_timestamp + ); + return false; + } + } + true + } + } + + fn calc_time_delta(t1: SystemTime, t2: SystemTime) -> Duration { + t1.duration_since(t2).or(t2.duration_since(t1)).unwrap() + } + + async fn handle_vote( + &mut self, + vote: &HotstuffBlockQCVote, + prev_block: Option<&GroupConsensusBlock>, + remote: ObjectId, + ) -> BuckyResult<()> { + log::debug!("[hotstuff] local: {:?}, handle_vote: {:?}/{:?}, prev: {:?}, voter: {:?}, remote: {:?},", + self, + vote.block_id, vote.round, + vote.prev_block_id, + vote.voter, remote); + + if vote.round < self.round { + log::warn!( + "[hotstuff] local: {:?}, receive timeout vote({}/{}/{:?}), local-round: {}", + self, + vote.block_id, + vote.round, + vote.prev_block_id, + self.round + ); + return Ok(()); + } + + self.committee.verify_vote(vote).await.map_err(|err| { + log::warn!( + "[hotstuff] local: {:?}, verify vote({}/{}/{:?}) failed {:?}", + self, + vote.block_id, + vote.round, + vote.prev_block_id, + err + ); + err + })?; + + let prev_block = match prev_block { + Some(b) => Some(b.clone()), + None => self + .store + .find_block_in_cache(&vote.block_id) + .map_or(None, |b| Some(b)), + }; + + let is_prev_none = prev_block.is_none(); + let qc = self + .vote_mgr + .add_vote(vote.clone(), prev_block) + .await + .map_err(|err| { + log::warn!( + "[hotstuff] local: {:?}, add vote({}/{}/{:?}) prev-block: {} failed {:?}", + self, + vote.block_id, + vote.round, + vote.prev_block_id, + if is_prev_none { "None" } else { "Some" }, + err + ); + err + })?; + + if let Some((qc, block)) = qc { + log::info!( + "[hotstuff] local: {:?}, vote({}/{}/{:?}) prev-block: {} qc", + self, + vote.block_id, + vote.round, + vote.prev_block_id, + if is_prev_none { "None" } else { "Some" } + ); + + self.process_block_qc(qc, &block, &remote).await?; + } else if vote.round > self.round && is_prev_none { + self.fetch_block(&vote.block_id, remote).await?; + } + Ok(()) + } + + async fn process_block_qc( + &mut self, + qc: HotstuffBlockQC, + prev_block: &GroupConsensusBlock, + remote: &ObjectId, + ) -> BuckyResult<()> { + let qc_block_id = qc.block_id; + let qc_round = qc.round; + let qc_prev_block_id = qc.prev_block_id; + + log::debug!("[hotstuff] local: {:?}, save-qc round {}", self, qc_round); + + self.store.save_qc(&qc).await?; + + self.process_qc(&Some(qc)).await; + + self.update_max_quorum_height(prev_block.height()); + + let new_leader = self.committee.get_leader(None, self.round, Some(remote)).await.map_err(|err| { + log::warn!( + "[hotstuff] local: {:?}, get leader for vote-qc({}/{}/{:?}) with round {} failed {:?}", + self, + qc_block_id, qc_round, qc_prev_block_id, + self.round, + err + ); + err + })?; + + if self.local_device_id == new_leader { + self.generate_block(self.with_tc()).await; + } + Ok(()) + } + + async fn handle_timeout( + &mut self, + timeout: &HotstuffTimeoutVote, + remote: ObjectId, + ) -> BuckyResult<()> { + log::debug!( + "[hotstuff] local: {:?}, handle_timeout: {:?}, qc: {:?}, voter: {:?}, remote: {:?},", + self, + timeout.round, + timeout.high_qc.as_ref().map(|qc| format!( + "{:?}/{:?}/{:?}/{:?}", + qc.block_id, + qc.round, + qc.prev_block_id, + qc.votes + .iter() + .map(|v| v.voter.to_string()) + .collect::>() + )), + timeout.voter, + remote + ); + + if timeout.round < self.round { + if let Some(tc) = self.tc.as_ref() { + // if there is a timeout-qc, notify the remote to advance the round + if tc.round + 1 == self.round { + self.network_sender + .post_message( + HotstuffMessage::Timeout(tc.clone()), + self.rpath.clone(), + &remote, + ) + .await; + } + } + return Ok(()); + } + + let high_qc_round = timeout.high_qc.as_ref().map_or(0, |qc| qc.round); + if high_qc_round >= timeout.round { + log::warn!( + "[hotstuff] local: {:?}, handle_timeout: {:?}, ignore for high-qc(round={}) invalid", + self, + timeout.round, + high_qc_round + ); + return Ok(()); + } + + match self.check_group_is_latest(&timeout.group_shell_id).await { + Ok(is) if is => {} + _ => { + log::warn!( + "[hotstuff] local: {:?}, handle_timeout: {:?}, ignore for is not latest group.", + self, + timeout.round, + ); + return Ok(()); + } + } + + let block = match timeout.high_qc.as_ref() { + Some(qc) => match self.store.find_block_in_cache(&qc.block_id) { + Ok(block) => Some(block), + Err(err) => { + log::warn!( + "[hotstuff] local: {:?}, handle_timeout: {:?}, find qc-block {} failed {:?}", + self, + timeout.round, + qc.block_id, + err + ); + + self.fetch_block(&qc.block_id, remote).await; + return Ok(()); + } + }, + None => None, + }; + + self.committee + .verify_timeout(timeout, block.as_ref()) + .await + .map_err(|err| { + log::warn!( + "[hotstuff] local: {:?}, handle_timeout: {:?}, verify failed {:?}", + self, + timeout.round, + err + ); + + err + })?; + + self.process_qc(&timeout.high_qc).await; + + let tc = self + .vote_mgr + .add_timeout(timeout.clone()) + .await + .map_err(|err| { + log::warn!( + "[hotstuff] local: {:?}, handle_timeout: {:?}, check tc failed {:?}", + self, + timeout.round, + err + ); + err + })?; + + if let Some(tc) = tc { + self.process_timeout_qc(tc, &remote).await?; + } + Ok(()) + } + + async fn process_timeout_qc( + &mut self, + tc: HotstuffTimeout, + remote: &ObjectId, + ) -> BuckyResult<()> { + log::debug!( + "[hotstuff] local: {:?}, process_timeout_qc: {:?}, voter: {:?}.", + self, + tc.round, + tc.votes + .iter() + .map(|vote| format!("{:?}/{:?}", vote.high_qc_round, vote.voter,)) + .collect::>(), + ); + + let quorum_round = tc.round; + self.update_max_quorum_round(quorum_round); + + self.store + .save_tc( + &tc, + tc.group_shell_id + .expect("group-shell-id should not be None."), + ) + .await?; + + self.advance_round(tc.round).await; + self.tc = Some(tc.clone()); + + log::debug!("[hotstuff] local: {:?}, save-tc round {}", self, tc.round); + + let new_leader = self + .committee + .get_leader(None, self.round, Some(remote)) + .await + .map_err(|err| { + log::warn!( + "[hotstuff] local: {:?}, process_timeout_qc: {:?}, get new leader failed {:?}", + self, + tc.round, + err + ); + + err + })?; + if self.local_device_id == new_leader { + self.generate_block(Some(tc)).await; + Ok(()) + } else { + let (latest_group, latest_shell_id) = self.shell_mgr.group(); + + self.broadcast(HotstuffMessage::Timeout(tc), &latest_group) + } + } + + async fn handle_tc(&mut self, tc: &HotstuffTimeout, remote: ObjectId) -> BuckyResult<()> { + let max_high_qc = tc + .votes + .iter() + .max_by(|high_qc_l, high_qc_r| high_qc_l.high_qc_round.cmp(&high_qc_r.high_qc_round)); + + log::debug!( + "[hotstuff] local: {:?}, handle_tc: {:?}, voter: {:?}, remote: {:?}, max-qc: {:?}", + self, + tc.round, + tc.votes + .iter() + .map(|vote| format!("{:?}/{:?}", vote.high_qc_round, vote.voter,)) + .collect::>(), + remote, + max_high_qc.as_ref().map(|qc| qc.high_qc_round) + ); + + let max_high_qc = match max_high_qc { + Some(max_high_qc) => max_high_qc, + None => return Ok(()), + }; + + if tc.round < self.round { + log::warn!( + "[hotstuff] local: {:?}, handle_tc: {:?} ignore for round timeout", + self, + tc.round, + ); + return Ok(()); + } + + if max_high_qc.high_qc_round >= tc.round { + log::warn!( + "[hotstuff] local: {:?}, handle_tc: {:?} ignore for high-qc round {} invalid", + self, + tc.round, + max_high_qc.high_qc_round + ); + + return Ok(()); + } + + let group_shell_id = match tc.group_shell_id.as_ref() { + Some(group_shell_id) => group_shell_id.clone(), + None => { + log::warn!( + "[hotstuff] local: {:?}, handle_tc: {:?} ignore for group-shell-id is None.", + self, + tc.round + ); + return Ok(()); + } + }; + + if max_high_qc.high_qc_round > 0 { + if let Err(err) = self + .store + .find_block_in_cache_by_round(max_high_qc.high_qc_round) + { + log::warn!( + "[hotstuff] local: {:?}, handle_tc: {:?} find prev-block by round {} failed {:?}", + self, + tc.round, max_high_qc.high_qc_round, + err + ); + + // 同步前序block + let max_round_block = self.store.block_with_max_round(); + self.synchronizer.sync_with_round( + max_round_block.map_or(1, |block| block.height() + 1), + max_high_qc.high_qc_round, + remote, + ); + }; + } + + self.committee + .verify_tc(tc, &group_shell_id) + .await + .map_err(|err| { + log::warn!( + "[hotstuff] local: {:?}, handle_tc: {:?} verify tc failed {:?}", + self, + tc.round, + err + ); + err + })?; + + log::debug!("[hotstuff] local: {:?}, save-tc round {}", self, tc.round); + + let quorum_round = tc.round; + self.update_max_quorum_round(quorum_round); + + self.store.save_tc(&tc, group_shell_id).await?; + + self.advance_round(tc.round).await; + self.tc = Some(tc.clone()); + + let new_leader = self + .committee + .get_leader(None, self.round, Some(&remote)) + .await + .map_err(|err| { + log::warn!( + "[hotstuff] local: {:?}, handle_tc: {:?} get new leader failed {:?}", + self, + tc.round, + err + ); + err + })?; + + if self.local_device_id == new_leader { + self.generate_block(Some(tc.clone())).await; + } + Ok(()) + } + + async fn local_timeout_round(&mut self) -> BuckyResult<()> { + log::debug!( + "[hotstuff] local: {:?}, local_timeout_round, max_quorum_height: {}, max_height: {}", + self, + self.max_quorum_height, + self.store.max_height() + ); + + if self.is_synchronizing() { + log::info!("[hotstuff] local: {:?}, local_timeout_round, is synchronizing, ignore the timeout. max_quorum_height: {}, max_height: {}", self, self.max_quorum_height, self.store.max_height()); + return Ok(()); + } + + let latest_group = match self + .shell_mgr + .get_group(self.rpath.group_id(), None, None) + .await + { + Ok(group) => { + self.timer.reset(GROUP_DEFAULT_CONSENSUS_INTERVAL); + group + } + Err(err) => { + log::warn!( + "[hotstuff] local: {:?}, local_timeout_round get latest group failed {:?}", + self, + err + ); + + self.timer.reset(HOTSTUFF_TIMEOUT_DEFAULT); + return Err(err); + } + }; + + log::debug!( + "[hotstuff] local: {:?}, local_timeout_round, latest group got.", + self, + ); + + let latest_group_shell = latest_group.to_shell(); + let latest_group_shell_id = latest_group_shell.shell_id(); + let timeout = HotstuffTimeoutVote::new( + latest_group_shell_id, + self.high_qc.clone(), + self.round, + self.local_device_id, + &self.signer, + ) + .await + .map_err(|err| { + log::warn!( + "[hotstuff] local: {:?}, local_timeout_round create new timeout-vote failed {:?}", + self, + err + ); + err + })?; + + log::debug!( + "[hotstuff] local: {:?}, local_timeout_round, vote for timeout created.", + self, + ); + + let ret = self.store.set_last_vote_round(self.round).await; + log::debug!("[hotstuff] local: {:?}, local_timeout_round, round last vote is stored. round = {}, result: {:?}", self, self.round, ret); + ret?; + + self.broadcast(HotstuffMessage::TimeoutVote(timeout.clone()), &latest_group); + + log::debug!( + "[hotstuff] local: {:?}, local_timeout_round, broadcast.", + self, + ); + + self.tx_message + .send((HotstuffMessage::TimeoutVote(timeout), self.local_device_id)) + .await; + + Ok(()) + } + + fn is_synchronizing(&self) -> bool { + self.max_quorum_height > self.store.max_height() + BLOCK_COUNT_REST_TO_SYNC + } + + async fn generate_block(&mut self, tc: Option) -> BuckyResult<()> { + let now = SystemTime::now(); + + log::debug!( + "[hotstuff] local: {:?}, generate_block with qc {:?} and tc {:?}, now: {:?}", + self, + self.high_qc.as_ref().map(|qc| format!( + "{}/{}/{:?}", + qc.block_id, + qc.round, + qc.votes.iter().map(|v| v.voter).collect::>() + )), + tc.as_ref().map(|tc| format!( + "{}/{:?}", + tc.round, + tc.votes.iter().map(|v| v.voter).collect::>() + )), + now + ); + + let mut proposals = self + .proposal_consumer + .query_proposals() + .await + .map_err(|err| { + log::warn!( + "[hotstuff] local: {:?}, generate_block query proposal failed {:?}", + self, + err + ); + err + })?; + + proposals.sort_by(|left, right| left.desc().create_time().cmp(&right.desc().create_time())); + + let prev_block = match self.high_qc.as_ref() { + Some(qc) => { + let prev_block = self.store.find_block_in_cache(&qc.block_id)?; + if let Some(result_state_id) = prev_block.result_state_id() { + self.make_sure_result_state(result_state_id, &[prev_block.owner()]) + .await?; + } + Some(prev_block) + } + None => None, + }; + let latest_group = self + .shell_mgr + .get_group(self.rpath.group_id(), None, None) + .await + .map_err(|err| { + log::warn!( + "[hotstuff] local: {:?}, generate_block get latest group failed {:?}", + self, + err + ); + + err + })?; + + let mut remove_proposals = vec![]; + // let mut dup_proposals = vec![]; + let mut time_adjust_proposals = vec![]; + let mut timeout_proposals = vec![]; + let mut executed_proposals = vec![]; + let mut failed_proposals = vec![]; + let mut result_state_id = match prev_block.as_ref() { + Some(block) => block.result_state_id().clone(), + None => self.store.dec_state_id().clone(), + }; + + // TODO: The time may be too long for too many proposals + for proposal in proposals { + let proposal_id = proposal.desc().object_id(); + if let Some(high_qc) = self.high_qc.as_ref() { + if let Ok(is_finished) = self + .store + .is_proposal_finished(&proposal_id, &high_qc.block_id) + .await + { + if is_finished { + // dup_proposals.push(proposal); + remove_proposals.push(proposal_id); + continue; + } + } + } + + let create_time = bucky_time_to_system_time(proposal.desc().create_time()); + if Self::calc_time_delta(now, create_time) > TIME_PRECISION { + // 时间误差太大 + remove_proposals.push(proposal.desc().object_id()); + time_adjust_proposals.push(proposal); + continue; + } + + let ending = proposal + .effective_ending() + .map_or(now.checked_add(PROPOSAL_MAX_TIMEOUT).unwrap(), |ending| { + bucky_time_to_system_time(ending) + }); + if now >= ending { + remove_proposals.push(proposal.desc().object_id()); + timeout_proposals.push(proposal); + continue; + } + + match self + .event_notifier + .on_execute(proposal.clone(), result_state_id) + .await + { + Ok(exe_result) => { + result_state_id = exe_result.result_state_id; + executed_proposals.push((proposal, exe_result)); + } + Err(e) => { + remove_proposals.push(proposal_id); + failed_proposals.push((proposal, e)); + } + }; + } + + self.notify_adjust_time_proposals(time_adjust_proposals) + .await; + self.notify_timeout_proposals(timeout_proposals).await; + self.notify_failed_proposals(failed_proposals).await; + self.remove_pending_proposals(remove_proposals).await; + + if self + .try_wait_proposals(executed_proposals.len(), &prev_block) + .await + { + log::debug!( + "[hotstuff] local: {:?}, generate_block empty block, will ignore", + self, + ); + return Ok(()); + } + + let proposals_map = HashMap::from_iter( + executed_proposals + .iter() + .map(|(proposal, _)| (proposal.desc().object_id(), proposal.clone())), + ); + + let block = self + .package_block_with_proposals( + executed_proposals, + &latest_group, + result_state_id, + &prev_block, + tc, + ) + .await?; + + self.broadcast(HotstuffMessage::Block(block.clone()), &latest_group); + self.tx_block_gen.send((block, proposals_map)).await; + + self.rx_proposal_waiter = None; + Ok(()) + } + + async fn notify_adjust_time_proposals(&self, time_adjust_proposals: Vec) { + if time_adjust_proposals.len() > 0 { + log::warn!( + "[hotstuff] local: {:?}, generate_block timestamp err {:?}", + self, + time_adjust_proposals + .iter() + .map(|proposal| { + let desc = proposal.desc(); + ( + desc.object_id(), + desc.owner(), + bucky_time_to_system_time(desc.create_time()), + ) + }) + .collect::>() + ); + } + + for proposal in time_adjust_proposals { + // timestamp is error + self.notify_proposal_err( + &proposal, + BuckyError::new(BuckyErrorCode::ErrorTimestamp, "error timestamp"), + ) + .await; + } + } + + async fn notify_timeout_proposals(&self, timeout_proposals: Vec) { + if timeout_proposals.len() > 0 { + log::warn!( + "[hotstuff] local: {:?}, generate_block timeout {:?}", + self, + timeout_proposals + .iter() + .map(|proposal| { + let desc = proposal.desc(); + ( + desc.object_id(), + desc.owner(), + bucky_time_to_system_time(desc.create_time()), + proposal + .effective_ending() + .as_ref() + .map(|ending| bucky_time_to_system_time(*ending)), + ) + }) + .collect::>() + ); + } + + for proposal in timeout_proposals { + // has timeout + self.notify_proposal_err( + &proposal, + BuckyError::new(BuckyErrorCode::Timeout, "timeout"), + ) + .await; + } + } + + async fn notify_failed_proposals(&self, failed_proposals: Vec<(GroupProposal, BuckyError)>) { + if failed_proposals.len() > 0 { + log::warn!( + "[hotstuff] local: {:?}, generate_block failed proposal {:?}", + self, + failed_proposals + .iter() + .map(|(proposal, err)| { + let desc = proposal.desc(); + (desc.object_id(), desc.owner(), err.clone()) + }) + .collect::>() + ); + } + + for (proposal, err) in failed_proposals { + // failed + self.notify_proposal_err(&proposal, err).await; + } + } + + async fn remove_pending_proposals(&self, pending_proposals: Vec) { + if pending_proposals.len() > 0 { + log::warn!( + "[hotstuff] local: {:?}, generate_block finish proposal {:?}", + self, + pending_proposals + ); + } + + self.proposal_consumer + .remove_proposals(pending_proposals) + .await; + } + + async fn package_block_with_proposals( + &self, + executed_proposals: Vec<(GroupProposal, ExecuteResult)>, + group: &Group, + result_state_id: Option, + prev_block: &Option, + tc: Option, + ) -> BuckyResult { + let proposal_count = executed_proposals.len(); + let proposals_param = executed_proposals + .into_iter() + .map(|(proposal, exe_result)| GroupConsensusBlockProposal { + proposal: proposal.desc().object_id(), + result_state: exe_result.result_state_id, + receipt: exe_result.receipt.map(|receipt| receipt.to_vec().unwrap()), + context: exe_result.context, + }) + .collect(); + + let group_shell_id = group.to_shell().shell_id(); + + let mut block = GroupConsensusBlock::create( + self.rpath.clone(), + proposals_param, + result_state_id, + prev_block.as_ref().map_or(0, |b| b.height()) + 1, + ObjectId::default(), // TODO: meta block id + self.round, + group_shell_id, + self.high_qc.clone(), + tc, + self.local_id, + ); + + log::info!( + "[hotstuff] local: {:?}, generate_block new block {}/{}/{}, with proposals: {}", + self, + block.block_id(), + block.height(), + block.round(), + proposal_count + ); + + self.sign_block(&mut block).await.map_err(|err| { + log::warn!( + "[hotstuff] local: {:?}, generate_block new block {} sign failed {:?}", + self, + block.block_id(), + err + ); + + err + })?; + + self.non_driver.put_block(&block).await?; + + Ok(block) + } + + async fn sign_block(&self, block: &mut GroupConsensusBlock) -> BuckyResult<()> { + let sign_source = SignatureSource::Object(ObjectLink { + obj_id: self.local_device_id, + obj_owner: None, + }); + + let desc_hash = block.named_object().desc().raw_hash_value()?; + let signature = self.signer.sign(desc_hash.as_slice(), &sign_source).await?; + block + .named_object_mut() + .signs_mut() + .set_desc_sign(signature); + + Ok(()) + } + + fn broadcast(&self, msg: HotstuffMessage, group: &Group) -> BuckyResult<()> { + let targets: Vec = group + .ood_list() + .iter() + .filter(|ood_id| **ood_id != self.local_device_id) + .map(|ood_id| ood_id.object_id().clone()) + .collect(); + + let network_sender = self.network_sender.clone(); + let rpath = self.rpath.clone(); + + async_std::task::spawn(async move { + network_sender + .broadcast(msg, rpath.clone(), targets.as_slice()) + .await + }); + + Ok(()) + } + + async fn try_wait_proposals( + &mut self, + proposal_count: usize, + pre_block: &Option, + ) -> bool { + // empty block, qc only, it's unuseful when no block to qc + let mut will_wait_proposals = false; + if proposal_count == 0 { + match pre_block.as_ref() { + None => { + log::warn!( + "[hotstuff] local: {:?}, new empty block will ignore for first block is empty.", + self, + ); + + will_wait_proposals = true + } + Some(pre_block) => { + if pre_block.proposals().len() == 0 { + match pre_block.prev_block_id() { + Some(pre_pre_block_id) => { + let pre_pre_block = match self + .store + .find_block_in_cache(pre_pre_block_id) + { + Ok(pre_pre_block) => pre_pre_block, + Err(err) => { + log::warn!( + "[hotstuff] local: {:?}, new empty block will generate for find prev-block {} failed {:?}", + self, + pre_pre_block_id, + err + ); + return false; + } + }; + if pre_pre_block.proposals().len() == 0 { + log::warn!( + "[hotstuff] local: {:?}, new empty block will ignore for 2 prev-block({}/{}) is empty", + self, + pre_pre_block_id, pre_block.block_id() + ); + + will_wait_proposals = true; + } + } + None => { + log::warn!( + "[hotstuff] local: {:?}, new empty block will ignore for prev-prev-block is None and prev-block is {}, maybe is a bug.", + self, + pre_block.block_id() + ); + + will_wait_proposals = true; + } + } + } + } + } + } + + if will_wait_proposals { + match self.proposal_consumer.wait_proposals().await { + Ok(rx) => self.rx_proposal_waiter = Some((rx, self.round)), + _ => return false, + } + } + + will_wait_proposals + } + + async fn handle_proposal_waiting(&mut self) -> BuckyResult<()> { + log::debug!("[hotstuff] local: {:?}, handle_proposal_waiting", self); + + assert_eq!( + self.committee.get_leader(None, self.round, None).await?, + self.local_device_id + ); + + self.generate_block(self.with_tc()).await + } + + fn with_tc(&self) -> Option { + self.tc.as_ref().map_or(None, |tc| { + if tc.round + 1 == self.round { + Some(tc.clone()) + } else { + None + } + }) + } + + async fn fetch_block(&mut self, block_id: &ObjectId, remote: ObjectId) -> BuckyResult<()> { + let block = self + .non_driver + .get_block(block_id, Some(&remote)) + .await + .map_err(|err| { + log::error!( + "[hotstuff] local: {:?}, fetch block({}) from {} failed, err: {:?}.", + self, + block_id, + remote, + err + ); + err + })?; + + self.tx_message_inner.send((block, remote)).await; + Ok(()) + } + + async fn sync_to_block(&mut self, latest_block: &GroupConsensusBlock, remote: ObjectId) { + let fetch_height_immediate = self.store.header_height() + 3; + + if latest_block.height() <= fetch_height_immediate { + // little blocks, get them from remote immediately. + if self + .sync_to_block_by_get_prev(latest_block.clone(), remote) + .await + .is_err() + { + self.network_sender + .post_message( + HotstuffMessage::SyncRequest( + SyncBound::Height(self.store.header_height() + 1), + SyncBound::Height(latest_block.height() - 1), + ), + self.rpath.clone(), + &remote, + ) + .await; + + self.synchronizer.push_outorder_block( + latest_block.clone(), + fetch_height_immediate, + remote, + ); + } + } else { + // large blocks, notify remote to push. + log::debug!( + "[hotstuff] local: {:?}, will sync blocks from height({}) to height({}). block.round={}, remote: {}", + self, + self.store.max_height(), + latest_block.height(), + latest_block.round(), + remote + ); + + if self.store.max_height() + 1 <= fetch_height_immediate - 1 { + self.network_sender + .post_message( + HotstuffMessage::SyncRequest( + SyncBound::Height(self.store.max_height() + 1), + SyncBound::Height(fetch_height_immediate - 1), + ), + self.rpath.clone(), + &remote, + ) + .await; + } + + self.synchronizer.push_outorder_block( + latest_block.clone(), + fetch_height_immediate, + remote, + ); + } + } + + async fn sync_to_block_by_get_prev( + &mut self, + latest_block: GroupConsensusBlock, + remote: ObjectId, + ) -> BuckyResult<()> { + // 1. fetch prev blocks in range (header_height, latest_block.height) + let header_height = self.store.header_height(); + let max_height = latest_block.height(); + if max_height <= header_height { + return Ok(()); + } + + let mut blocks = Vec::with_capacity((max_height - header_height) as usize); + let mut fetching_block_id = latest_block.prev_block_id().cloned(); + + blocks.push(latest_block); + + for i in 0..(max_height - header_height - 1) { + let expected_height = max_height - i - 1; + match fetching_block_id.as_ref() { + Some(block_id) => { + if self.store.find_block_in_cache(block_id).is_ok() { + break; + } + + let block = self + .non_driver + .get_block(block_id, Some(&remote)) + .await + .map_err(|err| { + log::error!( + "[hotstuff] local: {:?}, sync block({}) at height({}) from {} failed, err: {:?}.", + self, + block_id, + expected_height, + remote, + err + ); + err + })?; + + if block.height() != expected_height { + log::error!("[hotstuff] local: {:?}, sync block({}) at height({}) from {} failed, block.height is {}.", self, block_id, expected_height, remote, block.height()); + return Err(BuckyError::new( + BuckyErrorCode::Unmatch, + "unexpected block height", + )); + } + + log::debug!( + "[hotstuff] local: {:?}, sync block({}) at height({}) from {}, fetch success.", + self, + block_id, + expected_height, + remote + ); + + fetching_block_id = block.prev_block_id().cloned(); + blocks.push(block); + } + None => { + log::error!("[hotstuff] local: {:?}, sync block at height({}) from {} failed, block id is None.", self, expected_height, remote); + return Err(BuckyError::new( + BuckyErrorCode::Failed, + "unexpected block id", + )); + } + } + } + + // 2. handle blocks in order + for block in blocks.into_iter().rev() { + log::debug!( + "[hotstuff] local: {:?}, sync block({}) at height({}) from {}, will handle it.", + self, + block.block_id(), + block.height(), + remote + ); + + self.tx_message_inner.send((block, remote)).await; + } + + Ok(()) + } + + async fn handle_query_state(&self, sub_path: String, remote: ObjectId) -> BuckyResult<()> { + let result = self.store.get_by_path(sub_path.as_str()).await; + self.network_sender + .post_message( + HotstuffMessage::VerifiableState(sub_path, result), + self.rpath.clone(), + &remote, + ) + .await; + + Ok(()) + } + + async fn check_group_is_latest(&self, group_shell_id: &ObjectId) -> BuckyResult { + let (_, latest_shell_id) = self.shell_mgr.group(); + if &latest_shell_id == group_shell_id { + Ok(true) + } else { + let latest_group = self + .shell_mgr + .get_group(self.rpath.group_id(), None, None) + .await?; + let group_shell = latest_group.to_shell(); + let latest_shell_id = group_shell.shell_id(); + Ok(&latest_shell_id == group_shell_id) + } + } + + async fn make_sure_result_state( + &self, + result_state_id: &ObjectId, + remotes: &[&ObjectId], + ) -> BuckyResult<()> { + // TODO: 需要一套通用的同步ObjectMap树的实现,这里缺少对于异常的处理 + let obj_map_processor = self.store.get_object_map_processor(); + let local_trace_log = format!("{:?}", self); + + #[async_recursion::async_recursion] + async fn make_sure_sub_tree( + root_id: &ObjectId, + non_driver: crate::network::NONDriverHelper, + remote: &ObjectId, + obj_map_processor: &dyn GroupObjectMapProcessor, + local_trace_log: &str, + ) -> BuckyResult<()> { + if root_id.is_data() { + return Ok(()); + } + + if non_driver.get_object(&root_id, None).await.is_ok() { + // TODO: 可能有下级分支子树因为异常不齐全 + log::debug!( + "[hotstuff] {} make_sure_result_state {} already exist.", + local_trace_log, + root_id + ); + return Ok(()); + } + let obj = non_driver + .get_object(root_id, Some(remote)) + .await + .map_err(|err| { + log::warn!( + "[hotstuff] {} get branch {} failed {:?}", + local_trace_log, + root_id, + err + ); + err + })?; + match obj.object.as_ref() { + Some(obj) if obj.obj_type_code() == ObjectTypeCode::ObjectMap => { + let single_op_env = obj_map_processor.create_single_op_env().await.map_err(|err| { + log::warn!("[hotstuff] {} make_sure_result_state {} create_single_op_env failed {:?}.", local_trace_log, root_id, err); + err + })?; + single_op_env.load(root_id).await.map_err(|err| { + log::warn!( + "[hotstuff] {} make_sure_result_state {} load failed {:?}.", + local_trace_log, + root_id, + err + ); + err + })?; + loop { + let branchs = single_op_env.next(16).await?; + for branch in branchs.list.iter() { + let branch_id = match branch { + cyfs_base::ObjectMapContentItem::DiffMap(diff_map) => { + match diff_map.1.altered.as_ref() { + Some(branch_id) => branch_id, + None => continue, + } + } + cyfs_base::ObjectMapContentItem::Map(map) => &map.1, + cyfs_base::ObjectMapContentItem::DiffSet(diff_set) => { + match diff_set.altered.as_ref() { + Some(branch_id) => branch_id, + None => continue, + } + } + cyfs_base::ObjectMapContentItem::Set(set) => set, + }; + make_sure_sub_tree( + branch_id, + non_driver.clone(), + remote, + obj_map_processor, + local_trace_log, + ) + .await?; + } + + if branchs.list.len() < 16 { + return Ok(()); + } + } + } + _ => return Ok(()), + } + } + + let mut result = Ok(()); + for remote in remotes { + result = make_sure_sub_tree( + result_state_id, + self.non_driver.clone(), + remote, + obj_map_processor, + local_trace_log.as_str(), + ) + .await; + if result.is_ok() { + return result; + } + } + result + } + + async fn recover(&mut self) { + // Upon booting, generate the very first block (if we are the leader). + // Also, schedule a timer in case we don't hear from the leader. + let max_round_block = self.store.block_with_max_round(); + let group_shell_id = max_round_block.as_ref().map(|block| block.group_shell_id()); + let last_group = self + .shell_mgr + .get_group(self.rpath.group_id(), group_shell_id, None) + .await; + let latest_group = match group_shell_id.as_ref() { + Some(_) => { + self.shell_mgr + .get_group(self.rpath.group_id(), None, None) + .await + } + None => last_group.clone(), + }; + + let duration = latest_group.as_ref().map_or(HOTSTUFF_TIMEOUT_DEFAULT, |g| { + GROUP_DEFAULT_CONSENSUS_INTERVAL + }); + self.timer.reset(duration); + + if let Ok(leader) = self.committee.get_leader(None, self.round, None).await { + if leader == self.local_device_id { + match max_round_block { + Some(max_round_block) + if max_round_block.owner() == &self.local_id + && max_round_block.round() == self.round + && latest_group.is_ok() + && last_group.is_ok() + && last_group + .as_ref() + .unwrap() + .is_same_ood_list(latest_group.as_ref().unwrap()) => + { + // discard the generated block when the ood-list is changed + self.broadcast( + HotstuffMessage::Block(max_round_block), + &latest_group.unwrap(), + ); + } + _ => { + self.generate_block(self.with_tc()).await; + } + } + } + } + } + + fn proposal_waiter(waiter: Option<(Receiver<()>, u64)>) -> impl futures::Future { + async move { + match waiter.as_ref() { + Some((waiter, wait_round)) => { + waiter.recv().await; + *wait_round + } + None => std::future::pending::().await, + } + } + } + + async fn run(&mut self) -> ! { + log::info!("[hotstuff] {:?} start, will recover.", self); + + self.recover().await; + + log::info!("[hotstuff] {:?} start, recovered.", self); + + // This is the main loop: it processes incoming blocks and votes, + // and receive timeout notifications from our Timeout Manager. + loop { + let result = futures::select! { + message = self.rx_message.recv().fuse() => match message { + Ok((HotstuffMessage::Block(block), remote)) => { + if remote == self.local_device_id { + self.process_block(&block, remote, &HashMap::new()).await + } else { + self.handle_block(&block, remote).await + } + }, + Ok((HotstuffMessage::BlockVote(vote), remote)) => self.handle_vote(&vote, None, remote).await, + Ok((HotstuffMessage::TimeoutVote(timeout), remote)) => self.handle_timeout(&timeout, remote).await, + Ok((HotstuffMessage::Timeout(tc), remote)) => self.handle_tc(&tc, remote).await, + Ok((HotstuffMessage::SyncRequest(min_bound, max_bound), remote)) => self.synchronizer.process_sync_request(min_bound, max_bound, remote, &self.store).await, + Ok((HotstuffMessage::LastStateRequest, _)) => panic!("should process by StatePusher"), + Ok((HotstuffMessage::StateChangeNotify(_, _), _)) => panic!("should process by DecStateSynchronizer"), + Ok((HotstuffMessage::ProposalResult(_, _), _)) => panic!("should process by DecStateSynchronizer"), + Ok((HotstuffMessage::QueryState(sub_path), remote)) => self.handle_query_state(sub_path, remote).await, + Ok((HotstuffMessage::VerifiableState(_, _), _)) => panic!("should process by DecStateRequestor"), + Err(e) => { + log::warn!("[hotstuff] rx_message closed."); + Ok(()) + }, + }, + message = self.rx_message_inner.recv().fuse() => match message { + Ok((block, remote)) => { + log::debug!( + "[hotstuff] local: {:?}, receive block({}) from ({}) from rx_message_inner, height: {}, round: {}.", + self, + block.block_id(), + remote, + block.height(), + block.round() + ); + + if remote == self.local_device_id { + self.process_block(&block, remote, &HashMap::new()).await + } else { + self.handle_block(&block, remote).await + } + }, + Err(e) => { + log::warn!("[hotstuff] rx_message_inner closed."); + Ok(()) + }, + }, + block = self.rx_block_gen.recv().fuse() => match block { + Ok((block, proposals)) => self.process_block(&block, self.local_device_id, &proposals).await, + Err(e) => { + log::warn!("[hotstuff] rx_block_gen closed."); + Ok(()) + } + }, + () = self.timer.wait_next().fuse() => self.local_timeout_round().await, + wait_round = Self::proposal_waiter(self.rx_proposal_waiter.clone()).fuse() => { + self.rx_proposal_waiter = None; + if wait_round == self.round { + // timeout + self.handle_proposal_waiting().await + } else { + Ok(()) + } + } + }; + + log::debug!( + "[hotstuff] local: {:?}, new message response. result: {:?}", + self, + result + ); + } + } + + fn debug_identify(&self) -> String { + format!("{:?}-{:?}-{}", self.rpath, self.local_device_id, self.round) + } +} diff --git a/src/component/cyfs-group/src/consensus/hotstuff/mod.rs b/src/component/cyfs-group/src/consensus/hotstuff/mod.rs new file mode 100644 index 000000000..aacfc1367 --- /dev/null +++ b/src/component/cyfs-group/src/consensus/hotstuff/mod.rs @@ -0,0 +1,3 @@ +mod hotstuff; + +pub use hotstuff::*; diff --git a/src/component/cyfs-group/src/consensus/mod.rs b/src/component/cyfs-group/src/consensus/mod.rs new file mode 100644 index 000000000..15810090f --- /dev/null +++ b/src/component/cyfs-group/src/consensus/mod.rs @@ -0,0 +1,8 @@ +mod hotstuff; +mod proposal; +mod synchronizer; +mod vote; + +pub use hotstuff::*; +pub use proposal::*; +pub use vote::*; diff --git a/src/component/cyfs-group/src/consensus/proposal/mod.rs b/src/component/cyfs-group/src/consensus/proposal/mod.rs new file mode 100644 index 000000000..6efc53d99 --- /dev/null +++ b/src/component/cyfs-group/src/consensus/proposal/mod.rs @@ -0,0 +1,3 @@ +mod pending_proposal_mgr; + +pub use pending_proposal_mgr::*; diff --git a/src/component/cyfs-group/src/consensus/proposal/pending_proposal_mgr.rs b/src/component/cyfs-group/src/consensus/proposal/pending_proposal_mgr.rs new file mode 100644 index 000000000..d16bb4536 --- /dev/null +++ b/src/component/cyfs-group/src/consensus/proposal/pending_proposal_mgr.rs @@ -0,0 +1,158 @@ +use std::collections::HashMap; + +use async_std::channel::{Receiver, Sender}; +use cyfs_base::{BuckyError, BuckyErrorCode, BuckyResult, NamedObject, ObjectDesc, ObjectId}; +use cyfs_core::GroupProposal; +use futures::FutureExt; + +use crate::CHANNEL_CAPACITY; + +pub enum ProposalConsumeMessage { + Query(Sender>), + Wait(Sender<()>), + Remove(Vec), +} + +pub struct PendingProposalMgr { + rx_product: Receiver, + rx_consume: Receiver, + tx_proposal_waker: Option>, + + // TODO: 需要设计一个结构便于按时间或数量拆分 + buffer: HashMap, +} + +impl PendingProposalMgr { + pub fn new() -> (PendingProposalHandler, PendingProposalConsumer) { + let (tx_product, rx_product) = async_std::channel::bounded(CHANNEL_CAPACITY); + let (tx_consume, rx_consume) = async_std::channel::bounded(CHANNEL_CAPACITY); + + async_std::task::spawn(async move { + PendingProposalMgrRunner { + rx_product, + rx_consume, + buffer: HashMap::new(), + tx_proposal_waker: None, + } + .run() + .await + }); + + ( + PendingProposalHandler { tx_product }, + PendingProposalConsumer { tx_consume }, + ) + } +} + +pub struct PendingProposalHandler { + tx_product: Sender, +} + +impl PendingProposalHandler { + pub async fn on_proposal(&self, proposal: GroupProposal) -> BuckyResult<()> { + self.tx_product.send(proposal).await.map_err(|e| { + log::error!( + "[pending_proposal_mgr] send message(on_proposal) failed: {}", + e + ); + BuckyError::new(BuckyErrorCode::ErrorState, "channel closed") + }) + } +} + +pub struct PendingProposalConsumer { + tx_consume: Sender, +} + +impl PendingProposalConsumer { + pub async fn query_proposals(&self) -> BuckyResult> { + let (sender, receiver) = async_std::channel::bounded(1); + self.tx_consume + .send(ProposalConsumeMessage::Query(sender)) + .await + .map_err(|e| { + log::error!("[pending_proposal_mgr] send message(query) failed: {}", e); + BuckyError::new(BuckyErrorCode::ErrorState, "channel closed") + })?; + + receiver.recv().await.map_err(|e| { + log::error!("[pending_proposal_mgr] recv message(query) failed: {}", e); + BuckyError::new(BuckyErrorCode::ErrorState, "channel closed") + }) + } + + pub async fn wait_proposals(&self) -> BuckyResult> { + let (sender, receiver) = async_std::channel::bounded(1); + self.tx_consume + .send(ProposalConsumeMessage::Wait(sender)) + .await + .map_err(|e| { + log::error!("[pending_proposal_mgr] send message(wait) failed: {}", e); + BuckyError::new(BuckyErrorCode::ErrorState, "channel closed") + })?; + Ok(receiver) + } + + pub async fn remove_proposals(&self, proposal_ids: Vec) -> BuckyResult<()> { + self.tx_consume + .send(ProposalConsumeMessage::Remove(proposal_ids)) + .await + .map_err(|e| { + log::error!("[pending_proposal_mgr] send message(remove) failed: {}", e); + BuckyError::new(BuckyErrorCode::ErrorState, "channel closed") + }) + } +} + +struct PendingProposalMgrRunner { + rx_product: Receiver, + rx_consume: Receiver, + tx_proposal_waker: Option>, + + // TODO: 需要设计一个结构便于按时间或数量拆分 + buffer: HashMap, +} + +impl PendingProposalMgrRunner { + async fn handle_query_proposals(&mut self) -> Vec { + self.buffer.iter().map(|(_, p)| p.clone()).collect() + } + + async fn run(&mut self) { + loop { + futures::select! { + proposal = self.rx_product.recv().fuse() => { + if let Ok(proposal) = proposal { + self.buffer.insert(proposal.desc().object_id(), proposal); + if let Some(waker) = self.tx_proposal_waker.take() { + waker.send(()).await; + } + } + }, + message = self.rx_consume.recv().fuse() => { + if let Ok(message) = message { + match message { + ProposalConsumeMessage::Query(sender) => { + let proposals = self.handle_query_proposals().await; + sender.send(proposals).await; + }, + ProposalConsumeMessage::Remove(proposal_ids) => { + for id in &proposal_ids { + self.buffer.remove(id); + } + }, + ProposalConsumeMessage::Wait(tx_waker) => { + if self.buffer.len() > 0 { + tx_waker.send(()).await; + } else { + self.tx_proposal_waker = Some(tx_waker) + } + } + } + } + } + } + } + } +} diff --git a/src/component/cyfs-group/src/consensus/synchronizer/mod.rs b/src/component/cyfs-group/src/consensus/synchronizer/mod.rs new file mode 100644 index 000000000..2fa7f37ba --- /dev/null +++ b/src/component/cyfs-group/src/consensus/synchronizer/mod.rs @@ -0,0 +1,3 @@ +mod synchronizer; + +pub use synchronizer::*; \ No newline at end of file diff --git a/src/component/cyfs-group/src/consensus/synchronizer/synchronizer.rs b/src/component/cyfs-group/src/consensus/synchronizer/synchronizer.rs new file mode 100644 index 000000000..caa0949a4 --- /dev/null +++ b/src/component/cyfs-group/src/consensus/synchronizer/synchronizer.rs @@ -0,0 +1,685 @@ +use std::{ + collections::HashSet, + sync::Arc, + time::{Duration, Instant}, + vec, +}; + +use async_std::channel::{Receiver, Sender}; +use cyfs_base::{BuckyResult, NamedObject, ObjectId}; +use cyfs_core::{GroupConsensusBlock, GroupConsensusBlockObject, GroupRPath}; +use futures::FutureExt; + +use crate::{ + helper::Timer, storage::GroupStorage, HotstuffMessage, SyncBound, CHANNEL_CAPACITY, + SYNCHRONIZER_TIMEOUT, SYNCHRONIZER_TRY_TIMES, +}; + +enum SynchronizerMessage { + Sync(u64, SyncBound, ObjectId), // ([min-height, max-bound], remote) + PushBlock(u64, GroupConsensusBlock, ObjectId), // (min-height, block, remote) + PopBlock(u64, u64, ObjectId), // (new-height, new-round, blockid) +} + +pub(crate) struct Synchronizer { + tx_sync_message: Sender, + network_sender: crate::network::Sender, + rpath: GroupRPath, +} + +impl Synchronizer { + pub fn new( + local_device_id: ObjectId, + network_sender: crate::network::Sender, + rpath: GroupRPath, + height: u64, + round: u64, + tx_block: Sender<(GroupConsensusBlock, ObjectId)>, + ) -> Self { + let (tx_sync_message, rx_sync_message) = async_std::channel::bounded(CHANNEL_CAPACITY); + let mut runner = SynchronizerRunner::new( + local_device_id, + network_sender.clone(), + rpath.clone(), + tx_block, + rx_sync_message, + height, + round, + ); + + async_std::task::spawn(async move { runner.run().await }); + + Self { + tx_sync_message, + network_sender, + rpath, + } + } + + pub fn sync_with_height(&self, min_height: u64, max_height: u64, remote: ObjectId) { + if min_height > max_height { + return; + } + + let tx_sync_message = self.tx_sync_message.clone(); + async_std::task::spawn(async move { + tx_sync_message + .send(SynchronizerMessage::Sync( + min_height, + SyncBound::Height(max_height), + remote, + )) + .await + }); + } + + pub fn sync_with_round(&self, min_height: u64, max_round: u64, remote: ObjectId) { + if min_height > max_round { + return; + } + + let tx_sync_message = self.tx_sync_message.clone(); + async_std::task::spawn(async move { + tx_sync_message + .send(SynchronizerMessage::Sync( + min_height, + SyncBound::Round(max_round), + remote, + )) + .await + }); + } + + pub fn push_outorder_block( + &self, + block: GroupConsensusBlock, + min_height: u64, + remote: ObjectId, + ) { + let tx_sync_message = self.tx_sync_message.clone(); + async_std::task::spawn(async move { + tx_sync_message + .send(SynchronizerMessage::PushBlock(min_height, block, remote)) + .await + }); + } + + pub fn pop_link_from(&self, block: &GroupConsensusBlock) { + let tx_sync_message = self.tx_sync_message.clone(); + let height = block.height(); + let round = block.round(); + let block_id = block.block_id().object_id().clone(); + async_std::task::spawn(async move { + tx_sync_message + .send(SynchronizerMessage::PopBlock(height, round, block_id)) + .await + }); + } + + pub async fn process_sync_request( + &self, + min_bound: SyncBound, + max_bound: SyncBound, + remote: ObjectId, + store: &GroupStorage, + ) -> BuckyResult<()> { + // TODO: combine the requests + + let header_block = store.header_block(); + if header_block.is_none() { + return Ok(()); + } + + let mut blocks = vec![]; + + // map SyncBound::Round(x) to height, and collect the blocks found + let header_block = header_block.as_ref().unwrap(); + let min_height = match min_bound { + SyncBound::Round(round) => { + if round > header_block.round() { + return Ok(()); + } + + let (ret, mut cached_blocks) = store.find_block_by_round(round).await; + cached_blocks.retain(|block| { + let is_include = block.round() >= round + && match max_bound { + SyncBound::Round(max_round) => block.round() <= max_round, + SyncBound::Height(max_height) => block.height() <= max_height, + }; + is_include + }); + cached_blocks.sort_unstable_by(|left, right| left.height().cmp(&right.height())); + blocks = cached_blocks; + + match ret { + Ok(found_block) => Some(found_block.height()), + Err(_) => None, + } + } + SyncBound::Height(height) => { + if height > header_block.height() { + return Ok(()); + } + + Some(height) + } + }; + + // load all blocks in [min_height, max_bound] + // TODO: limit count + if let Some(min_height) = min_height { + let mut pos = 0; + for height in min_height..(header_block.height() + 1) { + let exist_block = blocks.get(pos); + if let Some(exist_block) = exist_block { + match exist_block.height().cmp(&height) { + std::cmp::Ordering::Less => unreachable!(), + std::cmp::Ordering::Equal => { + pos += 1; + continue; + } + std::cmp::Ordering::Greater => {} + } + } + + if let Ok(block) = store.get_block_by_height(height).await { + let is_include = match max_bound { + SyncBound::Height(height) => block.height() <= height, + SyncBound::Round(round) => block.round() <= round, + }; + if !is_include { + break; + } + blocks.insert(pos, block); + pos += 1; + } + } + } + + let network_sender = self.network_sender.clone(); + let rpath = self.rpath.clone(); + async_std::task::spawn(async move { + futures::future::join_all(blocks.into_iter().map(|block| { + network_sender.post_message(HotstuffMessage::Block(block), rpath.clone(), &remote) + })) + .await; + }); + + Ok(()) + } +} + +#[derive(Clone)] +struct ResendInfo { + last_send_time: Instant, + send_times: usize, + cmd: Arc<(u64, SyncBound, ObjectId)>, +} + +#[derive(Clone)] +struct RequestSendInfo { + min_bound: SyncBound, + max_bound: SyncBound, + + resends: Vec, +} + +impl RequestSendInfo { + fn new( + min_bound: SyncBound, + max_bound: SyncBound, + req: Arc<(u64, SyncBound, ObjectId)>, + ) -> Self { + RequestSendInfo { + min_bound, + max_bound: max_bound, + resends: vec![ResendInfo { + last_send_time: Instant::now(), + send_times: 0, + cmd: req, + }], + } + } + + fn splite(&mut self, bound: SyncBound) -> Option { + match bound.cmp(&self.max_bound) { + std::cmp::Ordering::Greater => None, + _ => match bound.cmp(&self.min_bound) { + std::cmp::Ordering::Greater => { + self.max_bound = bound.sub(1); + Some(Self { + min_bound: bound, + max_bound: self.max_bound, + resends: self.resends.clone(), + }) + } + _ => None, + }, + } + } + + fn try_send(&mut self, rpath: GroupRPath, sender: &crate::network::Sender) { + // 选send次数最少,间隔最长的发送一次 + if let SyncBound::Round(_) = self.min_bound { + return; + } + + let now = Instant::now(); + let mut max_send_info_pos = 0; + for i in 1..self.resends.len() { + let resend_info = self.resends.get(i).unwrap(); + let max_send_info = self.resends.get(max_send_info_pos).unwrap(); + + if now.duration_since(resend_info.last_send_time) + <= Duration::from_millis(SYNCHRONIZER_TIMEOUT * (1 << resend_info.send_times)) + { + return; + } + match resend_info.send_times.cmp(&max_send_info.send_times) { + std::cmp::Ordering::Less => { + max_send_info_pos = i; + } + std::cmp::Ordering::Greater => {} + std::cmp::Ordering::Equal => { + if let std::cmp::Ordering::Greater = now + .duration_since(resend_info.last_send_time) + .cmp(&now.duration_since(max_send_info.last_send_time)) + { + max_send_info_pos = i; + } + } + } + } + + if let Some(resend_info) = self.resends.get_mut(max_send_info_pos) { + resend_info.last_send_time = now; + resend_info.send_times += 1; + + let msg = HotstuffMessage::SyncRequest(self.min_bound, self.max_bound); + let remote = resend_info.cmd.2; + let sender = sender.clone(); + async_std::task::spawn(async move { sender.post_message(msg, rpath, &remote).await }); + + if resend_info.send_times >= SYNCHRONIZER_TRY_TIMES { + self.resends.remove(max_send_info_pos); + } + } + } + + fn is_valid(&self) -> bool { + if let std::cmp::Ordering::Greater = self.min_bound.cmp(&self.max_bound) { + false + } else { + true + } + } +} + +struct SynchronizerRunner { + local_device_id: ObjectId, + network_sender: crate::network::Sender, + rpath: GroupRPath, + tx_block: Sender<(GroupConsensusBlock, ObjectId)>, + rx_message: Receiver, + timer: Timer, + height: u64, + round: u64, + + sync_requests: Vec, // order by min_bound + out_order_blocks: Vec<(GroupConsensusBlock, ObjectId)>, // Vec<(block, remote)> +} + +impl SynchronizerRunner { + fn new( + local_device_id: ObjectId, + network_sender: crate::network::Sender, + rpath: GroupRPath, + tx_block: Sender<(GroupConsensusBlock, ObjectId)>, + rx_message: Receiver, + height: u64, + round: u64, + ) -> Self { + Self { + network_sender, + rpath, + rx_message, + timer: Timer::new(SYNCHRONIZER_TIMEOUT), + height, + round, + sync_requests: vec![], + out_order_blocks: vec![], + tx_block, + local_device_id, + } + } + + async fn handle_sync(&mut self, min_height: u64, max_bound: SyncBound, remote: ObjectId) { + let min_height = min_height.max(self.height + 1); + let max_bound = match max_bound { + SyncBound::Height(height) => SyncBound::Height(height.max(self.height + 1)), + SyncBound::Round(round) => SyncBound::Round(round.max(self.round + 1)), + }; + + let requests: Vec> = self + .filter_outorder_blocks(min_height, max_bound) + .into_iter() + .map(|req| Arc::new((req.0, req.1, remote))) + .collect(); + + // combine requests + let mut pos = 0; + for req in requests { + let mut range = (SyncBound::Height(req.0), req.1); + while range.0 <= range.1 { + while pos < self.sync_requests.len() { + let req1 = self.sync_requests.get_mut(pos).unwrap(); + match range.0.cmp(&req1.min_bound) { + std::cmp::Ordering::Less => { + let max_bound = match range.1.cmp(&req1.min_bound) { + std::cmp::Ordering::Less => range.1, + _ => req1.min_bound.sub(1), + }; + + let mut new_req = RequestSendInfo::new(range.0, max_bound, req.clone()); + new_req.try_send(self.rpath.clone(), &self.network_sender); + self.sync_requests.insert(pos, new_req); + range.0 = max_bound.add(1); + } + std::cmp::Ordering::Equal => { + let cut_req = match range.1.cmp(&req1.max_bound) { + std::cmp::Ordering::Greater => { + range.0 = req1.max_bound.add(1); + None + } + _ => { + range.0 = range.1.add(1); + let cut = req1.splite(range.0); + assert!(req1.is_valid()); + cut + } + }; + req1.resends.push(ResendInfo { + last_send_time: Instant::now() + .checked_sub(Duration::from_millis(SYNCHRONIZER_TIMEOUT << 1)) + .unwrap(), + send_times: 0, + cmd: req.clone(), + }); + if let Some(cut) = cut_req { + self.sync_requests.insert(pos + 1, cut); + } + } + std::cmp::Ordering::Greater => match range.0.cmp(&req1.max_bound) { + std::cmp::Ordering::Greater => {} + _ => { + let cut = req1.splite(range.0); + assert!(req1.is_valid()); + if let Some(cut) = cut { + self.sync_requests.insert(pos + 1, cut); + } + } + }, + } + pos += 1; + + if range.0 > range.1 { + break; + } + } + + if pos == self.sync_requests.len() { + if range.0 <= range.1 { + let mut new_req = RequestSendInfo::new(range.0, max_bound, req.clone()); + new_req.try_send(self.rpath.clone(), &self.network_sender); + self.sync_requests.push(new_req); + pos += 1; + } + break; + } + } + } + } + + fn filter_outorder_blocks( + &self, + min_height: u64, + max_bound: SyncBound, + ) -> Vec<(u64, SyncBound)> { + // TODO: limit the length of per range + let mut last_range = Some((SyncBound::Height(min_height), max_bound)); + let mut requests = vec![]; + for (block, _) in self.out_order_blocks.as_slice() { + match last_range { + Some(range) => { + let (range1, range2) = + Self::splite_range_with_block(range, block.height(), block.round()); + if let Some(range1) = range1 { + requests.push((range1.0.height(), range1.1)); + } + last_range = range2; + } + None => break, + } + } + + if let Some(last_range) = last_range { + requests.push((last_range.0.height(), last_range.1)); + } + + requests + } + + fn splite_range_with_block( + mut range: (SyncBound, SyncBound), + cut_height: u64, + cut_round: u64, + ) -> ( + Option<(SyncBound, SyncBound)>, + Option<(SyncBound, SyncBound)>, + ) { + let min_ord = match range.0 { + SyncBound::Height(height) => cut_height.cmp(&height), + SyncBound::Round(round) => cut_round.cmp(&round), + }; + + match min_ord { + std::cmp::Ordering::Less => (None, Some((range.0, range.1))), + std::cmp::Ordering::Equal => { + range.0 = range.0.add(1); + match range.0.cmp(&range.1) { + std::cmp::Ordering::Greater => (None, None), + _ => (None, Some((range.0, range.1))), + } + } + std::cmp::Ordering::Greater => { + let ord = match range.1 { + SyncBound::Height(height) => cut_height.cmp(&height), + SyncBound::Round(round) => cut_round.cmp(&round), + }; + + match ord { + std::cmp::Ordering::Less => ( + Some((range.0, SyncBound::Height(cut_height - 1))), + Some((SyncBound::Height(cut_height + 1), range.1)), + ), + std::cmp::Ordering::Equal => { + (Some((range.0, SyncBound::Height(cut_height - 1))), None) + } + std::cmp::Ordering::Greater => (Some((range.0, range.1)), None), + } + } + } + } + + async fn handle_push_block( + &mut self, + min_height: u64, + block: GroupConsensusBlock, + remote: ObjectId, + ) { + log::debug!("[synchronizer] local: {:?}, handle_push_block want sync blocks from height({}) to height({}). block.round={}, round={}, prev={:?}", + self.local_device_id, min_height, block.height(), block.round(), self.round, block.prev_block_id()); + + if block.round() <= self.round + || min_height >= block.height() + || block.prev_block_id().is_none() + { + return; + } + + let pos = self.out_order_blocks.binary_search_by(|(block0, _)| { + let ord = block0.height().cmp(&block.height()); + if let std::cmp::Ordering::Equal = ord { + block0.round().cmp(&block.round()) + } else { + ord + } + }); + + match pos { + Ok(_) => return, + Err(pos) => self.out_order_blocks.insert(pos, (block.clone(), remote)), + }; + + self.timer.reset(SYNCHRONIZER_TIMEOUT); + + for i in 0..self.sync_requests.len() { + let req = self.sync_requests.get_mut(i).unwrap(); + let (range1, range2) = Self::splite_range_with_block( + (req.min_bound, req.max_bound), + block.height(), + block.round(), + ); + match range1 { + Some(range1) => { + req.max_bound = range1.1; + if let Some(range2) = range2 { + let mut new_req = req.clone(); + new_req.min_bound = range2.0; + new_req.max_bound = range2.1; + self.sync_requests.insert(i + 1, new_req); + break; + } + } + None => { + match range2 { + Some(range2) => req.min_bound = range2.0, + None => { + self.sync_requests.remove(i); + } + } + break; + } + } + } + + self.handle_sync(min_height, SyncBound::Height(block.height()), remote) + .await; + } + + async fn handle_pop_block(&mut self, new_height: u64, new_round: u64, block_id: ObjectId) { + if new_round <= self.round { + return; + } + + self.timer.reset(SYNCHRONIZER_TIMEOUT); + + let mut max_height = self.height.max(new_height); + let mut max_round = new_round; + + let mut remove_block_ids = HashSet::from([block_id]); + + let mut remove_pos = None; + + for pos in 0..self.out_order_blocks.len() { + let (block, remote) = self.out_order_blocks.get(pos).unwrap(); + + let block_id_out = block.block_id().object_id(); + if remove_block_ids.contains(block.prev_block_id().unwrap()) + || block_id_out == &block_id + { + remove_block_ids.insert(block_id_out.clone()); + remove_pos = Some(pos); + max_height = max_height.max(block.height()); + max_round = max_round.max(block.round()); + } else if block.height() > max_height && block.round() > max_round { + break; + } + } + + let order_blocks = match remove_pos { + Some(remove_pos) => self + .out_order_blocks + .splice(0..(remove_pos + 1), []) + .collect(), + None => vec![], + }; + + self.height = max_height; + self.round = max_round; + + let mut remove_request_pos = None; + for pos in 0..self.sync_requests.len() { + let req = self.sync_requests.get_mut(pos).unwrap(); + let (first, second) = Self::splite_range_with_block( + (req.min_bound, req.max_bound), + self.height, + self.round, + ); + match first { + Some(first) => { + remove_request_pos = Some(pos); + req.max_bound = first.1; + if let Some(second) = second { + let mut new_req = req.clone(); + new_req.min_bound = second.0; + new_req.max_bound = second.1; + self.sync_requests.insert(pos + 1, new_req); + break; + } + } + None => { + if let Some(second) = second { + req.min_bound = second.0; + } + break; + } + }; + } + + if let Some(remove_request_pos) = remove_request_pos { + self.sync_requests.splice(0..(remove_request_pos + 1), []); + } + + futures::future::join_all( + order_blocks + .into_iter() + .map(|(order_block, remote)| self.tx_block.send((order_block, remote))), + ) + .await; + } + + async fn handle_timeout(&mut self) { + self.sync_requests.retain_mut(|req| { + req.try_send(self.rpath.clone(), &self.network_sender); + req.resends.len() > 0 + }); + } + + async fn run(&mut self) { + loop { + futures::select! { + message = self.rx_message.recv().fuse() => match message { + Ok(SynchronizerMessage::Sync(min_height, max_bound, remote)) => self.handle_sync(min_height, max_bound, remote).await, + Ok(SynchronizerMessage::PushBlock(min_height, block, remote)) => self.handle_push_block(min_height, block, remote).await, + Ok(SynchronizerMessage::PopBlock(new_height, new_round, block_id)) => self.handle_pop_block(new_height, new_round, block_id).await, + Err(e) => { + log::warn!("[synchronizer] rx_message closed.") + }, + }, + () = self.timer.wait_next().fuse() => self.handle_timeout().await, + }; + } + } +} diff --git a/src/component/cyfs-group/src/consensus/vote/committee.rs b/src/component/cyfs-group/src/consensus/vote/committee.rs new file mode 100644 index 000000000..26b0e1bf4 --- /dev/null +++ b/src/component/cyfs-group/src/consensus/vote/committee.rs @@ -0,0 +1,425 @@ +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; + +use async_std::sync::RwLock; +use cyfs_base::{ + verify_object_desc_sign, BuckyError, BuckyErrorCode, BuckyResult, Group, NamedObject, + ObjectDesc, ObjectId, OwnerObjectDesc, RsaCPUObjectVerifier, SignatureSource, + SingleKeyObjectDesc, Verifier, +}; +use cyfs_core::{ + GroupConsensusBlock, GroupConsensusBlockDesc, GroupConsensusBlockDescContent, + GroupConsensusBlockObject, HotstuffBlockQC, HotstuffTimeout, ToGroupShell, +}; +use cyfs_group_lib::{HotstuffBlockQCVote, HotstuffTimeoutVote}; + +use crate::{network::NONDriverHelper, storage::GroupShellManager}; + +#[derive(Clone)] +pub(crate) struct Committee { + group_id: ObjectId, + non_driver: NONDriverHelper, + shell_mgr: GroupShellManager, + local_device_id: ObjectId, +} + +impl Committee { + pub fn new( + group_id: ObjectId, + non_driver: NONDriverHelper, + shell_mgr: GroupShellManager, + local_device_id: ObjectId, + ) -> Self { + Committee { + group_id, + non_driver, + shell_mgr, + local_device_id, + } + } + + pub async fn quorum_threshold( + &self, + voters: &HashSet, + group_shell_id: Option<&ObjectId>, + ) -> BuckyResult { + let group = self + .shell_mgr + .get_group(&self.group_id, group_shell_id, None) + .await?; + let voters: Vec<&ObjectId> = voters + .iter() + .filter(|id| { + group + .ood_list() + .iter() + .find(|mem| mem.object_id() == *id) + .is_some() + }) + .collect(); + + let is_enough = voters.len() >= ((group.ood_list().len() << 1) / 3 + 1); + Ok(is_enough) + } + + pub async fn get_leader( + &self, + group_shell_id: Option<&ObjectId>, + round: u64, + remote: Option<&ObjectId>, + ) -> BuckyResult { + let group = if group_shell_id.is_none() { + self.shell_mgr.group().0 + } else { + self.shell_mgr + .get_group(&self.group_id, group_shell_id, remote) + .await? + }; + let i = (round % (group.ood_list().len() as u64)) as usize; + Ok(group.ood_list()[i].object_id().clone()) + } + + pub async fn verify_block( + &self, + block: &GroupConsensusBlock, + from: ObjectId, + ) -> BuckyResult<()> { + /* * + * 验证block下的签名是否符合对上一个block归属group的确认 + */ + let block_id = block.block_id(); + if !block.check() { + log::warn!( + "[group committee] error block with invalid content: {}", + block_id + ) + } + + log::debug!( + "[group committee] {} verify block {} step1", + self.local_device_id, + block_id + ); + + let group = self + .shell_mgr + .get_group(&self.group_id, Some(block.group_shell_id()), Some(&from)) + .await?; + + if !self.check_block_sign(&block, &group).await? { + log::warn!( + "[group committee] check signature failed: {}", + block.named_object().desc().calculate_id() + ); + return Err(BuckyError::new( + BuckyErrorCode::InvalidSignature, + "invalid signature", + )); + } + + log::debug!( + "[group committee] {} verify block {} step2", + self.local_device_id, + block_id + ); + + let prev_block = if let Some(qc) = block.qc() { + let prev_block = self + .non_driver + .get_block(&qc.block_id, Some(&from)) + .await + .map_err(|err| { + log::error!( + "get the prev-block({}) for verify block({}) failed: {:?}", + qc.block_id, + block_id, + err + ); + err + })?; + self.verify_qc(qc, &prev_block).await?; + Some(prev_block) + } else { + None + }; + + log::debug!( + "[group committee] {} verify block {} step3", + self.local_device_id, + block_id + ); + + if let Some(tc) = block.tc() { + self.verify_tc(tc, block.group_shell_id()).await?; + } + + log::debug!( + "[group committee] {} verify block {} step4", + self.local_device_id, + block_id + ); + + Ok(()) + } + + pub async fn verify_block_desc_with_qc( + &self, + block_desc: &GroupConsensusBlockDesc, + qc: &HotstuffBlockQC, + from: ObjectId, + ) -> BuckyResult<()> { + let block_id = block_desc.object_id(); + + log::debug!( + "[group committee] {} verify block desc {} step1", + self.local_device_id, + block_id + ); + + if block_id != qc.block_id { + return Err(BuckyError::new( + BuckyErrorCode::Unmatch, + "the block id is unmatch with the qc", + )); + } + + self.shell_mgr + .get_group( + &self.group_id, + Some(block_desc.content().group_shell_id()), + Some(&from), + ) + .await?; + + log::debug!( + "[group committee] {} verify block desc {} step2", + self.local_device_id, + block_id + ); + + self.verify_qc_with_desc(qc, block_desc.content()).await?; + + log::debug!( + "[group committee] {} verify block desc {} step3", + self.local_device_id, + block_id + ); + + Ok(()) + } + + pub async fn verify_vote(&self, vote: &HotstuffBlockQCVote) -> BuckyResult<()> { + let hash = vote.hash(); + let device = self.non_driver.get_device(&vote.voter).await?; + let verifier = RsaCPUObjectVerifier::new(device.desc().public_key().clone()); + let is_ok = verifier.verify(hash.as_slice(), &vote.signature).await; + if !is_ok { + log::warn!("[group committee] vote with error signature"); + return Err(BuckyError::new( + BuckyErrorCode::InvalidSignature, + "invalid signature", + )); + } + Ok(()) + } + + pub async fn verify_timeout( + &self, + vote: &HotstuffTimeoutVote, + prev_block: Option<&GroupConsensusBlock>, + ) -> BuckyResult<()> { + // 用block验vote.high_qc + let hash = vote.hash(); + let device = self.non_driver.get_device(&vote.voter).await?; + let verifier = RsaCPUObjectVerifier::new(device.desc().public_key().clone()); + let is_ok = verifier.verify(hash.as_slice(), &vote.signature).await; + if !is_ok { + log::warn!("[group committee] vote with error signature"); + return Err(BuckyError::new( + BuckyErrorCode::InvalidSignature, + "invalid signature", + )); + } + + if let Some(high_qc) = vote.high_qc.as_ref() { + let prev_block = prev_block.expect("no block for high-qc in timeout"); + if prev_block.round() != high_qc.round { + log::warn!("[group committee] vote with error round in timeout"); + return Err(BuckyError::new( + BuckyErrorCode::InvalidSignature, + "invalid round", + )); + } + self.verify_qc(high_qc, prev_block).await?; + } + Ok(()) + } + + pub async fn verify_tc( + &self, + tc: &HotstuffTimeout, + group_shell_id: &ObjectId, + ) -> BuckyResult<()> { + let tc_group_shell_id = tc.group_shell_id.as_ref().unwrap_or(group_shell_id); + + let is_enough = self + .quorum_threshold( + &tc.votes.iter().map(|v| v.voter).collect(), + Some(tc_group_shell_id), + ) + .await?; + + if !is_enough { + log::warn!("[group committee] tc with vote not enough."); + return Err(BuckyError::new( + BuckyErrorCode::InvalidSignature, + "not enough", + )); + } + + let verify_vote_results = futures::future::join_all(tc.votes.iter().map(|vote| async { + let hash = + HotstuffTimeoutVote::hash_content(vote.high_qc_round, tc.round, tc_group_shell_id); + match self.non_driver.get_device(&vote.voter).await { + Ok(device) => { + let verifier = RsaCPUObjectVerifier::new(device.desc().public_key().clone()); + let is_ok = verifier.verify(hash.as_slice(), &vote.signature).await; + if !is_ok { + log::warn!("[group committee] vote with error signature"); + Err(BuckyError::new( + BuckyErrorCode::InvalidSignature, + "invalid signature", + )) + } else { + Ok(()) + } + } + Err(e) => Err(e), + } + })) + .await; + + verify_vote_results + .into_iter() + .find(|r| r.is_err()) + .map_or(Ok(()), |e| e) + } + + pub async fn verify_qc( + &self, + qc: &HotstuffBlockQC, + prev_block: &GroupConsensusBlock, + ) -> BuckyResult<()> { + self.verify_qc_with_desc(qc, prev_block.named_object().desc().content()) + .await + } + + pub async fn verify_qc_with_desc( + &self, + qc: &HotstuffBlockQC, + prev_block_desc: &GroupConsensusBlockDescContent, + ) -> BuckyResult<()> { + if qc.round != prev_block_desc.round() { + log::warn!("[group committee] round is not match with prev-block in qc, round: {}, prev_round: {}", qc.round, prev_block_desc.round()); + return Err(BuckyError::new( + BuckyErrorCode::NotMatch, + "round not match in qc", + )); + } + + let is_enough = self + .quorum_threshold( + &qc.votes.iter().map(|v| v.voter).collect(), + Some(prev_block_desc.group_shell_id()), + ) + .await?; + + if !is_enough { + log::warn!("[group committee] qc with vote not enough."); + return Err(BuckyError::new( + BuckyErrorCode::InvalidSignature, + "not enough", + )); + } + + let vote_verify_results = futures::future::join_all(qc.votes.iter().map(|vote| async { + let vote = HotstuffBlockQCVote { + block_id: qc.block_id, + prev_block_id: qc.prev_block_id.clone(), + round: qc.round, + voter: vote.voter, + signature: vote.signature.clone(), + }; + self.verify_vote(&vote).await + })) + .await; + + vote_verify_results + .into_iter() + .find(|r| r.is_err()) + .map_or(Ok(()), |e| e) + } + + async fn check_block_sign( + &self, + block: &GroupConsensusBlock, + group: &Group, + ) -> BuckyResult { + let signs = match block.named_object().signs().desc_signs() { + Some(signs) if signs.len() > 0 => signs, + _ => { + log::warn!("[group committee] no signatures"); + return Err(BuckyError::new( + BuckyErrorCode::InvalidSignature, + "not signature", + )); + } + }; + + let mut sign_device = None; + + for sign in signs { + if let SignatureSource::Object(obj) = sign.sign_source() { + if let Ok(device) = self.non_driver.get_device(&obj.obj_id).await { + let device_id = device.desc().device_id(); + if group.ood_list().contains(&device_id) + && device.desc().owner().and_then(|dev_owner| { + block + .named_object() + .desc() + .owner() + .and_then(|blk_owner| Some(blk_owner == dev_owner)) + }) == Some(true) + { + sign_device = Some((device, sign)); + break; + } else { + log::warn!("[group committee] block signed by invalid object."); + } + } + } else { + log::warn!("[group committee] support the SignatureSource::Object only."); + return Err(BuckyError::new( + BuckyErrorCode::InvalidSignature, + "not SignatureSource::Object", + )); + } + } + + let sign_device = match sign_device { + Some(device) => device, + None => { + log::warn!("[group committee] not found the sign device."); + return Err(BuckyError::new( + BuckyErrorCode::InvalidSignature, + "not found device", + )); + } + }; + + let verifier = RsaCPUObjectVerifier::new(sign_device.0.desc().public_key().clone()); + verify_object_desc_sign(&verifier, block.named_object(), &sign_device.1).await + } +} diff --git a/src/component/cyfs-group/src/consensus/vote/mod.rs b/src/component/cyfs-group/src/consensus/vote/mod.rs new file mode 100644 index 000000000..5e1313752 --- /dev/null +++ b/src/component/cyfs-group/src/consensus/vote/mod.rs @@ -0,0 +1,5 @@ +mod committee; +mod vote_mgr; + +pub use committee::*; +pub use vote_mgr::*; diff --git a/src/component/cyfs-group/src/consensus/vote/vote_mgr.rs b/src/component/cyfs-group/src/consensus/vote/vote_mgr.rs new file mode 100644 index 000000000..35f1b1fb0 --- /dev/null +++ b/src/component/cyfs-group/src/consensus/vote/vote_mgr.rs @@ -0,0 +1,251 @@ +use std::collections::{HashMap, HashSet}; + +use cyfs_base::{BuckyError, BuckyErrorCode, BuckyResult, ObjectId, Signature}; +use cyfs_core::{ + GroupConsensusBlock, GroupConsensusBlockObject, HotstuffBlockQC, HotstuffBlockQCSign, + HotstuffTimeout, HotstuffTimeoutSign, +}; +use cyfs_group_lib::{HotstuffBlockQCVote, HotstuffTimeoutVote}; + +use crate::Committee; + +pub(crate) struct VoteMgr { + committee: Committee, + round: u64, + blocks: HashMap, + votes: HashMap>>, // > + timeouts: HashMap>>, // > +} + +pub(crate) enum VoteThresholded { + QC(HotstuffBlockQC), + None, +} + +// TODO: 丢弃太大的round,避免恶意节点恶意用太大的round攻击内存 + +impl VoteMgr { + pub fn new(committee: Committee, round: u64) -> Self { + Self { + committee, + votes: HashMap::new(), + timeouts: HashMap::new(), + round, + blocks: HashMap::new(), + } + } + + pub async fn add_voting_block(&mut self, block: &GroupConsensusBlock) -> VoteThresholded { + if block.round() < self.round { + return VoteThresholded::None; + } + + let block_id = block.block_id().object_id(); + self.blocks.insert(block_id.clone(), block.clone()); + + if let Some(qc_makers) = self.votes.get_mut(&block.round()) { + if let Some(qc_maker) = qc_makers.get_mut(block_id) { + if let Some(qc) = qc_maker + .on_block(block, &self.committee) + .await + .unwrap_or(None) + { + return VoteThresholded::QC(qc); + } + } + } + + VoteThresholded::None + } + + pub(crate) async fn add_vote( + &mut self, + vote: HotstuffBlockQCVote, + block: Option, + ) -> BuckyResult> { + assert!(block + .as_ref() + .map_or(true, |b| b.block_id().object_id() == &vote.block_id)); + + let block_id = vote.block_id; + + if let Some(block) = block.as_ref() { + self.blocks.insert(block_id, block.clone()); + } + let block_ref = block.as_ref().or(self.blocks.get(&block_id)); + + // Add the new vote to our aggregator and see if we have a QC. + self.votes + .entry(vote.round) + .or_insert_with(HashMap::new) + .entry(block_id) + .or_insert_with(|| Box::new(QCMaker::new())) + .append(vote, &self.committee, block_ref) + .await + .map(|vote| vote.map(|v| (v, block.unwrap().clone()))) + } + + pub(crate) async fn add_timeout( + &mut self, + timeout: HotstuffTimeoutVote, + ) -> BuckyResult> { + // Add the new timeout to our aggregator and see if we have a TC. + let tc_maker = self + .timeouts + .entry(timeout.round) + .or_insert_with(|| HashMap::new()) + .entry(timeout.group_shell_id.clone()) + .or_insert_with(|| { + Box::new(TCMaker::new(timeout.round, timeout.group_shell_id.clone())) + }); + + tc_maker.append(timeout, &self.committee).await + } + + pub fn cleanup(&mut self, round: u64) { + self.votes.retain(|k, _| k >= &round); + self.timeouts.retain(|k, _| k >= &round); + self.blocks.retain(|_, block| block.round() >= round); + self.round = round; + } +} + +struct QCMaker { + votes: Vec<(ObjectId, Signature)>, + used: HashSet, + thresholded: bool, +} + +impl QCMaker { + pub fn new() -> Self { + Self { + votes: Vec::new(), + used: HashSet::new(), + thresholded: false, + } + } + + /// Try to append a signature to a (partial) quorum. + pub async fn append( + &mut self, + vote: HotstuffBlockQCVote, + committee: &Committee, + block: Option<&GroupConsensusBlock>, + ) -> BuckyResult> { + let author = vote.voter; + + if !self.used.insert(author) { + return Err(BuckyError::new(BuckyErrorCode::AlreadyExists, "has voted")); + } + + self.votes.push((author, vote.signature)); + + match block { + Some(block) => self.on_block(block, committee).await, + None => Ok(None), + } + } + + pub async fn on_block( + &mut self, + block: &GroupConsensusBlock, + committee: &Committee, + ) -> BuckyResult> { + if !self.thresholded { + self.thresholded = committee + .quorum_threshold( + &self.votes.iter().map(|v| v.0).collect(), + Some(block.group_shell_id()), + ) + .await?; + if self.thresholded { + return Ok(Some(HotstuffBlockQC { + block_id: block.block_id().object_id().clone(), + prev_block_id: block.prev_block_id().map(|id| id.clone()), + round: block.round(), + votes: self + .votes + .iter() + .map(|(voter, signature)| HotstuffBlockQCSign { + voter: voter.clone(), + signature: signature.clone(), + }) + .collect(), + })); + } + } + Ok(None) + } +} + +struct TCMaker { + round: u64, + votes: Vec, + group_shell_id: ObjectId, + used: HashSet, + thresholded: bool, +} + +impl TCMaker { + pub fn new(round: u64, group_shell_id: ObjectId) -> Self { + Self { + round, + votes: Vec::new(), + used: HashSet::new(), + thresholded: false, + group_shell_id, + } + } + + /// Try to append a signature to a (partial) quorum. + pub async fn append( + &mut self, + timeout: HotstuffTimeoutVote, + committee: &Committee, + ) -> BuckyResult> { + let author = timeout.voter; + + assert_eq!(self.round, timeout.round); + + // Ensure it is the first time this authority votes. + if !self.used.insert(author) { + return Err(BuckyError::new(BuckyErrorCode::AlreadyExists, "has voted")); + } + + // Add the timeout to the accumulator. + self.votes.push(timeout); + + self.on_block(committee).await + } + + pub async fn on_block( + &mut self, + committee: &Committee, + ) -> BuckyResult> { + if !self.thresholded { + self.thresholded = committee + .quorum_threshold( + &self.votes.iter().map(|v| v.voter).collect(), + Some(&self.group_shell_id), + ) + .await?; + + if self.thresholded { + return Ok(Some(HotstuffTimeout { + round: self.round, + votes: self + .votes + .iter() + .map(|v| HotstuffTimeoutSign { + voter: v.voter, + high_qc_round: v.high_qc.as_ref().map_or(0, |qc| qc.round), + signature: v.signature.clone(), + }) + .collect(), + group_shell_id: Some(self.group_shell_id.clone()), + })); + } + } + Ok(None) + } +} diff --git a/src/component/cyfs-group/src/constant.rs b/src/component/cyfs-group/src/constant.rs new file mode 100644 index 000000000..e5db191b0 --- /dev/null +++ b/src/component/cyfs-group/src/constant.rs @@ -0,0 +1,26 @@ +use std::time::Duration; + +pub const GROUP_METHOD_UPDATE: &str = ".update"; +pub const GROUP_METHOD_DECIDE: &str = ".decide"; +pub enum GroupUpdateDecide { + Accept = 1, + Reject = 2, + JoinAdmin = 3, + JoinMember = 4, +} + +// Some config +pub const NETWORK_TIMEOUT: Duration = Duration::from_millis(5000); +pub const HOTSTUFF_TIMEOUT_DEFAULT: u64 = 5000; +pub const CHANNEL_CAPACITY: usize = 1000; +pub const TIME_PRECISION: Duration = Duration::from_millis(60000); +pub const PROPOSAL_MAX_TIMEOUT: Duration = Duration::from_secs(3600); +pub const SYNCHRONIZER_TIMEOUT: u64 = 500; +pub const SYNCHRONIZER_TRY_TIMES: usize = 3; +pub const CLIENT_POLL_TIMEOUT: Duration = Duration::from_millis(5000); +pub const STATE_NOTIFY_COUNT_PER_ROUND: usize = 8; +pub const NET_PROTOCOL_VPORT: u16 = 2048; +pub const MEMORY_CACHE_SIZE: usize = 1024; +pub const MEMORY_CACHE_DURATION: Duration = Duration::from_secs(300); +pub const GROUP_DEFAULT_CONSENSUS_INTERVAL: u64 = 5000; // default 5000 ms +pub const BLOCK_COUNT_REST_TO_SYNC: u64 = 8; // the node will stop most work, and synchronize the lost blocks. diff --git a/src/component/cyfs-group/src/dataflow.vsdx b/src/component/cyfs-group/src/dataflow.vsdx new file mode 100644 index 000000000..665a8e4fb Binary files /dev/null and b/src/component/cyfs-group/src/dataflow.vsdx differ diff --git a/src/component/cyfs-group/src/dec/group_events.rs b/src/component/cyfs-group/src/dec/group_events.rs new file mode 100644 index 000000000..5573cab44 --- /dev/null +++ b/src/component/cyfs-group/src/dec/group_events.rs @@ -0,0 +1,155 @@ +use std::sync::Arc; + +use cyfs_base::{ + BuckyError, BuckyErrorCode, BuckyResult, NamedObject, ObjectDesc, ObjectId, + ObjectMapIsolatePathOpEnvRef, ObjectMapSingleOpEnvRef, RawConvertTo, RawDecode, RawFrom, + TypelessCoreObject, +}; +use cyfs_core::{GroupConsensusBlock, GroupProposal}; +use cyfs_group_lib::{ + ExecuteResult, GroupCommand, GroupCommandCommited, GroupCommandExecute, + GroupCommandExecuteResult, GroupCommandVerify, +}; +use cyfs_lib::NONObjectInfo; + +use crate::NONDriverHelper; + +#[derive(Clone)] +pub(crate) struct RPathEventNotifier { + non_driver: NONDriverHelper, +} + +impl RPathEventNotifier { + pub fn new(driver: NONDriverHelper) -> Self { + Self { non_driver: driver } + } + + pub async fn on_execute( + &self, + proposal: GroupProposal, + prev_state_id: Option, + ) -> BuckyResult { + let cmd = GroupCommandExecute { + proposal, + prev_state_id, + }; + + let cmd = GroupCommand::from(cmd); + let object_raw_buf = cmd.to_vec()?; + let any_obj = cyfs_base::AnyNamedObject::Core(TypelessCoreObject::clone_from_slice( + object_raw_buf.as_slice(), + )?); + + let result = self + .non_driver + .post_object( + NONObjectInfo { + object_id: cmd.desc().object_id(), + object_raw: object_raw_buf, + object: Some(Arc::new(any_obj)), + }, + None, + ) + .await?; + + assert!(result.is_some()); + match result.as_ref() { + Some(result) => { + let (cmd, _remain) = GroupCommand::raw_decode(result.object_raw.as_slice())?; + assert_eq!(_remain.len(), 0); + let mut cmd = TryInto::::try_into(cmd)?; + Ok(ExecuteResult { + result_state_id: cmd.result_state_id.take(), + receipt: cmd.receipt.take(), + context: cmd.context.take(), + }) + } + None => Err(BuckyError::new( + BuckyErrorCode::Unknown, + "expect some result from dec-app", + )), + } + } + + pub async fn on_verify( + &self, + proposal: GroupProposal, + prev_state_id: Option, + execute_result: &ExecuteResult, + ) -> BuckyResult<()> { + let cmd = GroupCommandVerify { + proposal, + prev_state_id, + result_state_id: execute_result.result_state_id.clone(), + receipt: execute_result.receipt.clone(), + context: execute_result.context.clone(), + }; + + let cmd = GroupCommand::from(cmd); + let object_raw_buf = cmd.to_vec()?; + let any_obj = cyfs_base::AnyNamedObject::Core(TypelessCoreObject::clone_from_slice( + object_raw_buf.as_slice(), + )?); + + let result = self + .non_driver + .post_object( + NONObjectInfo { + object_id: cmd.desc().object_id(), + object_raw: object_raw_buf, + object: Some(Arc::new(any_obj)), + }, + None, + ) + .await?; + + assert!(result.is_none()); + Ok(()) + } + + pub async fn on_commited( + &self, + prev_state_id: Option, + block: GroupConsensusBlock, + ) { + let cmd = GroupCommandCommited { + prev_state_id, + block, + }; + + let cmd = GroupCommand::from(cmd); + let object_raw_buf = cmd + .to_vec() + .expect(format!("on_commited {} failed for encode", self.non_driver.dec_id()).as_str()); + let any_obj = cyfs_base::AnyNamedObject::Core( + TypelessCoreObject::clone_from_slice(object_raw_buf.as_slice()).expect( + format!( + "on_commited {} failed for convert to any", + self.non_driver.dec_id() + ) + .as_str(), + ), + ); + + let result = self + .non_driver + .post_object( + NONObjectInfo { + object_id: cmd.desc().object_id(), + object_raw: object_raw_buf, + object: Some(Arc::new(any_obj)), + }, + None, + ) + .await + .map_err(|err| log::warn!("on_commited {} failed {:?}", self.non_driver.dec_id(), err)); + + assert!(result.is_err() || result.unwrap().is_none()); + } +} + +#[async_trait::async_trait] +pub trait GroupObjectMapProcessor: Send + Sync { + async fn create_single_op_env(&self) -> BuckyResult; + async fn create_sub_tree_op_env(&self) -> BuckyResult; +} diff --git a/src/component/cyfs-group/src/dec/group_manager.rs b/src/component/cyfs-group/src/dec/group_manager.rs new file mode 100644 index 000000000..ef873b774 --- /dev/null +++ b/src/component/cyfs-group/src/dec/group_manager.rs @@ -0,0 +1,702 @@ +use std::{collections::HashMap, sync::Arc}; + +use async_std::sync::RwLock; +use cyfs_base::{ + BuckyErrorCode, BuckyResult, GroupId, NamedObject, ObjectDesc, ObjectId, OwnerObjectDesc, + RawConvertTo, RawFrom, RsaCPUObjectSigner, TypelessCoreObject, +}; +use cyfs_bdt::{DatagramTunnelGuard, StackGuard}; +use cyfs_core::{DecAppId, GroupConsensusBlock, GroupConsensusBlockObject, GroupRPath}; +use cyfs_group_lib::{GroupCommand, GroupCommandNewRPath}; +use cyfs_lib::{GlobalStateManagerRawProcessorRef, NONObjectInfo}; +use cyfs_meta_lib::{MetaClient, MetaMinerTarget}; + +use crate::{ + storage::{GroupShellManager, GroupStorage}, + HotstuffMessage, HotstuffPackage, NONDriver, NONDriverHelper, RPathClient, RPathEventNotifier, + RPathService, NET_PROTOCOL_VPORT, +}; + +type ServiceByRPath = HashMap; +type ServiceByDec = HashMap; +type ServiceByGroup = HashMap; + +type ClientByRPath = HashMap; +type ClientByDec = HashMap; +type ClientByGroup = HashMap; + +struct GroupRPathMgrRaw { + service_by_group: ServiceByGroup, + client_by_group: ClientByGroup, + shell_mgr: HashMap, +} + +struct LocalInfo { + signer: Arc, + non_driver: Arc>, + datagram: DatagramTunnelGuard, + bdt_stack: StackGuard, + global_state_mgr: GlobalStateManagerRawProcessorRef, + meta_client: Arc, +} + +#[derive(Clone)] +pub struct GroupManager(Arc<(LocalInfo, RwLock)>); + +impl GroupManager { + pub fn new( + signer: RsaCPUObjectSigner, + non_driver: Box, + bdt_stack: StackGuard, + global_state_mgr: GlobalStateManagerRawProcessorRef, + ) -> BuckyResult { + let datagram = bdt_stack.datagram_manager().bind(NET_PROTOCOL_VPORT)?; + let local_device_id = bdt_stack.local_device_id().object_id().clone(); + let metaclient = MetaClient::new_target(MetaMinerTarget::default()); + + let local_info = LocalInfo { + signer: Arc::new(signer), + non_driver: Arc::new(non_driver), + datagram: datagram.clone(), + bdt_stack, + global_state_mgr, + meta_client: Arc::new(metaclient), + }; + + let raw = GroupRPathMgrRaw { + service_by_group: ServiceByGroup::default(), + client_by_group: ClientByGroup::default(), + shell_mgr: HashMap::default(), + }; + + let mgr = Self(Arc::new((local_info, RwLock::new(raw)))); + + crate::network::Listener::spawn(datagram, mgr.clone(), local_device_id); + + Ok(mgr) + } + + pub async fn find_rpath_service( + &self, + group_id: &ObjectId, + dec_id: &ObjectId, + rpath: &str, + is_auto_create: bool, + ) -> BuckyResult { + log::info!( + "find rpath service, group: {}, dec_id: {}, rpath: {}, is_auto_create: {}. local: {}", + group_id, + dec_id, + rpath, + is_auto_create, + self.local_info().bdt_stack.local_device_id() + ); + + self.find_rpath_service_inner(group_id, dec_id, rpath, is_auto_create, None, None) + .await + } + + pub async fn rpath_client( + &self, + group_id: &ObjectId, + dec_id: &ObjectId, + rpath: &str, + ) -> BuckyResult { + { + // read + let raw = self.read().await; + let found = raw + .client_by_group + .get(group_id) + .map_or(None, |by_dec| by_dec.get(dec_id)) + .map_or(None, |by_rpath| by_rpath.get(rpath)); + + if let Some(found) = found { + return Ok(found.clone()); + } + } + + { + // write + + let local_info = self.local_info(); + let local_id = local_info.bdt_stack.local_const().owner().unwrap(); + let local_device_id = local_info.bdt_stack.local_device_id(); + let state_mgr = local_info.global_state_mgr.clone(); + let non_driver = NONDriverHelper::new( + local_info.non_driver.clone(), + dec_id.clone(), + local_device_id.object_id().clone(), + ); + let network_sender = crate::network::Sender::new( + local_info.datagram.clone(), + non_driver.clone(), + local_device_id.object_id().clone(), + ); + + let mut raw = self.write().await; + + let found = raw + .client_by_group + .entry(group_id.clone()) + .or_insert_with(HashMap::new) + .entry(dec_id.clone()) + .or_insert_with(HashMap::new) + .entry(rpath.to_string()); + + match found { + std::collections::hash_map::Entry::Occupied(found) => Ok(found.get().clone()), + std::collections::hash_map::Entry::Vacant(entry) => { + let state_proccessor = state_mgr + .load_root_state(local_device_id.object_id(), Some(local_id), true) + .await?; + let shell_mgr = self + .check_group_shell_mgr(group_id, non_driver.clone(), None) + .await?; + let client = RPathClient::load( + local_device_id.object_id().clone(), + GroupRPath::new(group_id.clone(), dec_id.clone(), rpath.to_string()), + state_proccessor.unwrap(), + non_driver, + shell_mgr, + network_sender, + ) + .await?; + entry.insert(client.clone()); + Ok(client) + } + } + } + } + + pub async fn set_sync_path(&self, dec_id: &str, path: String) -> BuckyResult<()> { + unimplemented!() + } + + // return Vec + pub async fn enum_group(&self) -> BuckyResult> { + unimplemented!() + } + + // return + pub async fn enum_rpath_service( + &self, + group_id: &ObjectId, + ) -> BuckyResult> { + unimplemented!() + } + + fn local_info(&self) -> &LocalInfo { + &self.0 .0 + } + + async fn read(&self) -> async_std::sync::RwLockReadGuard<'_, GroupRPathMgrRaw> { + self.0 .1.read().await + } + + async fn write(&self) -> async_std::sync::RwLockWriteGuard<'_, GroupRPathMgrRaw> { + self.0 .1.write().await + } + + pub(crate) async fn on_message( + &self, + msg: HotstuffPackage, + remote: ObjectId, + ) -> BuckyResult<()> { + match msg { + HotstuffPackage::Block(block) => { + let rpath = block.rpath(); + let service = self + .find_rpath_service_inner( + rpath.group_id(), + rpath.dec_id(), + rpath.rpath(), + true, + Some(&block), + Some(&remote), + ) + .await + .map_err(|err| { + log::error!( + "new msg(Block) received, and find rpath service failed, {:?}. local: {}, err: {:?}", + rpath, + self.local_info().bdt_stack.local_device_id(), + err + ); + err + })?; + service + .on_message(HotstuffMessage::Block(block), remote) + .await; + } + HotstuffPackage::BlockVote(target, vote) => { + let rpath = target.check_rpath(); + let service = self + .find_rpath_service_inner( + rpath.group_id(), + rpath.dec_id(), + rpath.rpath(), + true, + None, + Some(&remote), + ) + .await + .map_err(|err| { + log::error!( + "new msg(BlockVote) received, and find rpath service failed, {:?}. local: {}, err: {:?}", + rpath, + self.local_info().bdt_stack.local_device_id(), + err + ); + err + })?; + service + .on_message(HotstuffMessage::BlockVote(vote), remote) + .await; + } + HotstuffPackage::TimeoutVote(target, vote) => { + let rpath = target.check_rpath(); + let service = self + .find_rpath_service_inner( + rpath.group_id(), + rpath.dec_id(), + rpath.rpath(), + true, + None, + Some(&remote), + ) + .await + .map_err(|err| { + log::error!( + "new msg(TimeoutVote) received, and find rpath service failed, {:?}. local: {}, err: {:?}", + rpath, + self.local_info().bdt_stack.local_device_id(), + err + ); + err + })?; + service + .on_message(HotstuffMessage::TimeoutVote(vote), remote) + .await; + } + HotstuffPackage::Timeout(target, tc) => { + let rpath = target.check_rpath(); + let service = self + .find_rpath_service_inner( + rpath.group_id(), + rpath.dec_id(), + rpath.rpath(), + true, + None, + Some(&remote), + ) + .await + .map_err(|err| { + log::error!( + "new msg(Timeout) received, and find rpath service failed, {:?}. local: {}, err: {:?}", + rpath, + self.local_info().bdt_stack.local_device_id(), + err + ); + err + })?; + service + .on_message(HotstuffMessage::Timeout(tc), remote) + .await; + } + HotstuffPackage::SyncRequest(target, min_bound, max_bound) => { + let rpath = target.check_rpath(); + let service = self + .find_rpath_service_inner( + rpath.group_id(), + rpath.dec_id(), + rpath.rpath(), + true, + None, + Some(&remote), + ) + .await + .map_err(|err| { + log::error!( + "new msg(SyncRequest) received, and find rpath service failed, {:?}. local: {}, err: {:?}", + rpath, + self.local_info().bdt_stack.local_device_id(), + err + ); + err + })?; + service + .on_message(HotstuffMessage::SyncRequest(min_bound, max_bound), remote) + .await; + } + HotstuffPackage::LastStateRequest(target) => { + let rpath = target.check_rpath(); + let service = self + .find_rpath_service_inner( + rpath.group_id(), + rpath.dec_id(), + rpath.rpath(), + true, + None, + Some(&remote), + ) + .await + .map_err(|err| { + log::error!( + "new msg(LastStateRequest) received, and find rpath service failed, {:?}. local: {}, err: {:?}", + rpath, + self.local_info().bdt_stack.local_device_id(), + err + ); + err + })?; + service + .on_message(HotstuffMessage::LastStateRequest, remote) + .await; + } + HotstuffPackage::StateChangeNotify(header_block, qc_block) => { + // TODO: unimplemented + // let rpath = header_block.rpath(); + // let client = self + // .rpath_client(rpath.group_id(), rpath.dec_id(), rpath.rpath()) + // .await?; + // client + // .on_message( + // HotstuffMessage::StateChangeNotify(header_block, qc_block), + // remote, + // ) + // .await; + } + HotstuffPackage::ProposalResult(proposal_id, result) => { + // TODO: unimplemented + // let rpath = result.as_ref().map_or_else( + // |(_, target)| target.check_rpath(), + // |(_, block, _)| block.rpath(), + // ); + // let client = self + // .rpath_client(rpath.group_id(), rpath.dec_id(), rpath.rpath()) + // .await?; + // client + // .on_message( + // HotstuffMessage::ProposalResult( + // proposal_id, + // result.map_err(|(err, _)| err), + // ), + // remote, + // ) + // .await; + } + HotstuffPackage::QueryState(target, sub_path) => { + let rpath = target.check_rpath(); + let service = self + .find_rpath_service_inner( + rpath.group_id(), + rpath.dec_id(), + rpath.rpath(), + true, + None, + Some(&remote), + ) + .await; + + match service { + Ok(service) => { + service + .on_message(HotstuffMessage::QueryState(sub_path), remote) + .await; + } + Err(err) => { + log::debug!( + "new msg(QueryState) received, and find rpath service failed, will try query state from client, {:?}. local: {}", + rpath, + self.local_info().bdt_stack.local_device_id() + ); + + let client = self + .rpath_client(rpath.group_id(), rpath.dec_id(), rpath.rpath()) + .await + .map_err(|err| { + log::error!( + "new msg(QueryState) received, and find rpath client failed, {:?}. local: {}, err: {:?}", + rpath, + self.local_info().bdt_stack.local_device_id(), + err + ); + err + })?; + client + .on_message(HotstuffMessage::QueryState(sub_path), remote) + .await; + } + } + } + HotstuffPackage::VerifiableState(sub_path, result) => { + let rpath = result.as_ref().map_or_else( + |(_, target)| target.check_rpath(), + |status| status.block_desc.content().rpath(), + ); + let client = self + .rpath_client(rpath.group_id(), rpath.dec_id(), rpath.rpath()) + .await + .map_err(|err| { + log::error!( + "new msg(VerifiableState) received, and find rpath client failed, {:?}. local: {}, err: {:?}", + rpath, + self.local_info().bdt_stack.local_device_id(), + err + ); + err + })?; + client + .on_message( + HotstuffMessage::VerifiableState(sub_path, result.map_err(|(err, _)| err)), + remote, + ) + .await; + } + } + + Ok(()) + } + + async fn find_rpath_service_inner( + &self, + group_id: &ObjectId, + dec_id: &ObjectId, + rpath: &str, + is_auto_create: bool, + block: Option<&GroupConsensusBlock>, + remote: Option<&ObjectId>, + ) -> BuckyResult { + { + log::debug!( + "try will find service({}) from exist cache, local: {}.", + rpath, + self.local_info().bdt_stack.local_device_id() + ); + + // read + let raw = self.read().await; + let found = raw + .service_by_group + .get(group_id) + .map_or(None, |by_dec| by_dec.get(dec_id)) + .map_or(None, |by_rpath| by_rpath.get(rpath)); + + if let Some(found) = found { + return Ok(found.clone()); + } + } + + { + log::debug!( + "find service({}) from exist cache failed, will try create new, local: {}.", + rpath, + self.local_info().bdt_stack.local_device_id() + ); + + // write + let local_info = self.local_info(); + let local_id = local_info.bdt_stack.local_const().owner().unwrap(); + let local_device_id = local_info.bdt_stack.local_device_id(); + let signer = local_info.signer.clone(); + let non_driver = NONDriverHelper::new( + local_info.non_driver.clone(), + dec_id.clone(), + local_device_id.object_id().clone(), + ); + let root_state_mgr = local_info.global_state_mgr.clone(); + let network_sender = crate::network::Sender::new( + local_info.datagram.clone(), + non_driver.clone(), + local_device_id.object_id().clone(), + ); + let local_device_id = local_info.bdt_stack.local_device_id().clone(); + + let store = GroupStorage::load( + group_id, + dec_id, + rpath, + non_driver.clone(), + local_device_id.object_id().clone(), + &root_state_mgr, + ) + .await; + let store = match store { + Ok(store) => Some(store), + Err(e) => { + if let BuckyErrorCode::NotFound = e.code() { + log::warn!("{}/{}/{} not found in storage", group_id, dec_id, rpath); + + self.on_new_rpath_request( + group_id.clone(), + dec_id, + rpath.to_string(), + block.cloned(), + ) + .await?; + + if !is_auto_create { + return Err(e); + } else { + None + } + } else { + return Err(e); + } + } + }; + + log::debug!( + "find service({}) from exist cache failed, will create new auto, local: {}.", + rpath, + self.local_info().bdt_stack.local_device_id() + ); + + let shell_mgr = self + .check_group_shell_mgr(group_id, non_driver.clone(), remote) + .await?; + + let store = match store { + Some(store) => store, + None => { + GroupStorage::create( + group_id, + dec_id, + rpath, + non_driver.clone(), + local_device_id.object_id().clone(), + &root_state_mgr, + ) + .await? + } + }; + + let mut raw = self.write().await; + + let found = raw + .service_by_group + .entry(group_id.clone()) + .or_insert_with(HashMap::new) + .entry(dec_id.clone()) + .or_insert_with(HashMap::new) + .entry(rpath.to_string()); + + match found { + std::collections::hash_map::Entry::Occupied(found) => Ok(found.get().clone()), + std::collections::hash_map::Entry::Vacant(entry) => { + let service = RPathService::start( + local_id, + local_device_id.object_id().clone(), + GroupRPath::new(group_id.clone(), dec_id.clone(), rpath.to_string()), + signer, + RPathEventNotifier::new(non_driver.clone()), + network_sender, + non_driver, + shell_mgr, + store, + ); + entry.insert(service.clone()); + Ok(service) + } + } + } + } + + async fn on_new_rpath_request( + &self, + group_id: ObjectId, + dec_id: &ObjectId, + rpath: String, + with_block: Option, + ) -> BuckyResult<()> { + let cmd = GroupCommandNewRPath { + group_id, + rpath, + with_block, + }; + + let cmd = GroupCommand::from(cmd); + let object_raw_buf = cmd.to_vec()?; + let any_obj = cyfs_base::AnyNamedObject::Core(TypelessCoreObject::clone_from_slice( + object_raw_buf.as_slice(), + )?); + let result = self + .local_info() + .non_driver + .post_object( + dec_id, + NONObjectInfo { + object_id: cmd.desc().object_id(), + object_raw: object_raw_buf, + object: Some(Arc::new(any_obj)), + }, + None, + ) + .await?; + + assert!(result.is_none()); + Ok(()) + } + + async fn check_group_shell_mgr( + &self, + group_id: &ObjectId, + non_driver: NONDriverHelper, + remote: Option<&ObjectId>, + ) -> BuckyResult { + { + let raw = self.read().await; + if let Some(shell_mgr) = raw.shell_mgr.get(group_id) { + return Ok(shell_mgr.clone()); + } + } + + let local_info = self.local_info(); + let ret = GroupShellManager::load( + group_id, + non_driver.clone(), + local_info.meta_client.clone(), + local_info.bdt_stack.local_device_id().object_id().clone(), + &local_info.global_state_mgr.clone(), + ) + .await; + + let shell_mgr = match ret { + Ok(shell_mgr) => shell_mgr, + Err(err) => { + if err.code() == BuckyErrorCode::NotFound { + log::debug!( + "shells of group({}) not found, will create automatically.", + group_id + ); + GroupShellManager::create( + group_id, + non_driver.clone(), + local_info.meta_client.clone(), + local_info.bdt_stack.local_device_id().object_id().clone(), + &local_info.global_state_mgr.clone(), + remote, + ) + .await? + } else { + log::error!("load shells of group({}) failed, err {:?}", group_id, err); + return Err(err); + } + } + }; + + { + let mut raw = self.write().await; + let shell_mgr = match raw.shell_mgr.get(group_id) { + Some(shell_mgr) => shell_mgr.clone(), + None => { + raw.shell_mgr.insert(group_id.clone(), shell_mgr.clone()); + shell_mgr + } + }; + + Ok(shell_mgr) + } + } +} diff --git a/src/component/cyfs-group/src/dec/mod.rs b/src/component/cyfs-group/src/dec/mod.rs new file mode 100644 index 000000000..f881db19c --- /dev/null +++ b/src/component/cyfs-group/src/dec/mod.rs @@ -0,0 +1,11 @@ +// dec framework + +mod group_events; +mod group_manager; +mod rpath_client; +mod rpath_service; + +pub use group_events::*; +pub use group_manager::*; +pub use rpath_client::*; +pub use rpath_service::*; diff --git a/src/component/cyfs-group/src/dec/rpath_client.rs b/src/component/cyfs-group/src/dec/rpath_client.rs new file mode 100644 index 000000000..45aa57dd4 --- /dev/null +++ b/src/component/cyfs-group/src/dec/rpath_client.rs @@ -0,0 +1,258 @@ +use std::sync::Arc; + +use cyfs_base::{ + BuckyError, BuckyErrorCode, BuckyResult, GroupMemberScope, NamedObject, ObjectDesc, ObjectId, + RawConvertTo, +}; +use cyfs_core::{GroupConsensusBlock, GroupProposal, GroupProposalObject, GroupRPath}; +use cyfs_lib::{GlobalStateRawProcessorRef, NONObjectInfo}; +use rand::Rng; + +use crate::{ + dec_state::{DecStateRequestor, DecStateSynchronizer}, + storage::{DecStorage, GroupShellManager}, + Committee, HotstuffMessage, CLIENT_POLL_TIMEOUT, +}; + +struct RPathClientRaw { + rpath: GroupRPath, + local_device_id: ObjectId, + non_driver: crate::network::NONDriverHelper, + shell_mgr: GroupShellManager, + network_sender: crate::network::Sender, + state_sync: DecStateSynchronizer, + state_requestor: DecStateRequestor, +} + +#[derive(Clone)] +pub struct RPathClient(Arc); + +impl RPathClient { + pub(crate) async fn load( + local_device_id: ObjectId, + rpath: GroupRPath, + state_processor: GlobalStateRawProcessorRef, + non_driver: crate::network::NONDriverHelper, + shell_mgr: GroupShellManager, + network_sender: crate::network::Sender, + ) -> BuckyResult { + let dec_store = DecStorage::load(state_processor).await?; + let committee = Committee::new( + rpath.group_id().clone(), + non_driver.clone(), + shell_mgr.clone(), + local_device_id, + ); + + let state_sync = DecStateSynchronizer::new( + local_device_id, + rpath.clone(), + committee.clone(), + non_driver.clone(), + shell_mgr.clone(), + dec_store.clone(), + ); + + let state_requestor = DecStateRequestor::new( + local_device_id, + rpath.clone(), + committee, + network_sender.clone(), + non_driver.clone(), + dec_store.clone(), + ); + + let raw = RPathClientRaw { + rpath, + non_driver, + network_sender, + local_device_id, + state_sync, + state_requestor, + shell_mgr, + }; + + Ok(Self(Arc::new(raw))) + } + + pub fn rpath(&self) -> &GroupRPath { + &self.0.rpath + } + + pub async fn post_proposal( + &self, + proposal: &GroupProposal, + ) -> BuckyResult> { + assert_eq!(proposal.rpath(), &self.0.rpath); + + // TODO: signature + let group = self + .0 + .shell_mgr + .get_group(proposal.rpath().group_id(), None, None) + .await?; + let oods = group.ood_list_with_distance(&self.0.local_device_id); + let proposal_id = proposal.desc().object_id(); + let non_proposal = NONObjectInfo::new(proposal_id, proposal.to_vec()?, None); + + let waiter = self.0.state_sync.wait_proposal_result(proposal_id).await; + let mut waiter_future = Some(waiter.wait()); + + let mut post_result = None; + let mut exe_result = None; + + for ood in oods { + match self + .0 + .non_driver + .post_object(non_proposal.clone(), Some(ood)) + .await + { + Ok(r) => post_result = Some(Ok(())), + Err(e) => { + if post_result.is_none() { + post_result = Some(Err(e)); + } + continue; + } + } + + match futures::future::select( + waiter_future.take().unwrap(), + Box::pin(async_std::task::sleep(CLIENT_POLL_TIMEOUT)), + ) + .await + { + futures::future::Either::Left((result, _)) => match result { + Err(_) => return Err(BuckyError::new(BuckyErrorCode::Unknown, "unknown")), + Ok(result) => match result { + Ok(result) => return Ok(result), + Err(e) => exe_result = Some(e), + }, + }, + futures::future::Either::Right((_, waiter)) => { + waiter_future = Some(waiter); + } + } + } + + post_result.map_or( + Err(BuckyError::new(BuckyErrorCode::InvalidTarget, "no admin")), + |result| match result { + Ok(_) => { + let err = exe_result + .map_or(BuckyError::new(BuckyErrorCode::Timeout, "timeout"), |e| e); + Err(err) + } + Err(e) => Err(e), + }, + ) + } + + // request last state from random ood in group.ood_list() + pub async fn refresh_state(&self) -> BuckyResult<()> { + let group = self + .0 + .shell_mgr + .get_group(&self.0.rpath.group_id(), None, None) + .await?; + + let oods = group.ood_list_with_distance(&self.0.local_device_id); + let random = rand::thread_rng().gen_range(0..oods.len()); + let ood = oods.get(random).unwrap().clone(); + + self.0 + .network_sender + .post_message(HotstuffMessage::LastStateRequest, self.0.rpath.clone(), ood) + .await; + Ok(()) + } + + pub async fn get_by_path(&self, sub_path: &str) -> BuckyResult> { + let group = self + .0 + .shell_mgr + .get_group(self.0.rpath.group_id(), None, None) + .await?; + + let members = + group.select_members_with_distance(&self.0.local_device_id, GroupMemberScope::All); + let req_msg = HotstuffMessage::QueryState(sub_path.to_string()); + + let waiter = self + .0 + .state_requestor + .wait_query_state(sub_path.to_string()) + .await; + let mut waiter_future = Some(waiter.wait()); + + let mut exe_result = None; + + for member in members { + self.0 + .network_sender + .post_message(req_msg.clone(), self.0.rpath.clone(), member) + .await; + + match futures::future::select( + waiter_future.take().unwrap(), + Box::pin(async_std::task::sleep(CLIENT_POLL_TIMEOUT)), + ) + .await + { + futures::future::Either::Left((result, _)) => match result { + Err(_) => return Err(BuckyError::new(BuckyErrorCode::Unknown, "unknown")), + Ok(result) => match result { + Ok(result) => return Ok(result), + Err(e) => exe_result = Some(e), + }, + }, + futures::future::Either::Right((_, waiter)) => { + waiter_future = Some(waiter); + } + } + } + + let err = exe_result.map_or(BuckyError::new(BuckyErrorCode::Timeout, "timeout"), |e| e); + Err(err) + } + + pub async fn get_block(&self, height: Option) -> BuckyResult { + unimplemented!() + } + + pub(crate) async fn on_message(&self, msg: HotstuffMessage, remote: ObjectId) { + match msg { + HotstuffMessage::Block(block) => unreachable!(), + HotstuffMessage::BlockVote(vote) => unreachable!(), + HotstuffMessage::TimeoutVote(vote) => unreachable!(), + HotstuffMessage::Timeout(tc) => unreachable!(), + HotstuffMessage::SyncRequest(min_bound, max_bound) => unreachable!(), + HotstuffMessage::LastStateRequest => unreachable!(), + HotstuffMessage::StateChangeNotify(header_block, qc) => { + self.0 + .state_sync + .on_state_change(header_block, qc, remote) + .await + } + HotstuffMessage::ProposalResult(proposal_id, result) => { + self.0 + .state_sync + .on_proposal_complete(proposal_id, result, remote) + .await + } + HotstuffMessage::QueryState(sub_path) => { + self.0 + .state_requestor + .on_query_state(sub_path, remote) + .await + } + HotstuffMessage::VerifiableState(sub_path, result) => { + self.0 + .state_requestor + .on_verifiable_state(sub_path, result, remote) + .await + } + } + } +} diff --git a/src/component/cyfs-group/src/dec/rpath_service.rs b/src/component/cyfs-group/src/dec/rpath_service.rs new file mode 100644 index 000000000..01c45cae8 --- /dev/null +++ b/src/component/cyfs-group/src/dec/rpath_service.rs @@ -0,0 +1,128 @@ +use std::sync::Arc; + +use cyfs_base::{ + AnyNamedObject, BuckyError, BuckyErrorCode, BuckyResult, NamedObject, ObjectDesc, ObjectId, + RawConvertTo, RawFrom, RsaCPUObjectSigner, TypelessCoreObject, +}; +use cyfs_core::{GroupProposal, GroupRPath}; +use cyfs_lib::NONObjectInfo; + +use crate::{ + network::NONDriverHelper, + storage::{GroupShellManager, GroupStorage}, + Committee, Hotstuff, HotstuffMessage, PendingProposalHandler, PendingProposalMgr, + RPathEventNotifier, +}; + +struct RPathServiceRaw { + local_id: ObjectId, + rpath: GroupRPath, + network_sender: crate::network::Sender, + pending_proposal_handle: PendingProposalHandler, + hotstuff: Hotstuff, + non_driver: NONDriverHelper, +} + +#[derive(Clone)] +pub struct RPathService(Arc); + +impl RPathService { + pub(crate) fn start( + local_id: ObjectId, + local_device_id: ObjectId, + rpath: GroupRPath, + signer: Arc, + event_notifier: RPathEventNotifier, + network_sender: crate::network::Sender, + non_driver: NONDriverHelper, + shell_mgr: GroupShellManager, + store: GroupStorage, + ) -> Self { + let (pending_proposal_handle, pending_proposal_consumer) = PendingProposalMgr::new(); + let committee = Committee::new( + rpath.group_id().clone(), + non_driver.clone(), + shell_mgr.clone(), + local_device_id, + ); + let hotstuff = Hotstuff::new( + local_id, + local_device_id, + committee.clone(), + store, + signer, + network_sender.clone(), + non_driver.clone(), + shell_mgr, + pending_proposal_consumer, + event_notifier, + rpath.clone(), + ); + + let raw = RPathServiceRaw { + network_sender, + pending_proposal_handle, + local_id, + rpath, + hotstuff, + non_driver, + }; + + Self(Arc::new(raw)) + } + + pub fn rpath(&self) -> &GroupRPath { + &self.0.rpath + } + + pub async fn push_proposal( + &self, + proposal: GroupProposal, + ) -> BuckyResult> { + let proposal_id = proposal.desc().object_id(); + + log::info!("group({:?}) push proposal {}", self.rpath(), proposal_id); + + let object_raw = proposal.to_vec()?; + let any_obj = + AnyNamedObject::Core(TypelessCoreObject::clone_from_slice(object_raw.as_slice())?); + let non_obj = NONObjectInfo::new(proposal_id, object_raw, Some(Arc::new(any_obj))); + self.0.non_driver.put_object(non_obj).await?; + self.0.pending_proposal_handle.on_proposal(proposal).await?; + + let waiter = self.0.hotstuff.wait_proposal_result(proposal_id).await; + + waiter.wait().await.map_or( + Err(BuckyError::new(BuckyErrorCode::Unknown, "unknown")), + |r| r, + ) + } + + pub fn select_branch(&self, block_id: ObjectId, source: ObjectId) -> BuckyResult<()> { + unimplemented!() + } + + pub(crate) async fn on_message(&self, msg: HotstuffMessage, remote: ObjectId) { + match msg { + HotstuffMessage::Block(block) => self.0.hotstuff.on_block(block, remote).await, + HotstuffMessage::BlockVote(vote) => self.0.hotstuff.on_block_vote(vote, remote).await, + HotstuffMessage::TimeoutVote(vote) => { + self.0.hotstuff.on_timeout_vote(vote, remote).await + } + HotstuffMessage::Timeout(tc) => self.0.hotstuff.on_timeout(tc, remote).await, + HotstuffMessage::SyncRequest(min_bound, max_bound) => { + self.0 + .hotstuff + .on_sync_request(min_bound, max_bound, remote) + .await + } + HotstuffMessage::LastStateRequest => self.0.hotstuff.request_last_state(remote).await, + HotstuffMessage::StateChangeNotify(_, _) => unreachable!(), + HotstuffMessage::ProposalResult(_, _) => unreachable!(), + HotstuffMessage::QueryState(sub_path) => { + self.0.hotstuff.on_query_state(sub_path, remote).await + } + HotstuffMessage::VerifiableState(_, _) => unreachable!(), + } + } +} diff --git a/src/component/cyfs-group/src/dec_state/call_reply.rs b/src/component/cyfs-group/src/dec_state/call_reply.rs new file mode 100644 index 000000000..d73b5196e --- /dev/null +++ b/src/component/cyfs-group/src/dec_state/call_reply.rs @@ -0,0 +1,101 @@ +use std::{collections::HashMap, sync::Arc}; + +use async_std::sync::{Mutex, RwLock}; + +use crate::CHANNEL_CAPACITY; + +type CallSeq = u64; + +pub struct CallReplyWaiter { + rx: async_std::channel::Receiver, + seq: CallSeq, + container: Arc>>>, +} + +impl CallReplyWaiter { + pub fn wait(&self) -> async_std::channel::Recv<'_, T> { + self.rx.recv() + } +} + +impl Drop for CallReplyWaiter { + fn drop(&mut self) { + let container = self.container.clone(); + async_std::task::block_on(async move { + let mut container = container.lock().await; + container.remove(&self.seq); + }); + } +} + +struct CallReplyNotifierRaw { + next_seq: CallSeq, + senders: Arc>>>, + call_keys: HashMap>, +} + +#[derive(Clone)] +pub struct CallReplyNotifier( + Arc>>, +); + +impl CallReplyNotifier { + pub fn new() -> Self { + Self(Arc::new(RwLock::new(CallReplyNotifierRaw { + next_seq: 1, + senders: Arc::new(Mutex::new(HashMap::new())), + call_keys: HashMap::new(), + }))) + } + + pub async fn prepare(&self, k: K) -> CallReplyWaiter { + let (tx, rx) = async_std::channel::bounded(CHANNEL_CAPACITY); + let mut mgr = self.0.write().await; + let seq = mgr.next_seq; + mgr.next_seq += 1; + mgr.senders.lock().await.insert(seq, tx); + mgr.call_keys.entry(k).or_insert_with(Vec::new).push(seq); + + CallReplyWaiter { + rx, + seq, + container: mgr.senders.clone(), + } + } + + pub async fn reply(&self, key: &K, value: T) { + let (abort_calls, senders) = { + let mgr = self.0.read().await; + let senders = mgr.senders.lock().await; + match mgr.call_keys.get(key) { + Some(call_seqs) => { + let mut valid_senders = vec![]; + let mut abort_calls = vec![]; + for call_seq in call_seqs { + match senders.get(call_seq) { + Some(sender) => valid_senders.push(sender.clone()), + None => abort_calls.push(*call_seq), + } + } + + (abort_calls, valid_senders) + } + None => return, + } + }; + + if abort_calls.len() > 0 { + let mut mgr = self.0.write().await; + if let Some(call_seqs) = mgr.call_keys.get_mut(key) { + call_seqs.retain(|seq| !abort_calls.contains(seq)); + if call_seqs.len() == 0 { + mgr.call_keys.remove(key); + } + } + } + + for sender in senders { + sender.send(value.clone()).await; + } + } +} diff --git a/src/component/cyfs-group/src/dec_state/dec_state_requestor.rs b/src/component/cyfs-group/src/dec_state/dec_state_requestor.rs new file mode 100644 index 000000000..b9d33efc4 --- /dev/null +++ b/src/component/cyfs-group/src/dec_state/dec_state_requestor.rs @@ -0,0 +1,199 @@ +// the manager of the DEC's state that synchronized from the group's rpath + +use std::sync::Arc; + +use cyfs_base::{BuckyError, BuckyErrorCode, BuckyResult, ObjectId}; +use cyfs_core::{GroupRPath}; +use cyfs_group_lib::GroupRPathStatus; +use cyfs_lib::NONObjectInfo; +use futures::FutureExt; + +use crate::{storage::DecStorage, Committee, HotstuffMessage, CHANNEL_CAPACITY}; + +use super::{CallReplyNotifier, CallReplyWaiter}; + +enum DecStateRequestorMessage { + QueryState(String), // sub-path + VerifiableState(String, BuckyResult), // (sub-path, result) +} + +struct DecStateRequestorRaw { + local_device_id: ObjectId, + tx_dec_state_req_message: async_std::channel::Sender<(DecStateRequestorMessage, ObjectId)>, + query_state_notifier: CallReplyNotifier>>, +} + +#[derive(Clone)] +pub struct DecStateRequestor(Arc); + +impl DecStateRequestor { + pub(crate) fn new( + local_device_id: ObjectId, + rpath: GroupRPath, + committee: Committee, + network_sender: crate::network::Sender, + non_driver: crate::network::NONDriverHelper, + store: DecStorage, + ) -> Self { + let (tx, rx) = async_std::channel::bounded(CHANNEL_CAPACITY); + let notifier = CallReplyNotifier::new(); + + let mut runner = DecStateRequestorRunner::new( + local_device_id, + rpath, + committee, + rx, + store, + network_sender, + non_driver, + notifier.clone(), + ); + + async_std::task::spawn(async move { runner.run().await }); + + Self(Arc::new(DecStateRequestorRaw { + local_device_id, + tx_dec_state_req_message: tx, + query_state_notifier: notifier, + })) + } + + pub async fn wait_query_state( + &self, + sub_path: String, + ) -> CallReplyWaiter>> { + self.0.query_state_notifier.prepare(sub_path).await + } + + pub async fn on_query_state(&self, sub_path: String, remote: ObjectId) { + self.0 + .tx_dec_state_req_message + .send((DecStateRequestorMessage::QueryState(sub_path), remote)) + .await; + } + + pub async fn on_verifiable_state( + &self, + sub_path: String, + result: BuckyResult, + remote: ObjectId, + ) { + self.0 + .tx_dec_state_req_message + .send(( + DecStateRequestorMessage::VerifiableState(sub_path, result), + remote, + )) + .await; + } +} + +struct DecStateRequestorRunner { + local_device_id: ObjectId, + rpath: GroupRPath, + committee: Committee, + rx_dec_state_req_message: async_std::channel::Receiver<(DecStateRequestorMessage, ObjectId)>, + // timer: Timer, + store: DecStorage, + + network_sender: crate::network::Sender, + non_driver: crate::network::NONDriverHelper, + query_state_notifier: CallReplyNotifier>>, +} + +impl DecStateRequestorRunner { + fn new( + local_device_id: ObjectId, + rpath: GroupRPath, + committee: Committee, + rx_dec_state_req_message: async_std::channel::Receiver<( + DecStateRequestorMessage, + ObjectId, + )>, + store: DecStorage, + network_sender: crate::network::Sender, + non_driver: crate::network::NONDriverHelper, + query_state_notifier: CallReplyNotifier>>, + ) -> Self { + Self { + local_device_id, + rpath, + rx_dec_state_req_message, + // timer: Timer::new(SYNCHRONIZER_TIMEOUT), + store, + query_state_notifier, + network_sender, + non_driver, + committee, + } + } + + async fn handle_query_state(&mut self, sub_path: String, remote: ObjectId) { + let result = self.store.get_by_path(sub_path.as_str()).await; + self.network_sender + .post_message( + HotstuffMessage::VerifiableState(sub_path, result), + self.rpath.clone(), + &remote, + ) + .await; + } + + async fn handle_verifiable_state( + &mut self, + sub_path: String, + result: BuckyResult, + remote: ObjectId, + ) { + match result { + Ok(result) => { + let result = self + .check_sub_path_value(sub_path.as_str(), &result, &remote) + .await + .map(|r| r.cloned()); + + log::debug!( + "handle_verifiable_state sub_path: {}, result: {:?}", + sub_path, + result + ); + self.query_state_notifier.reply(&sub_path, result).await + } + Err(e) => self.query_state_notifier.reply(&sub_path, Err(e)).await, + } + } + + async fn check_sub_path_value<'a>( + &self, + sub_path: &str, + verifiable_status: &'a GroupRPathStatus, + remote: &ObjectId, + ) -> BuckyResult> { + self.committee + .verify_block_desc_with_qc( + &verifiable_status.block_desc, + &verifiable_status.certificate, + remote.clone(), + ) + .await?; + + self.store + .check_sub_path_value(sub_path, verifiable_status) + .await + } + + async fn run(&mut self) { + loop { + futures::select! { + message = self.rx_dec_state_req_message.recv().fuse() => match message { + Ok((DecStateRequestorMessage::QueryState(sub_path), remote)) => self.handle_query_state(sub_path, remote).await, + Ok((DecStateRequestorMessage::VerifiableState(sub_path, result), remote)) => self.handle_verifiable_state(sub_path, result, remote).await, + Err(e) => { + log::warn!("[dec-state-sync] rx closed.") + }, + }, + // () = self.timer.wait_next().fuse() => {self.sync_state().await;}, + }; + } + } +} diff --git a/src/component/cyfs-group/src/dec_state/dec_state_synchronizer.rs b/src/component/cyfs-group/src/dec_state/dec_state_synchronizer.rs new file mode 100644 index 000000000..d4d30c3e1 --- /dev/null +++ b/src/component/cyfs-group/src/dec_state/dec_state_synchronizer.rs @@ -0,0 +1,363 @@ +// the manager of the DEC's state that synchronized from the group's rpath + +use std::{collections::HashSet, sync::Arc}; + +use cyfs_base::{BuckyError, BuckyErrorCode, BuckyResult, Group, NamedObject, ObjectId}; +use cyfs_core::{GroupConsensusBlock, GroupConsensusBlockObject, GroupRPath, HotstuffBlockQC}; +use cyfs_lib::NONObjectInfo; +use futures::FutureExt; + +use crate::{ + network::NONDriverHelper, + storage::{DecStorage, DecStorageCache, GroupShellManager}, + Committee, CHANNEL_CAPACITY, +}; + +use super::{CallReplyNotifier, CallReplyWaiter}; + +enum DecStateSynchronizerMessage { + ProposalResult( + ObjectId, + BuckyResult<(Option, GroupConsensusBlock, HotstuffBlockQC)>, + ), + StateChange(GroupConsensusBlock, HotstuffBlockQC), + DelaySync(Option<(ObjectId, Option)>), // (proposal-id, Ok(result)) +} + +struct DecStateSynchronizerRaw { + local_device_id: ObjectId, + tx_dec_state_sync_message: async_std::channel::Sender<(DecStateSynchronizerMessage, ObjectId)>, + proposal_result_notifier: CallReplyNotifier>>, +} + +#[derive(Clone)] +pub struct DecStateSynchronizer(Arc); + +impl DecStateSynchronizer { + pub(crate) fn new( + local_device_id: ObjectId, + rpath: GroupRPath, + committee: Committee, + non_driver: crate::network::NONDriverHelper, + shell_mgr: GroupShellManager, + store: DecStorage, + ) -> Self { + let (tx, rx) = async_std::channel::bounded(CHANNEL_CAPACITY); + let notifier = CallReplyNotifier::new(); + + let mut runner = DecStateSynchronizerRunner::new( + local_device_id, + rpath, + committee, + tx.clone(), + rx, + store, + non_driver, + shell_mgr, + notifier.clone(), + ); + + async_std::task::spawn(async move { runner.run().await }); + + Self(Arc::new(DecStateSynchronizerRaw { + local_device_id, + tx_dec_state_sync_message: tx, + proposal_result_notifier: notifier, + })) + } + + pub async fn wait_proposal_result( + &self, + proposal_id: ObjectId, + ) -> CallReplyWaiter>> { + self.0.proposal_result_notifier.prepare(proposal_id).await + } + + pub async fn on_proposal_complete( + &self, + proposal_id: ObjectId, + result: BuckyResult<(Option, GroupConsensusBlock, HotstuffBlockQC)>, + remote: ObjectId, + ) { + self.0 + .tx_dec_state_sync_message + .send(( + DecStateSynchronizerMessage::ProposalResult(proposal_id, result), + remote, + )) + .await; + } + + pub async fn on_state_change( + &self, + header_block: GroupConsensusBlock, + qc: HotstuffBlockQC, + remote: ObjectId, + ) { + self.0 + .tx_dec_state_sync_message + .send(( + DecStateSynchronizerMessage::StateChange(header_block, qc), + remote, + )) + .await; + } +} + +struct DecStateSynchronizerCache { + state_cache: DecStorageCache, + group_shell_id: ObjectId, + group: Group, +} + +struct UpdateNotifyInfo { + header_block: GroupConsensusBlock, + qc: HotstuffBlockQC, + remotes: HashSet, + group_shell_id: ObjectId, + group: Group, +} + +struct DecStateSynchronizerRunner { + local_device_id: ObjectId, + rpath: GroupRPath, + committee: Committee, + tx_dec_state_sync_message: async_std::channel::Sender<(DecStateSynchronizerMessage, ObjectId)>, + rx_dec_state_sync_message: + async_std::channel::Receiver<(DecStateSynchronizerMessage, ObjectId)>, + // timer: Timer, + store: DecStorage, + state_cache: Option, + update_notifies: Option, + + non_driver: NONDriverHelper, + shell_mgr: GroupShellManager, + proposal_result_notifier: CallReplyNotifier>>, +} + +impl DecStateSynchronizerRunner { + fn new( + local_device_id: ObjectId, + rpath: GroupRPath, + committee: Committee, + tx_dec_state_sync_message: async_std::channel::Sender<( + DecStateSynchronizerMessage, + ObjectId, + )>, + rx_dec_state_sync_message: async_std::channel::Receiver<( + DecStateSynchronizerMessage, + ObjectId, + )>, + store: DecStorage, + non_driver: NONDriverHelper, + shell_mgr: GroupShellManager, + proposal_result_notifier: CallReplyNotifier>>, + ) -> Self { + Self { + local_device_id, + rpath, + tx_dec_state_sync_message, + rx_dec_state_sync_message, + // timer: Timer::new(SYNCHRONIZER_TIMEOUT), + store, + state_cache: None, + update_notifies: None, + non_driver, + proposal_result_notifier, + committee, + shell_mgr, + } + } + + async fn handle_proposal_complete( + &mut self, + proposal_id: ObjectId, + result: BuckyResult<(Option, GroupConsensusBlock, HotstuffBlockQC)>, + remote: ObjectId, + ) { + match result { + Ok((result, header_block, qc)) => { + if header_block + .proposals() + .iter() + .find(|p| p.proposal == proposal_id) + .is_none() + { + return; + } + if !header_block.check() { + return; + } + + if self + .push_update_notify(header_block, qc, remote) + .await + .is_err() + { + return; + } + + self.tx_dec_state_sync_message + .send(( + DecStateSynchronizerMessage::DelaySync(Some((proposal_id, result))), + remote, + )) + .await; + } + Err(e) => { + // notify the app + self.proposal_result_notifier + .reply(&proposal_id, Err(e)) + .await; + } + }; + } + + async fn handle_state_change( + &mut self, + header_block: GroupConsensusBlock, + qc: HotstuffBlockQC, + remote: ObjectId, + ) { + if self + .push_update_notify(header_block, qc, remote) + .await + .is_ok() + { + self.tx_dec_state_sync_message + .send((DecStateSynchronizerMessage::DelaySync(None), remote)) + .await; + } + } + + async fn sync_state( + &mut self, + proposal_result: Option<(ObjectId, Option)>, + remote: ObjectId, + ) { + let result = match self.update_notifies.as_ref() { + Some(notify_info) => { + let mut err = None; + for remote in notify_info.remotes.iter() { + match self + .store + .sync(¬ify_info.header_block, ¬ify_info.qc, remote.clone()) + .await + { + Ok(_) => { + err = None; + self.state_cache = Some(DecStateSynchronizerCache { + state_cache: DecStorageCache { + state: notify_info.header_block.result_state_id().clone(), + header_block: notify_info.header_block.clone(), + qc: notify_info.qc.clone(), + }, + group_shell_id: notify_info.group_shell_id, + group: notify_info.group.clone(), + }); + self.update_notifies = None; + break; + } + Err(e) => { + err = err.or(Some(e)); + } + } + } + err.map_or(Ok(()), |e| Err(e)) + } + None => Ok(()), + }; + + if let Some((proposal_id, proposal_result)) = proposal_result { + let proposal_result = result.map(|_| proposal_result); + // notify app dec + self.proposal_result_notifier + .reply(&proposal_id, proposal_result) + .await; + } + } + + async fn check_cache(&mut self) -> &Option { + if self.state_cache.is_none() { + let state_cache = self.store.cur_state().await; + if let Some(state_cache) = state_cache { + let group_shell_id = state_cache.header_block.group_shell_id().clone(); + let group = self + .shell_mgr + .get_group(self.rpath.group_id(), Some(&group_shell_id), None) + .await; + if let Ok(group) = group { + self.state_cache = Some(DecStateSynchronizerCache { + state_cache, + group_shell_id, + group, + }); + } + } + } + + &self.state_cache + } + + async fn push_update_notify( + &mut self, + header_block: GroupConsensusBlock, + qc: HotstuffBlockQC, + remote: ObjectId, + ) -> BuckyResult<()> { + if let Some(notify) = self.update_notifies.as_mut() { + match notify.header_block.height().cmp(&header_block.height()) { + std::cmp::Ordering::Less => {} + std::cmp::Ordering::Equal => { + notify.remotes.insert(remote); + return Ok(()); + } + std::cmp::Ordering::Greater => return Ok(()), + } + } + + if header_block.check() + && self + .committee + .verify_block_desc_with_qc(header_block.named_object().desc(), &qc, remote) + .await + .is_ok() + { + let group = self + .shell_mgr + .get_group( + self.rpath.group_id(), + Some(header_block.group_shell_id()), + None, + ) + .await?; + let group_shell_id = header_block.group_shell_id().clone(); + + self.update_notifies = Some(UpdateNotifyInfo { + header_block: header_block, + qc: qc, + remotes: HashSet::from([remote]), + group_shell_id, + group, + }); + }; + + Ok(()) + } + + async fn run(&mut self) { + loop { + futures::select! { + message = self.rx_dec_state_sync_message.recv().fuse() => match message { + Ok((DecStateSynchronizerMessage::ProposalResult(proposal, result), remote)) => self.handle_proposal_complete(proposal, result, remote).await, + Ok((DecStateSynchronizerMessage::StateChange(block, qc_block), remote)) => self.handle_state_change(block, qc_block, remote).await, + Ok((DecStateSynchronizerMessage::DelaySync(proposal_result), remote)) => self.sync_state(proposal_result, remote).await, + Err(e) => { + log::warn!("[dec-state-sync] rx closed.") + }, + }, + // () = self.timer.wait_next().fuse() => {self.sync_state().await;}, + }; + } + } +} diff --git a/src/component/cyfs-group/src/dec_state/mod.rs b/src/component/cyfs-group/src/dec_state/mod.rs new file mode 100644 index 000000000..18162d16a --- /dev/null +++ b/src/component/cyfs-group/src/dec_state/mod.rs @@ -0,0 +1,9 @@ +mod call_reply; +mod dec_state_requestor; +mod dec_state_synchronizer; +mod state_pusher; + +pub(crate) use call_reply::*; +pub(crate) use dec_state_requestor::*; +pub(crate) use dec_state_synchronizer::*; +pub(crate) use state_pusher::*; diff --git a/src/component/cyfs-group/src/dec_state/state_pusher.rs b/src/component/cyfs-group/src/dec_state/state_pusher.rs new file mode 100644 index 000000000..6d9d62a60 --- /dev/null +++ b/src/component/cyfs-group/src/dec_state/state_pusher.rs @@ -0,0 +1,380 @@ +// notify the members when the state of rpath changed + +use std::collections::HashSet; + +use cyfs_base::{ + BuckyError, GroupMemberScope, NamedObject, ObjectDesc, ObjectId, OwnerObjectDesc, RawDecode, +}; +use cyfs_core::{GroupConsensusBlock, GroupConsensusBlockObject, GroupProposal, GroupRPath}; +use cyfs_lib::NONObjectInfo; +use futures::FutureExt; + +use crate::{ + storage::GroupShellManager, HotstuffMessage, CHANNEL_CAPACITY, STATE_NOTIFY_COUNT_PER_ROUND, +}; + +enum StatePushMessage { + ProposalResult(GroupProposal, BuckyError), + BlockCommit(GroupConsensusBlock, GroupConsensusBlock), // + LastStateRequest(ObjectId), + DelayBroadcast, +} + +#[derive(Clone)] +pub struct StatePusher { + local_id: ObjectId, + tx_notifier: async_std::channel::Sender, +} + +impl StatePusher { + pub(crate) fn new( + local_id: ObjectId, + network_sender: crate::network::Sender, + rpath: GroupRPath, + non_driver: crate::network::NONDriverHelper, + shell_mgr: GroupShellManager, + ) -> Self { + let (tx, rx) = async_std::channel::bounded(CHANNEL_CAPACITY); + + let mut runner = StateChanggeRunner::new( + local_id, + network_sender, + rpath, + non_driver, + shell_mgr, + tx.clone(), + rx, + ); + + async_std::task::spawn(async move { runner.run().await }); + + Self { + local_id, + tx_notifier: tx, + } + } + + pub async fn notify_proposal_err(&self, proposal: GroupProposal, err: BuckyError) { + self.tx_notifier + .send(StatePushMessage::ProposalResult(proposal, err)) + .await; + } + + pub async fn notify_block_commit( + &self, + block: GroupConsensusBlock, + qc_block: GroupConsensusBlock, + ) { + let block_id = block.block_id(); + if qc_block.height() != block.height() + 1 + || qc_block.qc().as_ref().expect("qc should not empty").round != block.round() + || qc_block.round() <= block.round() + || qc_block.prev_block_id().unwrap() != block_id.object_id() + { + log::error!( + "the qc-block({}) should be next block({})", + qc_block.block_id(), + block_id + ); + return; + } + + if block.owner() != &self.local_id { + return; + } + + self.tx_notifier + .send(StatePushMessage::BlockCommit(block, qc_block)) + .await; + } + + pub async fn request_last_state(&self, remote: ObjectId) { + self.tx_notifier + .send(StatePushMessage::LastStateRequest(remote)) + .await; + } +} + +struct HeaderBlockNotifyProgress { + header_block: GroupConsensusBlock, + qc_block: GroupConsensusBlock, + group_shell_id: ObjectId, + members: Vec, + total_notify_times: usize, + cur_block_notify_times: usize, +} + +struct StateChanggeRunner { + local_id: ObjectId, + network_sender: crate::network::Sender, + rpath: GroupRPath, + non_driver: crate::network::NONDriverHelper, + shell_mgr: GroupShellManager, + tx_notifier: async_std::channel::Sender, + rx_notifier: async_std::channel::Receiver, + delay_notify_times: usize, + // timer: Timer, + request_last_state_remotes: HashSet, + notify_progress: Option, +} + +impl StateChanggeRunner { + fn new( + local_id: ObjectId, + network_sender: crate::network::Sender, + rpath: GroupRPath, + non_driver: crate::network::NONDriverHelper, + shell_mgr: GroupShellManager, + tx_notifier: async_std::channel::Sender, + rx_notifier: async_std::channel::Receiver, + ) -> Self { + Self { + network_sender, + rpath, + non_driver, + tx_notifier, + rx_notifier, + delay_notify_times: 0, + // timer: Timer::new(SYNCHRONIZER_TIMEOUT), + notify_progress: None, + local_id, + request_last_state_remotes: HashSet::new(), + shell_mgr, + } + } + + pub async fn notify_proposal_err(&self, proposal: GroupProposal, err: BuckyError) { + // notify to the proposer + let proposal_id = proposal.desc().object_id(); + match proposal.desc().owner() { + Some(proposer) => { + let network_sender = self.network_sender.clone(); + let proposer = proposer.clone(); + let rpath = self.rpath.clone(); + + network_sender + .post_message( + HotstuffMessage::ProposalResult(proposal_id, Err(err)), + rpath.clone(), + &proposer, + ) + .await + } + None => log::warn!("proposal({}) without owner", proposal_id), + } + } + + pub async fn notify_proposal_result_for_block( + &self, + block: &GroupConsensusBlock, + qc_block: &GroupConsensusBlock, + ) { + let network_sender = self.network_sender.clone(); + let rpath = self.rpath.clone(); + let non_driver = self.non_driver.clone(); + let proposal_exe_infos = block.proposals().clone(); + + let proposals = futures::future::join_all( + proposal_exe_infos + .iter() + .map(|proposal| non_driver.get_proposal(&proposal.proposal, None)), + ) + .await; + + for i in 0..proposal_exe_infos.len() { + let proposal = proposals.get(i).unwrap(); + if proposal.is_err() { + continue; + } + let proposal = proposal.as_ref().unwrap(); + let proposer = proposal.desc().owner(); + if proposer.is_none() { + continue; + } + + let proposer = proposer.as_ref().unwrap(); + let exe_info = proposal_exe_infos.get(i).unwrap(); + + let receipt = match exe_info.receipt.as_ref() { + Some(receipt) => match NONObjectInfo::raw_decode(receipt.as_slice()) { + Ok((obj, _)) => Some(obj), + _ => continue, + }, + None => None, + }; + + network_sender + .post_message( + HotstuffMessage::ProposalResult( + exe_info.proposal, + Ok(( + receipt, + block.clone(), + qc_block.qc().as_ref().unwrap().clone(), + )), + ), + rpath.clone(), + &proposer, + ) + .await + } + } + + async fn update_commit_block( + &mut self, + block: GroupConsensusBlock, + qc_block: GroupConsensusBlock, + ) { + match self.notify_progress.as_mut() { + Some(progress) => { + if progress.header_block.height() >= block.height() { + return; + } + + if block.group_shell_id() != progress.header_block.group_shell_id() { + let group = self + .shell_mgr + .get_group(block.rpath().group_id(), Some(block.group_shell_id()), None) + .await; + if group.is_err() { + return; + } + progress.members = group + .unwrap() + .select_members_with_distance(&self.local_id, GroupMemberScope::All) + .into_iter() + .map(|id| id.clone()) + .collect(); + } + + progress.group_shell_id = block.group_shell_id().clone(); + progress.total_notify_times += progress.cur_block_notify_times; + progress.cur_block_notify_times = 0; + progress.header_block = block; + progress.qc_block = qc_block; + } + None => { + let group = self + .shell_mgr + .get_group(block.rpath().group_id(), Some(block.group_shell_id()), None) + .await; + if group.is_err() { + return; + } + + let members: Vec = group + .unwrap() + .select_members_with_distance(&self.local_id, GroupMemberScope::All) + .into_iter() + .map(|id| id.clone()) + .collect(); + let total_notify_times = match members.iter().position(|id| id == &self.local_id) { + Some(pos) => pos, + None => return, + }; + + let group_shell_id = block.group_shell_id().clone(); + + self.notify_progress = Some(HeaderBlockNotifyProgress { + header_block: block, + qc_block, + group_shell_id, + members, + total_notify_times, + cur_block_notify_times: 0, + }); + } + } + + self.delay_notify(true).await; + } + + async fn request_last_state(&mut self, remote: ObjectId) { + if self.request_last_state_remotes.insert(remote) { + self.delay_notify(true).await; + } + } + + async fn try_notify_block_commit(&mut self) { + self.delay_notify_times -= 1; + + if let Some(progress) = self.notify_progress.as_mut() { + let mut notify_targets = HashSet::new(); + std::mem::swap(&mut self.request_last_state_remotes, &mut notify_targets); + + if progress.cur_block_notify_times < progress.members.len() { + let notify_count = STATE_NOTIFY_COUNT_PER_ROUND + .min(progress.members.len() - progress.cur_block_notify_times); + + progress.cur_block_notify_times += notify_count; + + let start_pos = (progress.total_notify_times + progress.cur_block_notify_times) + % progress.members.len(); + let notify_targets_1 = &progress.members.as_slice() + [start_pos..progress.members.len().min(start_pos + notify_count)]; + + notify_targets_1.iter().for_each(|remote| { + notify_targets.insert(remote.clone()); + }); + + if notify_targets_1.len() < notify_count { + let notify_targets_2 = + &progress.members.as_slice()[0..notify_count - notify_targets.len()]; + + notify_targets_2.iter().for_each(|remote| { + notify_targets.insert(remote.clone()); + }); + } + } + + let msg = HotstuffMessage::StateChangeNotify( + progress.header_block.clone(), + progress.qc_block.qc().as_ref().unwrap().clone(), + ); + + if notify_targets.len() > 0 { + let notify_targets: Vec = notify_targets.into_iter().collect(); + self.network_sender + .broadcast(msg.clone(), self.rpath.clone(), notify_targets.as_slice()) + .await; + } + + if progress.cur_block_notify_times < progress.members.len() { + self.delay_notify(false).await; + } + } + } + + async fn delay_notify(&mut self, is_force: bool) { + if is_force || self.delay_notify_times == 0 { + self.tx_notifier + .send(StatePushMessage::DelayBroadcast) + .await; + self.delay_notify_times += 1; + } + } + + async fn run(&mut self) { + loop { + futures::select! { + message = self.rx_notifier.recv().fuse() => match message { + Ok(StatePushMessage::ProposalResult(proposal, err)) => self.notify_proposal_err(proposal, err).await, + Ok(StatePushMessage::BlockCommit(block, qc_block)) => { + self.notify_proposal_result_for_block(&block, &qc_block).await; + self.update_commit_block(block, qc_block).await; + }, + Ok(StatePushMessage::LastStateRequest(remote)) => { + self.request_last_state(remote); + }, + Ok(StatePushMessage::DelayBroadcast) => { + self.try_notify_block_commit(); + }, + Err(e) => { + log::warn!("[change-notifier] rx_notifier closed.") + }, + }, + // () = self.timer.wait_next().fuse() => self.try_notify_block_commit().await, + }; + } + } +} diff --git a/src/component/cyfs-group/src/helper/mod.rs b/src/component/cyfs-group/src/helper/mod.rs new file mode 100644 index 000000000..4d3f1b1c6 --- /dev/null +++ b/src/component/cyfs-group/src/helper/mod.rs @@ -0,0 +1,3 @@ +mod timer; + +pub use timer::*; diff --git a/src/component/cyfs-group/src/helper/timer.rs b/src/component/cyfs-group/src/helper/timer.rs new file mode 100644 index 000000000..5edda71b4 --- /dev/null +++ b/src/component/cyfs-group/src/helper/timer.rs @@ -0,0 +1,33 @@ +use std::ops::Sub; +use std::time::{Duration, Instant}; + +pub struct Timer { + last_wake_time: Instant, + duration: Duration, +} + +impl Timer { + pub fn new(duration: u64) -> Self { + Self { + last_wake_time: Instant::now(), + duration: Duration::from_millis(duration), + } + } + + pub fn reset(&mut self, duration: u64) { + self.duration = Duration::from_millis(duration); + self.last_wake_time = Instant::now(); + } + + pub async fn wait_next(&mut self) { + let elapsed = Instant::now().duration_since(self.last_wake_time); + if elapsed < self.duration { + let _ = async_std::future::timeout( + self.duration.sub(elapsed), + std::future::pending::<()>(), + ) + .await; + } + self.last_wake_time = Instant::now(); + } +} diff --git a/src/component/cyfs-group/src/lib.rs b/src/component/cyfs-group/src/lib.rs new file mode 100644 index 000000000..c493f13c5 --- /dev/null +++ b/src/component/cyfs-group/src/lib.rs @@ -0,0 +1,17 @@ +mod consensus; +mod constant; +mod dec; +mod dec_state; +mod helper; +mod network; +mod statepath; +mod storage; + +pub use consensus::*; +pub use constant::*; +pub use dec::*; +pub(crate) use dec_state::*; +pub(crate) use helper::*; +pub use network::*; +pub use statepath::*; +pub(crate) use storage::*; diff --git a/src/component/cyfs-group/src/network/listener.rs b/src/component/cyfs-group/src/network/listener.rs new file mode 100644 index 000000000..02017184d --- /dev/null +++ b/src/component/cyfs-group/src/network/listener.rs @@ -0,0 +1,61 @@ +use std::time::{Instant}; + +use cyfs_base::{ObjectId, RawDecode}; +use cyfs_bdt::DatagramTunnelGuard; + +use crate::{GroupManager, HotstuffPackage}; + +pub struct Listener; + +impl Listener { + pub fn spawn( + datagram: DatagramTunnelGuard, + processor: GroupManager, + local_device_id: ObjectId, + ) { + async_std::task::spawn(async move { + Self::run(datagram, processor, local_device_id).await; + }); + } + + async fn run( + datagram: DatagramTunnelGuard, + processor: GroupManager, + local_device_id: ObjectId, + ) { + loop { + match datagram.recv_v().await { + Ok(pkgs) => { + for datagram in pkgs { + let remote = datagram.source.remote.object_id().clone(); + match HotstuffPackage::raw_decode(datagram.data.as_slice()) { + Ok((pkg, remain)) => { + log::debug!( + "[group-listener] {:?}-{} recv group message from {:?}, msg: {:?}, len: {}, delay: {}", + pkg.rpath(), + local_device_id, + remote, + pkg, + datagram.data.len(), + Instant::now().elapsed().as_millis() as u64 - datagram.options.create_time.unwrap() + ); + assert_eq!(remain.len(), 0); + processor.on_message(pkg, remote).await; + } + Err(err) => { + log::debug!( + "[group-listener] {} recv message from {:?}, len: {} decode failed {:?}", + local_device_id, + remote, + datagram.data.len(), + err + ); + } + } + } + } + Err(e) => log::warn!("group listener failed: {:?}", e), + } + } + } +} diff --git a/src/component/cyfs-group/src/network/meta_client_timeout.rs b/src/component/cyfs-group/src/network/meta_client_timeout.rs new file mode 100644 index 000000000..26b55fdb1 --- /dev/null +++ b/src/component/cyfs-group/src/network/meta_client_timeout.rs @@ -0,0 +1,43 @@ +use std::time::Duration; + +use cyfs_base::{BuckyError, BuckyErrorCode, BuckyResult, ObjectId}; +use cyfs_base_meta::SavedMetaObject; +use cyfs_meta_lib::MetaClient; +use futures::FutureExt; + +const TIMEOUT_HALF: Duration = Duration::from_millis(2000); + +#[async_trait::async_trait] +pub trait MetaClientTimeout { + async fn get_desc_timeout(&self, id: &ObjectId) -> BuckyResult; +} + +#[async_trait::async_trait] +impl MetaClientTimeout for MetaClient { + async fn get_desc_timeout(&self, id: &ObjectId) -> BuckyResult { + let fut1 = match futures::future::select( + self.get_desc(id).boxed(), + async_std::future::timeout(TIMEOUT_HALF, futures::future::pending::<()>()).boxed(), + ) + .await + { + futures::future::Either::Left((ret, _)) => return ret, + futures::future::Either::Right((_, fut)) => fut, + }; + + log::warn!("get desc timeout (id={})", id,); + + match futures::future::select( + self.get_desc(id).boxed(), + async_std::future::timeout(TIMEOUT_HALF, fut1).boxed(), + ) + .await + { + futures::future::Either::Left((ret, _)) => ret, + futures::future::Either::Right((ret, _)) => ret.map_or( + Err(BuckyError::new(BuckyErrorCode::Timeout, "timeout")), + |ret| ret, + ), + } + } +} diff --git a/src/component/cyfs-group/src/network/mod.rs b/src/component/cyfs-group/src/network/mod.rs new file mode 100644 index 000000000..6dc60dea4 --- /dev/null +++ b/src/component/cyfs-group/src/network/mod.rs @@ -0,0 +1,11 @@ +mod listener; +mod meta_client_timeout; +mod non_driver; +mod protocol; +mod sender; + +pub(crate) use listener::*; +pub(crate) use meta_client_timeout::*; +pub use non_driver::*; +pub(crate) use protocol::*; +pub(crate) use sender::*; diff --git a/src/component/cyfs-group/src/network/non_driver.rs b/src/component/cyfs-group/src/network/non_driver.rs new file mode 100644 index 000000000..fa88497c2 --- /dev/null +++ b/src/component/cyfs-group/src/network/non_driver.rs @@ -0,0 +1,318 @@ +use std::{collections::HashMap, sync::Arc, time::Instant}; + +use async_std::sync::RwLock; +use cyfs_base::{ + AnyNamedObject, BuckyError, BuckyErrorCode, BuckyResult, Device, DeviceId, NamedObject, + ObjectDesc, ObjectId, ObjectTypeCode, People, PeopleId, RawConvertTo, RawDecode, RawFrom, + TypelessCoreObject, +}; + +use cyfs_core::{ + GroupConsensusBlock, GroupConsensusBlockObject, GroupProposal, GroupQuorumCertificate, +}; +use cyfs_lib::NONObjectInfo; + +use crate::{MEMORY_CACHE_DURATION, MEMORY_CACHE_SIZE}; + +#[async_trait::async_trait] +pub trait NONDriver: Send + Sync { + async fn get_object( + &self, + dec_id: &ObjectId, + object_id: &ObjectId, + from: Option<&ObjectId>, + ) -> BuckyResult; + + async fn put_object(&self, dec_id: &ObjectId, obj: NONObjectInfo) -> BuckyResult<()>; + + async fn post_object( + &self, + dec_id: &ObjectId, + obj: NONObjectInfo, + to: Option<&ObjectId>, + ) -> BuckyResult>; +} + +#[derive(Clone)] +pub(crate) struct NONDriverHelper { + driver: Arc>, + dec_id: ObjectId, + cache: NONObjectCache, + local_device_id: ObjectId, +} + +impl NONDriverHelper { + pub fn new( + driver: Arc>, + dec_id: ObjectId, + local_device_id: ObjectId, + ) -> Self { + Self { + driver, + dec_id, + cache: NONObjectCache::new(), + local_device_id, + } + } + + pub fn dec_id(&self) -> &ObjectId { + &self.dec_id + } + + pub async fn get_object( + &self, + object_id: &ObjectId, + from: Option<&ObjectId>, + ) -> BuckyResult { + if let Some(obj) = self.cache.find_in_cache(object_id).await { + return Ok(obj); + } + + { + let result = self + .driver + .get_object(&self.dec_id, object_id, from) + .await?; + + self.cache.insert_cache(&result).await; + + Ok(result) + } + } + + pub async fn put_object(&self, obj: NONObjectInfo) -> BuckyResult<()> { + self.driver.put_object(&self.dec_id, obj).await + } + + pub async fn post_object( + &self, + obj: NONObjectInfo, + to: Option<&ObjectId>, + ) -> BuckyResult> { + self.driver.post_object(&self.dec_id, obj, to).await + } + + pub async fn broadcast(&self, obj: NONObjectInfo, to: &[ObjectId]) { + futures::future::join_all(to.iter().map(|to| self.post_object(obj.clone(), Some(to)))) + .await; + } + + pub async fn get_block( + &self, + object_id: &ObjectId, + from: Option<&ObjectId>, + ) -> BuckyResult { + // TODO: remove block without signatures + let obj = self.get_object(object_id, from).await?; + let (block, remain) = GroupConsensusBlock::raw_decode(obj.object_raw.as_slice())?; + assert_eq!(remain.len(), 0); + Ok(block) + } + + pub async fn put_block(&self, block: &GroupConsensusBlock) -> BuckyResult<()> { + let buf = block.to_vec()?; + let block_any = Arc::new(AnyNamedObject::Core( + TypelessCoreObject::clone_from_slice(buf.as_slice()).unwrap(), + )); + + let block = NONObjectInfo { + object_id: block.block_id().object_id().clone(), + object_raw: block.to_vec()?, + object: Some(block_any), + }; + self.put_object(block).await?; + Ok(()) + } + + pub async fn get_qc( + &self, + object_id: &ObjectId, + from: Option<&ObjectId>, + ) -> BuckyResult { + let obj = self.get_object(object_id, from).await?; + let (block, remain) = GroupQuorumCertificate::raw_decode(obj.object_raw.as_slice())?; + assert_eq!(remain.len(), 0); + Ok(block) + } + + pub async fn put_qc(&self, qc: &GroupQuorumCertificate) -> BuckyResult<()> { + let buf = qc.to_vec()?; + let block_any = Arc::new(AnyNamedObject::Core( + TypelessCoreObject::clone_from_slice(buf.as_slice()).unwrap(), + )); + + let qc = NONObjectInfo { + object_id: qc.desc().object_id(), + object_raw: qc.to_vec()?, + object: Some(block_any), + }; + self.put_object(qc).await?; + Ok(()) + } + + pub async fn get_proposal( + &self, + object_id: &ObjectId, + from: Option<&ObjectId>, + ) -> BuckyResult { + let obj = self.get_object(object_id, from).await?; + let (block, remain) = GroupProposal::raw_decode(obj.object_raw.as_slice())?; + assert_eq!(remain.len(), 0); + Ok(block) + } + + pub async fn get_ood(&self, people_id: &PeopleId) -> BuckyResult { + let people = self + .get_object(people_id.object_id(), Some(people_id.object_id())) + .await?; + let (people, remain) = People::raw_decode(people.object_raw.as_slice())?; + assert_eq!(remain.len(), 0); + people + .ood_list() + .get(0) + .ok_or(BuckyError::new(BuckyErrorCode::NotFound, "empty ood-list")) + .map(|d| d.clone()) + } + + pub async fn get_device(&self, device_id: &ObjectId) -> BuckyResult { + if let ObjectTypeCode::Device = device_id.obj_type_code() { + let device = self.get_object(device_id, Some(device_id)).await?; + let (device, remain) = Device::raw_decode(device.object_raw.as_slice())?; + assert_eq!(remain.len(), 0); + Ok(device) + } else { + Err(BuckyError::new(BuckyErrorCode::Unmatch, "not device-id")) + } + } + + pub async fn load_all_proposals_for_block( + &self, + block: &GroupConsensusBlock, + proposals_map: &mut HashMap, + ) -> BuckyResult<()> { + let non_driver = self.clone(); + let block_owner = block.owner().clone(); + + let remote = match block_owner.obj_type_code() { + cyfs_base::ObjectTypeCode::Device => DeviceId::try_from(block_owner).unwrap(), + cyfs_base::ObjectTypeCode::People => { + let people_id = PeopleId::try_from(block_owner).unwrap(); + match self.get_ood(&people_id).await { + Ok(device_id) => device_id, + Err(e) => { + log::warn!( + "[non driver] load_all_proposals_for_block get ood of {}, failed err: {:?}", + block_owner, + e + ); + return Err(e); + } + } + } + _ => panic!("invalid remote type: {:?}", block_owner.obj_type_code()), + }; + + log::debug!( + "{} load_all_proposals_for_block {} from {}", + self.local_device_id, + block.block_id(), + remote + ); + + let load_futs = block.proposals().iter().map(|proposal| { + let proposal_id = proposal.proposal; + let non_driver = non_driver.clone(); + let remote = remote.clone(); + async move { + let ret = non_driver + .get_proposal(&proposal_id, Some(remote.object_id())) + .await; + + log::debug!( + "{} load_all_proposals_for_block {}/{} from {}, ret: {:?}", + self.local_device_id, + block.block_id(), + proposal_id, + remote, + ret.as_ref().map(|_| ()) + ); + + ret + } + }); + + let mut results = futures::future::join_all(load_futs).await; + let proposal_count = results.len(); + + for i in 0..proposal_count { + let result = results.pop().unwrap(); + let proposal_id = block + .proposals() + .get(proposal_count - i - 1) + .unwrap() + .proposal; + match result { + Ok(proposal) => { + assert_eq!(proposal_id, proposal.desc().object_id()); + proposals_map.insert(proposal_id, proposal); + } + Err(err) => return Err(err), + } + } + + Ok(()) + } +} + +#[derive(Clone)] +struct NONObjectCache { + cache: Arc, Instant)>>, + cache_1: Arc>>, +} + +impl NONObjectCache { + fn new() -> Self { + Self { + cache: Arc::new(RwLock::new((HashMap::new(), Instant::now()))), + cache_1: Arc::new(RwLock::new(HashMap::new())), + } + } + + async fn find_in_cache(&self, object_id: &ObjectId) -> Option { + { + let cache = self.cache.read().await; + if let Some(obj) = cache.0.get(object_id) { + return Some(obj.clone()); + } + } + + { + let cache = self.cache_1.read().await; + cache.get(object_id).cloned() + } + } + + async fn insert_cache(&self, obj: &NONObjectInfo) { + let new_cache_1 = { + let mut cache = self.cache.write().await; + let now = Instant::now(); + if now.duration_since(cache.1) > MEMORY_CACHE_DURATION + || cache.0.len() > MEMORY_CACHE_SIZE + { + let mut new_cache = HashMap::new(); + std::mem::swap(&mut new_cache, &mut cache.0); + cache.1 = now; + cache.0.insert(obj.object_id, obj.clone()); + new_cache + } else { + cache.0.insert(obj.object_id, obj.clone()); + return; + } + }; + + { + let mut cache_1 = self.cache_1.write().await; + *cache_1 = new_cache_1; + } + } +} diff --git a/src/component/cyfs-group/src/network/protocol.rs b/src/component/cyfs-group/src/network/protocol.rs new file mode 100644 index 000000000..b6eec1177 --- /dev/null +++ b/src/component/cyfs-group/src/network/protocol.rs @@ -0,0 +1,689 @@ +use cyfs_base::*; +use cyfs_core::{GroupConsensusBlock, GroupConsensusBlockObject, GroupRPath, HotstuffBlockQC}; +use cyfs_group_lib::{GroupRPathStatus, HotstuffBlockQCVote, HotstuffTimeoutVote}; +use cyfs_lib::NONObjectInfo; +use itertools::Itertools; + +#[derive(RawEncode, RawDecode, PartialEq, Eq, Ord, Clone, Debug)] +pub enum SyncBound { + Height(u64), + Round(u64), +} + +impl Copy for SyncBound {} + +impl SyncBound { + pub fn value(&self) -> u64 { + match self { + Self::Height(h) => *h, + Self::Round(r) => *r, + } + } + + pub fn height(&self) -> u64 { + match self { + Self::Height(h) => *h, + Self::Round(r) => panic!("should be height"), + } + } + + pub fn round(&self) -> u64 { + match self { + Self::Round(r) => *r, + Self::Height(h) => panic!("should be round"), + } + } + + pub fn add(&self, value: u64) -> Self { + match self { + Self::Height(h) => Self::Height(*h + value), + Self::Round(r) => Self::Round(*r + value), + } + } + + pub fn sub(&self, value: u64) -> Self { + match self { + Self::Height(h) => Self::Height(*h - value), + Self::Round(r) => Self::Round(*r - value), + } + } +} + +impl PartialOrd for SyncBound { + fn partial_cmp(&self, other: &SyncBound) -> Option { + let ord = match self { + Self::Height(height) => match other { + Self::Height(other_height) => height.cmp(other_height), + Self::Round(other_round) => { + if height >= other_round { + std::cmp::Ordering::Greater + } else { + std::cmp::Ordering::Less + } + } + }, + Self::Round(round) => match other { + Self::Round(other_round) => round.cmp(other_round), + Self::Height(other_height) => { + if other_height >= round { + std::cmp::Ordering::Less + } else { + std::cmp::Ordering::Greater + } + } + }, + }; + + Some(ord) + } +} + +#[derive(Clone, RawEncode, RawDecode)] +pub(crate) enum HotstuffMessage { + Block(cyfs_core::GroupConsensusBlock), + BlockVote(HotstuffBlockQCVote), + TimeoutVote(HotstuffTimeoutVote), + Timeout(cyfs_core::HotstuffTimeout), + + SyncRequest(SyncBound, SyncBound), // [min, max] + + LastStateRequest, + StateChangeNotify(GroupConsensusBlock, HotstuffBlockQC), // (block, qc) + ProposalResult( + ObjectId, + BuckyResult<(Option, GroupConsensusBlock, HotstuffBlockQC)>, + ), // (proposal-id, (ExecuteResult, block, qc)) + QueryState(String), + VerifiableState(String, BuckyResult), +} + +impl std::fmt::Debug for HotstuffMessage { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Block(block) => { + write!( + f, + "HotstuffMessage::Block({}/{})", + block.block_id(), + block.round() + ) + } + Self::BlockVote(vote) => { + write!( + f, + "HotstuffMessage::BlockVote({}/{})", + vote.block_id, vote.round + ) + } + Self::TimeoutVote(vote) => { + write!( + f, + "HotstuffMessage::TimeoutVote({}/{})", + vote.round, vote.voter + ) + } + Self::Timeout(tc) => { + write!( + f, + "HotstuffMessage::Timeout({}/{:?})", + tc.round, + tc.votes.iter().map(|v| v.voter).collect::>() + ) + } + Self::SyncRequest(min, max) => { + write!(f, "HotstuffMessage::SyncRequest([{:?}-{:?}])", min, max) + } + Self::StateChangeNotify(block, qc) => { + write!( + f, + "HotstuffMessage::StateChangeNotify({}/{}, {}/{})", + block.block_id(), + block.round(), + qc.block_id, + qc.round + ) + } + Self::LastStateRequest => { + write!(f, "HotstuffMessage::LastStateRequest",) + } + Self::ProposalResult(proposal_id, result) => { + write!( + f, + "HotstuffMessage::ProposalResult({}, {:?})", + proposal_id, + result.as_ref().map(|(obj, block, qc)| { + format!( + "({:?}, {}/{}, {}/{})", + obj.as_ref().map(|o| o.object_id), + block.block_id(), + block.round(), + qc.block_id, + qc.round + ) + }) + ) + } + Self::QueryState(sub_path) => { + write!(f, "HotstuffMessage::QueryState({})", sub_path) + } + Self::VerifiableState(sub_path, result) => { + write!( + f, + "HotstuffMessage::VerifiableState({}, {:?})", + sub_path, + result.as_ref().map(|status| { + let desc = status.block_desc.content(); + format!( + "({:?}/{:?}, {}/{}/{}) sub-count: {:?}", + desc.result_state_id(), + status.block_desc.object_id(), + desc.height(), + desc.round(), + status.certificate.round, + status.status_map.iter().map(|(key, _)| key).collect_vec() + ) + }) + ) + } + } + } +} + +const PACKAGE_FLAG_BITS: usize = 1; +const PACKAGE_FLAG_PROPOSAL_RESULT_OK: u8 = 0x80u8; +const PACKAGE_FLAG_QUERY_STATE_RESULT_OK: u8 = 0x80u8; + +#[derive(Clone)] +pub(crate) enum HotstuffPackage { + Block(cyfs_core::GroupConsensusBlock), + BlockVote(ProtocolAddress, HotstuffBlockQCVote), + TimeoutVote(ProtocolAddress, HotstuffTimeoutVote), + Timeout(ProtocolAddress, cyfs_core::HotstuffTimeout), + + SyncRequest(ProtocolAddress, SyncBound, SyncBound), + + StateChangeNotify(GroupConsensusBlock, HotstuffBlockQC), // (block, qc) + LastStateRequest(ProtocolAddress), + ProposalResult( + ObjectId, + Result< + (Option, GroupConsensusBlock, HotstuffBlockQC), + (BuckyError, ProtocolAddress), + >, + ), // (proposal-id, ExecuteResult) + QueryState(ProtocolAddress, String), + VerifiableState( + String, + Result, + ), +} + +impl std::fmt::Debug for HotstuffPackage { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Block(block) => { + write!( + f, + "HotstuffPackage::Block({}/{})", + block.block_id(), + block.round() + ) + } + Self::BlockVote(_, vote) => { + write!( + f, + "HotstuffPackage::BlockVote({}/{})", + vote.block_id, vote.round + ) + } + Self::TimeoutVote(_, vote) => { + write!( + f, + "HotstuffPackage::TimeoutVote({}/{})", + vote.round, vote.voter + ) + } + Self::Timeout(_, tc) => { + write!( + f, + "HotstuffPackage::Timeout({}/{:?})", + tc.round, + tc.votes.iter().map(|v| v.voter).collect::>() + ) + } + Self::SyncRequest(_, min, max) => { + write!(f, "HotstuffPackage::SyncRequest([{:?}-{:?}])", min, max) + } + Self::StateChangeNotify(block, qc) => { + write!( + f, + "HotstuffPackage::StateChangeNotify({}/{}, {}/{})", + block.block_id(), + block.round(), + qc.block_id, + qc.round + ) + } + Self::LastStateRequest(_) => { + write!(f, "HotstuffPackage::LastStateRequest",) + } + Self::ProposalResult(proposal_id, result) => { + write!( + f, + "HotstuffPackage::ProposalResult({}, {:?})", + proposal_id, + result.as_ref().map_or_else( + |(err, _)| { Err(err) }, + |(obj, block, qc)| { + let ok = format!( + "({:?}, {}/{}, {}/{})", + obj.as_ref().map(|o| o.object_id), + block.block_id(), + block.round(), + qc.block_id, + qc.round + ); + Ok(ok) + } + ) + ) + } + Self::QueryState(_, sub_path) => { + write!(f, "HotstuffPackage::QueryState({})", sub_path) + } + Self::VerifiableState(sub_path, result) => { + write!( + f, + "HotstuffPackage::VerifiableState({}, {:?})", + sub_path, + result.as_ref().map_or_else( + |(err, _)| { Err(err) }, + |status| { + let desc = status.block_desc.content(); + let ok = format!( + "({:?}/{:?}, {}/{}/{}) sub-count: {:?}", + desc.result_state_id(), + status.block_desc.object_id(), + desc.height(), + desc.round(), + status.certificate.round, + status.status_map.iter().map(|(key, _)| key).collect_vec() + ); + Ok(ok) + } + ) + ) + } + } + } +} + +impl HotstuffPackage { + pub(crate) fn rpath(&self) -> &GroupRPath { + match self { + HotstuffPackage::Block(block) => block.rpath(), + HotstuffPackage::BlockVote(addr, _) => addr.check_rpath(), + HotstuffPackage::TimeoutVote(addr, _) => addr.check_rpath(), + HotstuffPackage::Timeout(addr, _) => addr.check_rpath(), + HotstuffPackage::SyncRequest(addr, _, _) => addr.check_rpath(), + HotstuffPackage::StateChangeNotify(block, _) => block.rpath(), + HotstuffPackage::LastStateRequest(addr) => addr.check_rpath(), + HotstuffPackage::ProposalResult(_, result) => result.as_ref().map_or_else( + |(_, addr)| addr.check_rpath(), + |(_, block, _)| block.rpath(), + ), + HotstuffPackage::QueryState(addr, _) => addr.check_rpath(), + HotstuffPackage::VerifiableState(_, result) => result.as_ref().map_or_else( + |(_, addr)| addr.check_rpath(), + |status| status.block_desc.content().rpath(), + ), + } + } +} + +fn encode_with_length<'a, O: RawEncode>( + buf: &'a mut [u8], + obj: &O, + purpose: &Option, + length_size: usize, +) -> BuckyResult<&'a mut [u8]> { + let (len_buf, buf) = buf.split_at_mut(length_size); + let before_len = buf.len(); + let buf = obj.raw_encode(buf, purpose)?; + let len = before_len - buf.len(); + assert!(len <= (1 << (length_size << 3)) - 1); + len_buf.copy_from_slice(&len.to_le_bytes()[..length_size]); + + Ok(buf) +} + +fn decode_with_length<'de, O: RawDecode<'de>>( + buf: &'de [u8], + length_size: usize, +) -> BuckyResult<(O, &'de [u8])> { + assert!(length_size <= 4); + let (len_buf, buf) = buf.split_at(length_size); + + let mut len_buf_4 = [0u8; 4]; + len_buf_4[..length_size].copy_from_slice(len_buf); + let len = u32::from_le_bytes(len_buf_4) as usize; + + let before_len = buf.len(); + let (obj, remain) = O::raw_decode(&buf[..len])?; + assert_eq!(remain.len(), 0); + + Ok((obj, &buf[len..])) +} + +impl RawEncode for HotstuffPackage { + fn raw_measure(&self, purpose: &Option) -> BuckyResult { + let len = match self { + HotstuffPackage::Block(b) => 3 + b.raw_measure(purpose)?, + HotstuffPackage::BlockVote(addr, vote) => { + 2 + addr.raw_measure(purpose)? + 3 + vote.raw_measure(purpose)? + } + HotstuffPackage::TimeoutVote(addr, vote) => { + 2 + addr.raw_measure(purpose)? + 3 + vote.raw_measure(purpose)? + } + HotstuffPackage::Timeout(addr, tc) => { + 2 + addr.raw_measure(purpose)? + 3 + tc.raw_measure(purpose)? + } + HotstuffPackage::SyncRequest(addr, min, max) => { + 2 + addr.raw_measure(purpose)? + + min.raw_measure(purpose)? + + max.raw_measure(purpose)? + } + HotstuffPackage::StateChangeNotify(block, qc) => { + 3 + block.raw_measure(purpose)? + 3 + qc.raw_measure(purpose)? + } + HotstuffPackage::LastStateRequest(addr) => 2 + addr.raw_measure(purpose)?, + HotstuffPackage::ProposalResult(id, result) => { + id.raw_measure(purpose)? + + match result { + Ok((non, block, block_qc)) => { + non.raw_measure(purpose)? + + 3 + + block.raw_measure(purpose)? + + 3 + + block_qc.raw_measure(purpose)? + } + Err((err, addr)) => { + err.raw_measure(purpose)? + 2 + addr.raw_measure(purpose)? + } + } + } + HotstuffPackage::QueryState(addr, sub_path) => { + 2 + addr.raw_measure(purpose)? + sub_path.raw_measure(purpose)? + } + HotstuffPackage::VerifiableState(sub_path, result) => { + sub_path.raw_measure(purpose)? + + match result { + Ok(status) => 3 + status.raw_measure(purpose)?, + Err((err, addr)) => { + err.raw_measure(purpose)? + 2 + addr.raw_measure(purpose)? + } + } + } + }; + + Ok(1 + len) + } + + fn raw_encode<'a>( + &self, + buf: &'a mut [u8], + purpose: &Option, + ) -> BuckyResult<&'a mut [u8]> { + match self { + HotstuffPackage::Block(b) => { + buf[0] = 0; + let buf = &mut buf[1..]; + encode_with_length(buf, b, purpose, 3) + } + HotstuffPackage::BlockVote(addr, vote) => { + buf[0] = 1; + let buf = &mut buf[1..]; + let buf = encode_with_length(buf, addr, purpose, 2)?; + encode_with_length(buf, vote, purpose, 3) + } + HotstuffPackage::TimeoutVote(addr, vote) => { + buf[0] = 2; + let buf = &mut buf[1..]; + let buf = encode_with_length(buf, addr, purpose, 2)?; + encode_with_length(buf, vote, purpose, 3) + } + HotstuffPackage::Timeout(addr, tc) => { + buf[0] = 3; + let buf = &mut buf[1..]; + let buf = encode_with_length(buf, addr, purpose, 2)?; + encode_with_length(buf, tc, purpose, 3) + } + HotstuffPackage::SyncRequest(addr, min, max) => { + buf[0] = 4; + let buf = &mut buf[1..]; + let buf = encode_with_length(buf, addr, purpose, 2)?; + let buf = min.raw_encode(buf, purpose)?; + max.raw_encode(buf, purpose) + } + HotstuffPackage::StateChangeNotify(block, qc) => { + buf[0] = 5; + let buf = &mut buf[1..]; + let buf = encode_with_length(buf, block, purpose, 3)?; + encode_with_length(buf, qc, purpose, 3) + } + HotstuffPackage::LastStateRequest(addr) => { + buf[0] = 6; + let buf = &mut buf[1..]; + encode_with_length(buf, addr, purpose, 2) + } + HotstuffPackage::ProposalResult(id, result) => { + buf[0] = 7; + if result.is_ok() { + buf[0] |= PACKAGE_FLAG_PROPOSAL_RESULT_OK; + } + + let buf = &mut buf[1..]; + let buf = id.raw_encode(buf, purpose)?; + match result { + Ok((non, block, qc)) => { + let buf = non.raw_encode(buf, purpose)?; + let buf = encode_with_length(buf, block, purpose, 3)?; + encode_with_length(buf, qc, purpose, 3) + } + Err((err, addr)) => { + let buf = err.raw_encode(buf, purpose)?; + encode_with_length(buf, addr, purpose, 2) + } + } + } + HotstuffPackage::QueryState(addr, sub_path) => { + buf[0] = 8; + let buf = &mut buf[1..]; + encode_with_length(buf, addr, purpose, 2)?; + sub_path.raw_encode(buf, purpose) + } + HotstuffPackage::VerifiableState(sub_path, result) => { + buf[0] = 9; + if result.is_ok() { + buf[0] |= PACKAGE_FLAG_QUERY_STATE_RESULT_OK; + } + let buf = &mut buf[1..]; + let buf = sub_path.raw_encode(buf, purpose)?; + match result { + Ok(status) => encode_with_length(buf, status, purpose, 3), + Err((err, addr)) => { + let buf = err.raw_encode(buf, purpose)?; + encode_with_length(buf, addr, purpose, 2) + } + } + } + } + } +} + +impl<'de> RawDecode<'de> for HotstuffPackage { + fn raw_decode(buf: &'de [u8]) -> BuckyResult<(Self, &'de [u8])> { + let pkg_type = buf[0] << PACKAGE_FLAG_BITS >> PACKAGE_FLAG_BITS; + // let pkg_flag = buf[0] - pkg_type; + + match pkg_type { + 0 => { + let buf = &buf[1..]; + let (b, buf) = decode_with_length(buf, 3)?; + assert_eq!(buf.len(), 0); + Ok((HotstuffPackage::Block(b), buf)) + } + 1 => { + let buf = &buf[1..]; + let (addr, buf) = decode_with_length(buf, 2)?; + let (vote, buf) = decode_with_length(buf, 3)?; + assert_eq!(buf.len(), 0); + Ok((HotstuffPackage::BlockVote(addr, vote), buf)) + } + 2 => { + let buf = &buf[1..]; + let (addr, buf) = decode_with_length(buf, 2)?; + let (vote, buf) = decode_with_length(buf, 3)?; + assert_eq!(buf.len(), 0); + Ok((HotstuffPackage::TimeoutVote(addr, vote), buf)) + } + 3 => { + let buf = &buf[1..]; + let (addr, buf) = decode_with_length(buf, 2)?; + let (vote, buf) = decode_with_length(buf, 3)?; + assert_eq!(buf.len(), 0); + Ok((HotstuffPackage::Timeout(addr, vote), buf)) + } + 4 => { + let buf = &buf[1..]; + let (addr, buf) = decode_with_length(buf, 2)?; + let (min, buf) = SyncBound::raw_decode(buf)?; + let (max, buf) = SyncBound::raw_decode(buf)?; + assert_eq!(buf.len(), 0); + Ok((HotstuffPackage::SyncRequest(addr, min, max), buf)) + } + 5 => { + let buf = &buf[1..]; + let (block, buf) = decode_with_length(buf, 3)?; + let (qc, buf) = decode_with_length(buf, 3)?; + assert_eq!(buf.len(), 0); + Ok((HotstuffPackage::StateChangeNotify(block, qc), buf)) + } + 6 => { + let buf = &buf[1..]; + let (addr, buf) = decode_with_length(buf, 2)?; + assert_eq!(buf.len(), 0); + Ok((HotstuffPackage::LastStateRequest(addr), buf)) + } + 7 => { + let is_ok = (buf[0] & PACKAGE_FLAG_PROPOSAL_RESULT_OK) != 0; + let buf = &buf[1..]; + let (id, buf) = ObjectId::raw_decode(buf)?; + match is_ok { + true => { + let (non, buf) = Option::::raw_decode(buf)?; + let (block, buf) = decode_with_length(buf, 3)?; + let (qc, buf) = decode_with_length(buf, 3)?; + assert_eq!(buf.len(), 0); + Ok(( + HotstuffPackage::ProposalResult(id, Ok((non, block, qc))), + buf, + )) + } + false => { + let (err, buf) = BuckyError::raw_decode(buf)?; + let (addr, buf) = decode_with_length(buf, 2)?; + assert_eq!(buf.len(), 0); + Ok((HotstuffPackage::ProposalResult(id, Err((err, addr))), buf)) + } + } + } + 8 => { + let buf = &buf[1..]; + let (addr, buf) = decode_with_length(buf, 2)?; + let (sub_path, buf) = String::raw_decode(buf)?; + assert_eq!(buf.len(), 0); + Ok((HotstuffPackage::QueryState(addr, sub_path), buf)) + } + 9 => { + let is_ok = (buf[0] & PACKAGE_FLAG_QUERY_STATE_RESULT_OK) != 0; + let buf = &buf[1..]; + let (sub_path, buf) = String::raw_decode(buf)?; + match is_ok { + true => { + let (status, buf) = decode_with_length(buf, 3)?; + assert_eq!(buf.len(), 0); + Ok((HotstuffPackage::VerifiableState(sub_path, Ok(status)), buf)) + } + false => { + let (err, buf) = BuckyError::raw_decode(buf)?; + let (addr, buf) = decode_with_length(buf, 2)?; + assert_eq!(buf.len(), 0); + Ok(( + HotstuffPackage::VerifiableState(sub_path, Err((err, addr))), + buf, + )) + } + } + } + _ => unreachable!("unknown protocol"), + } + } +} + +impl HotstuffPackage { + pub fn from_msg(msg: HotstuffMessage, rpath: GroupRPath) -> Self { + match msg { + HotstuffMessage::Block(block) => HotstuffPackage::Block(block), + HotstuffMessage::BlockVote(vote) => { + HotstuffPackage::BlockVote(ProtocolAddress::Full(rpath), vote) + } + HotstuffMessage::TimeoutVote(vote) => { + HotstuffPackage::TimeoutVote(ProtocolAddress::Full(rpath), vote) + } + HotstuffMessage::Timeout(tc) => { + HotstuffPackage::Timeout(ProtocolAddress::Full(rpath), tc) + } + HotstuffMessage::SyncRequest(min_bound, max_bound) => { + HotstuffPackage::SyncRequest(ProtocolAddress::Full(rpath), min_bound, max_bound) + } + HotstuffMessage::LastStateRequest => { + HotstuffPackage::LastStateRequest(ProtocolAddress::Full(rpath)) + } + HotstuffMessage::StateChangeNotify(header_block, qc_block) => { + HotstuffPackage::StateChangeNotify(header_block, qc_block) + } + HotstuffMessage::ProposalResult(proposal_id, result) => { + HotstuffPackage::ProposalResult( + proposal_id, + result.map_err(|err| (err, ProtocolAddress::Full(rpath))), + ) + } + HotstuffMessage::QueryState(sub_path) => { + HotstuffPackage::QueryState(ProtocolAddress::Full(rpath), sub_path) + } + HotstuffMessage::VerifiableState(sub_path, result) => HotstuffPackage::VerifiableState( + sub_path, + result.map_err(|err| (err, ProtocolAddress::Full(rpath))), + ), + } + } +} + +#[derive(Clone, RawEncode, RawDecode)] +pub(crate) enum ProtocolAddress { + Full(GroupRPath), + Channel(u64), +} + +impl ProtocolAddress { + pub fn check_rpath(&self) -> &GroupRPath { + match self { + ProtocolAddress::Full(rpath) => rpath, + ProtocolAddress::Channel(_) => panic!("no rpath"), + } + } +} + +#[cfg(test)] +mod test {} diff --git a/src/component/cyfs-group/src/network/sender.rs b/src/component/cyfs-group/src/network/sender.rs new file mode 100644 index 000000000..a33e09187 --- /dev/null +++ b/src/component/cyfs-group/src/network/sender.rs @@ -0,0 +1,105 @@ +use std::{ + sync::{atomic::AtomicU32, Arc}, + time::Instant, +}; + +use cyfs_base::{DeviceId, ObjectId, PeopleId, RawEncode}; +use cyfs_bdt::{DatagramOptions, DatagramTunnelGuard}; +use cyfs_core::GroupRPath; + +use crate::{HotstuffMessage, HotstuffPackage}; + +use super::NONDriverHelper; + +#[derive(Clone)] +pub struct Sender { + datagram: DatagramTunnelGuard, + vport: u16, + non_driver: NONDriverHelper, + local_device_id: ObjectId, + sequence: Arc, +} + +impl Sender { + pub(crate) fn new( + datagram: DatagramTunnelGuard, + non_driver: NONDriverHelper, + local_device_id: ObjectId, + ) -> Self { + let vport = datagram.vport(); + Self { + datagram, + vport, + non_driver, + local_device_id, + sequence: Arc::new(AtomicU32::new(rand::random::() & 0x80000000)), + } + } + + pub(crate) async fn post_message( + &self, + msg: HotstuffMessage, + rpath: GroupRPath, + to: &ObjectId, + ) { + let remote = match to.obj_type_code() { + cyfs_base::ObjectTypeCode::Device => DeviceId::try_from(to).unwrap(), + cyfs_base::ObjectTypeCode::People => { + let people_id = PeopleId::try_from(to).unwrap(); + match self.non_driver.get_ood(&people_id).await { + Ok(device_id) => device_id, + Err(e) => { + log::warn!("[group-sender] post message to {}, failed err: {:?}", to, e); + return; + } + } + } + _ => panic!("invalid remote type: {:?}", to.obj_type_code()), + }; + + let pkg = HotstuffPackage::from_msg(msg, rpath); + + let len = pkg.raw_measure(&None).unwrap(); + let mut buf = Vec::with_capacity(len); + buf.resize(len, 0); + let remain = pkg.raw_encode(buf.as_mut_slice(), &None).unwrap(); + + assert_eq!( + remain.len(), + 0, + "[group-sender] {:?}-{} post group message to {:?} encode err, pkg: {:?}, len: {}", + pkg.rpath(), + self.local_device_id, + remote, + pkg, + buf.len() + ); + + log::debug!( + "[group-sender] {:?}-{} post group message to {:?}, pkg: {:?}, len: {}", + pkg.rpath(), + self.local_device_id, + remote, + pkg, + buf.len() + ); + + let mut options = DatagramOptions::default(); + options.create_time = Some(Instant::now().elapsed().as_millis() as u64); + options.sequence = Some(cyfs_bdt::TempSeq::from( + self.sequence + .fetch_add(1, std::sync::atomic::Ordering::SeqCst), + )); + + self.datagram + .send_to(buf.as_slice(), &mut options, &remote, self.vport); + } + + pub(crate) async fn broadcast(&self, msg: HotstuffMessage, rpath: GroupRPath, to: &[ObjectId]) { + futures::future::join_all( + to.iter() + .map(|remote| self.post_message(msg.clone(), rpath.clone(), remote)), + ) + .await; + } +} diff --git a/src/component/cyfs-group/src/statepath/dec_statepath.rs b/src/component/cyfs-group/src/statepath/dec_statepath.rs new file mode 100644 index 000000000..e69de29bb diff --git a/src/component/cyfs-group/src/statepath/design.md b/src/component/cyfs-group/src/statepath/design.md new file mode 100644 index 000000000..0c8ab2c36 --- /dev/null +++ b/src/component/cyfs-group/src/statepath/design.md @@ -0,0 +1,38 @@ +``` +|--${group-id} // one group +| |--${DecId("shells", ${group-id})} +| | |--.shells +| | |--.latest-->GroupShell // latest version +| | |--${group.version}-->GroupShell // add shells for history versions of group +| |--${dec-id} // one dec for a group +| |--${r-path} +| |--.dec-state-->ObjectId // for dec;the latest state of all groups +| | // one state of a r-path, It's calculated by the app, and it's a map-id in most times +| | // Each state change for same ${r-path} is serial +| | // Each state change for different ${r-path} is parallel +| | // **The process of state change for different ${r-path} should always not be nested, Because the change of each branch will affect the state of the root** +| |--.link // Blockchain for hotstuff, record the state change chain,Most of the time, application developers do not need to pay attention +| |--group-blob-->BLOB(Group) // the latest group, it's store as chunk, so, it'll not be updated by different version +| |--users // info of any user, is useful? +| | |--${user-id} +| | |--xxx +| |--last-vote-round-->u64 // the round that I voted last time +| |--last-qc-->GroupQuorumCertificate +| | +| |--range-->(${first_height}, ${header_height}) // the range retained, we can remove some history +| |--str(${height})->block // commited blocks with any height, QC(Quorum Certificate) by next blocks at least 2 +| | +| |--prepares // prepare blocks, with QC for pre-block(pre-commit/commited), but not QC by any one +| | |--${block.id} +| | |--block +| | |--result-state-->ObjectId(result-state) // hold the ref to avoid recycle +| |--pre-commits // pre-commit blocks, with QC for the header block, and is QC by a prepare block +| | |--${block.id} +| | |--block +| | |--result-state-->ObjectId(result-state) // hold the ref to avoid recycle +| | +| |--finish-proposals // The proposal is de-duplicated. Proposals that exceed the timeout period are directly discarded, and those within the timeout period are de-duplicated by the list +| | |--flip-time-->Timestamp // the timestamp of the first block +| | |--recycle-->Set +| | |--adding-->Set +``` diff --git a/src/component/cyfs-group/src/statepath/group_shell_statepath.rs b/src/component/cyfs-group/src/statepath/group_shell_statepath.rs new file mode 100644 index 000000000..f529af9b8 --- /dev/null +++ b/src/component/cyfs-group/src/statepath/group_shell_statepath.rs @@ -0,0 +1,39 @@ +const STATE_PATH_SEPARATOR: &str = "/"; +pub const GROUP_STATE_PATH_SHELLS: &str = ".shells"; +pub const GROUP_STATE_PATH_LATEST: &str = ".latest"; + +pub struct GroupShellStatePath { + root: &'static str, + shells: String, + latest: String, +} + +impl GroupShellStatePath { + pub fn new() -> Self { + Self { + root: STATE_PATH_SEPARATOR, + shells: Self::join(&["", GROUP_STATE_PATH_SHELLS]), + latest: Self::join(&["", GROUP_STATE_PATH_SHELLS, GROUP_STATE_PATH_LATEST]), + } + } + + pub fn join(fields: &[&str]) -> String { + fields.join(STATE_PATH_SEPARATOR) + } + + pub fn root(&self) -> &str { + self.root + } + + pub fn shells(&self) -> &str { + self.shells.as_str() + } + + pub fn latest(&self) -> &str { + self.latest.as_str() + } + + pub fn version(&self, version: u64) -> String { + Self::join(&[self.shells.as_str(), version.to_string().as_str()]) + } +} diff --git a/src/component/cyfs-group/src/statepath/group_statepath.rs b/src/component/cyfs-group/src/statepath/group_statepath.rs new file mode 100644 index 000000000..86e51d816 --- /dev/null +++ b/src/component/cyfs-group/src/statepath/group_statepath.rs @@ -0,0 +1,203 @@ +use cyfs_base::ObjectId; + +pub const STATE_PATH_SEPARATOR: &str = "/"; +pub const GROUP_STATE_PATH_DEC_STATE: &str = ".dec-state"; +pub const GROUP_STATE_PATH_LINK: &str = ".link"; +pub const GROUP_STATE_PATH_LAST_VOTE_ROUNDS: &str = "last-vote-round"; +pub const GROUP_STATE_PATH_LAST_QC: &str = "last-qc"; +pub const GROUP_STATE_PATH_LAST_TC: &str = "last-tc"; +pub const GROUP_STATE_PATH_RANGE: &str = "range"; +pub const GROUP_STATE_PATH_PREPARES: &str = "prepares"; +pub const GROUP_STATE_PATH_PRE_COMMITS: &str = "pre-commits"; +pub const GROUP_STATE_PATH_BLOCK: &str = "block"; +pub const GROUP_STATE_PATH_RESULT_STATE: &str = "result-state"; +pub const GROUP_STATE_PATH_FINISH_PROPOSALS: &str = "finish-proposals"; +pub const GROUP_STATE_PATH_FLIP_TIME: &str = "flip-time"; +pub const GROUP_STATE_PATH_RECYCLE: &str = "recycle"; +pub const GROUP_STATE_PATH_ADDING: &str = "adding"; + +pub const STATEPATH_GROUP_DEC_RPATH: &str = ".update"; +pub const STATEPATH_GROUP_DEC_LATEST_VERSION: &str = "latest-version"; + +pub struct GroupStatePath { + rpath: String, + root: String, + dec_state: String, + link: String, + last_vote_round: String, + last_qc: String, + last_tc: String, + range: String, + prepares: String, + pre_commits: String, + finish_proposals: String, + flip_time: String, + recycle: String, + adding: String, +} + +impl GroupStatePath { + pub fn new(rpath: String) -> Self { + Self { + root: Self::join(&["", rpath.as_str()]), + dec_state: Self::join(&["", rpath.as_str(), GROUP_STATE_PATH_DEC_STATE]), + link: Self::join(&["", rpath.as_str(), GROUP_STATE_PATH_LINK]), + last_vote_round: Self::join(&[ + "", + rpath.as_str(), + GROUP_STATE_PATH_LINK, + GROUP_STATE_PATH_LAST_VOTE_ROUNDS, + ]), + last_qc: Self::join(&[ + "", + rpath.as_str(), + GROUP_STATE_PATH_LINK, + GROUP_STATE_PATH_LAST_QC, + ]), + last_tc: Self::join(&[ + "", + rpath.as_str(), + GROUP_STATE_PATH_LINK, + GROUP_STATE_PATH_LAST_TC, + ]), + range: Self::join(&[ + "", + rpath.as_str(), + GROUP_STATE_PATH_LINK, + GROUP_STATE_PATH_RANGE, + ]), + prepares: Self::join(&[ + "", + rpath.as_str(), + GROUP_STATE_PATH_LINK, + GROUP_STATE_PATH_PREPARES, + ]), + pre_commits: Self::join(&[ + "", + rpath.as_str(), + GROUP_STATE_PATH_LINK, + GROUP_STATE_PATH_PRE_COMMITS, + ]), + finish_proposals: Self::join(&[ + "", + rpath.as_str(), + GROUP_STATE_PATH_LINK, + GROUP_STATE_PATH_FINISH_PROPOSALS, + ]), + flip_time: Self::join(&[ + "", + rpath.as_str(), + GROUP_STATE_PATH_LINK, + GROUP_STATE_PATH_FINISH_PROPOSALS, + GROUP_STATE_PATH_FLIP_TIME, + ]), + recycle: Self::join(&[ + "", + rpath.as_str(), + GROUP_STATE_PATH_LINK, + GROUP_STATE_PATH_FINISH_PROPOSALS, + GROUP_STATE_PATH_RECYCLE, + ]), + adding: Self::join(&[ + "", + rpath.as_str(), + GROUP_STATE_PATH_LINK, + GROUP_STATE_PATH_FINISH_PROPOSALS, + GROUP_STATE_PATH_ADDING, + ]), + rpath, + } + } + + pub fn join(fields: &[&str]) -> String { + fields.join(STATE_PATH_SEPARATOR) + } + + pub fn root(&self) -> &str { + self.root.as_str() + } + + pub fn dec_state(&self) -> &str { + self.dec_state.as_str() + } + + pub fn link(&self) -> &str { + self.link.as_str() + } + + pub fn last_vote_round(&self) -> &str { + self.last_vote_round.as_str() + } + + pub fn last_qc(&self) -> &str { + self.last_qc.as_str() + } + + pub fn last_tc(&self) -> &str { + self.last_tc.as_str() + } + + pub fn range(&self) -> &str { + self.range.as_str() + } + + pub fn commit_height(&self, height: u64) -> String { + Self::join(&[self.link.as_str(), height.to_string().as_str()]) + } + + pub fn prepares(&self) -> &str { + self.prepares.as_str() + } + + pub fn prepares_block(&self, block_id: &ObjectId) -> String { + Self::join(&[ + self.prepares.as_str(), + block_id.to_string().as_str(), + GROUP_STATE_PATH_BLOCK, + ]) + } + + pub fn prepares_result_state(&self, block_id: &ObjectId) -> String { + Self::join(&[ + self.prepares.as_str(), + block_id.to_string().as_str(), + GROUP_STATE_PATH_RESULT_STATE, + ]) + } + + pub fn pre_commits(&self) -> &str { + self.pre_commits.as_str() + } + + pub fn pre_commits_block(&self, block_id: &ObjectId) -> String { + Self::join(&[ + self.prepares.as_str(), + block_id.to_string().as_str(), + GROUP_STATE_PATH_BLOCK, + ]) + } + + pub fn pre_commits_result_state(&self, block_id: &ObjectId) -> String { + Self::join(&[ + self.prepares.as_str(), + block_id.to_string().as_str(), + GROUP_STATE_PATH_RESULT_STATE, + ]) + } + + pub fn finish_proposals(&self) -> &str { + self.finish_proposals.as_str() + } + + pub fn flip_time(&self) -> &str { + self.flip_time.as_str() + } + + pub fn recycle(&self) -> &str { + self.recycle.as_str() + } + + pub fn adding(&self) -> &str { + self.adding.as_str() + } +} diff --git a/src/component/cyfs-group/src/statepath/mod.rs b/src/component/cyfs-group/src/statepath/mod.rs new file mode 100644 index 000000000..7ff8c583f --- /dev/null +++ b/src/component/cyfs-group/src/statepath/mod.rs @@ -0,0 +1,7 @@ +mod dec_statepath; +mod group_statepath; +mod group_shell_statepath; + +pub(crate) use dec_statepath::*; +pub(crate) use group_shell_statepath::*; +pub(crate) use group_statepath::*; diff --git a/src/component/cyfs-group/src/storage/dec_storage.rs b/src/component/cyfs-group/src/storage/dec_storage.rs new file mode 100644 index 000000000..0d937487c --- /dev/null +++ b/src/component/cyfs-group/src/storage/dec_storage.rs @@ -0,0 +1,121 @@ +use std::{collections::HashMap, sync::Arc}; + +use async_std::sync::RwLock; +use cyfs_base::{ + BuckyError, BuckyErrorCode, BuckyResult, ObjectId, ObjectMap, ObjectMapOpEnvMemoryCache, + ObjectTypeCode, RawDecode, +}; +use cyfs_core::{GroupConsensusBlock, HotstuffBlockQC}; +use cyfs_group_lib::GroupRPathStatus; +use cyfs_lib::{GlobalStateRawProcessorRef, NONObjectInfo}; + +use crate::STATE_PATH_SEPARATOR; + +#[derive(Clone)] +pub struct DecStorageCache { + pub state: Option, + pub header_block: GroupConsensusBlock, + pub qc: HotstuffBlockQC, +} + +#[derive(Clone)] +pub struct DecStorage { + cache: Arc>>, + pub state_processor: GlobalStateRawProcessorRef, +} + +impl DecStorage { + pub async fn load(state_processor: GlobalStateRawProcessorRef) -> BuckyResult { + // unimplemented!(); + let obj = Self { + cache: Arc::new(RwLock::new(None)), + state_processor, + }; + + Ok(obj) + } + + pub async fn cur_state(&self) -> Option { + let cur = self.cache.read().await; + (*cur).clone() + } + + pub async fn sync( + &self, + header_block: &GroupConsensusBlock, + qc: &HotstuffBlockQC, + remote: ObjectId, + ) -> BuckyResult<()> { + unimplemented!() + } + + pub async fn get_by_path(&self, path: &str) -> BuckyResult { + unimplemented!() + } + + pub async fn check_sub_path_value<'a>( + &self, + sub_path: &str, + verifiable_status: &'a GroupRPathStatus, + ) -> BuckyResult> { + let block_desc = &verifiable_status.block_desc; + let qc = &verifiable_status.certificate; + + let mut parent_state_id = match block_desc.content().result_state_id() { + Some(state_id) => state_id.clone(), + None => return Ok(None), + }; + + let root_cache = self.state_processor.root_cache(); + let cache = ObjectMapOpEnvMemoryCache::new_ref(root_cache.clone()); + + for folder in sub_path.split(STATE_PATH_SEPARATOR) { + if folder.len() == 0 { + continue; + } + + let parent_state = match verifiable_status.status_map.get(&parent_state_id) { + Some(state) => state, + None => return Ok(None), + }; + + if ObjectTypeCode::ObjectMap != parent_state.object().obj_type_code() { + let msg = format!( + "unmatch object type at path {} in folder {}, expect: ObjectMap, got: {:?}", + sub_path, + folder, + parent_state.object().obj_type_code() + ); + log::warn!("{}", msg); + return Err(BuckyError::new(BuckyErrorCode::Unmatch, msg)); + } + + let (parent, remain) = ObjectMap::raw_decode(parent_state.object_raw.as_slice()) + .map_err(|err| { + let msg = format!( + "decode failed at path {} in folder {}, {:?}", + sub_path, folder, err + ); + log::warn!("{}", msg); + BuckyError::new(err.code(), msg) + })?; + + assert_eq!(remain.len(), 0); + + let sub_map_id = parent.get_by_key(&cache, folder).await?; + log::debug!("get sub-folder {} result: {:?}", folder, sub_map_id); + + match sub_map_id { + Some(sub_map_id) => { + // for next folder + parent_state_id = sub_map_id; + } + None => { + return Ok(None); + } + } + } + + Ok(verifiable_status.status_map.get(&parent_state_id)) + } +} diff --git a/src/component/cyfs-group/src/storage/engine/mod.rs b/src/component/cyfs-group/src/storage/engine/mod.rs new file mode 100644 index 000000000..c185cb24c --- /dev/null +++ b/src/component/cyfs-group/src/storage/engine/mod.rs @@ -0,0 +1,7 @@ +mod storage_engine; +mod storage_engine_group_state; +mod storage_engine_mock; + +pub(super) use storage_engine::*; +pub(super) use storage_engine_group_state::*; +pub(super) use storage_engine_mock::*; diff --git a/src/component/cyfs-group/src/storage/engine/storage_engine.rs b/src/component/cyfs-group/src/storage/engine/storage_engine.rs new file mode 100644 index 000000000..e14445695 --- /dev/null +++ b/src/component/cyfs-group/src/storage/engine/storage_engine.rs @@ -0,0 +1,89 @@ +use std::collections::{HashMap, HashSet}; + +use cyfs_base::{BuckyResult, ObjectId, ObjectMapSingleOpEnvRef}; +use cyfs_core::{GroupConsensusBlock, HotstuffBlockQC, HotstuffTimeout}; + +pub struct FinishProposalMgr { + pub flip_timestamp: u64, + pub over: HashSet, + pub adding: HashSet, +} + +pub struct StorageCacheInfo { + pub dec_state_id: Option, // commited/header state id + pub last_vote_round: u64, // 参与投票的最后一个轮次 + pub last_qc: Option, + pub last_tc: Option, + pub header_block: Option, + pub first_block: Option, + pub prepares: HashMap, + pub pre_commits: HashMap, + pub finish_proposals: FinishProposalMgr, +} + +impl StorageCacheInfo { + pub fn new(dec_state_id: Option) -> Self { + Self { + dec_state_id, + last_vote_round: 0, + last_qc: None, + last_tc: None, + header_block: None, + first_block: None, + prepares: HashMap::new(), + pre_commits: HashMap::new(), + finish_proposals: FinishProposalMgr { + flip_timestamp: 0, + over: HashSet::new(), + adding: HashSet::new(), + }, + } + } +} + +#[async_trait::async_trait] +pub trait StorageWriter: Send + Sync { + async fn insert_prepares( + &mut self, + block_id: &ObjectId, + result_state_id: &Option, + ) -> BuckyResult<()>; + + async fn insert_pre_commit( + &mut self, + block_id: &ObjectId, + result_state_id: &Option, + is_instead: bool, + ) -> BuckyResult<()>; + + async fn push_commit( + &mut self, + height: u64, + block_id: &ObjectId, + result_state_id: &Option, + prev_result_state_id: &Option, + min_height: u64, + ) -> BuckyResult<()>; + + async fn remove_prepares(&mut self, block_ids: &[ObjectId]) -> BuckyResult<()>; + + async fn push_proposals( + &mut self, + proposal_ids: &[ObjectId], + timestamp: Option<(u64, u64)>, // (timestamp, prev_timestamp), 0 if the first + ) -> BuckyResult<()>; + + async fn set_last_vote_round(&mut self, round: u64, prev_value: u64) -> BuckyResult<()>; + + async fn save_last_qc(&mut self, qc_id: &ObjectId) -> BuckyResult<()>; + + async fn save_last_tc(&mut self, tc_id: &ObjectId) -> BuckyResult<()>; + + async fn commit(mut self) -> BuckyResult<()>; +} + +#[async_trait::async_trait] +pub trait StorageEngine { + async fn find_block_by_height(&self, height: u64) -> BuckyResult; + // async fn is_proposal_finished(&self, proposal_id: &ObjectId) -> BuckyResult; +} diff --git a/src/component/cyfs-group/src/storage/engine/storage_engine_group_state.rs b/src/component/cyfs-group/src/storage/engine/storage_engine_group_state.rs new file mode 100644 index 000000000..cf04e93ff --- /dev/null +++ b/src/component/cyfs-group/src/storage/engine/storage_engine_group_state.rs @@ -0,0 +1,812 @@ +use std::{collections::HashSet, sync::Arc}; + +use cyfs_base::{ + BuckyError, BuckyErrorCode, BuckyResult, ObjectId, ObjectIdDataBuilder, ObjectMapContentItem, + ObjectMapIsolatePathOpEnvRef, ObjectMapPathOpEnvRef, ObjectMapRootCacheRef, + ObjectMapRootManagerRef, ObjectMapSimpleContentType, ObjectMapSingleOpEnvRef, OpEnvPathAccess, +}; +use cyfs_core::GroupConsensusBlockObject; + +use crate::{ + GroupObjectMapProcessor, GroupStatePath, NONDriverHelper, GROUP_STATE_PATH_BLOCK, + GROUP_STATE_PATH_DEC_STATE, GROUP_STATE_PATH_FLIP_TIME, GROUP_STATE_PATH_RANGE, + GROUP_STATE_PATH_RESULT_STATE, +}; + +use super::{StorageCacheInfo, StorageEngine, StorageWriter}; + +const ACCESS: Option = None; + +#[derive(Clone)] +pub struct StorageEngineGroupState { + group_id: ObjectId, + dec_id: ObjectId, + state_mgr: ObjectMapRootManagerRef, + state_path: Arc, +} + +impl StorageEngineGroupState { + pub(crate) async fn load_cache( + state_mgr: &ObjectMapRootManagerRef, + non_driver: &NONDriverHelper, + state_path: &GroupStatePath, + ) -> BuckyResult { + let op_env = state_mgr.create_op_env(ACCESS).map_err(|err| { + log::warn!("create_op_env failed {:?}", err); + err + })?; + + let dec_state_id = op_env.get_by_path(state_path.dec_state()).await; + let dec_state_id = map_not_found_option_to_option(dec_state_id)?; + + let last_vote_round = op_env.get_by_path(state_path.last_vote_round()).await; + let last_vote_round = + map_not_found_option_to_option(last_vote_round)?.map(|id| parse_u64_obj(&id)); + + let last_qc = op_env.get_by_path(state_path.last_qc()).await; + let last_qc = map_not_found_option_to_option(last_qc)?; + let last_qc = match last_qc.as_ref() { + Some(qc_id) => non_driver + .get_qc(qc_id, None) + .await? + .try_into() + .map_or(None, |qc| Some(qc)), + None => None, + }; + + let last_tc = op_env.get_by_path(state_path.last_tc()).await; + let last_tc = map_not_found_option_to_option(last_tc)?; + let last_tc = match last_tc.as_ref() { + Some(tc_id) => non_driver + .get_qc(tc_id, None) + .await? + .try_into() + .map_or(None, |tc| Some(tc)), + None => None, + }; + + let mut first_header_block_ids: Vec = vec![]; + let commit_range = op_env.get_by_path(state_path.range()).await; + let commit_range = + map_not_found_option_to_option(commit_range)?.map(|id| parse_range_obj(&id)); + let commit_block = match commit_range { + Some((first_height, header_height)) => { + let first_block_id = op_env + .get_by_path(state_path.commit_height(first_height).as_str()) + .await; + let first_block_id = + map_not_found_option_to_option(first_block_id)?.expect("first block is lost"); + first_header_block_ids.push(first_block_id); + + if header_height == first_height { + Some((first_block_id, first_block_id)) + } else { + let header_block_id = op_env + .get_by_path(state_path.commit_height(header_height).as_str()) + .await; + let header_block_id = map_not_found_option_to_option(header_block_id)? + .expect("first block is lost"); + first_header_block_ids.push(header_block_id); + Some((first_block_id, header_block_id)) + } + } + None => None, + }; + + let prepare_block_ids = + load_object_ids_with_path_map_key(&op_env, state_path.prepares()).await?; + if prepare_block_ids.len() == 0 && commit_range.is_none() { + return Err(BuckyError::new( + BuckyErrorCode::NotFound, + "not found in storage", + )); + } + + let pre_commit_block_ids = + load_object_ids_with_path_map_key(&op_env, state_path.pre_commits()).await?; + + let flip_timestamp = op_env.get_by_path(state_path.flip_time()).await; + let flip_timestamp = map_not_found_option_to_option(flip_timestamp)?.map_or(0, |id| { + let n = parse_u64_obj(&id); + // log::debug!( + // "load flip timestamp {}/{} -> {}", + // id, + // id.to_hex().unwrap(), + // n + // ); + n + }); + + let adding_proposal_ids = + load_object_ids_with_path_set(&op_env, state_path.adding()).await?; + let over_proposal_ids = + load_object_ids_with_path_set(&op_env, state_path.recycle()).await?; + + let load_block_ids = [ + first_header_block_ids.as_slice(), + prepare_block_ids.as_slice(), + pre_commit_block_ids.as_slice(), + ] + .concat(); + + let load_blocks = futures::future::join_all(load_block_ids.iter().map(|id| async { + let id = id.clone(); + non_driver.get_block(&id, None).await.map_err(|err| { + log::warn!("get block {} failed {:?}", id, err); + err + }) + })) + .await; + + let mut cache = StorageCacheInfo::new(dec_state_id); + cache.last_vote_round = last_vote_round.map_or(0, |round| round); + cache.last_qc = last_qc; + cache.last_tc = last_tc; + cache.finish_proposals.adding = HashSet::from_iter(adding_proposal_ids.into_iter()); + cache.finish_proposals.over = HashSet::from_iter(over_proposal_ids.into_iter()); + cache.finish_proposals.flip_timestamp = flip_timestamp; + + let prepare_block_pos = match commit_block { + Some((first_block_id, header_block_id)) => { + cache.first_block = Some(load_blocks.get(0).unwrap().clone()?); + if header_block_id == first_block_id { + cache.header_block = cache.first_block.clone(); + 1 + } else { + cache.header_block = Some(load_blocks.get(1).unwrap().clone()?); + 2 + } + } + None => 0, + }; + + let dec_state_id_in_header = cache + .header_block + .as_ref() + .map_or(None, |b| b.result_state_id().clone()); + + assert_eq!(dec_state_id, dec_state_id_in_header); + if dec_state_id != dec_state_id_in_header { + return Err(BuckyError::new( + BuckyErrorCode::Unmatch, + "the state should same as it in header-block", + )); + } + + let (prepare_blocks, pre_commit_blocks) = + load_blocks.as_slice()[prepare_block_pos..].split_at(prepare_block_ids.len()); + for (block, block_id) in prepare_blocks.iter().zip(prepare_block_ids) { + cache.prepares.insert(block_id, block.clone()?); + } + for (block, block_id) in pre_commit_blocks.iter().zip(pre_commit_block_ids) { + cache.pre_commits.insert(block_id, block.clone()?); + } + + Ok(cache) + } + + pub fn new( + state_mgr: ObjectMapRootManagerRef, + state_path: GroupStatePath, + group_id: ObjectId, + dec_id: ObjectId, + ) -> Self { + Self { + state_mgr, + state_path: Arc::new(state_path), + group_id, + dec_id, + } + } + + pub async fn create_writer(&self) -> BuckyResult { + Ok(StorageEngineGroupStateWriter::new( + self.state_mgr.clone(), + self.state_path.clone(), + self.group_id, + self.dec_id, + ) + .await?) + } + + pub fn root_cache(&self) -> &ObjectMapRootCacheRef { + self.state_mgr.root_cache() + } +} + +#[async_trait::async_trait] +impl StorageEngine for StorageEngineGroupState { + async fn find_block_by_height(&self, height: u64) -> BuckyResult { + let op_env = self.state_mgr.create_op_env(ACCESS)?; + let block_id = op_env + .get_by_path(self.state_path.commit_height(height).as_str()) + .await?; + block_id.map_or( + Err(BuckyError::new(BuckyErrorCode::NotFound, "not found")), + |block_id| Ok(block_id), + ) + } +} + +#[derive(Clone)] +pub struct StorageEngineGroupStateWriter { + group_id: ObjectId, + dec_id: ObjectId, + state_mgr: ObjectMapRootManagerRef, + op_env: ObjectMapPathOpEnvRef, + prepare_op_env: ObjectMapSingleOpEnvRef, + prepare_map_id: Option, + state_path: Arc, + write_result: BuckyResult<()>, +} + +impl StorageEngineGroupStateWriter { + async fn new( + state_mgr: ObjectMapRootManagerRef, + state_path: Arc, + group_id: ObjectId, + dec_id: ObjectId, + ) -> BuckyResult { + let op_env = state_mgr.create_op_env(ACCESS)?; + let prepare_op_env = state_mgr.create_single_op_env(ACCESS)?; + let prepare_map_id = + if let Err(err) = prepare_op_env.load_by_path(state_path.prepares()).await { + if err.code() == BuckyErrorCode::NotFound { + prepare_op_env + .create_new( + ObjectMapSimpleContentType::Map, + Some(group_id), + Some(dec_id), + ) + .await?; + None + } else { + return Err(err); + } + } else { + prepare_op_env.get_current_root().await + }; + + Ok(Self { + op_env, + prepare_op_env, + state_path, + state_mgr, + prepare_map_id, + write_result: Ok(()), + group_id, + dec_id, + }) + } + + async fn create_block_result_object_map( + &self, + block_id: &ObjectId, + result_state_id: &Option, + ) -> BuckyResult { + let single_op_env = self.state_mgr.create_single_op_env(ACCESS)?; + single_op_env + .create_new( + ObjectMapSimpleContentType::Map, + Some(self.group_id), + Some(self.dec_id), + ) + .await?; + single_op_env + .insert_with_key(GROUP_STATE_PATH_BLOCK, block_id) + .await?; + if let Some(state_id) = result_state_id.as_ref() { + single_op_env + .insert_with_key(GROUP_STATE_PATH_RESULT_STATE, state_id) + .await?; + } + + single_op_env.commit().await + } + + async fn insert_prepares_inner( + &mut self, + block_id: &ObjectId, + result_state_id: &Option, + ) -> BuckyResult<()> { + let block_result_pair = self + .create_block_result_object_map(block_id, result_state_id) + .await?; + self.prepare_op_env + .insert_with_key(block_id.to_string().as_str(), &block_result_pair) + .await + } + + async fn insert_pre_commit_inner( + &mut self, + block_id: &ObjectId, + result_state_id: &Option, + is_instead: bool, + ) -> BuckyResult<()> { + let block_result_pair = self + .prepare_op_env + .remove_with_key(block_id.to_string().as_str(), &None) + .await?; + assert!(block_result_pair.is_some()); + + if is_instead { + self.op_env + .remove_with_path(self.state_path.pre_commits(), &None) + .await?; + } + + let block_result_pair = self + .create_block_result_object_map(block_id, result_state_id) + .await?; + + self.op_env + .insert_with_key( + self.state_path.pre_commits(), + block_id.to_string().as_str(), + &block_result_pair, + ) + .await + } + + async fn push_commit_inner( + &mut self, + height: u64, + block_id: &ObjectId, + result_state_id: &Option, + prev_result_state_id: &Option, + min_height: u64, + ) -> BuckyResult<()> { + self.op_env + .insert_with_path(self.state_path.commit_height(height).as_str(), block_id) + .await?; + + if height == 1 { + let range_obj = make_range_obj(1, height); + self.op_env + .insert_with_key(self.state_path.link(), GROUP_STATE_PATH_RANGE, &range_obj) + .await?; + } else { + assert!(min_height < height); + let range_obj = make_range_obj(min_height, height); + let prev_range = make_range_obj(min_height, height - 1); + let prev_value = self + .op_env + .set_with_key( + self.state_path.link(), + GROUP_STATE_PATH_RANGE, + &range_obj, + &Some(prev_range), + false, + ) + .await?; + assert_eq!(prev_value.unwrap(), prev_range); + }; + + // update state from dec-app + if result_state_id == prev_result_state_id { + return Ok(()); + } else { + match result_state_id { + Some(result_state_id) => { + if prev_result_state_id.is_none() { + self.op_env + .insert_with_key( + self.state_path.root(), + GROUP_STATE_PATH_DEC_STATE, + result_state_id, + ) + .await?; + } else { + let prev_value = self + .op_env + .set_with_key( + self.state_path.root(), + GROUP_STATE_PATH_DEC_STATE, + result_state_id, + prev_result_state_id, + false, + ) + .await?; + assert_eq!(&prev_value, prev_result_state_id); + } + } + None => { + self.op_env + .remove_with_path(self.state_path.dec_state(), prev_result_state_id) + .await?; + } + } + } + + Ok(()) + } + + async fn remove_prepares_inner(&mut self, block_ids: &[ObjectId]) -> BuckyResult<()> { + for block_id in block_ids { + let block_result_pair = self + .prepare_op_env + .remove_with_key(block_id.to_string().as_str(), &None) + .await?; + assert!(block_result_pair.is_some()); + } + Ok(()) + } + + async fn push_proposals_inner( + &mut self, + proposal_ids: &[ObjectId], + timestamp: Option<(u64, u64)>, // (timestamp, prev_timestamp), 0 if the first + ) -> BuckyResult<()> { + if proposal_ids.is_empty() { + return Ok(()); + } + + let add_single_op_env = self.state_mgr.create_single_op_env(ACCESS)?; + + if let Some((timestamp, prev_timestamp)) = timestamp { + let new_over = self + .op_env + .remove_with_path(self.state_path.adding(), &None) + .await?; + + if let Some(new_over) = new_over.as_ref() { + self.op_env + .set_with_path(self.state_path.recycle(), new_over, &None, true) + .await?; + } + + let timestamp_obj = make_u64_obj(timestamp); + if prev_timestamp != 0 { + let prev_timestamp_obj = make_u64_obj(prev_timestamp); + // log::debug!( + // "will update flip-time from {} -> {}/{} to {} -> {}/{}", + // prev_timestamp, + // prev_timestamp_obj, + // prev_timestamp_obj.to_hex().unwrap(), + // timestamp, + // timestamp_obj, + // timestamp_obj.to_hex().unwrap(), + // ); + let prev_value = self + .op_env + .set_with_path( + self.state_path.flip_time(), + ×tamp_obj, + &Some(prev_timestamp_obj), + false, + ) + .await?; + assert_eq!(prev_value.unwrap(), prev_timestamp_obj); + } else { + // log::debug!("will update flip-time from None to {}", timestamp); + self.op_env + .insert_with_key( + self.state_path.finish_proposals(), + GROUP_STATE_PATH_FLIP_TIME, + ×tamp_obj, + ) + .await?; + } + + add_single_op_env + .create_new( + ObjectMapSimpleContentType::Set, + Some(self.group_id), + Some(self.dec_id), + ) + .await?; + } else { + add_single_op_env + .load_by_path(self.state_path.adding()) + .await?; + } + + for proposal_id in proposal_ids { + let is_new = add_single_op_env.insert(proposal_id).await?; + assert!(is_new); + } + let adding_set_id = add_single_op_env.commit().await?; + let prev_value = self + .op_env + .set_with_path(self.state_path.adding(), &adding_set_id, &None, true) + .await?; + + Ok(()) + } + + async fn set_last_vote_round_inner(&mut self, round: u64, prev_value: u64) -> BuckyResult<()> { + assert!(round > prev_value); + if round == prev_value { + return Ok(()); + } + + let round_obj = make_u64_obj(round); + + if prev_value == 0 { + self.op_env + .insert_with_path(self.state_path.last_vote_round(), &round_obj) + .await + } else { + let prev_obj = make_u64_obj(prev_value); + let prev_value = self + .op_env + .set_with_path( + self.state_path.last_vote_round(), + &round_obj, + &Some(prev_obj), + false, + ) + .await?; + assert_eq!(prev_value.unwrap(), prev_obj); + Ok(()) + } + } + + async fn save_last_qc_inner(&mut self, qc_id: &ObjectId) -> BuckyResult<()> { + self.op_env + .set_with_path(self.state_path.last_qc(), qc_id, &None, true) + .await + .map(|_| ()) + } + + async fn save_last_tc_inner(&mut self, tc_id: &ObjectId) -> BuckyResult<()> { + self.op_env + .set_with_path(self.state_path.last_tc(), tc_id, &None, true) + .await + .map(|_| ()) + } +} + +#[async_trait::async_trait] +impl StorageWriter for StorageEngineGroupStateWriter { + async fn insert_prepares( + &mut self, + block_id: &ObjectId, + result_state_id: &Option, + ) -> BuckyResult<()> { + self.write_result.as_ref().map_err(|e| e.clone())?; + self.write_result = self.insert_prepares_inner(block_id, result_state_id).await; + self.write_result.clone() + } + + async fn insert_pre_commit( + &mut self, + block_id: &ObjectId, + result_state_id: &Option, + is_instead: bool, + ) -> BuckyResult<()> { + self.write_result.as_ref().map_err(|e| e.clone())?; + self.write_result = self + .insert_pre_commit_inner(block_id, result_state_id, is_instead) + .await; + self.write_result.clone() + } + + async fn push_commit( + &mut self, + height: u64, + block_id: &ObjectId, + result_state_id: &Option, + prev_result_state_id: &Option, + min_height: u64, + ) -> BuckyResult<()> { + self.write_result.as_ref().map_err(|e| e.clone())?; + self.write_result = self + .push_commit_inner( + height, + block_id, + result_state_id, + prev_result_state_id, + min_height, + ) + .await; + self.write_result.clone() + } + + async fn remove_prepares(&mut self, block_ids: &[ObjectId]) -> BuckyResult<()> { + self.write_result.as_ref().map_err(|e| e.clone())?; + self.write_result = self.remove_prepares_inner(block_ids).await; + self.write_result.clone() + } + + async fn push_proposals( + &mut self, + proposal_ids: &[ObjectId], + timestamp: Option<(u64, u64)>, // (timestamp, prev_timestamp), 0 if the first + ) -> BuckyResult<()> { + self.write_result.as_ref().map_err(|e| e.clone())?; + self.write_result = self.push_proposals_inner(proposal_ids, timestamp).await; + self.write_result.clone() + } + + async fn set_last_vote_round(&mut self, round: u64, prev_value: u64) -> BuckyResult<()> { + self.write_result.as_ref().map_err(|e| e.clone())?; + self.write_result = self.set_last_vote_round_inner(round, prev_value).await; + self.write_result.clone() + } + + async fn save_last_qc(&mut self, qc_id: &ObjectId) -> BuckyResult<()> { + self.write_result.as_ref().map_err(|e| e.clone())?; + self.write_result = self.save_last_qc_inner(qc_id).await; + self.write_result.clone() + } + + async fn save_last_tc(&mut self, tc_id: &ObjectId) -> BuckyResult<()> { + self.write_result.as_ref().map_err(|e| e.clone())?; + self.write_result = self.save_last_tc_inner(tc_id).await; + self.write_result.clone() + } + + async fn commit(mut self) -> BuckyResult<()> { + self.write_result.as_ref().map_err(|e| e.clone())?; + + let prepare_map_id = self.prepare_op_env.commit().await?; + self.op_env + .set_with_path( + self.state_path.prepares(), + &prepare_map_id, + &self.prepare_map_id, + self.prepare_map_id.is_none(), + ) + .await?; + self.op_env.commit().await.map_or_else( + |err| { + if err.code() == BuckyErrorCode::AlreadyExists { + Ok(()) + } else { + Err(err) + } + }, + |_| Ok(()), + ) + } +} + +fn make_range_obj(min: u64, max: u64) -> ObjectId { + let mut range_buf = [0u8; 24]; + let (low, high) = range_buf.split_at_mut(12); + low[..8].copy_from_slice(&min.to_le_bytes()); + high[..8].copy_from_slice(&max.to_le_bytes()); + ObjectIdDataBuilder::new().data(&range_buf).build().unwrap() +} + +fn parse_range_obj(obj: &ObjectId) -> (u64, u64) { + let range_buf = obj.data(); + assert_eq!(range_buf.len(), 24); + let (low_buf, high_buf) = range_buf.split_at(12); + let mut low = [0u8; 8]; + low.copy_from_slice(&low_buf[..8]); + let mut high = [0u8; 8]; + high.copy_from_slice(&high_buf[..8]); + + (u64::from_le_bytes(low), u64::from_le_bytes(high)) +} + +fn make_u64_obj(value: u64) -> ObjectId { + let mut range_buf = [0u8; 8]; + range_buf.copy_from_slice(&value.to_le_bytes()); + ObjectIdDataBuilder::new().data(&range_buf).build().unwrap() +} + +fn parse_u64_obj(obj: &ObjectId) -> u64 { + let mut buf = [0u8; 8]; + buf.copy_from_slice(obj.data()); + u64::from_le_bytes(buf) +} + +async fn load_object_ids_with_path_set( + op_env: &ObjectMapPathOpEnvRef, + full_path: &str, +) -> BuckyResult> { + let content = match op_env.list(full_path).await { + Ok(content) => content, + Err(err) => { + log::warn!("list by path {} failed {:?}", full_path, err); + if err.code() == BuckyErrorCode::NotFound { + return Ok(vec![]); + } else { + return Err(err); + } + } + }; + + let mut object_ids: Vec = vec![]; + for item in content.list.iter() { + match item { + ObjectMapContentItem::Set(id) => object_ids.push(id.clone()), + _ => { + log::error!("should be a set in path {}", full_path); + return Err(BuckyError::new( + BuckyErrorCode::InvalidFormat, + format!("should be a set in path {}", full_path), + )); + } + } + } + + Ok(object_ids) +} + +async fn load_object_ids_with_path_map_key( + op_env: &ObjectMapPathOpEnvRef, + full_path: &str, +) -> BuckyResult> { + let content = match op_env.list(full_path).await { + Ok(content) => content, + Err(err) => { + log::warn!("list by path {} failed {:?}", full_path, err); + if err.code() == BuckyErrorCode::NotFound { + return Ok(vec![]); + } else { + return Err(err); + } + } + }; + + let mut object_ids: Vec = vec![]; + for item in content.list.iter() { + match item { + ObjectMapContentItem::Map((key_id_base58, _)) => { + object_ids.push(ObjectId::from_base58(key_id_base58)?) + } + _ => { + log::error!("should be a set in path {}", full_path); + return Err(BuckyError::new( + BuckyErrorCode::InvalidFormat, + format!("should be a set in path {}", full_path), + )); + } + } + } + + Ok(object_ids) +} + +fn map_not_found_to_option(r: BuckyResult) -> BuckyResult> { + match r { + Ok(t) => Ok(Some(t)), + Err(err) => { + if err.code() == BuckyErrorCode::NotFound { + Ok(None) + } else { + Err(err) + } + } + } +} + +fn map_not_found_option_to_option(r: BuckyResult>) -> BuckyResult> { + match r { + Ok(t) => Ok(t), + Err(err) => { + if err.code() == BuckyErrorCode::NotFound { + Ok(None) + } else { + Err(err) + } + } + } +} + +pub struct GroupObjectMapProcessorGroupState { + state_mgr: ObjectMapRootManagerRef, +} + +impl GroupObjectMapProcessorGroupState { + pub fn new(state_mgr: &ObjectMapRootManagerRef) -> Self { + Self { + state_mgr: state_mgr.clone(), + } + } +} + +#[async_trait::async_trait] +impl GroupObjectMapProcessor for GroupObjectMapProcessorGroupState { + async fn create_single_op_env(&self) -> BuckyResult { + self.state_mgr.create_single_op_env(ACCESS) + } + + async fn create_sub_tree_op_env(&self) -> BuckyResult { + self.state_mgr.create_isolate_path_op_env(ACCESS) + } +} diff --git a/src/component/cyfs-group/src/storage/engine/storage_engine_mock.rs b/src/component/cyfs-group/src/storage/engine/storage_engine_mock.rs new file mode 100644 index 000000000..fbc7cafdc --- /dev/null +++ b/src/component/cyfs-group/src/storage/engine/storage_engine_mock.rs @@ -0,0 +1,211 @@ +use std::collections::{HashMap, HashSet}; + +use cyfs_base::{BuckyError, BuckyErrorCode, BuckyResult, ObjectId}; + +use super::{StorageEngine, StorageWriter}; + +struct StorageEngineMockFinishProposalMgr { + flip_timestamp: u64, + over: HashSet, + adding: HashSet, +} + +pub struct StorageEngineMock { + last_vote_round: u64, + + result_state_id: Option, + block_height_range: (u64, u64), + + commit_blocks: HashMap, + prepare_blocks: HashSet, + pre_commit_blocks: HashSet, + + finish_proposals: StorageEngineMockFinishProposalMgr, +} + +impl StorageEngineMock { + pub fn new() -> Self { + Self { + last_vote_round: 0, + block_height_range: (0, 0), + commit_blocks: HashMap::new(), + prepare_blocks: HashSet::new(), + pre_commit_blocks: HashSet::new(), + result_state_id: None, + finish_proposals: StorageEngineMockFinishProposalMgr { + flip_timestamp: 0, + over: HashSet::new(), + adding: HashSet::new(), + }, + } + } + + pub async fn create_writer(&mut self) -> BuckyResult { + Ok(StorageEngineMockWriter { engine: self }) + } +} + +#[async_trait::async_trait] +impl StorageEngine for StorageEngineMock { + async fn find_block_by_height(&self, height: u64) -> BuckyResult { + self.commit_blocks + .get(&height) + .map(|id| id.clone()) + .ok_or(BuckyError::new(BuckyErrorCode::NotFound, "not found")) + } + + // async fn is_proposal_finished(&self, proposal_id: &ObjectId) -> BuckyResult { + // let is_finished = self + // .finish_proposals + // .adding + // .get(proposal_id) + // .or(self.finish_proposals.over.get(proposal_id)) + // .is_some(); + // Ok(is_finished) + // } +} + +pub struct StorageEngineMockWriter<'a> { + engine: &'a mut StorageEngineMock, +} + +#[async_trait::async_trait] +impl<'a> StorageWriter for StorageEngineMockWriter<'a> { + async fn insert_prepares( + &mut self, + block_id: &ObjectId, + result_state_id: &Option, + ) -> BuckyResult<()> { + if !self.engine.prepare_blocks.insert(block_id.clone()) { + assert!(false); + return Err(BuckyError::new( + BuckyErrorCode::ErrorState, + "block prepare twice", + )); + } + Ok(()) + } + + async fn insert_pre_commit( + &mut self, + block_id: &ObjectId, + result_state_id: &Option, + is_instead: bool, + ) -> BuckyResult<()> { + if !self.engine.prepare_blocks.remove(block_id) { + assert!(false); + return Err(BuckyError::new( + BuckyErrorCode::ErrorState, + "block should be prepared before pre-commit", + )); + } + + if is_instead { + self.engine.pre_commit_blocks = HashSet::from([block_id.clone()]); + } else { + if !self.engine.pre_commit_blocks.insert(block_id.clone()) { + assert!(false); + return Err(BuckyError::new( + BuckyErrorCode::ErrorState, + "block pre-commit twice", + )); + } + } + + Ok(()) + } + + async fn push_commit( + &mut self, + height: u64, + block_id: &ObjectId, + result_state_id: &Option, + prev_result_state_id: &Option, + min_height: u64, + ) -> BuckyResult<()> { + assert!(height > min_height); + assert_eq!(height, self.engine.block_height_range.1 + 1); + assert_eq!(prev_result_state_id, &self.engine.result_state_id); + + if self + .engine + .commit_blocks + .insert(height, block_id.clone()) + .is_some() + { + assert!(false); + return Err(BuckyError::new( + BuckyErrorCode::ErrorState, + "block commit twice", + )); + } + + self.engine.block_height_range.1 = height; + self.engine.result_state_id = result_state_id.clone(); + + Ok(()) + } + + async fn remove_prepares(&mut self, block_ids: &[ObjectId]) -> BuckyResult<()> { + for block_id in block_ids { + if !self.engine.prepare_blocks.remove(block_id) { + assert!(false); + return Err(BuckyError::new( + BuckyErrorCode::ErrorState, + "try remove prepare not exists", + )); + } + } + Ok(()) + } + + async fn push_proposals( + &mut self, + proposal_ids: &[ObjectId], + timestamp: Option<(u64, u64)>, // (timestamp, prev_timestamp), 0 if the first + ) -> BuckyResult<()> { + if let Some((timestamp, prev_timestamp)) = timestamp { + let mut new_over = HashSet::new(); + std::mem::swap(&mut new_over, &mut self.engine.finish_proposals.adding); + std::mem::swap(&mut new_over, &mut self.engine.finish_proposals.over); + assert_eq!(prev_timestamp, self.engine.finish_proposals.flip_timestamp); + self.engine.finish_proposals.flip_timestamp = timestamp; + } + + for proposal_id in proposal_ids { + if !self + .engine + .finish_proposals + .adding + .insert(proposal_id.clone()) + { + assert!(false); + return Err(BuckyError::new( + BuckyErrorCode::AlreadyExists, + "dup finish proposal", + )); + } + } + + Ok(()) + } + + async fn set_last_vote_round(&mut self, round: u64, prev_value: u64) -> BuckyResult<()> { + assert_eq!(self.engine.last_vote_round, prev_value); + self.engine.last_vote_round = round; + + Ok(()) + } + + async fn save_last_qc(&mut self, qc_id: &ObjectId) -> BuckyResult<()> { + Ok(()) + } + + async fn save_last_tc(&mut self, tc_id: &ObjectId) -> BuckyResult<()> { + Ok(()) + } + + async fn commit(mut self) -> BuckyResult<()> { + Ok(()) + } +} diff --git a/src/component/cyfs-group/src/storage/group_shell_mgr.rs b/src/component/cyfs-group/src/storage/group_shell_mgr.rs new file mode 100644 index 000000000..e1f03ba55 --- /dev/null +++ b/src/component/cyfs-group/src/storage/group_shell_mgr.rs @@ -0,0 +1,376 @@ +use std::{collections::HashMap, sync::Arc}; + +use async_std::sync::RwLock; +use cyfs_base::{ + AnyNamedObject, BuckyError, BuckyErrorCode, BuckyResult, Group, GroupDesc, NamedObject, + ObjectDesc, ObjectId, ObjectMapRootManagerRef, OpEnvPathAccess, RawConvertTo, RawDecode, + RawFrom, TypelessCoreObject, +}; +use cyfs_base_meta::SavedMetaObject; +use cyfs_core::{DecApp, DecAppObj, GroupShell, ToGroupShell}; +use cyfs_lib::{GlobalStateManagerRawProcessorRef, NONObjectInfo}; +use cyfs_meta_lib::MetaClient; + +use crate::{GroupShellStatePath, MetaClientTimeout, NONDriverHelper}; + +const ACCESS: Option = None; + +struct GroupShellCache { + latest_shell_id: ObjectId, + groups_by_shell: HashMap>, + groups_by_version: HashMap>, +} + +struct GroupShellManagerRaw { + cache: RwLock, + group_desc: GroupDesc, + shell_state: ObjectMapRootManagerRef, + state_path: GroupShellStatePath, + meta_client: Arc, + non_driver: NONDriverHelper, +} + +#[derive(Clone)] +pub struct GroupShellManager(Arc); + +impl GroupShellManager { + pub(crate) async fn create( + group_id: &ObjectId, + non_driver: NONDriverHelper, + meta_client: Arc, + local_device_id: ObjectId, + root_state_mgr: &GlobalStateManagerRawProcessorRef, + remote: Option<&ObjectId>, + ) -> BuckyResult { + let (group, shell_id) = Self::get_group_impl( + &non_driver, + &meta_client, + group_id, + None, + remote, + None, + None, + ) + .await?; + + let shell_dec_id = Self::shell_dec_id(group_id); + + let group_state = root_state_mgr + .load_root_state(group_id, Some(group_id.clone()), true) + .await? + .expect("create group-shell state failed."); + + let shell_state = group_state + .get_dec_root_manager(&shell_dec_id, true) + .await?; + + let group_version = group.version(); + let group = Arc::new(group); + let raw = GroupShellManagerRaw { + cache: RwLock::new(GroupShellCache { + latest_shell_id: shell_id, + groups_by_shell: HashMap::from([(shell_id.clone(), group.clone())]), + groups_by_version: HashMap::from([(group.version(), group.clone())]), + }), + shell_state, + meta_client, + non_driver, + state_path: GroupShellStatePath::new(), + group_desc: group.desc().clone(), + }; + + let ret = Self(Arc::new(raw)); + Self::mount_shell( + &ret.0.shell_state, + &ret.0.state_path, + &shell_id, + group_version, + &None, + true, + ) + .await?; + + Ok(ret) + } + + pub(crate) async fn load( + group_id: &ObjectId, + non_driver: NONDriverHelper, + meta_client: Arc, + local_device_id: ObjectId, + root_state_mgr: &GlobalStateManagerRawProcessorRef, + ) -> BuckyResult { + let shell_dec_id = Self::shell_dec_id(group_id); + + let group_state = root_state_mgr + .load_root_state(group_id, Some(group_id.clone()), true) + .await? + .expect("create group-shell state failed."); + + let shell_state = group_state + .get_dec_root_manager(&shell_dec_id, true) + .await?; + + // load from cache + let state_path = GroupShellStatePath::new(); + + let op_env = shell_state.create_op_env(ACCESS)?; + let latest_shell_id = op_env.get_by_path(state_path.latest()).await?; + op_env.abort()?; + + let latest_shell_id = match latest_shell_id { + Some(latest_shell_id) => latest_shell_id, + None => { + return Err(BuckyError::new( + BuckyErrorCode::NotFound, + format!("group({}) state for shell is not exist.", group_id), + )) + } + }; + + let (group, latest_shell_id) = match Self::get_group_impl( + &non_driver, + &meta_client, + group_id, + Some(&latest_shell_id), + None, + None, + None, + ) + .await + { + Ok(cache) => cache, + Err(err) => { + log::error!( + "get group({}) with shell({}) mounted to cache failed {:?}, will research it.", + group_id, + latest_shell_id, + err + ); + let (group, shell_id) = Self::get_group_impl( + &non_driver, + &meta_client, + group_id, + None, + None, + None, + None, + ) + .await?; + + Self::mount_shell( + &shell_state, + &state_path, + &shell_id, + group.version(), + &Some(latest_shell_id), + true, + ) + .await?; + + (group, shell_id) + } + }; + + let group = Arc::new(group); + let raw = GroupShellManagerRaw { + cache: RwLock::new(GroupShellCache { + latest_shell_id, + groups_by_shell: HashMap::from([(latest_shell_id.clone(), group.clone())]), + groups_by_version: HashMap::from([(group.version(), group.clone())]), + }), + shell_state, + meta_client, + non_driver, + state_path: GroupShellStatePath::new(), + group_desc: group.desc().clone(), + }; + + let ret = Self(Arc::new(raw)); + + Ok(ret) + } + + /// get latest group in cache without query. + /// let (latest_group, latest_shell_id) = self.group(); + pub fn group(&self) -> (Group, ObjectId) { + async_std::task::block_on(async move { + let cache = self.0.cache.read().await; + let group = cache + .groups_by_shell + .get(&cache.latest_shell_id) + .expect("lastest group must be exists."); + (group.as_ref().clone(), cache.latest_shell_id.clone()) + }) + } + + pub async fn get_group( + &self, + group_id: &ObjectId, + group_shell_id: Option<&ObjectId>, + from: Option<&ObjectId>, + ) -> BuckyResult { + let latest_shell_id = { + let cache = self.0.cache.read().await; + if let Some(shell_id) = group_shell_id.as_ref() { + let group = cache.groups_by_shell.get(*shell_id); + if let Some(group) = group { + return Ok(group.as_ref().clone()); + } + } + cache.latest_shell_id + }; + + let (group, shell_id) = Self::get_group_impl( + &self.0.non_driver, + &self.0.meta_client, + group_id, + group_shell_id, + from, + Some(&latest_shell_id), + Some(&self.0.group_desc), + ) + .await?; + + Self::mount_shell( + &self.0.shell_state, + &self.0.state_path, + &shell_id, + group.version(), + &Some(latest_shell_id), + group_shell_id.is_none(), + ) + .await?; + + { + let mut cache = self.0.cache.write().await; + let cached_group = Arc::new(group.clone()); + if cache + .groups_by_shell + .insert(shell_id, cached_group.clone()) + .is_none() + { + cache + .groups_by_version + .insert(group.version(), cached_group.clone()); + } + + if group_shell_id.is_none() && cache.latest_shell_id == latest_shell_id { + cache.latest_shell_id = shell_id; + } + } + + Ok(group) + } + + async fn mount_shell( + shell_state: &ObjectMapRootManagerRef, + state_path: &GroupShellStatePath, + shell_id: &ObjectId, + version: u64, + prev_latest_shell_id: &Option, + is_latest: bool, + ) -> BuckyResult<()> { + let op_env = shell_state.create_op_env(ACCESS)?; + + let version_path = state_path.version(version); + op_env + .set_with_path(version_path.as_str(), shell_id, &None, true) + .await?; + + if is_latest { + match prev_latest_shell_id { + Some(prev_latest_shell_id) => { + if prev_latest_shell_id != shell_id { + op_env + .set_with_path( + state_path.latest(), + shell_id, + &Some(*prev_latest_shell_id), + false, + ) + .await?; + } + } + None => { + op_env + .insert_with_path(state_path.latest(), shell_id) + .await?; + } + } + } + + op_env.commit().await.map(|_| ()) + } + + async fn get_group_impl( + non_driver: &NONDriverHelper, + meta_client: &MetaClient, + group_id: &ObjectId, + group_shell_id: Option<&ObjectId>, + from: Option<&ObjectId>, + latest_group_shell_id: Option<&ObjectId>, + group_desc: Option<&GroupDesc>, + ) -> BuckyResult<(Group, ObjectId)> { + match group_shell_id { + Some(group_shell_id) => { + let shell = non_driver.get_object(group_shell_id, from).await?; + let (group_shell, remain) = GroupShell::raw_decode(shell.object_raw.as_slice())?; + assert_eq!(remain.len(), 0); + let group = if !group_shell.with_full_desc() { + match group_desc { + Some(group_desc) => group_shell.try_into_object(Some(group_desc))?, + None => { + let group = non_driver.get_object(group_id, from).await?; + let (group, _remain) = Group::raw_decode(group.object_raw.as_slice())?; + group_shell.try_into_object(Some(group.desc()))? + } + } + } else { + group_shell.try_into_object(None)? + }; + + let body_hash = group.body().as_ref().unwrap().calculate_hash()?; + // TODO: 用`body_hash`从链上验证其合法性 + let group_id_from_shell = group.desc().object_id(); + if &group_id_from_shell == group_id { + Ok((group, group_shell_id.clone())) + } else { + let msg = format!( + "groupid({}) from GroupShell unmatch with the original group({})", + group_id_from_shell, group_id + ); + log::warn!("{}", msg); + Err(BuckyError::new(BuckyErrorCode::Unmatch, msg)) + } + } + None => { + let group = meta_client.get_desc(group_id).await?; + if let SavedMetaObject::Group(group) = group { + let group_shell = group.to_shell(); + let shell_id = group_shell.shell_id(); + if latest_group_shell_id != Some(&shell_id) { + // put to noc + let buf = group_shell.to_vec()?; + let shell_any = Arc::new(AnyNamedObject::Core( + TypelessCoreObject::clone_from_slice(buf.as_slice()).unwrap(), + )); + let shell_obj = + NONObjectInfo::new(shell_id, group_shell.to_vec()?, Some(shell_any)); + non_driver.put_object(shell_obj).await?; + } + + Ok((group, shell_id)) + } else { + let msg = format!("Object({}) from MetaChain is not a group", group_id); + log::warn!("{}", msg); + Err(BuckyError::new(BuckyErrorCode::Unmatch, msg)) + } + } + } + } + + fn shell_dec_id(group_id: &ObjectId) -> ObjectId { + DecApp::generate_id(group_id.clone(), "shell") + } +} diff --git a/src/component/cyfs-group/src/storage/group_storage.rs b/src/component/cyfs-group/src/storage/group_storage.rs new file mode 100644 index 000000000..371986cdc --- /dev/null +++ b/src/component/cyfs-group/src/storage/group_storage.rs @@ -0,0 +1,868 @@ +use std::collections::{HashMap, HashSet}; + +use cyfs_base::{ + BuckyError, BuckyErrorCode, BuckyResult, NamedObject, ObjectDesc, ObjectId, ObjectMap, + ObjectMapOpEnvMemoryCache, ObjectTypeCode, RawConvertTo, RawDecode, +}; + +use cyfs_core::{ + GroupConsensusBlock, GroupConsensusBlockObject, GroupQuorumCertificate, HotstuffBlockQC, + HotstuffTimeout, +}; +use cyfs_group_lib::GroupRPathStatus; +use cyfs_lib::{GlobalStateManagerRawProcessorRef, NONObjectInfo}; + +use crate::{ + storage::StorageWriter, GroupObjectMapProcessor, GroupStatePath, NONDriverHelper, + PROPOSAL_MAX_TIMEOUT, STATE_PATH_SEPARATOR, +}; + +use super::{ + engine::{ + GroupObjectMapProcessorGroupState, StorageCacheInfo, StorageEngineGroupState, + StorageEngineMock, + }, + StorageEngine, +}; + +const PROPOSAL_MAX_TIMEOUT_AS_MICRO_SEC: u64 = PROPOSAL_MAX_TIMEOUT.as_micros() as u64; + +pub enum BlockLinkState { + Expired, + Duplicate, + Link(Option), // + Pending, + InvalidBranch, +} + +pub struct GroupStorage { + group_id: ObjectId, + dec_id: ObjectId, + rpath: String, + local_device_id: ObjectId, + non_driver: NONDriverHelper, + + cache: StorageCacheInfo, + + storage_engine: StorageEngineGroupState, + object_map_processor: GroupObjectMapProcessorGroupState, +} + +impl GroupStorage { + pub(crate) async fn create( + group_id: &ObjectId, + dec_id: &ObjectId, + rpath: &str, + non_driver: NONDriverHelper, + local_device_id: ObjectId, + root_state_mgr: &GlobalStateManagerRawProcessorRef, + ) -> BuckyResult { + let group_state = root_state_mgr + .load_root_state(group_id, Some(group_id.clone()), true) + .await? + .expect("create group state failed."); + + let dec_group_state = group_state.get_dec_root_manager(dec_id, true).await?; + let object_map_processor = GroupObjectMapProcessorGroupState::new(&dec_group_state); + + Ok(Self { + group_id: group_id.clone(), + dec_id: dec_id.clone(), + rpath: rpath.to_string(), + non_driver, + storage_engine: StorageEngineGroupState::new( + dec_group_state, + GroupStatePath::new(rpath.to_string()), + group_id.clone(), + dec_id.clone(), + ), + local_device_id, + cache: StorageCacheInfo::new(None), + object_map_processor, + }) + } + + pub(crate) async fn load( + group_id: &ObjectId, + dec_id: &ObjectId, + rpath: &str, + non_driver: NONDriverHelper, + local_device_id: ObjectId, + root_state_mgr: &GlobalStateManagerRawProcessorRef, + ) -> BuckyResult { + // 用hash加载chunk + // 从chunk解析group + + let group_state = root_state_mgr + .load_root_state(group_id, Some(group_id.clone()), true) + .await + .map_err(|err| { + log::warn!("load root state for group {} failed {:?}", group_id, err); + err + })? + .expect("create group state failed."); + + let dec_group_state = group_state + .get_dec_root_manager(dec_id, true) + .await + .map_err(|err| { + log::warn!("get root state manager for dec {} failed {:?}", dec_id, err); + err + })?; + + let state_path = GroupStatePath::new(rpath.to_string()); + let cache = + StorageEngineGroupState::load_cache(&dec_group_state, &non_driver, &state_path).await?; + let object_map_processor = GroupObjectMapProcessorGroupState::new(&dec_group_state); + + Ok(Self { + group_id: group_id.clone(), + dec_id: dec_id.clone(), + rpath: rpath.to_string(), + non_driver, + storage_engine: StorageEngineGroupState::new( + dec_group_state, + state_path, + group_id.clone(), + dec_id.clone(), + ), + local_device_id, + cache, + object_map_processor, + }) + } + + pub fn header_block(&self) -> &Option { + &self.cache.header_block + } + + pub fn header_round(&self) -> u64 { + self.cache.header_block.as_ref().map_or(0, |b| b.round()) + } + + pub fn header_height(&self) -> u64 { + self.cache.header_block.as_ref().map_or(0, |b| b.height()) + } + + pub fn max_height(&self) -> u64 { + let mut max_height = self.header_height(); + if self.cache.prepares.len() > 0 { + max_height += 1; + } + if self.cache.pre_commits.len() > 0 { + max_height += 1; + } + max_height + } + + pub fn first_block(&self) -> &Option { + &self.cache.first_block + } + + pub fn prepares(&self) -> &HashMap { + &self.cache.prepares + } + + pub fn pre_commits(&self) -> &HashMap { + &self.cache.pre_commits + } + + pub fn dec_state_id(&self) -> &Option { + &self.cache.dec_state_id + } + + pub async fn get_block_by_height(&self, height: u64) -> BuckyResult { + let header_height = self.header_height(); + let block = match height.cmp(&header_height) { + std::cmp::Ordering::Less => { + if height == self.cache.first_block.as_ref().map_or(0, |b| b.height()) { + self.cache.first_block.clone() + } else { + // find in storage + let block_id = self.storage_engine.find_block_by_height(height).await?; + Some(self.non_driver.get_block(&block_id, None).await?) + } + } + std::cmp::Ordering::Equal => self.cache.header_block.clone(), + std::cmp::Ordering::Greater => { + if height == header_height + 1 { + self.cache + .pre_commits + .iter() + .find(|(_, block)| block.height() == height) + .or(self + .cache + .prepares + .iter() + .find(|(_, block)| block.height() == height)) + .map(|(_, block)| block.clone()) + } else if height == header_height + 2 { + self.cache + .prepares + .iter() + .find(|(_, block)| block.height() == height) + .map(|(_, block)| block.clone()) + } else { + None + } + } + }; + + block.ok_or(BuckyError::new(BuckyErrorCode::NotFound, "not found")) + } + + pub async fn push_block( + &mut self, + block: GroupConsensusBlock, + ) -> BuckyResult< + Option<( + &GroupConsensusBlock, + Option, + Vec, + )>, + > { + let header_height = self.header_height(); + assert!(block.height() > header_height && block.height() <= header_height + 3); + + let block_id = block.block_id(); + let prev_block_id = block.prev_block_id(); + + let mut remove_prepares = vec![]; + let mut new_pre_commit = None; + let mut new_header = None; + + // prepare update memory + if let Some(prev_block_id) = prev_block_id { + if let Some(prev_block) = self.cache.prepares.get(prev_block_id) { + new_pre_commit = Some((prev_block_id.clone(), prev_block.clone())); + + if let Some(prev_prev_block_id) = prev_block.prev_block_id() { + if let Some(prev_prev_block) = self.cache.pre_commits.get(prev_prev_block_id) { + assert_eq!(block.height(), header_height + 3); + assert_eq!(prev_prev_block.height(), header_height + 1); + assert_eq!( + prev_prev_block.prev_block_id(), + self.cache + .header_block + .as_ref() + .map(|b| b.block_id().object_id().clone()) + .as_ref() + ); + + new_header = Some(prev_prev_block.clone()); + let new_header_id = prev_prev_block.block_id().object_id(); + + for (id, block) in self.cache.prepares.iter() { + if block.prev_block_id().map(|prev_id| { + assert_ne!(prev_id, prev_block_id); + prev_id == new_header_id + }) != Some(true) + && id != prev_block_id + { + remove_prepares.push(id.clone()); + } + } + } else { + assert_eq!(block.height(), header_height + 2); + } + } + } else { + assert_ne!(block.height(), header_height + 3); + } + } + + /** + * 1. 把block存入prepares + * 2. 把block.qc.block从prepares存入pre-commits + * 3. 把block.qc.block.qc.block从pre-commits存入链上 + * 4. 把其他分叉block清理掉 + * 5. 追加去重proposal, 注意翻页清理过期proposal + * 6. 如果header有变更,返回新的header和被清理的分叉blocks + */ + // storage + let mut writer = self.storage_engine.create_writer().await?; + writer + .insert_prepares(block_id.object_id(), block.result_state_id()) + .await?; + if let Some((new_pre_commit_id, new_pre_commit)) = new_pre_commit.as_ref() { + writer + .insert_pre_commit( + new_pre_commit_id, + new_pre_commit.result_state_id(), + new_header.is_some(), + ) + .await?; + } + if let Some(new_header) = new_header.as_ref() { + writer + .push_commit( + new_header.height(), + new_header.block_id().object_id(), + new_header.result_state_id(), + self.cache + .header_block + .as_ref() + .map_or(&None, |b| b.result_state_id()), + self.cache.first_block.as_ref().map_or(0, |b| b.height()), + ) + .await?; + + writer.remove_prepares(remove_prepares.as_slice()).await?; + + if new_header.proposals().len() > 0 { + let finish_proposals: Vec = new_header + .proposals() + .iter() + .map(|p| p.proposal.clone()) + .collect(); + + let timestamp = new_header.named_object().desc().create_time(); + // log::debug!( + // "push proposals storage flip-time from {} to {}", + // self.cache.finish_proposals.flip_timestamp, + // timestamp + // ); + if timestamp - self.cache.finish_proposals.flip_timestamp + > PROPOSAL_MAX_TIMEOUT_AS_MICRO_SEC + { + writer + .push_proposals( + finish_proposals.as_slice(), + Some((timestamp, self.cache.finish_proposals.flip_timestamp)), + ) + .await?; + } else { + writer + .push_proposals(finish_proposals.as_slice(), None) + .await?; + } + } + } + + writer.commit().await?; + + // update memory + if self + .cache + .prepares + .insert(block_id.object_id().clone(), block) + .is_some() + { + assert!(false); + } + + match new_header { + Some(new_header) => { + self.cache.dec_state_id = new_header.result_state_id().clone(); + + let new_pre_commit = new_pre_commit.expect("shoud got new pre-commit block"); + self.cache.prepares.remove(&new_pre_commit.0); + + let mut removed_blocks = HashMap::from([new_pre_commit]); + + std::mem::swap(&mut self.cache.pre_commits, &mut removed_blocks); + let mut removed_blocks: Vec = + removed_blocks.into_values().collect(); + + for id in remove_prepares.iter() { + removed_blocks.push(self.cache.prepares.remove(id).unwrap()); + } + + if self.cache.first_block.is_none() { + self.cache.first_block = Some(new_header.clone()); + } + + if new_header.proposals().len() > 0 { + let timestamp = new_header.named_object().desc().create_time(); + + // log::debug!( + // "push proposals flip-time from {} to {}", + // self.cache.finish_proposals.flip_timestamp, + // timestamp + // ); + + if timestamp - self.cache.finish_proposals.flip_timestamp + > PROPOSAL_MAX_TIMEOUT_AS_MICRO_SEC + { + let mut new_over = HashSet::new(); + std::mem::swap(&mut new_over, &mut self.cache.finish_proposals.adding); + std::mem::swap(&mut new_over, &mut self.cache.finish_proposals.over); + self.cache.finish_proposals.flip_timestamp = timestamp; + } + + for proposal in new_header.proposals() { + let is_new = self.cache.finish_proposals.adding.insert(proposal.proposal); + assert!(is_new); + } + } + + let old_header_block = self.cache.header_block.replace(new_header); + return Ok(Some(( + self.cache.header_block.as_ref().unwrap(), + old_header_block, + removed_blocks, + ))); + } + None => { + if let Some(new_pre_commit) = new_pre_commit { + assert!(remove_prepares.is_empty()); + + if self + .cache + .pre_commits + .insert(new_pre_commit.0, new_pre_commit.1) + .is_some() + { + assert!(false); + } + self.cache + .prepares + .remove(&new_pre_commit.0) + .expect("any block in pre-commit should be from prepare"); + } + } + } + + Ok(None) + } + + pub fn last_vote_round(&self) -> u64 { + self.cache.last_vote_round + } + + pub async fn set_last_vote_round(&mut self, round: u64) -> BuckyResult<()> { + if round <= self.cache.last_vote_round { + return Ok(()); + } + + // storage + let mut writer = self.storage_engine.create_writer().await?; + writer + .set_last_vote_round(round, self.cache.last_vote_round) + .await?; + writer.commit().await?; + + self.cache.last_vote_round = round; + + Ok(()) + } + + pub fn last_qc(&self) -> &Option { + &self.cache.last_qc + } + + pub async fn save_qc(&mut self, qc: &HotstuffBlockQC) -> BuckyResult<()> { + let quorum_round = qc.round; + if quorum_round < self.cache.last_vote_round + || quorum_round <= self.cache.last_qc.as_ref().map_or(0, |qc| qc.round) + { + return Ok(()); + } + + let qc = GroupQuorumCertificate::from(qc.clone()); + self.non_driver.put_qc(&qc).await?; + + let mut writer = self.storage_engine.create_writer().await?; + writer.save_last_qc(&qc.desc().object_id()).await?; + writer.commit().await?; + + self.cache.last_qc = Some(qc.try_into().unwrap()); + Ok(()) + } + + pub fn last_tc(&self) -> &Option { + &self.cache.last_tc + } + + pub async fn save_tc( + &mut self, + tc: &HotstuffTimeout, + group_shell_id: ObjectId, + ) -> BuckyResult<()> { + let quorum_round = tc.round; + if quorum_round < self.cache.last_vote_round + || quorum_round <= self.cache.last_tc.as_ref().map_or(0, |tc| tc.round) + { + return Ok(()); + } + + let mut tc = tc.clone(); + if tc.group_shell_id.is_none() { + tc.group_shell_id = Some(group_shell_id); + } + let tc = GroupQuorumCertificate::from(tc); + self.non_driver.put_qc(&tc).await?; + + let mut writer = self.storage_engine.create_writer().await?; + writer.save_last_tc(&tc.desc().object_id()).await?; + writer.commit().await?; + + self.cache.last_tc = Some(tc.try_into().unwrap()); + Ok(()) + } + + pub async fn block_linked(&self, block: &GroupConsensusBlock) -> BuckyResult { + log::debug!( + "[group storage] {} block_linked {} step1", + self.local_device_id, + block.block_id() + ); + + let header_height = self.header_height(); + let header_round = self.header_round(); + let qc_round = block.qc().as_ref().map_or(0, |q| q.round); + if block.height() <= header_height + || block.round() <= header_round + || qc_round < header_round + { + return Ok(BlockLinkState::Expired); + } + + if block.height() > header_height + 3 { + return Ok(BlockLinkState::Pending); + }; + + // BlockLinkState::Link状态也可能因为缺少前序成为BlockLinkState::Pending + // 去重proposal,BlockLinkState::DuplicateProposal,去重只检查相同分叉链上的proposal,不同分叉上允许有相同proposal + // 检查Proposal时间戳,早于去重proposal集合区间,或者晚于当前系统时间戳一定时间 + + let block_id = block.block_id(); + + if self.find_block_in_cache(block_id.object_id()).is_ok() { + return Ok(BlockLinkState::Duplicate); + } + + log::debug!( + "[group storage] {} block_linked {} step2", + self.local_device_id, + block.block_id() + ); + + let prev_block = match block.prev_block_id() { + Some(prev_block_id) => match self.find_block_in_cache(prev_block_id) { + Ok(prev_block) => { + if prev_block.height() + 1 != block.height() { + return Err(BuckyError::new(BuckyErrorCode::Failed, "height error")); + } else if prev_block.round() >= block.round() { + return Err(BuckyError::new(BuckyErrorCode::Failed, "qc round to large")); + } else { + Some(prev_block) + } + } + Err(_) => { + if block.height() == header_height + 1 { + return Ok(BlockLinkState::InvalidBranch); + } + return Ok(BlockLinkState::Pending); + } + }, + None => { + if block.height() != 1 { + return Err(BuckyError::new(BuckyErrorCode::Failed, "height error")); + } else if header_height != 0 { + return Ok(BlockLinkState::InvalidBranch); + } else { + None + } + } + }; + + if prev_block.as_ref().map_or(0, |b| b.round()) + != block.qc().as_ref().map_or(0, |q| q.round) + { + return Err(BuckyError::new(BuckyErrorCode::Failed, "qc round error")); + } + + log::debug!( + "[group storage] {} block_linked {} step3", + self.local_device_id, + block.block_id() + ); + + Ok(BlockLinkState::Link(prev_block)) + } + + pub fn find_block_in_cache(&self, block_id: &ObjectId) -> BuckyResult { + if let Some(block) = self.cache.header_block.as_ref() { + if block.block_id().object_id() == block_id { + return Ok(block.clone()); + } + } + + self.cache + .prepares + .get(block_id) + .or(self.cache.pre_commits.get(block_id)) + .ok_or(BuckyError::new(BuckyErrorCode::NotFound, "not found")) + .map(|block| block.clone()) + } + + pub fn find_block_in_cache_by_round(&self, round: u64) -> BuckyResult { + let header_round = self.header_round(); + + let found = match round.cmp(&header_round) { + std::cmp::Ordering::Less => { + return Err(BuckyError::new(BuckyErrorCode::NotFound, "not found")) + } + std::cmp::Ordering::Equal => self.cache.header_block.as_ref(), + std::cmp::Ordering::Greater => if round == header_round + 1 { + self.cache + .pre_commits + .iter() + .find(|(_, block)| block.round() == round) + .or(self + .cache + .prepares + .iter() + .find(|(_, block)| block.round() == round)) + } else { + self.cache + .prepares + .iter() + .find(|(_, block)| block.round() == round) + .or(self + .cache + .pre_commits + .iter() + .find(|(_, block)| block.round() == round)) + } + .map(|(_, block)| block), + }; + + found + .ok_or(BuckyError::new(BuckyErrorCode::NotFound, "not found")) + .map(|block| block.clone()) + } + + pub async fn is_proposal_finished( + &self, + proposal_id: &ObjectId, + prev_block_id: &ObjectId, + ) -> BuckyResult { + let prev_block = self.find_block_in_cache(prev_block_id); + + // find in cache + if let Ok(prev_block) = prev_block.as_ref() { + match prev_block + .proposals() + .iter() + .find(|proposal| &proposal.proposal == proposal_id) + { + Some(_) => return Ok(true), + None => { + if let Some(prev_prev_block_id) = prev_block.prev_block_id() { + let prev_prev_block = self.find_block_in_cache(prev_prev_block_id); + if let Ok(prev_prev_block) = prev_prev_block.as_ref() { + if prev_prev_block + .proposals() + .iter() + .find(|proposal| &proposal.proposal == proposal_id) + .is_some() + { + return Ok(true); + } + } + } + } + } + } + + // find in storage + + let is_finished = self + .cache + .finish_proposals + .adding + .get(proposal_id) + .or(self.cache.finish_proposals.over.get(proposal_id)) + .is_some(); + Ok(is_finished) + } + + pub fn max_round(&self) -> u64 { + self.block_with_max_round().map_or(0, |b| b.round()) + } + + pub fn block_with_max_round(&self) -> Option { + let mut max_round = 0; + let mut max_block = None; + for block in self.prepares().values() { + if block.round() > max_round { + max_round = block.round(); + max_block = Some(block); + } + } + + for block in self.pre_commits().values() { + if block.round() > max_round { + max_round = block.round(); + max_block = Some(block); + } + } + max_block.map(|block| block.clone()) + } + + // (found_block, cached_blocks) + pub async fn find_block_by_round( + &self, + round: u64, + ) -> (BuckyResult, Vec) { + if self.cache.header_block.is_none() { + return ( + Err(BuckyError::new(BuckyErrorCode::NotFound, "not exist")), + vec![], + ); + } + + let mut blocks = vec![]; + let mut block = self.cache.header_block.clone().unwrap(); + let mut min_height = 1; + let mut min_round = 1; + let mut max_height = block.height(); + let mut max_round = block.round(); + + while min_height < max_height { + blocks.push(block.clone()); + match block.round().cmp(&round) { + std::cmp::Ordering::Equal => { + return (Ok(block), blocks); + } + std::cmp::Ordering::Less => { + min_round = block.round() + 1; + min_height = block.height() + 1; + } + std::cmp::Ordering::Greater => { + max_round = block.round() - 1; + max_height = block.height() - 1; + } + } + + let height = min_height + + (round - min_round) * (max_height - min_height) / (max_round - min_round); + + block = match self.get_block_by_height(height).await { + Ok(block) => block, + Err(e) => return (Err(e), blocks), + } + } + + if block.round() == round { + (Ok(block), blocks) + } else { + ( + Err(BuckyError::new(BuckyErrorCode::NotFound, "not exist")), + blocks, + ) + } + } + + pub async fn get_by_path(&self, sub_path: &str) -> BuckyResult { + // TODO: 需要一套通用的同步ObjectMap树的实现 + let (header_block, qc) = match self.cache.header_block.as_ref() { + Some(block) => { + let (_, qc_block) = self + .cache + .pre_commits + .iter() + .next() + .expect("pre-commit should not be empty"); + + assert_eq!( + qc_block.prev_block_id().unwrap(), + block.block_id().object_id(), + "the prev-block for all pre-commits should be the header" + ); + + (block, qc_block.qc().as_ref().unwrap()) + } + None => { + return Err(BuckyError::new( + BuckyErrorCode::NotFound, + "the header block is none", + )); + } + }; + + let mut parent_state_id = match header_block.result_state_id() { + Some(state_id) => state_id.clone(), + None => { + return Ok(GroupRPathStatus { + block_desc: header_block.named_object().desc().clone(), + certificate: qc.clone(), + status_map: HashMap::new(), + }) + } + }; + + let mut status_map = HashMap::new(); + + let root_cache = self.storage_engine.root_cache(); + let cache = ObjectMapOpEnvMemoryCache::new_ref(root_cache.clone()); + + for folder in sub_path.split(STATE_PATH_SEPARATOR) { + if folder.len() == 0 { + continue; + } + let parent_state = self.non_driver.get_object(&parent_state_id, None).await?; + + if ObjectTypeCode::ObjectMap != parent_state.object().obj_type_code() { + let msg = format!( + "unmatch object type at path {} in folder {}, expect: ObjectMap, got: {:?}", + sub_path, + folder, + parent_state.object().obj_type_code() + ); + log::warn!("{}", msg); + return Err(BuckyError::new(BuckyErrorCode::Unmatch, msg)); + } + + let (parent, remain) = ObjectMap::raw_decode(parent_state.object_raw.as_slice()) + .map_err(|err| { + let msg = format!( + "decode failed at path {} in folder {}, {:?}", + sub_path, folder, err + ); + log::warn!("{}", msg); + BuckyError::new(err.code(), msg) + })?; + + assert_eq!(remain.len(), 0); + + status_map.insert(parent_state_id, parent_state); + + let sub_map_id = parent.get_by_key(&cache, folder).await?; + log::debug!("get sub-folder {} result: {:?}", folder, sub_map_id); + + match sub_map_id { + Some(sub_map_id) => { + // for next folder + parent_state_id = sub_map_id; + } + None => { + return Ok(GroupRPathStatus { + block_desc: header_block.named_object().desc().clone(), + certificate: qc.clone(), + status_map, + }); + } + } + } + + let leaf_state = if parent_state_id.is_data() { + NONObjectInfo::new(parent_state_id, parent_state_id.to_vec()?, None) + } else { + self.non_driver.get_object(&parent_state_id, None).await? + }; + status_map.insert(parent_state_id, leaf_state); + + return Ok(GroupRPathStatus { + block_desc: header_block.named_object().desc().clone(), + certificate: qc.clone(), + status_map, + }); + } + + pub fn get_object_map_processor(&self) -> &dyn GroupObjectMapProcessor { + &self.object_map_processor + } +} diff --git a/src/component/cyfs-group/src/storage/mod.rs b/src/component/cyfs-group/src/storage/mod.rs new file mode 100644 index 000000000..f64a9311d --- /dev/null +++ b/src/component/cyfs-group/src/storage/mod.rs @@ -0,0 +1,9 @@ +mod dec_storage; +mod engine; +mod group_shell_mgr; +mod group_storage; + +pub use dec_storage::*; +use engine::*; +pub use group_shell_mgr::*; +pub use group_storage::*; diff --git a/src/component/cyfs-lib/src/non/output_request.rs b/src/component/cyfs-lib/src/non/output_request.rs index d4950ce33..b6e96dcb3 100644 --- a/src/component/cyfs-lib/src/non/output_request.rs +++ b/src/component/cyfs-lib/src/non/output_request.rs @@ -4,7 +4,7 @@ use cyfs_base::*; use std::fmt; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct NONOutputRequestCommon { // 请求路径,可为空 pub req_path: Option, @@ -130,14 +130,22 @@ impl NONUpdateObjectMetaOutputRequest { Self::new(NONAPILevel::NOC, object_id, access) } - pub fn new_non(target: Option, object_id: ObjectId, access: Option) -> Self { + pub fn new_non( + target: Option, + object_id: ObjectId, + access: Option, + ) -> Self { let mut ret = Self::new(NONAPILevel::NON, object_id, access); ret.common.target = target.map(|v| v.into()); ret } - pub fn new_router(target: Option, object_id: ObjectId, access: Option) -> Self { + pub fn new_router( + target: Option, + object_id: ObjectId, + access: Option, + ) -> Self { let mut ret = Self::new(NONAPILevel::Router, object_id, access); ret.common.target = target; @@ -243,7 +251,6 @@ impl NONGetObjectOutputRequest { } } - impl fmt::Display for NONGetObjectOutputRequest { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "common: {}", self.common)?; @@ -338,7 +345,6 @@ impl fmt::Display for NONPostObjectOutputResponse { } } - // select #[derive(Clone)] pub struct NONSelectObjectOutputRequest { @@ -392,7 +398,6 @@ impl fmt::Display for NONSelectObjectOutputRequest { } } - #[derive(Clone)] pub struct NONSelectObjectOutputResponse { pub objects: Vec, @@ -470,7 +475,6 @@ impl fmt::Display for NONDeleteObjectOutputRequest { } } - #[derive(Clone)] pub struct NONDeleteObjectOutputResponse { pub object: Option, @@ -479,7 +483,7 @@ pub struct NONDeleteObjectOutputResponse { impl fmt::Display for NONDeleteObjectOutputResponse { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "object: {:?}", self.object)?; - + Ok(()) } } diff --git a/src/component/cyfs-lib/src/stack/stack.rs b/src/component/cyfs-lib/src/stack/stack.rs index 2f162c331..05cd8dfd7 100644 --- a/src/component/cyfs-lib/src/stack/stack.rs +++ b/src/component/cyfs-lib/src/stack/stack.rs @@ -94,7 +94,7 @@ pub struct SharedCyfsStack { // uni_stack uni_stack: Arc>, - requestor_holder: RequestorHolder, + requestor_holder: Arc>, } #[derive(Debug, Clone)] @@ -493,14 +493,15 @@ impl SharedCyfsStack { device_info: Arc::new(RwLock::new(None)), uni_stack: Arc::new(OnceCell::new()), - requestor_holder, + requestor_holder: Arc::new(RwLock::new(requestor_holder)), }; Ok(ret) } pub async fn stop(&self) { - self.requestor_holder.stop().await; + let requestor_holder = self.requestor_holder.read().unwrap(); + requestor_holder.stop().await; self.router_handlers.stop().await; @@ -744,6 +745,11 @@ impl SharedCyfsStack { pub fn uni_stack(&self) -> &UniCyfsStackRef { self.uni_stack.get_or_init(|| self.create_uni_stack()) } + + pub fn select_requestor(&self, requestor_type: &CyfsStackRequestorType) -> HttpRequestorRef { + let mut requestor_holder = self.requestor_holder.write().unwrap(); + requestor_holder.select_requestor(&self.param, requestor_type) + } } impl UniCyfsStack for SharedCyfsStack { diff --git a/src/component/cyfs-stack/Cargo.toml b/src/component/cyfs-stack/Cargo.toml index 52f9fca12..ddbc4076d 100644 --- a/src/component/cyfs-stack/Cargo.toml +++ b/src/component/cyfs-stack/Cargo.toml @@ -15,6 +15,8 @@ cyfs-bdt = { path = "../../component/cyfs-bdt" } cyfs-bdt-ext = { path = "../../component/cyfs-bdt-ext" } cyfs-base = { path = "../../component/cyfs-base" } cyfs-core = { path = "../../component/cyfs-core" } +cyfs-group = { path = "../../component/cyfs-group" } +cyfs-group-lib = { path = "../../component/cyfs-group-lib" } cyfs-debug = { path = "../../component/cyfs-debug" } cyfs-lib = { path = "../../component/cyfs-lib" } cyfs-chunk-lib = { path = "../../component/cyfs-chunk-lib" } diff --git a/src/component/cyfs-stack/src/forward/forward.rs b/src/component/cyfs-stack/src/forward/forward.rs index 11f6cdecb..a58a60802 100644 --- a/src/component/cyfs-stack/src/forward/forward.rs +++ b/src/component/cyfs-stack/src/forward/forward.rs @@ -170,6 +170,11 @@ impl ForwardRequestorContainer { } fn cacl_next_timeout_on_error(&self, error_count: u32) -> u64 { + log::debug!( + "cacl_next_timeout_on_error: min-interval {}, count {}.", + self.error_cache_min_interval, + error_count + ); let mut ret = self.error_cache_min_interval.pow(error_count + 1); if ret > self.error_cache_max_interval { ret = self.error_cache_max_interval; diff --git a/src/component/cyfs-stack/src/front/protocol.rs b/src/component/cyfs-stack/src/front/protocol.rs index fa9ed7597..1fe110bd8 100644 --- a/src/component/cyfs-stack/src/front/protocol.rs +++ b/src/component/cyfs-stack/src/front/protocol.rs @@ -768,7 +768,7 @@ impl FrontProtocolHandler { match seg_object.obj_type_code() { ObjectTypeCode::Device | ObjectTypeCode::People - | ObjectTypeCode::SimpleGroup => { + | ObjectTypeCode::Group => { // treat as two seg mode target = Some(seg_object); target_dec_id = Self::parse_dec_seg(url, &segs, 1)?; diff --git a/src/component/cyfs-stack/src/group/mod.rs b/src/component/cyfs-stack/src/group/mod.rs new file mode 100644 index 000000000..da7603e40 --- /dev/null +++ b/src/component/cyfs-stack/src/group/mod.rs @@ -0,0 +1,5 @@ +mod processor; +mod transform; + +pub(crate) use processor::*; +pub(crate) use transform::*; \ No newline at end of file diff --git a/src/component/cyfs-stack/src/group/processor.rs b/src/component/cyfs-stack/src/group/processor.rs new file mode 100644 index 000000000..bab6076a5 --- /dev/null +++ b/src/component/cyfs-stack/src/group/processor.rs @@ -0,0 +1,22 @@ +use cyfs_base::*; +use cyfs_group_lib::{ + GroupPushProposalInputRequest, GroupPushProposalInputResponse, GroupStartServiceInputRequest, + GroupStartServiceInputResponse, +}; + +use std::sync::Arc; + +#[async_trait::async_trait] +pub(crate) trait GroupInputProcessor: Sync + Send { + async fn start_service( + &self, + req: GroupStartServiceInputRequest, + ) -> BuckyResult; + + async fn push_proposal( + &self, + req: GroupPushProposalInputRequest, + ) -> BuckyResult; +} + +pub(crate) type GroupInputProcessorRef = Arc; diff --git a/src/component/cyfs-stack/src/group/transform.rs b/src/component/cyfs-stack/src/group/transform.rs new file mode 100644 index 000000000..7b8f5cfea --- /dev/null +++ b/src/component/cyfs-stack/src/group/transform.rs @@ -0,0 +1,161 @@ +use cyfs_base::*; +use cyfs_core::GroupProposal; +use cyfs_group_lib::{ + GroupInputRequestCommon, GroupOutputProcessor, GroupOutputProcessorRef, + GroupOutputRequestCommon, GroupPushProposalInputRequest, GroupPushProposalInputResponse, + GroupPushProposalOutputRequest, GroupPushProposalOutputResponse, GroupStartServiceInputRequest, + GroupStartServiceInputResponse, GroupStartServiceOutputRequest, + GroupStartServiceOutputResponse, +}; +use cyfs_lib::*; + +use std::sync::Arc; + +use super::{GroupInputProcessor, GroupInputProcessorRef}; + +// 实现从input到output的转换 +pub(crate) struct GroupInputTransformer { + processor: GroupOutputProcessorRef, +} + +impl GroupInputTransformer { + pub fn new(processor: GroupOutputProcessorRef) -> GroupInputProcessorRef { + let ret = Self { processor }; + Arc::new(ret) + } + + fn convert_common(common: GroupInputRequestCommon) -> GroupOutputRequestCommon { + GroupOutputRequestCommon { + dec_id: common.source.get_opt_dec().cloned(), + } + } + + async fn start_service( + &self, + req: GroupStartServiceInputRequest, + ) -> BuckyResult { + let out_req = GroupStartServiceOutputRequest { + group_id: req.group_id, + rpath: req.rpath, + common: Self::convert_common(req.common), + }; + + let out_resp = self.processor.start_service(out_req).await?; + + let resp = GroupStartServiceInputResponse {}; + + Ok(resp) + } + + async fn push_proposal( + &self, + req: GroupPushProposalInputRequest, + ) -> BuckyResult { + let out_req = GroupPushProposalOutputRequest { + proposal: req.proposal, + common: Self::convert_common(req.common), + }; + + let out_resp = self.processor.push_proposal(out_req).await?; + + let resp = GroupPushProposalInputResponse { + object: out_resp.object, + }; + + Ok(resp) + } +} + +#[async_trait::async_trait] +impl GroupInputProcessor for GroupInputTransformer { + async fn start_service( + &self, + req: GroupStartServiceInputRequest, + ) -> BuckyResult { + GroupInputTransformer::start_service(self, req).await + } + + async fn push_proposal( + &self, + req: GroupPushProposalInputRequest, + ) -> BuckyResult { + GroupInputTransformer::push_proposal(self, req).await + } +} + +// 实现从output到input的转换 +pub(crate) struct GroupOutputTransformer { + processor: GroupInputProcessorRef, + source: RequestSourceInfo, +} + +impl GroupOutputTransformer { + fn convert_common(&self, common: GroupOutputRequestCommon) -> GroupInputRequestCommon { + let mut source = self.source.clone(); + if let Some(dec_id) = common.dec_id { + source.set_dec(dec_id); + } + + GroupInputRequestCommon { source } + } + + pub fn new( + processor: GroupInputProcessorRef, + source: RequestSourceInfo, + ) -> GroupOutputProcessorRef { + let ret = Self { processor, source }; + Arc::new(Box::new(ret)) + } + + async fn push_proposal( + &self, + req: GroupPushProposalOutputRequest, + ) -> BuckyResult { + let in_req = GroupPushProposalInputRequest { + common: self.convert_common(req.common), + proposal: req.proposal, + }; + + let in_resp = self.processor.push_proposal(in_req).await?; + + let resp = GroupPushProposalOutputResponse { + object: in_resp.object, + }; + + Ok(resp) + } + + async fn start_service( + &self, + req: GroupStartServiceOutputRequest, + ) -> BuckyResult { + let in_req = GroupStartServiceInputRequest { + group_id: req.group_id, + rpath: req.rpath, + common: self.convert_common(req.common), + }; + + let in_resp = self.processor.start_service(in_req).await?; + + let resp = GroupStartServiceOutputResponse {}; + + Ok(resp) + } +} + +#[async_trait::async_trait] +impl GroupOutputProcessor for GroupOutputTransformer { + async fn start_service( + &self, + req: GroupStartServiceOutputRequest, + ) -> BuckyResult { + GroupOutputTransformer::start_service(self, req).await + } + + async fn push_proposal( + &self, + req: GroupPushProposalOutputRequest, + ) -> BuckyResult { + GroupOutputTransformer::push_proposal(self, req).await + } +} diff --git a/src/component/cyfs-stack/src/group_api/acl/group_acl.rs b/src/component/cyfs-stack/src/group_api/acl/group_acl.rs new file mode 100644 index 000000000..585394e6e --- /dev/null +++ b/src/component/cyfs-stack/src/group_api/acl/group_acl.rs @@ -0,0 +1,60 @@ +use crate::group::{GroupInputProcessor, GroupInputProcessorRef}; +use cyfs_base::*; +use cyfs_core::GroupProposal; +use cyfs_group_lib::{ + GroupInputRequestCommon, GroupPushProposalInputRequest, GroupPushProposalInputResponse, + GroupStartServiceInputRequest, GroupStartServiceInputResponse, +}; +use cyfs_lib::*; + +use std::sync::Arc; + +pub struct GroupAclInnerInputProcessor { + next: GroupInputProcessorRef, +} + +impl GroupAclInnerInputProcessor { + pub(crate) fn new(next: GroupInputProcessorRef) -> GroupInputProcessorRef { + Arc::new(Self { next }) + } + + fn check_local_zone_permit( + &self, + service: &str, + source: &RequestSourceInfo, + ) -> BuckyResult<()> { + // TODO + // if !source.is_current_zone() { + // let msg = format!( + // "{} service valid only in current zone! source={:?}, category={}", + // service, + // source.zone.device, + // source.zone.zone_category.as_str() + // ); + // error!("{}", msg); + + // return Err(BuckyError::new(BuckyErrorCode::PermissionDenied, msg)); + // } + + Ok(()) + } +} + +#[async_trait::async_trait] +impl GroupInputProcessor for GroupAclInnerInputProcessor { + async fn start_service( + &self, + req: GroupStartServiceInputRequest, + ) -> BuckyResult { + self.check_local_zone_permit("group.service", &req.common.source)?; + self.next.start_service(req).await + } + + async fn push_proposal( + &self, + req: GroupPushProposalInputRequest, + ) -> BuckyResult { + self.check_local_zone_permit("group.proposal", &req.common.source)?; + self.next.push_proposal(req).await + } +} diff --git a/src/component/cyfs-stack/src/group_api/acl/mod.rs b/src/component/cyfs-stack/src/group_api/acl/mod.rs new file mode 100644 index 000000000..53dad00b3 --- /dev/null +++ b/src/component/cyfs-stack/src/group_api/acl/mod.rs @@ -0,0 +1,3 @@ +mod group_acl; + +pub use group_acl::*; diff --git a/src/component/cyfs-stack/src/group_api/mod.rs b/src/component/cyfs-stack/src/group_api/mod.rs new file mode 100644 index 000000000..29e257330 --- /dev/null +++ b/src/component/cyfs-stack/src/group_api/mod.rs @@ -0,0 +1,7 @@ +mod acl; +mod router; +mod service; + +pub(crate) use acl::*; +pub(crate) use router::*; +pub(crate) use service::*; diff --git a/src/component/cyfs-stack/src/group_api/router/group_service_router.rs b/src/component/cyfs-stack/src/group_api/router/group_service_router.rs new file mode 100644 index 000000000..f9868ca2f --- /dev/null +++ b/src/component/cyfs-stack/src/group_api/router/group_service_router.rs @@ -0,0 +1,120 @@ +use std::sync::Arc; + +use cyfs_base::{BuckyResult, DeviceId, ObjectId}; +use cyfs_core::GroupProposal; +use cyfs_group_lib::{ + GroupInputRequestCommon, GroupPushProposalInputRequest, GroupPushProposalInputResponse, + GroupRequestor, GroupStartServiceInputRequest, GroupStartServiceInputResponse, +}; + +use crate::{ + forward::ForwardProcessorManager, + group::{GroupInputProcessor, GroupInputProcessorRef, GroupInputTransformer}, + group_api::GroupAclInnerInputProcessor, + ZoneManagerRef, +}; + +#[derive(Clone)] +pub struct GroupServiceRouter { + processor: GroupInputProcessorRef, + forward: ForwardProcessorManager, + zone_manager: ZoneManagerRef, +} + +impl GroupServiceRouter { + pub(crate) fn new( + forward: ForwardProcessorManager, + zone_manager: ZoneManagerRef, + processor: GroupInputProcessorRef, + ) -> GroupInputProcessorRef { + let processor = GroupAclInnerInputProcessor::new(processor); + let ret = Self { + processor, + zone_manager, + forward, + }; + Arc::new(ret) + } + + async fn get_forward( + &self, + dec_id: ObjectId, + target: DeviceId, + ) -> BuckyResult { + let requestor = self.forward.get(&target).await?; + let group_requestor = GroupRequestor::new(dec_id, requestor); + Ok(GroupInputTransformer::new( + group_requestor.clone_processor(), + )) + } + + // 不同于non/ndn的router,如果target为空,那么表示本地device + async fn get_target(&self, target: Option<&ObjectId>) -> BuckyResult> { + let ret = match target { + Some(object_id) => { + let info = self + .zone_manager + .target_zone_manager() + .resolve_target(Some(object_id)) + .await?; + if info.target_device == *self.zone_manager.get_current_device_id() { + None + } else { + Some(info.target_device) + } + } + None => None, + }; + + Ok(ret) + } + + async fn get_processor( + &self, + dec_id: ObjectId, + target: Option<&ObjectId>, + ) -> BuckyResult { + if let Some(device_id) = self.get_target(target).await? { + debug!("group target resolved: {:?} -> {}", target, device_id); + let processor = self.get_forward(dec_id, device_id).await?; + Ok(processor) + } else { + Ok(self.processor.clone()) + } + } + + pub async fn start_service( + &self, + req: GroupStartServiceInputRequest, + ) -> BuckyResult { + let processor = self.get_processor(req.common.source.dec, None).await?; + processor.start_service(req).await + } + + pub async fn push_proposal( + &self, + req: GroupPushProposalInputRequest, + ) -> BuckyResult { + let processor = self.get_processor(req.common.source.dec, None).await?; + processor.push_proposal(req).await + } +} + +#[async_trait::async_trait] +impl GroupInputProcessor for GroupServiceRouter { + async fn start_service( + &self, + req: GroupStartServiceInputRequest, + ) -> BuckyResult { + let processor = self.get_processor(req.common.source.dec, None).await?; + processor.start_service(req).await + } + + async fn push_proposal( + &self, + req: GroupPushProposalInputRequest, + ) -> BuckyResult { + let processor = self.get_processor(req.common.source.dec, None).await?; + processor.push_proposal(req).await + } +} diff --git a/src/component/cyfs-stack/src/group_api/router/local_service.rs b/src/component/cyfs-stack/src/group_api/router/local_service.rs new file mode 100644 index 000000000..56caa8ac0 --- /dev/null +++ b/src/component/cyfs-stack/src/group_api/router/local_service.rs @@ -0,0 +1,111 @@ +use std::sync::Arc; + +use cyfs_base::{BuckyError, BuckyErrorCode, BuckyResult, NamedObject, ObjectDesc}; +use cyfs_core::{GroupProposal, GroupProposalObject}; +use cyfs_group::GroupManager; +use cyfs_group_lib::{ + GroupInputRequestCommon, GroupPushProposalInputRequest, GroupPushProposalInputResponse, + GroupStartServiceInputRequest, GroupStartServiceInputResponse, +}; + +use crate::group::{GroupInputProcessor, GroupInputProcessorRef}; + +#[derive(Clone)] +pub(crate) struct LocalGroupService { + group_manager: GroupManager, +} + +impl LocalGroupService { + pub(crate) fn new(group_manager: GroupManager) -> Self { + Self { group_manager } + } + + pub fn clone_processor(&self) -> GroupInputProcessorRef { + Arc::new(self.clone()) + } +} + +#[async_trait::async_trait] +impl GroupInputProcessor for LocalGroupService { + async fn start_service( + &self, + req: GroupStartServiceInputRequest, + ) -> BuckyResult { + self.group_manager + .find_rpath_service( + &req.group_id, + &req.common.source.dec, + req.rpath.as_str(), + true, + ) + .await + .map(|_| GroupStartServiceInputResponse {}) + .map_err(|err| { + log::error!( + "group start service {}-{}-{} failed {:?}", + req.group_id, + req.common.source.dec, + req.rpath, + err + ); + err + }) + } + + async fn push_proposal( + &self, + req: GroupPushProposalInputRequest, + ) -> BuckyResult { + let proposal_id = req.proposal.desc().object_id(); + let rpath = req.proposal.rpath().clone(); + if &req.common.source.dec != rpath.dec_id() { + let msg = format!( + "group push proposal {}-{}-{} {} failed: the source dec({}) should be same as that in GroupProposal object", + rpath.group_id(), + rpath.dec_id(), + rpath.rpath(), + proposal_id, + req.common.source.dec + ); + log::error!("{}", msg); + return Err(BuckyError::new(BuckyErrorCode::Unmatch, msg)); + } + + let service = self + .group_manager + .find_rpath_service( + rpath.group_id(), + &req.common.source.dec, + rpath.rpath(), + true, + ) + .await + .map_err(|err| { + log::error!( + "group push proposal {}-{}-{} {} failed when find the service {:?}", + rpath.group_id(), + rpath.dec_id(), + rpath.rpath(), + proposal_id, + err + ); + err + })?; + + service + .push_proposal(req.proposal) + .await + .map(|object| GroupPushProposalInputResponse { object }) + .map_err(|err| { + log::error!( + "group push proposal {}-{}-{} {} failed {:?}", + rpath.group_id(), + rpath.dec_id(), + rpath.rpath(), + proposal_id, + err + ); + err + }) + } +} diff --git a/src/component/cyfs-stack/src/group_api/router/mod.rs b/src/component/cyfs-stack/src/group_api/router/mod.rs new file mode 100644 index 000000000..877b4eff6 --- /dev/null +++ b/src/component/cyfs-stack/src/group_api/router/mod.rs @@ -0,0 +1,5 @@ +mod group_service_router; +mod local_service; + +pub use group_service_router::*; +pub(crate) use local_service::*; diff --git a/src/component/cyfs-stack/src/group_api/service/group_handler.rs b/src/component/cyfs-stack/src/group_api/service/group_handler.rs new file mode 100644 index 000000000..017f401e0 --- /dev/null +++ b/src/component/cyfs-stack/src/group_api/service/group_handler.rs @@ -0,0 +1,113 @@ +use cyfs_base::*; +use cyfs_core::GroupProposal; +use cyfs_group_lib::{ + GroupInputRequestCommon, GroupPushProposalInputRequest, GroupPushProposalInputResponse, + GroupStartServiceInputRequest, GroupStartServiceInputResponse, +}; +use cyfs_lib::{NONRequestorHelper, RequestorHelper}; + +use crate::{group::GroupInputProcessorRef, non::NONInputHttpRequest}; + +#[derive(Clone)] +pub(crate) struct GroupRequestHandler { + processor: GroupInputProcessorRef, +} + +impl GroupRequestHandler { + pub fn new(processor: GroupInputProcessorRef) -> Self { + Self { processor } + } + + // 解析通用header字段 + fn decode_common_headers( + req: &NONInputHttpRequest, + ) -> BuckyResult { + let ret = GroupInputRequestCommon { + source: req.source.clone(), + }; + + Ok(ret) + } + + // group/service + pub async fn process_start_service( + &self, + req: NONInputHttpRequest, + ) -> tide::Response { + match self.on_start_service(req).await { + Ok(_resp) => { + let http_resp: tide::Response = RequestorHelper::new_ok_response(); + http_resp + } + Err(e) => RequestorHelper::trans_error(e), + } + } + + async fn on_start_service( + &self, + mut req: NONInputHttpRequest, + ) -> BuckyResult { + let common = Self::decode_common_headers(&req)?; + + // 提取body里面的object对象,如果有的话 + let body = req.request.body_json().await.map_err(|e| { + let msg = format!("group start service failed, read body bytes error! {}", e); + error!("{}", msg); + + BuckyError::new(BuckyErrorCode::InvalidParam, msg) + })?; + + let req = GroupStartServiceInputRequest::decode_json(&body)?; + + self.processor.start_service(req).await + } + + pub async fn process_push_proposal( + &self, + req: NONInputHttpRequest, + ) -> tide::Response { + match self.on_push_proposal(req).await { + Ok(resp) => Self::encode_push_proposal_response(resp), + Err(e) => RequestorHelper::trans_error(e), + } + } + + pub fn encode_push_proposal_response(resp: GroupPushProposalInputResponse) -> tide::Response { + let mut http_resp = RequestorHelper::new_response(tide::StatusCode::Ok); + + if let Some(object) = resp.object { + NONRequestorHelper::encode_object_info(&mut http_resp, object); + } + + http_resp.into() + } + + async fn on_push_proposal( + &self, + mut req: NONInputHttpRequest, + ) -> BuckyResult { + // 检查action + // let action = Self::decode_action(&req, NONAction::PutObject)?; + // if action != NONAction::PutObject { + // let msg = format!("invalid non put_object action! {:?}", action); + // error!("{}", msg); + + // return Err(BuckyError::new(BuckyErrorCode::InvalidData, msg)); + // } + + let common = Self::decode_common_headers(&req)?; + let object = NONRequestorHelper::decode_object_info(&mut req.request).await?; + let (proposal, remain) = GroupProposal::raw_decode(object.object_raw.as_slice())?; + assert_eq!(remain.len(), 0); + + // let access: Option = + // RequestorHelper::decode_optional_header(&req.request, cyfs_base::CYFS_ACCESS)?; + // let access = access.map(|v| AccessString::new(v)); + + info!("recv push proposal: {}", object.object_id); + + self.processor + .push_proposal(GroupPushProposalInputRequest { common, proposal }) + .await + } +} diff --git a/src/component/cyfs-stack/src/group_api/service/group_listener.rs b/src/component/cyfs-stack/src/group_api/service/group_listener.rs new file mode 100644 index 000000000..fc5a51874 --- /dev/null +++ b/src/component/cyfs-stack/src/group_api/service/group_listener.rs @@ -0,0 +1,77 @@ +use cyfs_lib::RequestProtocol; +use tide::Response; + +use crate::{non::NONInputHttpRequest, ZoneManagerRef}; + +use super::GroupRequestHandler; + +enum GroupRequestType { + StartService, + PushProposal, +} + +pub(crate) struct GroupRequestHandlerEndpoint { + zone_manager: ZoneManagerRef, + protocol: RequestProtocol, + req_type: GroupRequestType, + handler: GroupRequestHandler, +} + +impl GroupRequestHandlerEndpoint { + fn new( + zone_manager: ZoneManagerRef, + protocol: RequestProtocol, + req_type: GroupRequestType, + handler: GroupRequestHandler, + ) -> Self { + Self { + zone_manager, + protocol, + req_type, + handler, + } + } + + async fn process_request(&self, req: tide::Request) -> Response { + let req = match NONInputHttpRequest::new(&self.zone_manager, &self.protocol, req).await { + Ok(v) => v, + Err(resp) => return resp, + }; + match self.req_type { + GroupRequestType::StartService => self.handler.process_start_service(req).await, + GroupRequestType::PushProposal => self.handler.process_push_proposal(req).await, + } + } + + pub fn register_server( + zone_manager: &ZoneManagerRef, + protocol: &RequestProtocol, + handler: &GroupRequestHandler, + server: &mut tide::Server<()>, + ) { + server.at("/group/service").put(Self::new( + zone_manager.clone(), + protocol.to_owned(), + GroupRequestType::StartService, + handler.clone(), + )); + + server.at("/group/proposal").put(Self::new( + zone_manager.clone(), + protocol.to_owned(), + GroupRequestType::PushProposal, + handler.clone(), + )); + } +} + +#[async_trait::async_trait] +impl tide::Endpoint for GroupRequestHandlerEndpoint +where + State: Clone + Send + Sync + 'static, +{ + async fn call(&self, req: tide::Request) -> tide::Result { + let resp = self.process_request(req).await; + Ok(resp) + } +} diff --git a/src/component/cyfs-stack/src/group_api/service/group_service.rs b/src/component/cyfs-stack/src/group_api/service/group_service.rs new file mode 100644 index 000000000..3a25e282e --- /dev/null +++ b/src/component/cyfs-stack/src/group_api/service/group_service.rs @@ -0,0 +1,35 @@ +use cyfs_group::GroupManager; + +use crate::{ + forward::ForwardProcessorManager, + group::GroupInputProcessorRef, + group_api::{GroupServiceRouter, LocalGroupService}, + ZoneManagerRef, +}; + +#[derive(Clone)] +pub struct GroupService { + router: GroupInputProcessorRef, + local_service: LocalGroupService, +} + +impl GroupService { + pub(crate) fn new( + forward: ForwardProcessorManager, + zone_manager: ZoneManagerRef, + group_manager: GroupManager, + ) -> Self { + let local_service = LocalGroupService::new(group_manager); + let router = + GroupServiceRouter::new(forward, zone_manager, local_service.clone_processor()); + + Self { + router, + local_service, + } + } + + pub(crate) fn clone_processor(&self) -> GroupInputProcessorRef { + self.router.clone() + } +} diff --git a/src/component/cyfs-stack/src/group_api/service/mod.rs b/src/component/cyfs-stack/src/group_api/service/mod.rs new file mode 100644 index 000000000..1140a741d --- /dev/null +++ b/src/component/cyfs-stack/src/group_api/service/mod.rs @@ -0,0 +1,7 @@ +mod group_service; +mod group_listener; +mod group_handler; + +pub(crate) use group_service::*; +pub(crate) use group_listener::*; +pub(crate) use group_handler::*; diff --git a/src/component/cyfs-stack/src/interface/http_listener.rs b/src/component/cyfs-stack/src/interface/http_listener.rs index 21b6e86e3..e9ef12523 100644 --- a/src/component/cyfs-stack/src/interface/http_listener.rs +++ b/src/component/cyfs-stack/src/interface/http_listener.rs @@ -1,6 +1,7 @@ use crate::acl::AclManagerRef; use crate::crypto_api::*; use crate::front::{FrontProtocolHandler, FrontRequestHandlerEndpoint}; +use crate::group_api::{GroupRequestHandler, GroupRequestHandlerEndpoint, GroupService}; use crate::name::NameResolver; use crate::ndn_api::*; use crate::non_api::*; @@ -75,6 +76,7 @@ impl ObjectHttpListener { global_state_meta: &GlobalStateMetaService, name_resolver: &NameResolver, zone_manager: &ZoneManagerRef, + group_service: &GroupService, ) -> Self { let mut server = new_server(); @@ -225,6 +227,15 @@ impl ObjectHttpListener { &mut server, ); + // group manager + let handler = GroupRequestHandler::new(group_service.clone_processor()); + GroupRequestHandlerEndpoint::register_server( + zone_manager, + &protocol, + &handler, + &mut server, + ); + Self { server } } diff --git a/src/component/cyfs-stack/src/interface/listener_manager.rs b/src/component/cyfs-stack/src/interface/listener_manager.rs index c6b5510be..76d3c4e9d 100644 --- a/src/component/cyfs-stack/src/interface/listener_manager.rs +++ b/src/component/cyfs-stack/src/interface/listener_manager.rs @@ -9,6 +9,7 @@ use crate::acl::AclManagerRef; use crate::app::AuthenticatedAppList; use crate::config::StackGlobalConfig; use crate::events::RouterEventsManager; +use crate::group_api::GroupService; use crate::interface::http_ws_listener::ObjectHttpWSService; use crate::name::NameResolver; use crate::rmeta_api::GlobalStateMetaService; @@ -18,7 +19,7 @@ use crate::stack::ObjectServices; use crate::zone::ZoneRoleManager; use cyfs_base::*; use cyfs_bdt::StackGuard; -use cyfs_lib::{RequestProtocol, BrowserSanboxMode}; +use cyfs_lib::{BrowserSanboxMode, RequestProtocol}; use cyfs_debug::Mutex; use std::net::SocketAddr; @@ -131,6 +132,7 @@ impl ObjectListenerManager { root_state: &GlobalStateService, local_cache: &GlobalStateLocalService, global_state_meta: &GlobalStateMetaService, + group_service: &GroupService, ) { assert!(self.listeners.is_empty()); @@ -151,6 +153,7 @@ impl ObjectListenerManager { global_state_meta, name_resolver, role_manager.zone_manager(), + group_service, ); let raw_handler = RawHttpServer::new(server.into_server()); @@ -172,18 +175,14 @@ impl ObjectListenerManager { global_state_meta, name_resolver, role_manager.zone_manager(), + group_service, ); let raw_handler = RawHttpServer::new(server.into_server()); let http_server = DefaultHttpServer::new(raw_handler.into(), default_handler.clone()); let http_server = match config.get_stack_params().front.browser_mode { BrowserSanboxMode::None => http_server.into(), - mode @ _ => { - BrowserSanboxHttpServer::new( - http_server.into(), - mode, - ).into() - } + mode @ _ => BrowserSanboxHttpServer::new(http_server.into(), mode).into(), }; self.http_tcp_server = Some(http_server); } @@ -202,6 +201,7 @@ impl ObjectListenerManager { global_state_meta, name_resolver, role_manager.zone_manager(), + group_service, ); let raw_handler = RawHttpServer::new(server.into_server()); diff --git a/src/component/cyfs-stack/src/lib.rs b/src/component/cyfs-stack/src/lib.rs index 60e34fe1e..a9a0204f8 100644 --- a/src/component/cyfs-stack/src/lib.rs +++ b/src/component/cyfs-stack/src/lib.rs @@ -29,6 +29,8 @@ mod config; mod front; mod rmeta_api; mod rmeta; +mod group; +mod group_api; pub use stack::*; pub use storage::*; diff --git a/src/component/cyfs-stack/src/resolver/device_manager.rs b/src/component/cyfs-stack/src/resolver/device_manager.rs index b4c418b0d..650f4eff7 100644 --- a/src/component/cyfs-stack/src/resolver/device_manager.rs +++ b/src/component/cyfs-stack/src/resolver/device_manager.rs @@ -92,7 +92,7 @@ impl DeviceInfoManagerImpl { self.list.read().unwrap().get(device_id).map(|d| d.clone()) } - async fn verfiy_own_signs( + async fn verfiy_single_own_signs( &self, object_id: &ObjectId, object: &Arc, @@ -127,6 +127,40 @@ impl DeviceInfoManagerImpl { } } + async fn verfiy_group( + &self, + object_id: &ObjectId, + object: &Arc, + ) -> BuckyResult<()> { + // TODO: use object from `MetaChain` temporarily + let group = self + .obj_searcher + .search_ex(None, object_id, ObjectSearcherFlags::meta_only()) + .await?; + + let (group, _) = Group::raw_decode(group.object_raw.as_slice())?; + let group_id_meta = group.desc().object_id(); + if &group_id_meta != object_id { + let msg = format!( + "group({}) from MetaChain is unmatch with needed({}).", + group_id_meta, object_id + ); + log::warn!("{}", msg); + return Err(BuckyError::new(BuckyErrorCode::Unmatch, msg)); + } + let body_hash_meta = group.body().as_ref().unwrap().raw_hash_value()?; + let body_hash = object.body_hash()?; + if Some(body_hash_meta) != body_hash { + let msg = format!( + "group({}) body-hash({}) from MetaChain is unmatch with needed({:?}).", + object_id, body_hash_meta, body_hash + ); + log::warn!("{}", msg); + return Err(BuckyError::new(BuckyErrorCode::Unmatch, msg)); + } + Ok(()) + } + async fn verfiy_owner(&self, device_id: &DeviceId, device: Option<&Device>) -> BuckyResult<()> { let d; let device = match device { @@ -437,7 +471,11 @@ impl DeviceCache for DeviceInfoManager { object_id: &ObjectId, object: &Arc, ) -> BuckyResult<()> { - self.0.verfiy_own_signs(object_id, object).await + if ObjectTypeCode::Group == object_id.obj_type_code() { + self.0.verfiy_group(object_id, object).await + } else { + self.0.verfiy_single_own_signs(object_id, object).await + } } fn clone_cache(&self) -> Box { diff --git a/src/component/cyfs-stack/src/resolver/obj_searcher.rs b/src/component/cyfs-stack/src/resolver/obj_searcher.rs index f441205e3..e932f48ee 100644 --- a/src/component/cyfs-stack/src/resolver/obj_searcher.rs +++ b/src/component/cyfs-stack/src/resolver/obj_searcher.rs @@ -28,6 +28,10 @@ impl ObjectSearcherFlags { pub fn local_and_meta() -> u32 { OBJECT_SEARCH_FLAG_META | OBJECT_SEARCH_FLAG_NOC } + + pub fn meta_only() -> u32 { + OBJECT_SEARCH_FLAG_META + } } #[async_trait] diff --git a/src/component/cyfs-stack/src/resolver/ood_resolver.rs b/src/component/cyfs-stack/src/resolver/ood_resolver.rs index 304ce6a11..363195b89 100644 --- a/src/component/cyfs-stack/src/resolver/ood_resolver.rs +++ b/src/component/cyfs-stack/src/resolver/ood_resolver.rs @@ -115,7 +115,7 @@ impl OodResolver { let obj_type = object_id.obj_type_code(); // People,SimpleGroup 对象存在ood_list - if obj_type == ObjectTypeCode::People || obj_type == ObjectTypeCode::SimpleGroup { + if obj_type == ObjectTypeCode::People || obj_type == ObjectTypeCode::Group { match object.ood_list() { Ok(list) => { if list.len() > 0 { @@ -241,7 +241,7 @@ impl OodResolver { let obj_type = object_id.obj_type_code(); // People,SimpleGroup 对象存在ood_list - if obj_type == ObjectTypeCode::People || obj_type == ObjectTypeCode::SimpleGroup { + if obj_type == ObjectTypeCode::People || obj_type == ObjectTypeCode::Group { if object.is_some() { match object.as_ref().unwrap().ood_list() { Ok(list) => { @@ -346,4 +346,4 @@ impl OodResolver { } } } -} \ No newline at end of file +} diff --git a/src/component/cyfs-stack/src/stack/cyfs_stack.rs b/src/component/cyfs-stack/src/stack/cyfs_stack.rs index bfd914ae7..05b41ff40 100644 --- a/src/component/cyfs-stack/src/stack/cyfs_stack.rs +++ b/src/component/cyfs-stack/src/stack/cyfs_stack.rs @@ -10,6 +10,7 @@ use crate::crypto_api::{CryptoService, ObjectCrypto, ObjectVerifier}; use crate::events::RouterEventsManager; use crate::forward::ForwardProcessorManager; use crate::front::FrontService; +use crate::group_api::GroupService; use crate::interface::{ ObjectListenerManager, ObjectListenerManagerParams, ObjectListenerManagerRef, }; @@ -32,9 +33,12 @@ use crate::trans_api::{create_trans_store, TransService}; use crate::util::UtilOutputTransformer; use crate::util_api::UtilService; use crate::zone::{ZoneManager, ZoneManagerRef, ZoneRoleManager}; +use crate::GroupNONDriver; use cyfs_base::*; + use cyfs_bdt::{DeviceCache, SnStatus, StackGuard}; use cyfs_bdt_ext::{BdtStackParams, NamedDataComponents}; +use cyfs_group::GroupManager; use cyfs_lib::*; use cyfs_noc::*; use cyfs_task_manager::{SQLiteTaskStore, TaskManager}; @@ -100,6 +104,9 @@ pub struct CyfsStackImpl { // global_state_meta global_state_meta: GlobalStateMetaService, + + // group + group_service: GroupService, } impl CyfsStackImpl { @@ -277,6 +284,11 @@ impl CyfsStackImpl { config.clone(), ); + let signer = RsaCPUObjectSigner::new( + bdt_param.device.desc().public_key().clone(), + bdt_param.secret.clone(), + ); + // 初始化bdt协议栈 let (bdt_stack, bdt_event) = Self::init_bdt_stack( zone_manager.clone(), @@ -407,7 +419,7 @@ impl CyfsStackImpl { let services = ObjectServices { ndn_service, - non_service, + non_service: non_service.clone(), crypto_service, util_service, @@ -423,6 +435,18 @@ impl CyfsStackImpl { config.clone(), ); + let group_manager = GroupManager::new( + signer, + Box::new(GroupNONDriver::new( + non_service.clone(), + device_id.object_id().clone(), + )), + bdt_stack.clone(), + global_state_manager.clone_processor(), + )?; + let group_service = + GroupService::new(forward_manager.clone(), zone_manager.clone(), group_manager); + let mut stack = Self { config, @@ -458,6 +482,8 @@ impl CyfsStackImpl { fail_handler, acl_manager, + + group_service, }; // init an system-dec router-handler processor for later use @@ -547,6 +573,7 @@ impl CyfsStackImpl { &stack.root_state, &stack.local_cache, &stack.global_state_meta, + &stack.group_service, ); let interface = Arc::new(interface); @@ -1151,6 +1178,10 @@ impl CyfsStack { &self.stack.root_state } + pub fn group_service(&self) -> &GroupService { + &self.stack.group_service + } + // use system dec as default dec pub async fn root_state_stub( &self, diff --git a/src/component/cyfs-stack/src/stack/group_non_driver.rs b/src/component/cyfs-stack/src/stack/group_non_driver.rs new file mode 100644 index 000000000..2019f1607 --- /dev/null +++ b/src/component/cyfs-stack/src/stack/group_non_driver.rs @@ -0,0 +1,265 @@ +use std::{sync::Arc, time::Duration}; + +use cyfs_base::{AccessString, BuckyError, BuckyErrorCode, BuckyResult, ObjectId}; +use cyfs_lib::{ + DeviceZoneCategory, DeviceZoneInfo, NONAPILevel, NONGetObjectInputRequest, + NONInputRequestCommon, NONObjectInfo, NONPostObjectInputRequest, NONPutObjectInputRequest, + RequestGlobalStatePath, RequestProtocol, RequestSourceInfo, +}; +use futures::FutureExt; + +use crate::{non::NONInputProcessor, non_api::NONService}; + +const TIMEOUT_HALF: Duration = Duration::from_millis(2000); + +pub struct GroupNONDriver { + non_service: Arc, + local_device_id: ObjectId, +} + +impl GroupNONDriver { + pub fn new(non_service: Arc, local_device_id: ObjectId) -> Self { + Self { + non_service, + local_device_id, + } + } + + async fn get_object_impl( + &self, + dec_id: &ObjectId, + object_id: &ObjectId, + from: Option<&ObjectId>, + ) -> BuckyResult { + log::info!( + "get object {}, local: {}, from: {:?}", + object_id, + self.local_device_id, + from + ); + + let resp = self + .non_service + .get_object(NONGetObjectInputRequest { + common: NONInputRequestCommon { + req_path: None, + source: RequestSourceInfo { + protocol: RequestProtocol::DataBdt, + zone: DeviceZoneInfo { + device: None, + zone: None, + zone_category: DeviceZoneCategory::CurrentZone, + }, + dec: dec_id.clone(), + verified: None, + }, + + level: NONAPILevel::Router, // from.map_or(NONAPILevel::NOC, |_| NONAPILevel::Router), + + target: from.map(|remote| remote.clone()), + flags: 0, + }, + object_id: object_id.clone(), + inner_path: None, + }) + .await?; + + // TODO: only set the permissions + self.put_object_impl(dec_id, resp.object.clone()).await; + Ok(resp.object) + } + + async fn put_object_impl(&self, dec_id: &ObjectId, obj: NONObjectInfo) -> BuckyResult<()> { + let access = AccessString::full(); + + log::info!( + "put object {} with access {}, local: {}", + obj.object_id, + access, + self.local_device_id + ); + + self.non_service + .put_object(NONPutObjectInputRequest { + common: NONInputRequestCommon { + req_path: None, + source: RequestSourceInfo { + protocol: RequestProtocol::DataBdt, + zone: DeviceZoneInfo { + device: None, + zone: None, + zone_category: DeviceZoneCategory::CurrentZone, + }, + dec: dec_id.clone(), + verified: None, + }, + + level: NONAPILevel::Router, + + target: None, + flags: 0, + }, + object: obj, + access: Some(AccessString::full()), // TODO access + }) + .await + .map(|_| ()) + } + + async fn post_object_impl( + &self, + dec_id: &ObjectId, + obj: NONObjectInfo, + to: Option<&ObjectId>, + ) -> BuckyResult> { + let obj_type_code = obj.object_id.obj_type_code(); + let obj_type = obj.object.as_ref().map(|obj| obj.obj_type()); + + let req_path = RequestGlobalStatePath::new(Some(dec_id.clone()), Some("group/inner-cmd")); + + self.non_service + .post_object(NONPostObjectInputRequest { + common: NONInputRequestCommon { + req_path: Some(req_path.format_string()), + source: RequestSourceInfo { + protocol: RequestProtocol::DataBdt, + zone: DeviceZoneInfo { + device: None, + zone: None, + zone_category: DeviceZoneCategory::CurrentZone, + }, + dec: dec_id.clone(), + verified: None, + }, + + level: NONAPILevel::Router, // to.map_or(NONAPILevel::NOC, |_| NONAPILevel::Router), + + target: to.cloned(), + flags: 0, + }, + object: obj, + }) + .await + .map(|resp| resp.object) + .map_err(|err| { + log::warn!( + "group post object(type={:?}/{:?}) to {:?} failed {:?}", + obj_type_code, + obj_type, + to, + err + ); + err + }) + } +} + +#[async_trait::async_trait] +impl cyfs_group::NONDriver for GroupNONDriver { + async fn get_object( + &self, + dec_id: &ObjectId, + object_id: &ObjectId, + from: Option<&ObjectId>, + ) -> BuckyResult { + let fut1 = match futures::future::select( + self.get_object_impl(dec_id, object_id, from).boxed(), + async_std::future::timeout(TIMEOUT_HALF, futures::future::pending::<()>()).boxed(), + ) + .await + { + futures::future::Either::Left((ret, _)) => return ret, + futures::future::Either::Right((_, fut)) => fut, + }; + + log::warn!( + "group get object timeout (type={:?}) from {:?}, local: {:?}", + object_id.obj_type_code(), + from, + self.local_device_id + ); + + match futures::future::select( + self.get_object_impl(dec_id, object_id, from).boxed(), + async_std::future::timeout(TIMEOUT_HALF, fut1).boxed(), + ) + .await + { + futures::future::Either::Left((ret, _)) => ret, + futures::future::Either::Right((ret, _)) => ret.map_or( + Err(BuckyError::new(BuckyErrorCode::Timeout, "timeout")), + |ret| ret, + ), + } + } + + async fn put_object(&self, dec_id: &ObjectId, obj: NONObjectInfo) -> BuckyResult<()> { + let fut1 = match futures::future::select( + self.put_object_impl(dec_id, obj.clone()).boxed(), + async_std::future::timeout(TIMEOUT_HALF, futures::future::pending::<()>()).boxed(), + ) + .await + { + futures::future::Either::Left((ret, _)) => return ret, + futures::future::Either::Right((_, fut)) => fut, + }; + + log::warn!( + "group put object timeout (type={:?}/{:?}), local: {:?}", + obj.object_id.obj_type_code(), + obj.object.as_ref().map(|o| o.obj_type()), + self.local_device_id + ); + + match futures::future::select( + self.put_object_impl(dec_id, obj).boxed(), + async_std::future::timeout(TIMEOUT_HALF, fut1).boxed(), + ) + .await + { + futures::future::Either::Left((ret, _)) => ret, + futures::future::Either::Right((ret, _)) => ret.map_or( + Err(BuckyError::new(BuckyErrorCode::Timeout, "timeout")), + |ret| ret, + ), + } + } + + async fn post_object( + &self, + dec_id: &ObjectId, + obj: NONObjectInfo, + to: Option<&ObjectId>, + ) -> BuckyResult> { + let fut1 = match futures::future::select( + self.post_object_impl(dec_id, obj.clone(), to).boxed(), + async_std::future::timeout(TIMEOUT_HALF, futures::future::pending::<()>()).boxed(), + ) + .await + { + futures::future::Either::Left((ret, _)) => return ret, + futures::future::Either::Right((_, fut)) => fut, + }; + + log::warn!( + "group post object timeout (type={:?}/{:?}) to {:?}, local: {:?}", + obj.object_id.obj_type_code(), + obj.object.as_ref().map(|o| o.obj_type()), + to, + self.local_device_id + ); + + match futures::future::select( + self.post_object_impl(dec_id, obj, to).boxed(), + async_std::future::timeout(TIMEOUT_HALF, fut1).boxed(), + ) + .await + { + futures::future::Either::Left((ret, _)) => ret, + futures::future::Either::Right((ret, _)) => ret.map_or( + Err(BuckyError::new(BuckyErrorCode::Timeout, "timeout")), + |ret| ret, + ), + } + } +} diff --git a/src/component/cyfs-stack/src/stack/mod.rs b/src/component/cyfs-stack/src/stack/mod.rs index 15c8edd42..4e4afdf9e 100644 --- a/src/component/cyfs-stack/src/stack/mod.rs +++ b/src/component/cyfs-stack/src/stack/mod.rs @@ -1,7 +1,9 @@ mod cyfs_stack; +mod group_non_driver; mod params; mod uni_stack; pub use cyfs_stack::*; +pub(crate) use group_non_driver::*; pub use params::*; pub use cyfs_bdt_ext::NamedDataComponents; diff --git a/src/component/cyfs-stack/src/util_api/local/local.rs b/src/component/cyfs-stack/src/util_api/local/local.rs index 13cf01657..8a235d22f 100644 --- a/src/component/cyfs-stack/src/util_api/local/local.rs +++ b/src/component/cyfs-stack/src/util_api/local/local.rs @@ -116,9 +116,7 @@ impl UtilLocalService { let obj_type = object_id.obj_type_code(); let zone = match obj_type { - ObjectTypeCode::Device - | ObjectTypeCode::People - | ObjectTypeCode::SimpleGroup => { + ObjectTypeCode::Device | ObjectTypeCode::People | ObjectTypeCode::Group => { let zone = self .zone_manager .resolve_zone(&object_id, req.object_raw) diff --git a/src/component/cyfs-stack/src/zone/target_zone.rs b/src/component/cyfs-stack/src/zone/target_zone.rs index 5b471bcf7..ff4d005b4 100644 --- a/src/component/cyfs-stack/src/zone/target_zone.rs +++ b/src/component/cyfs-stack/src/zone/target_zone.rs @@ -48,7 +48,7 @@ impl TargetZoneManager { Ok((zone.zone_id(), device_id)) } - ObjectTypeCode::People | ObjectTypeCode::SimpleGroup => { + ObjectTypeCode::People | ObjectTypeCode::Group => { let zone = self.zone_manager.resolve_zone(target, object_raw).await?; Ok((zone.zone_id(), zone.ood().clone())) } @@ -178,7 +178,7 @@ impl TargetZoneManager { } } - ObjectTypeCode::People | ObjectTypeCode::SimpleGroup => { + ObjectTypeCode::People | ObjectTypeCode::Group => { if info.owner_id == *target { let ret = TargetZoneInfo { is_current_zone: true, diff --git a/src/component/cyfs-stack/src/zone/zone_manager.rs b/src/component/cyfs-stack/src/zone/zone_manager.rs index 4a058efbc..3ef1ea475 100644 --- a/src/component/cyfs-stack/src/zone/zone_manager.rs +++ b/src/component/cyfs-stack/src/zone/zone_manager.rs @@ -424,7 +424,7 @@ impl ZoneManager { // 目前owner只支持people和simplegroup // People,SimpleGroup对象存在ood_list match obj_type { - ObjectTypeCode::People | ObjectTypeCode::SimpleGroup => { + ObjectTypeCode::People | ObjectTypeCode::Group => { // 查找owner对象 info!("will search owner: type={:?}, {}", obj_type, owner_id); let object = self.search_object(&owner_id).await?; @@ -648,17 +648,35 @@ impl ZoneManager { // 目前owner只支持people和simplegroup // People,SimpleGroup对象存在ood_list - if obj_type == ObjectTypeCode::People || obj_type == ObjectTypeCode::SimpleGroup { + if obj_type == ObjectTypeCode::People || obj_type == ObjectTypeCode::Group { match owner_object.ood_list() { Ok(list) => { if list.len() > 0 { - let work_mode = owner_object.ood_work_mode().unwrap().to_owned(); - debug!( - "get ood list from owner object ood_list: owner={}, work_mode={:?}, list={:?}", - owner_id, work_mode, list - ); + if obj_type == ObjectTypeCode::People { + let work_mode = owner_object.ood_work_mode().unwrap().to_owned(); + debug!( + "get ood list from owner object ood_list: owner={}, work_mode={:?}, list={:?}", + owner_id, work_mode, list + ); + + return Ok((work_mode, list.to_owned())); + } else if obj_type == ObjectTypeCode::Group { + let group = owner_object.into_group(); + // TODO: 先简单处理,找最近的OOD,后面可能要依据具体操作向不同身份发起请求; + // 比如:读操作向任意member请求即可 + let list = group + .ood_list_with_distance( + self.get_current_device_id().object_id(), + ) + .into_iter() + .filter(|id| id.obj_type_code() == ObjectTypeCode::Device) + .map(|id| DeviceId::try_from(id).unwrap()) + .collect(); + return Ok((OODWorkMode::Standalone, list)); + } else { + unreachable!() + } - return Ok((work_mode, list.to_owned())); /* let ood_device_id = list[0].clone(); let obj_type = ood_device_id.object_id().obj_type_code(); @@ -736,7 +754,7 @@ impl ZoneManager { })?; match object_id.obj_type_code() { - ObjectTypeCode::People | ObjectTypeCode::SimpleGroup => { + ObjectTypeCode::People | ObjectTypeCode::Group => { // 需要校验签名 let obj = Arc::new(obj); diff --git a/src/meta/cyfs-meta/src/executor/context/object.rs b/src/meta/cyfs-meta/src/executor/context/object.rs index 07c4164d1..e747fe5ae 100644 --- a/src/meta/cyfs-meta/src/executor/context/object.rs +++ b/src/meta/cyfs-meta/src/executor/context/object.rs @@ -45,9 +45,6 @@ pub fn id_from_desc(desc: &SavedMetaObject) -> ObjectId { SavedMetaObject::Data(obj) => { obj.id.clone() }, - SavedMetaObject::Org(org) => { - org.desc().calculate_id() - }, SavedMetaObject::MinerGroup(group) => { group.desc().calculate_id() }, @@ -57,6 +54,10 @@ pub fn id_from_desc(desc: &SavedMetaObject) -> ObjectId { SavedMetaObject::SNService(service) => { service.desc().calculate_id() } + SavedMetaObject::SimpleGroup => { + panic!("SimpleGroup is deprecated, you can use the Group.") + } + SavedMetaObject::Org => panic!("Org is deprecated, you can use the Group."), } } diff --git a/src/meta/cyfs-meta/src/executor/tx_proc/account_proc.rs b/src/meta/cyfs-meta/src/executor/tx_proc/account_proc.rs index e55fac2b3..07ab08014 100644 --- a/src/meta/cyfs-meta/src/executor/tx_proc/account_proc.rs +++ b/src/meta/cyfs-meta/src/executor/tx_proc/account_proc.rs @@ -1,18 +1,27 @@ -use cyfs_base::*; use crate::executor::context; +use crate::executor::context::UnionAccountState; use crate::executor::transaction::ExecuteContext; use crate::executor::tx_executor::TxExecutor; use crate::helper::{get_meta_err_code, ArcWeakHelper}; -use std::convert::TryFrom; -use crate::executor::context::UnionAccountState; -use cyfs_base_meta::*; -use crate::{State}; use crate::meta_backend::MetaBackend; +use crate::State; +use cyfs_base::*; +use cyfs_base_meta::*; +use std::convert::TryFrom; impl TxExecutor { - pub async fn execute_trans_balance(&self, context: &mut ExecuteContext, fee_counter: &mut context::FeeCounter, tx: &TransBalanceTx, _backend: &mut MetaBackend, _config: &evm::Config) -> BuckyResult> { + pub async fn execute_trans_balance( + &self, + context: &mut ExecuteContext, + fee_counter: &mut context::FeeCounter, + tx: &TransBalanceTx, + _backend: &mut MetaBackend, + _config: &evm::Config, + ) -> BuckyResult> { if let context::AccountMethods::Single(methods) = context.caller().methods() { - let logs = methods.trans_balance(fee_counter, &tx.ctid, &tx.to/*, backend, config*/).await?; + let logs = methods + .trans_balance(fee_counter, &tx.ctid, &tx.to /*, backend, config*/) + .await?; // 下边是扣除租金的相关逻辑 for (to_id, _v) in &tx.to { @@ -32,16 +41,30 @@ impl TxExecutor { // } // } - self.rent_manager.to_rc()?.check_and_deduct_rent_arrears_for_desc(context.block(), &to_id, &tx.ctid).await?; + self.rent_manager + .to_rc()? + .check_and_deduct_rent_arrears_for_desc(context.block(), &to_id, &tx.ctid) + .await?; let owned_names = self.ref_state.to_rc()?.get_owned_names(to_id).await?; for name in owned_names { - let mut name_state = self.ref_state.to_rc()?.get_name_state(name.as_str()).await?; + let mut name_state = self + .ref_state + .to_rc()? + .get_name_state(name.as_str()) + .await?; if name_state == NameState::Lock { - let rent_state = self.rent_manager.to_rc()?.check_and_deduct_rent_arrears_for_name(context.block(), name.as_str()).await?; + let rent_state = self + .rent_manager + .to_rc()? + .check_and_deduct_rent_arrears_for_name(context.block(), name.as_str()) + .await?; if rent_state.rent_arrears == 0 { name_state = NameState::Normal; - self.ref_state.to_rc()?.update_name_state(name.as_str(), name_state).await?; + self.ref_state + .to_rc()? + .update_name_state(name.as_str(), name_state) + .await?; } } } @@ -56,38 +79,30 @@ impl TxExecutor { // break; // } // } - } + } Ok(logs) } else { Err(crate::meta_err!(ERROR_INVALID)) } } - pub async fn execute_withdraw_to_owner(&self, - context: &mut ExecuteContext, - _fee_counter: &mut context::FeeCounter, - tx: &WithdrawToOwner) -> BuckyResult<()> { + pub async fn execute_withdraw_to_owner( + &self, + context: &mut ExecuteContext, + _fee_counter: &mut context::FeeCounter, + tx: &WithdrawToOwner, + ) -> BuckyResult<()> { let beneficiary = self.ref_state.to_rc()?.get_beneficiary(&tx.id).await?; let owner = if beneficiary != tx.id { Some(beneficiary) } else { let desc_obj_ret = self.ref_state.to_rc()?.get_obj_desc(&tx.id).await?; match desc_obj_ret { - SavedMetaObject::Device(device) => { - device.desc().owner().clone() - } - SavedMetaObject::People(_) => { - None - } - SavedMetaObject::UnionAccount(_) => { - None - } - SavedMetaObject::Group(_) => { - None - } - SavedMetaObject::File(file) => { - file.desc().owner().clone() - } + SavedMetaObject::Device(device) => device.desc().owner().clone(), + SavedMetaObject::People(_) => None, + SavedMetaObject::UnionAccount(_) => None, + SavedMetaObject::Group(_) => None, + SavedMetaObject::File(file) => file.desc().owner().clone(), SavedMetaObject::Data(data) => { let ret = AnyNamedObject::clone_from_slice(data.data.as_slice()); if ret.is_ok() { @@ -96,84 +111,95 @@ impl TxExecutor { None } } - SavedMetaObject::Org(_) => { - None - } - SavedMetaObject::MinerGroup(_) => { - None - } - SavedMetaObject::SNService(_) => { - None - } - SavedMetaObject::Contract(contract) => { - contract.desc().owner().clone() + SavedMetaObject::MinerGroup(_) => None, + SavedMetaObject::SNService(_) => None, + SavedMetaObject::Contract(contract) => contract.desc().owner().clone(), + SavedMetaObject::SimpleGroup => { + panic!("SimpleGroup is deprecated, you can use the Group.") } + SavedMetaObject::Org => panic!("Org is deprecated, you can use the Group."), } }; if owner.is_some() && owner.as_ref().unwrap() == context.caller().id() { - self.ref_state.to_rc()?.dec_balance(&tx.ctid, &tx.id, tx.value).await?; - self.ref_state.to_rc()?.inc_balance(&tx.ctid, &context.caller().id(), tx.value).await?; - return Ok(()) + self.ref_state + .to_rc()? + .dec_balance(&tx.ctid, &tx.id, tx.value) + .await?; + self.ref_state + .to_rc()? + .inc_balance(&tx.ctid, &context.caller().id(), tx.value) + .await?; + return Ok(()); } Err(crate::meta_err!(ERROR_INVALID)) } -//理解下面逻辑需了解ffs-meta的闪电网络工作原理 -// 1.双方决定建立union account -// 2.由任一方执行CreateUnionTX,在Meta上建立union account(需要持续付租金) -// 3.另一方可按需要执行TransBalanceTX,在union account中增加余额 -// 4.双方进行链下交易,不断地创建有双签名的SetUnionTx,Set操作的left + right的和总是为0 -// 5.任何一方都可以在必要的时候,将有双签名的SetUnionTx上链条,只有最新的SetUnionTx会生效 -// 操作完成后,UnionAccount中的余额状态会公开的改变 -// 6.任何一方,都可以在必要的时候,调用带return flag的SetUnionTx,将UnionAccount中所属自己的余额反还。 -// 改操作在上链后永远不会立刻生效,而是需要等待一定的时间后才会操作UnionAccount -// 7.当一方发现另一方提交了单签名的SetUnionTx,可以提交最新的,有多签名的SetUnionTx来保障自己的收益 -// 综上:使用闪电网络,需要最少3个操作,创建->使用->返还 -// 问题:union account的租金谁出? 还是和Address Account一样,是不需要租金,永久保存的特殊存在? -// 扩展:包含超过2个object的超级union account? - - pub async fn execute_create_union(&self - , context: &mut ExecuteContext - , fee_counter: &mut context::FeeCounter - , tx: &CreateUnionTx) -> BuckyResult<()> { + //理解下面逻辑需了解ffs-meta的闪电网络工作原理 + // 1.双方决定建立union account + // 2.由任一方执行CreateUnionTX,在Meta上建立union account(需要持续付租金) + // 3.另一方可按需要执行TransBalanceTX,在union account中增加余额 + // 4.双方进行链下交易,不断地创建有双签名的SetUnionTx,Set操作的left + right的和总是为0 + // 5.任何一方都可以在必要的时候,将有双签名的SetUnionTx上链条,只有最新的SetUnionTx会生效 + // 操作完成后,UnionAccount中的余额状态会公开的改变 + // 6.任何一方,都可以在必要的时候,调用带return flag的SetUnionTx,将UnionAccount中所属自己的余额反还。 + // 改操作在上链后永远不会立刻生效,而是需要等待一定的时间后才会操作UnionAccount + // 7.当一方发现另一方提交了单签名的SetUnionTx,可以提交最新的,有多签名的SetUnionTx来保障自己的收益 + // 综上:使用闪电网络,需要最少3个操作,创建->使用->返还 + // 问题:union account的租金谁出? 还是和Address Account一样,是不需要租金,永久保存的特殊存在? + // 扩展:包含超过2个object的超级union account? + + pub async fn execute_create_union( + &self, + context: &mut ExecuteContext, + fee_counter: &mut context::FeeCounter, + tx: &CreateUnionTx, + ) -> BuckyResult<()> { let union_account = &tx.body.account; let mut public_key_list = Vec::new(); - let ret = context.ref_state().to_rc()?.get_obj_desc(&union_account.desc().content().left()).await; + let ret = context + .ref_state() + .to_rc()? + .get_obj_desc(&union_account.desc().content().left()) + .await; if let Err(err) = &ret { let err_code = get_meta_err_code(err)?; return if err_code == ERROR_NOT_FOUND { Err(crate::meta_err!(ERROR_CANT_FIND_LEFT_USER_DESC)) } else { Err(crate::meta_err2!(err_code, err.msg())) - } + }; } let left_account = ret.unwrap(); match left_account { - SavedMetaObject::Device(device) => { - public_key_list.push((union_account.desc().content().left().clone(), device.desc().public_key().clone())) - } + SavedMetaObject::Device(device) => public_key_list.push(( + union_account.desc().content().left().clone(), + device.desc().public_key().clone(), + )), // SavedMetaObject::People(people) => { // public_key_list.push((union_account.desc().content().right().clone(), people.desc())) // } - _ => { - return Err(crate::meta_err!(ERROR_LEFT_ACCOUNT_TYPE)) - } + _ => return Err(crate::meta_err!(ERROR_LEFT_ACCOUNT_TYPE)), } - let ret = context.ref_state().to_rc()?.get_obj_desc(&union_account.desc().content().right()).await; + let ret = context + .ref_state() + .to_rc()? + .get_obj_desc(&union_account.desc().content().right()) + .await; if let Err(err) = &ret { let err_code = get_meta_err_code(err)?; return if err_code == ERROR_NOT_FOUND { Err(crate::meta_err!(ERROR_CANT_FIND_RIGHT_USER_DESC)) } else { Err(crate::meta_err2!(err_code, err.msg())) - } + }; } let right_account = ret.unwrap(); match right_account { - SavedMetaObject::Device(device) => { - public_key_list.push((union_account.desc().content().right().clone(), device.desc().public_key().clone())) - } + SavedMetaObject::Device(device) => public_key_list.push(( + union_account.desc().content().right().clone(), + device.desc().public_key().clone(), + )), // SavedMetaObject::People(people) => { // public_key_list.push((union_account.desc().content().right().clone(), people.desc())) // } @@ -187,28 +213,71 @@ impl TxExecutor { } let account_id = tx.body.account.desc().calculate_id(); - context.ref_state().to_rc()?.create_obj_desc(&account_id, &SavedMetaObject::try_from(StandardObject::UnionAccount(tx.body.account.clone()))?).await?; + context + .ref_state() + .to_rc()? + .create_obj_desc( + &account_id, + &SavedMetaObject::try_from(StandardObject::UnionAccount(tx.body.account.clone()))?, + ) + .await?; //转账 if tx.body.left_balance > 0 { - self.ref_state.to_rc()?.dec_balance(&tx.body.ctid, &union_account.desc().content().left(), tx.body.left_balance).await?; - let account_state = UnionAccountState::new(union_account.desc(), &self.ref_state.to_rc()?)?; - account_state.deposit(fee_counter, &union_account.desc().content().left(), &tx.body.ctid, tx.body.left_balance).await?; + self.ref_state + .to_rc()? + .dec_balance( + &tx.body.ctid, + &union_account.desc().content().left(), + tx.body.left_balance, + ) + .await?; + let account_state = + UnionAccountState::new(union_account.desc(), &self.ref_state.to_rc()?)?; + account_state + .deposit( + fee_counter, + &union_account.desc().content().left(), + &tx.body.ctid, + tx.body.left_balance, + ) + .await?; } if tx.body.right_balance > 0 { - self.ref_state.to_rc()?.dec_balance(&tx.body.ctid, &union_account.desc().content().right(), tx.body.right_balance).await?; - let account_state = UnionAccountState::new(union_account.desc(), &self.ref_state.to_rc()?)?; - account_state.deposit(fee_counter, &union_account.desc().content().right(), &tx.body.ctid, tx.body.right_balance).await?; + self.ref_state + .to_rc()? + .dec_balance( + &tx.body.ctid, + &union_account.desc().content().right(), + tx.body.right_balance, + ) + .await?; + let account_state = + UnionAccountState::new(union_account.desc(), &self.ref_state.to_rc()?)?; + account_state + .deposit( + fee_counter, + &union_account.desc().content().right(), + &tx.body.ctid, + tx.body.right_balance, + ) + .await?; } Ok(()) } - pub async fn execute_deviate_union(&self - , context: &mut ExecuteContext - , fee_counter: &mut context::FeeCounter - , tx: &DeviateUnionTx) -> BuckyResult<()> { + pub async fn execute_deviate_union( + &self, + context: &mut ExecuteContext, + fee_counter: &mut context::FeeCounter, + tx: &DeviateUnionTx, + ) -> BuckyResult<()> { //获取联合账户信息 - let ret = context.ref_state().to_rc()?.get_obj_desc(&tx.body.union).await?; + let ret = context + .ref_state() + .to_rc()? + .get_obj_desc(&tx.body.union) + .await?; let union_account = if let SavedMetaObject::UnionAccount(union_account) = ret { union_account } else { @@ -216,42 +285,50 @@ impl TxExecutor { }; let mut public_key_list = Vec::new(); - let ret = context.ref_state().to_rc()?.get_obj_desc(&union_account.desc().content().left()).await; + let ret = context + .ref_state() + .to_rc()? + .get_obj_desc(&union_account.desc().content().left()) + .await; if let Err(err) = &ret { let err_code = get_meta_err_code(err)?; return if err_code == ERROR_NOT_FOUND { Err(crate::meta_err!(ERROR_CANT_FIND_LEFT_USER_DESC)) } else { Err(crate::meta_err2!(err_code, err.msg())) - } + }; } let left_account = ret.unwrap(); match left_account { - SavedMetaObject::Device(device) => { - public_key_list.push((union_account.desc().content().left().clone(), device.desc().public_key().clone())) - } + SavedMetaObject::Device(device) => public_key_list.push(( + union_account.desc().content().left().clone(), + device.desc().public_key().clone(), + )), // SavedMetaObject::People(people) => { // public_key_list.push((union_account.desc().content().right().clone(), people.desc())) // } - _ => { - return Err(crate::meta_err!(ERROR_LEFT_ACCOUNT_TYPE)) - } + _ => return Err(crate::meta_err!(ERROR_LEFT_ACCOUNT_TYPE)), } - let ret = context.ref_state().to_rc()?.get_obj_desc(&union_account.desc().content().right()).await; + let ret = context + .ref_state() + .to_rc()? + .get_obj_desc(&union_account.desc().content().right()) + .await; if let Err(err) = &ret { let err_code = get_meta_err_code(err)?; return if err_code == ERROR_NOT_FOUND { Err(crate::meta_err!(ERROR_CANT_FIND_RIGHT_USER_DESC)) } else { Err(crate::meta_err2!(err_code, err.msg())) - } + }; } let right_account = ret.unwrap(); match right_account { - SavedMetaObject::Device(device) => { - public_key_list.push((union_account.desc().content().right().clone(), device.desc().public_key().clone())) - } + SavedMetaObject::Device(device) => public_key_list.push(( + union_account.desc().content().right().clone(), + device.desc().public_key().clone(), + )), // SavedMetaObject::People(people) => { // public_key_list.push((union_account.desc().content().right().clone(), people.desc())) // } @@ -265,16 +342,19 @@ impl TxExecutor { } let account_state = UnionAccountState::new(union_account.desc(), &self.ref_state.to_rc()?)?; - account_state.deviate(fee_counter, &tx.body.ctid, tx.body.deviation, tx.body.seq).await?; + account_state + .deviate(fee_counter, &tx.body.ctid, tx.body.deviation, tx.body.seq) + .await?; Ok(()) } - - pub async fn execute_withdraw_from_union(&self - , context: &mut ExecuteContext - , _fee_counter: &mut context::FeeCounter - , tx: &WithdrawFromUnionTx) -> BuckyResult<()> { + pub async fn execute_withdraw_from_union( + &self, + context: &mut ExecuteContext, + _fee_counter: &mut context::FeeCounter, + tx: &WithdrawFromUnionTx, + ) -> BuckyResult<()> { if let context::AccountMethods::Single(_) = context.caller().methods() { //获取联合账户信息 let ret = context.ref_state().to_rc()?.get_obj_desc(&tx.union).await?; @@ -285,15 +365,16 @@ impl TxExecutor { }; let caller_id = context.caller().id().clone(); - if union_account.desc().content().right() != &caller_id && union_account.desc().content().left() != &caller_id { + if union_account.desc().content().right() != &caller_id + && union_account.desc().content().left() != &caller_id + { return Err(crate::meta_err!(ERROR_ACCESS_DENIED)); } - self.union_withdraw_manager.to_rc()?.withdraw(context.block() - , &tx.union - , &caller_id - , &tx.ctid - , tx.value).await + self.union_withdraw_manager + .to_rc()? + .withdraw(context.block(), &tx.union, &caller_id, &tx.ctid, tx.value) + .await // context::UnionAccountState::load(&tx.union // , &context.ref_state().to_rc()?)? // .withdraw( @@ -308,7 +389,10 @@ impl TxExecutor { // 1. caller就是target本身,有权限 // 2. caller是target的某一层owner,有权限 // 否则都算无权限 - async fn check_permission(context: &mut ExecuteContext, target: &ObjectId) -> BuckyResult { + async fn check_permission( + context: &mut ExecuteContext, + target: &ObjectId, + ) -> BuckyResult { let caller = context.caller().id().clone(); let mut check = target.clone(); loop { @@ -321,15 +405,24 @@ impl TxExecutor { let obj = AnyNamedObject::try_from(desc)?; // 一直找到没有owner的对象为止 if obj.owner().is_none() { - return Ok(false) + return Ok(false); } check = obj.owner().as_ref().unwrap().clone(); } } - pub async fn execute_set_benefi_tx(&self, context: &mut ExecuteContext, _fee_counter: &mut context::FeeCounter, tx: &SetBenefiTx) -> BuckyResult<()> { + pub async fn execute_set_benefi_tx( + &self, + context: &mut ExecuteContext, + _fee_counter: &mut context::FeeCounter, + tx: &SetBenefiTx, + ) -> BuckyResult<()> { // 获取address现在的受益人信息, 没有受益人的情况,benefi是address自己 - let benefi = context.ref_state().to_rc()?.get_beneficiary(&tx.address).await?; + let benefi = context + .ref_state() + .to_rc()? + .get_beneficiary(&tx.address) + .await?; // 检查caller是不是受益人,或者受益人的owner let mut permission = Self::check_permission(context, &benefi).await?; @@ -340,14 +433,20 @@ impl TxExecutor { } if !permission { - return Err(BuckyError::new(BuckyErrorCode::PermissionDenied, "caller is not benefi or benefi`s owner")); + return Err(BuckyError::new( + BuckyErrorCode::PermissionDenied, + "caller is not benefi or benefi`s owner", + )); } // 修改benefi - context.ref_state().to_rc()?.set_beneficiary(&tx.address, &tx.to).await?; + context + .ref_state() + .to_rc()? + .set_beneficiary(&tx.address, &tx.to) + .await?; Ok(()) } - } //Ok(feed),Error(errno) diff --git a/src/meta/cyfs-meta/src/executor/tx_proc/desc_proc.rs b/src/meta/cyfs-meta/src/executor/tx_proc/desc_proc.rs index 6130659d7..713a45b13 100644 --- a/src/meta/cyfs-meta/src/executor/tx_proc/desc_proc.rs +++ b/src/meta/cyfs-meta/src/executor/tx_proc/desc_proc.rs @@ -55,35 +55,60 @@ impl TxExecutor { let desc = ret.unwrap(); if desc.hash()? != tx.desc_hash { - error!("data hash mismatch!, except {}, actual {}", &tx.desc_hash, desc.hash()?); + error!( + "data hash mismatch!, except {}, actual {}", + &tx.desc_hash, + desc.hash()? + ); error!("tx data hex: {}", hex::encode(data)); error!("rust data hex: {}", hex::encode(desc.to_vec().unwrap())); return Err(meta_err!(ERROR_HASH_ERROR)); } let objid = context::id_from_desc(&desc); + let state_ref = context.ref_state().to_rc()?; + + if let SavedMetaObject::Group(group) = &desc { + group + .is_update_valid(None, &PeopleQuerier(state_ref.clone())) + .await?; + } //TODO:是否需要检测已经objid 已经有余额存在? // 是否需要校验caller能否创建desc? - context - .ref_state() - .to_rc()? - .create_obj_desc(&objid, &desc) - .await?; + state_ref.create_obj_desc(&objid, &desc).await?; if let SavedMetaObject::File(file) = &desc { if file.desc().owner().is_some() { - context.ref_state().to_rc()?.set_beneficiary(&file.desc().calculate_id(), file.desc().owner().as_ref().unwrap()).await?; + context + .ref_state() + .to_rc()? + .set_beneficiary( + &file.desc().calculate_id(), + file.desc().owner().as_ref().unwrap(), + ) + .await?; } } else if let SavedMetaObject::Data(data) = &desc { if let Ok(obj) = AnyNamedObject::clone_from_slice(data.data.as_slice()) { if obj.owner().is_some() { - context.ref_state().to_rc()?.set_beneficiary(&obj.calculate_id(), obj.owner().as_ref().unwrap()).await?; + context + .ref_state() + .to_rc()? + .set_beneficiary(&obj.calculate_id(), obj.owner().as_ref().unwrap()) + .await?; } } } else if let SavedMetaObject::Device(device) = &desc { if device.desc().owner().is_some() { - context.ref_state().to_rc()?.set_beneficiary(&device.desc().calculate_id(), device.desc().owner().as_ref().unwrap()).await?; + context + .ref_state() + .to_rc()? + .set_beneficiary( + &device.desc().calculate_id(), + device.desc().owner().as_ref().unwrap(), + ) + .await?; } } @@ -160,11 +185,29 @@ impl TxExecutor { // return Err(ERROR_ACCESS_DENIED); //} - let mut rent_state = context.ref_state().to_rc()?.get_desc_extra(&objid).await?; + let state_ref = context.ref_state().to_rc()?; + + let mut rent_state = state_ref.get_desc_extra(&objid).await?; if rent_state.rent_arrears > 0 { return Err(crate::meta_err!(ERROR_RENT_ARREARS)); } + if let SavedMetaObject::Group(group) = &desc { + let latest_group = state_ref.get_obj_desc(&objid).await?; + if let SavedMetaObject::Group(latest_group) = latest_group { + group + .is_update_valid(Some(&latest_group), &PeopleQuerier(state_ref.clone())) + .await?; + } else { + let msg = format!( + "Attempt get group({}) from different type.", + group.desc().object_id() + ); + log::warn!("{}", msg); + return Err(BuckyError::new(BuckyErrorCode::Unmatch, msg)); + } + } + context .ref_state() .to_rc()? @@ -278,3 +321,19 @@ impl TxExecutor { Ok(()) } } + +struct PeopleQuerier(StateRef); + +#[async_trait::async_trait] +impl MemberQuerier for PeopleQuerier { + async fn get_people(&self, people_id: &PeopleId) -> BuckyResult { + let obj = self.0.get_obj_desc(people_id.object_id()).await?; + if let SavedMetaObject::People(people) = obj { + Ok(people) + } else { + let msg = format!("Attempt get people({}) from different type.", people_id); + log::warn!("{}", msg); + Err(BuckyError::new(BuckyErrorCode::Unmatch, msg)) + } + } +} diff --git a/src/meta/cyfs-meta/src/executor/view.rs b/src/meta/cyfs-meta/src/executor/view.rs index 25531458a..47e3c8a7d 100644 --- a/src/meta/cyfs-meta/src/executor/view.rs +++ b/src/meta/cyfs-meta/src/executor/view.rs @@ -1,38 +1,39 @@ -use cyfs_base_meta::*; -use crate::state_storage::{StateRef, StateWeakRef}; use super::context; -use crate::{ViewBalanceResult, State}; +use crate::State; use crate::executor::context::AccountMethods; -use crate::helper::{ArcWeakHelper}; -use cyfs_base::*; +use crate::helper::ArcWeakHelper; use crate::meta_backend::MetaBackend; -use evm::executor::{MemoryStackState, StackSubstateMetadata, StackExecutor}; use crate::stat::Stat; +use crate::state_storage::{StateRef, StateWeakRef}; +use cyfs_base::{BuckyResult, RawConvertTo, ObjectId}; +use cyfs_base_meta::*; +use evm::executor::{MemoryStackState, StackExecutor, StackSubstateMetadata}; -struct ViewExecuteContext { - -} - -impl ViewExecuteContext { +struct ViewExecuteContext {} -} +impl ViewExecuteContext {} pub struct ViewMethodExecutor { method: M, ref_state: StateWeakRef, stat: Option, block: BlockDesc, - evm_config: evm::Config + evm_config: evm::Config, } -impl ViewMethodExecutor { - pub fn new(block: &BlockDesc, ref_state: &StateRef, stat: Option, method: M) -> ViewMethodExecutor { +impl ViewMethodExecutor { + pub fn new( + block: &BlockDesc, + ref_state: &StateRef, + stat: Option, + method: M, + ) -> ViewMethodExecutor { ViewMethodExecutor { method, ref_state: StateRef::downgrade(ref_state), stat, block: block.clone(), - evm_config: evm::Config::istanbul(), // 先把evm的config创建在这里,以后能自己设置了,应该是外边传进来的 + evm_config: evm::Config::istanbul(), // 先把evm的config创建在这里,以后能自己设置了,应该是外边传进来的 } } } @@ -48,14 +49,18 @@ impl ViewMethodExecutor { vec.push((*ctid, account.balance_of(ctid).await?)); } result = ViewBalanceResult::Single(vec) - }, + } AccountMethods::Union(u) => { let mut vec = vec![]; for ctid in &self.method.ctid { - vec.push((*ctid, u.get_union_balance(ctid).await?, u.get_deviation_seq(ctid).await?)); + vec.push(( + *ctid, + u.get_union_balance(ctid).await?, + u.get_deviation_seq(ctid).await?, + )); } result = ViewBalanceResult::Union(vec) - }, + } } Ok(result) @@ -76,7 +81,10 @@ impl ViewMethodExecutor { impl ViewMethodExecutor { pub async fn exec(&self) -> BuckyResult<::Result> { - self.ref_state.to_rc()?.get_name_info(&self.method.name).await + self.ref_state + .to_rc()? + .get_name_info(&self.method.name) + .await } } @@ -84,29 +92,27 @@ impl ViewMethodExecutor { impl ViewMethodExecutor { pub async fn exec(&self) -> BuckyResult<::Result> { let ret = match self.ref_state.to_rc()?.get_obj_desc(&self.method.id).await { - Ok(obj) => { - match obj { - SavedMetaObject::Device(obj) => Ok(obj.to_vec()?), - SavedMetaObject::People(obj) => Ok(obj.to_vec()?), - SavedMetaObject::UnionAccount(obj) => Ok(obj.to_vec()?), - SavedMetaObject::Group(obj) => Ok(obj.to_vec()?), - SavedMetaObject::File(obj) => Ok(obj.to_vec()?), - SavedMetaObject::Data(obj) => Ok(obj.data), - SavedMetaObject::Org(obj) => Ok(obj.to_vec()?), - SavedMetaObject::MinerGroup(obj) => Ok(obj.to_vec()?), - SavedMetaObject::SNService(obj) => Ok(obj.to_vec()?), - SavedMetaObject::Contract(obj) => Ok(obj.to_vec()?), + Ok(obj) => match obj { + SavedMetaObject::Device(obj) => Ok(obj.to_vec()?), + SavedMetaObject::People(obj) => Ok(obj.to_vec()?), + SavedMetaObject::UnionAccount(obj) => Ok(obj.to_vec()?), + SavedMetaObject::Group(obj) => Ok(obj.to_vec()?), + SavedMetaObject::File(obj) => Ok(obj.to_vec()?), + SavedMetaObject::Data(obj) => Ok(obj.data), + SavedMetaObject::MinerGroup(obj) => Ok(obj.to_vec()?), + SavedMetaObject::SNService(obj) => Ok(obj.to_vec()?), + SavedMetaObject::Contract(obj) => Ok(obj.to_vec()?), + SavedMetaObject::SimpleGroup => { + panic!("SimpleGroup is deprecated, you can use the Group.") } + SavedMetaObject::Org => panic!("Org is deprecated, you can use the Group."), }, - Err(e) => { - Err(e) - } + Err(e) => Err(e), }; if let Some(stat) = &self.stat { stat.query_desc(&self.method.id, ret.is_ok()); } ret - } } @@ -119,37 +125,54 @@ impl ViewMethodExecutor { &self.block, ObjectId::default(), None, - self.evm_config.clone() + self.evm_config.clone(), ); // 这里的gas_limit要怎么设置?为了避免出问题,这里设置一个定值 let view_gas_limit = 100000; - let config= evm::Config::istanbul(); - let state = MemoryStackState::new(StackSubstateMetadata::new(view_gas_limit, &config), &backend); + let config = evm::Config::istanbul(); + let state = MemoryStackState::new( + StackSubstateMetadata::new(view_gas_limit, &config), + &backend, + ); let mut executor = StackExecutor::new(state, &config); - let (ret, value) = executor.transact_call(ObjectId::default(), self.method.address, 0, self.method.data.clone(), view_gas_limit); + let (ret, value) = executor.transact_call( + ObjectId::default(), + self.method.address, + 0, + self.method.data.clone(), + view_gas_limit, + ); Ok(ViewContractResult { ret: evm_reason_to_code(ret) as u32, - value + value, }) } } impl ViewMethodExecutor { pub async fn exec(&self) -> BuckyResult<::Result> { - let benefi = self.ref_state.to_rc()?.get_beneficiary(&self.method.address).await?; - Ok(ViewBenefiResult{ - address: benefi - }) + let benefi = self + .ref_state + .to_rc()? + .get_beneficiary(&self.method.address) + .await?; + Ok(ViewBenefiResult { address: benefi }) } } impl ViewMethodExecutor { pub async fn exec(&self) -> BuckyResult<::Result> { - let logs = self.ref_state.to_rc()?.get_log(&self.method.address, self.method.from, self.method.to, &self.method.topics).await?; - Ok(ViewLogResult{ - logs - }) + let logs = self + .ref_state + .to_rc()? + .get_log( + &self.method.address, + self.method.from, + self.method.to, + &self.method.topics, + ) + .await?; + Ok(ViewLogResult { logs }) } } - diff --git a/src/meta/cyfs-meta/src/state_storage/state.rs b/src/meta/cyfs-meta/src/state_storage/state.rs index 8121eefcf..71f58d8f3 100644 --- a/src/meta/cyfs-meta/src/state_storage/state.rs +++ b/src/meta/cyfs-meta/src/state_storage/state.rs @@ -19,9 +19,10 @@ pub struct DescExtra { pub enum AccountInfo { People(PeopleDesc), Device(DeviceDesc), - Group(SimpleGroupDesc), + SimpleGroup, Union(UnionAccountDesc), MinerGroup(MinerGroup), + Group(GroupDesc), } impl AccountInfo { @@ -33,8 +34,8 @@ impl AccountInfo { Self::Device(desc) => { desc.calculate_id() } - Self::Group(desc) => { - desc.calculate_id() + Self::SimpleGroup => { + panic!("SimpleGroup is deprecated, you can use the Group.") } Self::Union(desc) => { desc.calculate_id() @@ -42,6 +43,9 @@ impl AccountInfo { Self::MinerGroup(group) => { group.desc().calculate_id() } + Self::Group(desc) => { + desc.calculate_id() + } } } @@ -62,6 +66,9 @@ impl AccountInfo { Self::MinerGroup(_) => { Err(BuckyError::new(BuckyErrorCode::Failed, "Failed")) } + Self::SimpleGroup => { + panic!("SimpleGroup is deprecated, you can use the Group.") + } } } } diff --git a/src/service/ood-daemon/src/config/system_config.rs b/src/service/ood-daemon/src/config/system_config.rs index ceb3344e9..e88a5f0aa 100644 --- a/src/service/ood-daemon/src/config/system_config.rs +++ b/src/service/ood-daemon/src/config/system_config.rs @@ -3,6 +3,7 @@ use super::version::{ServiceListVersion, ServiceVersion}; use crate::repo::REPO_MANAGER; use cyfs_base::*; use cyfs_util::TomlHelper; +use super::monitor::SystemConfigMonitor; use std::path::Path; use std::str::FromStr; diff --git a/src/tests/group-example/Cargo.toml b/src/tests/group-example/Cargo.toml new file mode 100644 index 000000000..2cf31aba9 --- /dev/null +++ b/src/tests/group-example/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "group-example" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +async-trait = "0.1.53" +async-std = '1.11' +log = '0.4' +serde_json = '1.0' +futures = '0.3.25' +serde = { version = '1.0', features = ['derive'] } +prost = { version = '0.11.5' } +protobuf = { version = '2', features = ['with-bytes'] } +lazy_static = '1.4' +sha2 = { version = '0.8' } +async-recursion = '1.0' +rand = '0.8.5' +cyfs-base = { path = '../../component/cyfs-base'} +cyfs-core = { path = '../../component/cyfs-core' } +cyfs-bdt = { path = '../../component/cyfs-bdt' } +cyfs-debug = { path = "../../component/cyfs-debug" } +cyfs-lib = { path = '../../component/cyfs-lib' } +cyfs-stack = { path = '../../component/cyfs-stack' } +cyfs-chunk-lib = { path = '../../component/cyfs-chunk-lib' } +cyfs-group-lib = { path = '../../component/cyfs-group-lib' } +cyfs-meta-lib = { path = '../../component/cyfs-meta-lib' } +cyfs-bdt-ext = { path = '../../component/cyfs-bdt-ext' } +cyfs-util = { path = '../../component/cyfs-util' } \ No newline at end of file diff --git a/src/tests/group-example/src/main.rs b/src/tests/group-example/src/main.rs new file mode 100644 index 000000000..a56ebdc64 --- /dev/null +++ b/src/tests/group-example/src/main.rs @@ -0,0 +1,1444 @@ +use std::{clone, collections::HashSet, sync::Arc, time::Duration}; + +use async_std::sync::Mutex; +use cyfs_base::{ + AccessString, AnyNamedObject, NamedObject, ObjectDesc, ObjectId, RawConvertTo, RawDecode, + RawFrom, TypelessCoreObject, +}; +use cyfs_core::{ + DecApp, DecAppId, DecAppObj, GroupProposal, GroupProposalObject, GroupRPath, Text, TextObj, +}; +use cyfs_group_lib::GroupManager; +use cyfs_lib::{ + CyfsStackRequestorType, DeviceZoneCategory, DeviceZoneInfo, NONObjectInfo, + NONOutputRequestCommon, NONPutObjectOutputRequest, NamedObjectCachePutObjectRequest, + NamedObjectStorageCategory, RequestProtocol, RequestSourceInfo, SharedCyfsStack, +}; +use cyfs_stack::CyfsStack; +use Common::{create_stack, EXAMPLE_RPATH}; +use GroupDecService::DecService; + +use crate::{ + Common::{init_admins, init_group, init_members, EXAMPLE_APP_NAME, EXAMPLE_VALUE_PATH}, + GroupDecService::MyRPathDelegate, +}; + +/** + * Build the group for test + * .\desc-tool create people --savepath=test-group/admins --idfile=test-group/admins/people-1.id + * .\desc-tool sign 67fz8e9wt6Yqb9ex61tr2C1PwCqavBLFe153wfiVAhqQ.desc -s=test-group/admins/people-1.sec -t=test-group/admins/people-1.desc -dba + * .\desc-tool sign 67fz8e9wt6Yqb9ex61tr2C1PwCqavBLFe153wfiVAhqQ.desc -s=test-group/admins/people-2.sec -t=test-group/admins/people-2.desc -dba + * .\desc-tool sign 67fz8e9wt6Yqb9ex61tr2C1PwCqavBLFe153wfiVAhqQ.desc -s=test-group/admins/people-3.sec -t=test-group/admins/people-3.desc -dba + * .\desc-tool sign 67fz8e9wt6Yqb9ex61tr2C1PwCqavBLFe153wfiVAhqQ.desc -s=test-group/admins/people-4.sec -t=test-group/admins/people-4.desc -dba + * .\desc-tool sign 67fz8e9wt6Yqb9ex61tr2C1PwCqavBLFe153wfiVAhqQ.desc -s=test-group/members/people-1.sec -t=test-group/members/people-1.desc -dba + * .\desc-tool sign 67fz8e9wt6Yqb9ex61tr2C1PwCqavBLFe153wfiVAhqQ.desc -s=test-group/members/people-2.sec -t=test-group/members/people-2.desc -dba + * .\desc-tool sign 67fz8e9wt6Yqb9ex61tr2C1PwCqavBLFe153wfiVAhqQ.desc -s=test-group/members/people-3.sec -t=test-group/members/people-3.desc -dba + * .\desc-tool sign 67fz8e9wt6Yqb9ex61tr2C1PwCqavBLFe153wfiVAhqQ.desc -s=test-group/members/people-4.sec -t=test-group/members/people-4.desc -dba + * .\desc-tool sign 67fz8e9wt6Yqb9ex61tr2C1PwCqavBLFe153wfiVAhqQ.desc -s=test-group/members/people-5.sec -t=test-group/members/people-5.desc -dba + * .\desc-tool sign 67fz8e9wt6Yqb9ex61tr2C1PwCqavBLFe153wfiVAhqQ.desc -s=test-group/members/people-6.sec -t=test-group/members/people-6.desc -dba + * .\desc-tool sign 67fz8e9wt6Yqb9ex61tr2C1PwCqavBLFe153wfiVAhqQ.desc -s=test-group/members/people-7.sec -t=test-group/members/people-7.desc -dba + * .\desc-tool sign 67fz8e9wt6Yqb9ex61tr2C1PwCqavBLFe153wfiVAhqQ.desc -s=test-group/members/people-8.sec -t=test-group/members/people-8.desc -dba + * .\desc-tool sign 67fz8e9wt6Yqb9ex61tr2C1PwCqavBLFe153wfiVAhqQ.desc -s=test-group/members/people-9.sec -t=test-group/members/people-9.desc -dba + * + * .\cyfs-meta-client.exe putdesc -c=test-group/admins/people-5 -d=test-group/admins/people-5.desc 1 0 + * .\cyfs-meta-client.exe putdesc -c=test-group/admins/people-6 -d=test-group/admins/people-6.desc 1 0 + * .\cyfs-meta-client.exe putdesc -c=test-group/admins/people-7 -d=test-group/admins/people-7.desc 1 0 + * .\cyfs-meta-client.exe putdesc -c=test-group/admins/people-8 -d=test-group/admins/people-8.desc 1 0 + * .\cyfs-meta-client.exe putdesc -c=test-group/members/people-10 -d=test-group/members/people-10.desc 1 0 + * .\cyfs-meta-client.exe putdesc -c=test-group/members/people-11 -d=test-group/members/people-11.desc 1 0 + * .\cyfs-meta-client.exe putdesc -c=test-group/members/people-12 -d=test-group/members/people-12.desc 1 0 + * .\cyfs-meta-client.exe putdesc -c=test-group/members/people-13 -d=test-group/members/people-13.desc 1 0 + * .\cyfs-meta-client.exe putdesc -c=test-group/members/people-14 -d=test-group/members/people-14.desc 1 0 + * .\cyfs-meta-client.exe putdesc -c=test-group/members/people-15 -d=test-group/members/people-15.desc 1 0 + * .\cyfs-meta-client.exe putdesc -c=test-group/members/people-16 -d=test-group/members/people-16.desc 1 0 + * .\cyfs-meta-client.exe putdesc -c=test-group/members/people-17 -d=test-group/members/people-17.desc 1 0 + * .\cyfs-meta-client.exe putdesc -c=test-group/members/people-18 -d=test-group/members/people-18.desc 1 0 + * + * .\cyfs-meta-client.exe putdesc -c=test-group/admins/people-1.desc -d=67fz8e9wt6Yqb9ex61tr2C1PwCqavBLFe153wfiVAhqQ.desc 0 0 + * + * .\desc-tool show -a 67fz8e9wt6Yqb9ex61tr2C1PwCqavBLFe153wfiVAhqQ.desc + * + * .\desc-tool modify 67fz8e9wt6Yqb9ex61tr2C1PwCqavBLFe153wfiVAhqQ.desc --add_admin=5r4MYfFBsQqy4r2LTccK1yyipRTtAjqvhX3GLU2qX3Lo --add_member=5r4MYfFapPzrXhfxWJNZJf4pk5Ncfrx5ax2yumWKZrZj + * .\desc-tool modify 67fz8e9wt6Yqb9ex61tr2C1PwCqavBLFe153wfiVAhqQ.desc --add_ood=5aSixgNLsF6r3qjDKP3XkBwnDRSr5G5hrGRM2v2LnLoA + * .\desc-tool modify 67fz8e9wt6Yqb9ex61tr2C1PwCqavBLFe153wfiVAhqQ.desc --prev_shell=9cfBkPt2RPa3MofMmsAXpq8xHYn8A2xvVPuQiBT4XTp9 -v=3 + * .\desc-tool sign 67fz8e9wt6Yqb9ex61tr2C1PwCqavBLFe153wfiVAhqQ.desc -s= + * +*/ + +mod Common { + use std::{ + fmt::format, io::ErrorKind, net::SocketAddrV4, sync::Arc, thread::sleep, time::Duration, + }; + + use async_std::fs; + use cyfs_base::{ + AnyNamedObject, Area, Device, DeviceCategory, DeviceId, Endpoint, EndpointArea, Group, + GroupMember, IpAddr, NamedObject, ObjectDesc, ObjectLink, People, PrivateKey, Protocol, + RawConvertTo, RawDecode, RawEncode, RawFrom, RsaCPUObjectSigner, Signer, SocketAddr, + StandardObject, TypelessCoreObject, UniqueId, NON_STACK_BDT_VPORT, + NON_STACK_SYNC_BDT_VPORT, SIGNATURE_SOURCE_REFINDEX_OWNER, SIGNATURE_SOURCE_REFINDEX_SELF, + }; + use cyfs_bdt_ext::{BdtStackParams, SNMode}; + use cyfs_chunk_lib::ChunkMeta; + use cyfs_core::{DecApp, DecAppId, ToGroupShell}; + use cyfs_lib::{BrowserSanboxMode, NONObjectInfo, SharedCyfsStack}; + use cyfs_meta_lib::MetaMinerTarget; + use cyfs_stack::{ + CyfsStack, CyfsStackConfigParams, CyfsStackFrontParams, CyfsStackInterfaceParams, + CyfsStackKnownObjects, CyfsStackKnownObjectsInitMode, CyfsStackMetaParams, + CyfsStackNOCParams, CyfsStackParams, + }; + + /** + * |--root + * |--folder1 + * |--folder2 + * |--value-->u64 + */ + + lazy_static::lazy_static! { + pub static ref EXAMPLE_APP_NAME: String = "group-example".to_string(); + pub static ref EXAMPLE_RPATH: String = "rpath-example-7".to_string(); + pub static ref EXAMPLE_VALUE_PATH: String = "/root/folder1/folder2/value".to_string(); + pub static ref STATE_PATH_SEPARATOR: String = "/".to_string(); + } + + fn create_member( + name_prefix: &str, + index: usize, + port: u16, + ) -> ((People, PrivateKey), (Device, PrivateKey)) { + log::info!("create members"); + + let name = format!("{}-{}", name_prefix, index); + let private_key = PrivateKey::generate_rsa(1024).unwrap(); + let device_private_key = PrivateKey::generate_rsa(1024).unwrap(); + let mut owner = + People::new(None, vec![], private_key.public(), None, Some(name), None).build(); + + let mut endpoint = Endpoint::default(); + endpoint.set_protocol(Protocol::Udp); + endpoint + .mut_addr() + .set_ip(IpAddr::V4(std::net::Ipv4Addr::new(192, 168, 100, 120))); + endpoint.mut_addr().set_port(port); + endpoint.set_area(EndpointArea::Wan); + + let mut device = Device::new( + Some(owner.desc().object_id()), + UniqueId::create_with_random(), + vec![endpoint], + vec![], // TODO: 当前版本是否支持无SN? + vec![], + device_private_key.public(), + Area::default(), + DeviceCategory::PC, + ) + .build(); + + owner + .ood_list_mut() + .push(DeviceId::try_from(device.desc().object_id()).unwrap()); + + let signer = RsaCPUObjectSigner::new(private_key.public(), private_key.clone()); + + let owner_desc_hash = owner.desc().raw_hash_value().unwrap(); + let owner_body_hash = owner.body().as_ref().unwrap().raw_hash_value().unwrap(); + let device_desc_hash = device.desc().raw_hash_value().unwrap(); + let device_body_hash = device.body().as_ref().unwrap().raw_hash_value().unwrap(); + + let (owner_desc_signature, owner_body_signature, desc_signature, body_signature) = + async_std::task::block_on(async move { + let owner_desc_signature = signer + .sign( + owner_desc_hash.as_slice(), + &cyfs_base::SignatureSource::RefIndex(SIGNATURE_SOURCE_REFINDEX_SELF), + ) + .await + .unwrap(); + + let owner_body_signature = signer + .sign( + owner_body_hash.as_slice(), + &cyfs_base::SignatureSource::RefIndex(SIGNATURE_SOURCE_REFINDEX_SELF), + ) + .await + .unwrap(); + + let desc_signature = signer + .sign( + device_desc_hash.as_slice(), + &cyfs_base::SignatureSource::RefIndex(SIGNATURE_SOURCE_REFINDEX_OWNER), + ) + .await + .unwrap(); + + let body_signature = signer + .sign( + device_body_hash.as_slice(), + &cyfs_base::SignatureSource::RefIndex(SIGNATURE_SOURCE_REFINDEX_OWNER), + ) + .await + .unwrap(); + + ( + owner_desc_signature, + owner_body_signature, + desc_signature, + body_signature, + ) + }); + + device.signs_mut().set_desc_sign(desc_signature.clone()); + device.signs_mut().set_body_sign(body_signature); + + log::info!( + "people: {:?}/{:?}, device: {:?}, public-key: {:?}, private-key: {:?}, sign: {:?}, object: {:?}", + owner.name().unwrap(), + owner.desc().object_id(), + device.desc().object_id(), + private_key.public().to_hex().unwrap().split_at(32).0, + private_key.to_string(), + desc_signature.to_hex().unwrap(), + owner.body().as_ref().unwrap().raw_hash_value().unwrap().to_hex() + ); + + owner.signs_mut().set_desc_sign(owner_desc_signature); + owner.signs_mut().set_body_sign(owner_body_signature); + ((owner, private_key), (device, device_private_key)) + } + + fn create_group( + founder: &People, + admins: Vec<(&People, &PrivateKey)>, + members: Vec<(&People, &PrivateKey)>, + oods: Vec<&Device>, + ) -> Group { + log::info!("create group"); + + let mut group = Group::new_org(Some(founder.desc().object_id()), Area::default()).build(); + group.check_org_body_content_mut().set_admins( + admins + .iter() + .map(|m| GroupMember::from_member_id(m.0.desc().object_id())) + .collect(), + ); + group.set_members( + members + .iter() + .map(|m| GroupMember::from_member_id(m.0.desc().object_id())) + .collect(), + ); + group.set_ood_list( + oods.iter() + .map(|d| DeviceId::try_from(d.desc().object_id()).unwrap()) + .collect(), + ); + + log::info!("create group: {:?}", group.desc().object_id()); + + let desc_hash = group.desc().raw_hash_value().unwrap(); + let body_hash = group.body().as_ref().unwrap().raw_hash_value().unwrap(); + let signers = [admins, members].concat(); + signers + .into_iter() + .map(|(owner, private_key)| { + let signer = RsaCPUObjectSigner::new(private_key.public(), private_key.clone()); + + async_std::task::block_on(async move { + let desc_signature = signer + .sign( + desc_hash.as_slice(), + &cyfs_base::SignatureSource::Object(ObjectLink { + obj_id: owner.desc().object_id(), + obj_owner: None, + }), + ) + .await + .unwrap(); + + let body_signature = signer + .sign( + body_hash.as_slice(), + &cyfs_base::SignatureSource::Object(ObjectLink { + obj_id: owner.desc().object_id(), + obj_owner: None, + }), + ) + .await + .unwrap(); + (desc_signature, body_signature) + }) + }) + .for_each(|(desc_signature, body_signature)| { + group.signs_mut().push_desc_sign(desc_signature); + group.signs_mut().push_body_sign(body_signature); + }); + + group + } + + // (succ, not-found) + fn check_read_buf(file_path: &str, result: &std::io::Result>) -> (bool, bool) { + match result.as_ref() { + Ok(b) => { + if b.len() == 0 { + (false, true) + } else { + (true, false) + } + } + Err(err) if ErrorKind::NotFound == err.kind() => (false, true), + Err(err) => { + log::warn!("read file {} failed: {:?}", file_path, err); + (false, false) + } + } + } + + async fn init_member_from_dir( + save_path: &str, + name_prefix: &str, + count: usize, + min_port: u16, + ) -> Vec<((People, PrivateKey), (Device, PrivateKey))> { + fs::create_dir_all(save_path) + .await + .expect(format!("create dir {} failed", save_path).as_str()); + + let mut members = vec![]; + + for i in 0..count { + let index = i + 1; + let people_desc_file_path = format!("{}/people-{}.desc", save_path, index); + let people_sec_file_path = format!("{}/people-{}.sec", save_path, index); + let device_desc_file_path = format!("{}/device-{}.desc", save_path, index); + let device_sec_file_path = format!("{}/device-{}.sec", save_path, index); + + let people_desc_r = fs::read(people_desc_file_path.clone()).await; + let people_sec_r = fs::read(people_sec_file_path.clone()).await; + let device_desc_r = fs::read(device_desc_file_path.clone()).await; + let device_sec_r = fs::read(device_sec_file_path.clone()).await; + + let (is_people_desc_succ, is_people_desc_not_found) = + check_read_buf(people_desc_file_path.as_str(), &people_desc_r); + let (is_people_sec_succ, is_people_sec_not_found) = + check_read_buf(people_sec_file_path.as_str(), &people_sec_r); + let (is_device_desc_succ, is_device_desc_not_found) = + check_read_buf(device_desc_file_path.as_str(), &device_desc_r); + let (is_device_sec_succ, is_device_sec_not_found) = + check_read_buf(device_sec_file_path.as_str(), &device_sec_r); + + if is_people_desc_succ + && is_people_sec_succ + && is_device_desc_succ + && is_device_sec_succ + { + // decode + let people_desc = People::raw_decode(people_desc_r.unwrap().as_slice()) + .expect(format!("decode file {} failed", people_desc_file_path).as_str()) + .0; + let people_sec = PrivateKey::raw_decode(people_sec_r.unwrap().as_slice()) + .expect(format!("decode file {} failed", people_sec_file_path).as_str()) + .0; + let device_desc = Device::raw_decode(device_desc_r.unwrap().as_slice()) + .expect(format!("decode file {} failed", device_desc_file_path).as_str()) + .0; + let device_sec = PrivateKey::raw_decode(device_sec_r.unwrap().as_slice()) + .expect(format!("decode file {} failed", device_sec_file_path).as_str()) + .0; + members.push(((people_desc, people_sec), (device_desc, device_sec))); + } else if is_people_desc_not_found + && is_people_sec_not_found + && is_device_desc_not_found + && is_device_sec_not_found + { + // create & save + let member = create_member(name_prefix, index, min_port + i as u16); + fs::write( + people_desc_file_path.as_str(), + member.0 .0.to_vec().unwrap(), + ) + .await + .expect(format!("save file {} failed", people_desc_file_path).as_str()); + fs::write(people_sec_file_path.as_str(), member.0 .1.to_vec().unwrap()) + .await + .expect(format!("save file {} failed", people_sec_file_path).as_str()); + fs::write( + device_desc_file_path.as_str(), + member.1 .0.to_vec().unwrap(), + ) + .await + .expect(format!("save file {} failed", device_desc_file_path).as_str()); + fs::write(device_sec_file_path.as_str(), member.1 .1.to_vec().unwrap()) + .await + .expect(format!("save file {} failed", device_sec_file_path).as_str()); + + members.push(member); + } else { + println!("read members failed!"); + std::process::exit(-1); + } + } + + members + } + + pub async fn init_admins() -> Vec<((People, PrivateKey), (Device, PrivateKey))> { + let min_port = 30217_u16; + init_member_from_dir("./test-group/admins", "admin", 5, min_port).await + } + + pub async fn init_members() -> Vec<((People, PrivateKey), (Device, PrivateKey))> { + let min_port = 31217_u16; + init_member_from_dir("./test-group/members", "member", 10, min_port).await + } + + pub async fn init_group( + admins: Vec<(&People, &PrivateKey)>, + members: Vec<(&People, &PrivateKey)>, + oods: Vec<&Device>, + ) -> Group { + fs::create_dir_all("./test-group") + .await + .expect("create dir ./test-group failed"); + + let read_group_r = fs::read("./test-group/group.desc").await; + match read_group_r { + Ok(buf) => { + if buf.len() > 0 { + return Group::raw_decode(buf.as_slice()) + .expect("decode ./test-group/group.desc failed") + .0; + } + } + Err(err) => { + if ErrorKind::NotFound != err.kind() { + println!("read group failed: {:?}", err); + std::process::exit(-1); + } + } + } + + let group = create_group(admins.get(0).unwrap().0, admins, members, oods); + fs::write("./test-group/group.desc", group.to_vec().unwrap()) + .await + .expect("save file ./test-group/group.desc failed"); + group + } + + fn init_stack_params( + people: &People, + private_key: &PrivateKey, + device: &Device, + admins: Vec<(People, Device)>, + members: Vec<(People, Device)>, + group: &Group, + dec_app: &DecApp, + rpc_port: u16, + ws_port: u16, + ) -> Box<(BdtStackParams, CyfsStackParams, CyfsStackKnownObjects)> { + log::info!("init_stack_params"); + + let mut admin_device: Vec = admins.iter().map(|m| m.1.clone()).collect(); + let mut member_device: Vec = members.iter().map(|m| m.1.clone()).collect(); + let known_device = vec![admin_device, member_device].concat(); + + let bdt_param = BdtStackParams { + device: device.clone(), + tcp_port_mapping: vec![], + secret: private_key.clone(), + known_sn: vec![], + known_device, + known_passive_pn: vec![], + udp_sn_only: None, + sn_mode: SNMode::Normal, + ping_interval: None, + }; + + let stack_param = CyfsStackParams { + config: CyfsStackConfigParams { + isolate: Some(device.desc().object_id().to_string()), + sync_service: false, + shared_stack: true, + perf_service: false, + }, + noc: CyfsStackNOCParams {}, + interface: CyfsStackInterfaceParams { + bdt_listeners: vec![NON_STACK_BDT_VPORT, NON_STACK_SYNC_BDT_VPORT], + tcp_listeners: vec![SocketAddr::V4(SocketAddrV4::new( + std::net::Ipv4Addr::new(127, 0, 0, 1), + rpc_port, + ))], + ws_listener: Some(SocketAddr::V4(SocketAddrV4::new( + std::net::Ipv4Addr::new(127, 0, 0, 1), + ws_port, + ))), + }, + meta: CyfsStackMetaParams { + target: MetaMinerTarget::Dev, + }, + front: CyfsStackFrontParams { + enable: false, + browser_mode: BrowserSanboxMode::None, + }, + }; + + let mut known_objects = CyfsStackKnownObjects { + list: vec![], + mode: CyfsStackKnownObjectsInitMode::Sync, + }; + + for (member, device) in admins.iter() { + known_objects.list.push(NONObjectInfo::new( + member.desc().object_id(), + member.to_vec().unwrap(), + Some(Arc::new(AnyNamedObject::Standard(StandardObject::People( + member.clone(), + )))), + )); + + known_objects.list.push(NONObjectInfo::new( + device.desc().object_id(), + device.to_vec().unwrap(), + Some(Arc::new(AnyNamedObject::Standard(StandardObject::Device( + device.clone(), + )))), + )); + } + + for (member, device) in members.iter() { + known_objects.list.push(NONObjectInfo::new( + member.desc().object_id(), + member.to_vec().unwrap(), + Some(Arc::new(AnyNamedObject::Standard(StandardObject::People( + member.clone(), + )))), + )); + + known_objects.list.push(NONObjectInfo::new( + device.desc().object_id(), + device.to_vec().unwrap(), + Some(Arc::new(AnyNamedObject::Standard(StandardObject::Device( + device.clone(), + )))), + )); + } + + known_objects.list.push(NONObjectInfo::new( + group.desc().object_id(), + group.to_vec().unwrap(), + Some(Arc::new(AnyNamedObject::Standard(StandardObject::Group( + group.clone(), + )))), + )); + + let group_shell = group.to_shell(); + let group_shell_vec = group_shell.to_vec().unwrap(); + let typeless = TypelessCoreObject::clone_from_slice(group_shell_vec.as_slice()).unwrap(); + known_objects.list.push(NONObjectInfo::new( + group_shell.shell_id(), + group_shell_vec, + Some(Arc::new(AnyNamedObject::Core(typeless))), + )); + + let dec_app_vec = dec_app.to_vec().unwrap(); + let typeless = TypelessCoreObject::clone_from_slice(dec_app_vec.as_slice()).unwrap(); + known_objects.list.push(NONObjectInfo::new( + dec_app.desc().object_id(), + dec_app_vec, + Some(Arc::new(AnyNamedObject::Core(typeless))), + )); + + Box::new((bdt_param, stack_param, known_objects)) + } + + pub async fn create_stack( + people: &People, + private_key: &PrivateKey, + device: &Device, + admins: Vec<(People, Device)>, + members: Vec<(People, Device)>, + group: &Group, + dec_app: &DecApp, + rpc_port: u16, + ws_port: u16, + ) -> (Box, Box) { + let params = init_stack_params( + people, + private_key, + device, + admins, + members, + group, + dec_app, + rpc_port, + ws_port, + ); + + log::info!("cyfs-stack.open"); + + let stack = Box::new( + CyfsStack::open(params.0, params.1, params.2) + .await + .map_err(|e| { + log::error!("stack start failed: {}", e); + e + }) + .unwrap(), + ); + + async_std::task::sleep(Duration::from_millis(1000)).await; + + let shared_stack = Box::new( + SharedCyfsStack::open_with_port(Some(dec_app.desc().object_id()), rpc_port, ws_port) + .await + .unwrap(), + ); + + shared_stack.wait_online(None).await.unwrap(); + + (stack, shared_stack) + } +} + +mod Client { + // use cyfs_base::ObjectId; + // use cyfs_core::GroupProposal; + // use cyfs_group_lib::RPathClient; + + // pub struct DecClient {} + + // impl DecClient { + // async fn do_something(&self) { + // let rpath_client = RPathClient::new(); + + // let field_path = "/xxx/yyy"; + // let old_value = rpath_client.get_field(field_path).await; + // let param = ObjectId::default(); // param = old_value.value + // let proposal = self.make_proposal(param); + // rpath_client.post_proposal(proposal).await; + // } + + // fn make_proposal(&self, param: ObjectId) -> GroupProposal { + // unimplemented!() + // } + // } +} + +mod GroupDecService { + use std::{collections::HashSet, fmt::format, sync::Arc}; + + use async_std::sync::Mutex; + use cyfs_base::*; + use cyfs_core::{ + CoreObjectType, DecAppId, GroupConsensusBlock, GroupConsensusBlockObject, GroupProposal, + GroupProposalObject, Text, TextObj, + }; + use cyfs_group_lib::{ + DelegateFactory, ExecuteResult, GroupManager, GroupObjectMapProcessor, RPathDelegate, + RPathService, + }; + use cyfs_lib::{ + CreateObjectMapOption, GlobalStatePathAccessItem, NONAPILevel, NONGetObjectInputRequest, + NONGetObjectOutputRequest, NONInputRequestCommon, NONObjectInfo, NONOutputRequestCommon, + NONPostObjectInputRequest, NONPostObjectInputResponse, RequestGlobalStatePath, + RequestSourceInfo, RootStateOpEnvAccess, RouterHandlerAction, RouterHandlerChain, + RouterHandlerManagerProcessor, RouterHandlerPostObjectRequest, + RouterHandlerPostObjectResult, SharedCyfsStack, + }; + use cyfs_util::EventListenerAsyncRoutine; + + use crate::Common::{EXAMPLE_VALUE_PATH, STATE_PATH_SEPARATOR}; + + pub struct DecService {} + + impl DecService { + pub async fn run( + cyfs_stack: &SharedCyfsStack, + local_name: String, + dec_app_id: DecAppId, + group_id: ObjectId, + rpath: &str, + ) -> GroupManager { + let group_mgr = GroupManager::open( + cyfs_stack.clone(), + Box::new(GroupRPathDelegateFactory { + local_name: local_name.clone(), + stack: cyfs_stack.clone(), + dec_id: dec_app_id.object_id().clone(), + }), + &cyfs_lib::CyfsStackRequestorType::Http, + ) + .await + .unwrap(); + + let filter = format!("obj_type == {}", CoreObjectType::GroupProposal as u16,); + let routine = ProposalListener { + service: group_mgr + .start_rpath_service( + group_id, + rpath.to_string(), + Box::new(MyRPathDelegate::new( + local_name.to_string(), + cyfs_stack.clone(), + dec_app_id.object_id().clone(), + )), + ) + .await + .unwrap(), + local_name, + }; + + let req_path = RequestGlobalStatePath::new( + Some(dec_app_id.object_id().clone()), + Option::::None, + ) + .format_string(); + + cyfs_stack + .root_state_meta_stub(None, None) + .add_access(GlobalStatePathAccessItem::new( + "group/proposal", + AccessString::full().value(), + )) + .await + .unwrap(); + + cyfs_stack + .router_handlers() + .post_object() + .add_handler( + RouterHandlerChain::Handler, + format!("group-proposal-listener-{}", dec_app_id).as_str(), + 0, + Some(filter), + Some(req_path), + RouterHandlerAction::Pass, + Some(Box::new(routine)), + ) + .await + .unwrap(); + + group_mgr + } + } + + pub struct ProposalListener { + service: RPathService, + local_name: String, + } + + #[async_trait::async_trait] + impl EventListenerAsyncRoutine + for ProposalListener + { + async fn call( + &self, + param: &RouterHandlerPostObjectRequest, + ) -> BuckyResult { + log::info!( + "recv proposal {} from {:?}, local: {}", + param.request.object.object_id, + param.request.common.source.zone, + self.local_name + ); + + let (proposal, remain) = + GroupProposal::raw_decode(param.request.object.object_raw.as_slice())?; + assert_eq!(remain.len(), 0); + + let result = self.service.push_proposal(&proposal).await; + + Ok(RouterHandlerPostObjectResult { + action: RouterHandlerAction::Response, + request: None, + response: Some(result.map(|result| NONPostObjectInputResponse { object: result })), + }) + } + } + + pub struct GroupRPathDelegateFactory { + local_name: String, + stack: SharedCyfsStack, + dec_id: ObjectId, + } + + impl GroupRPathDelegateFactory { + pub fn is_accept( + &self, + group_id: &ObjectId, + rpath: &str, + with_block: Option<&GroupConsensusBlock>, + ) -> bool { + // 由应用定义是否接收该rpath,并启动共识过程,参与该rpath的信息维护 + true + } + } + + #[async_trait::async_trait] + impl DelegateFactory for GroupRPathDelegateFactory { + async fn create_rpath_delegate( + &self, + group_id: &ObjectId, + rpath: &str, + with_block: Option<&GroupConsensusBlock>, + is_new: bool, + ) -> BuckyResult> { + if self.is_accept(group_id, rpath, with_block) { + // 如果接受,就提供该rpath的处理响应对象 + Ok(Box::new(MyRPathDelegate::new( + self.local_name.clone(), + self.stack.clone(), + self.dec_id.clone(), + ))) + } else { + Err(BuckyError::new(BuckyErrorCode::Reject, "")) + } + } + } + + pub struct MyRPathDelegate { + local_name: String, + stack: SharedCyfsStack, + dec_id: ObjectId, + finished_proposals: Arc>>, + } + + impl MyRPathDelegate { + pub fn new(local_name: String, stack: SharedCyfsStack, dec_id: ObjectId) -> Self { + MyRPathDelegate { + local_name, + finished_proposals: Arc::new(Mutex::new(HashSet::new())), + stack, + dec_id, + } + } + } + + impl MyRPathDelegate { + pub async fn execute( + &self, + proposal: &GroupProposal, + prev_state_id: &Option, + object_map_processor: &dyn GroupObjectMapProcessor, + ) -> BuckyResult { + let state_op_env = object_map_processor + .create_sub_tree_op_env(Some(RootStateOpEnvAccess { + path: "".to_string(), + access: AccessPermissions::Full, + })) + .await + .expect(format!("create_sub_tree_op_env failed").as_str()); + let prev_value = match prev_state_id { + Some(prev_state_id) => { + state_op_env.load(prev_state_id.to_owned()).await?; + state_op_env + .get_by_path(EXAMPLE_VALUE_PATH.as_str()) + .await + .expect( + format!("get_by_path {} failed", EXAMPLE_VALUE_PATH.as_str()).as_str(), + ) + } + None => { + state_op_env + .create_new_with_option( + ObjectMapSimpleContentType::Map, + &CreateObjectMapOption::new_with_owner( + proposal.rpath().group_id().clone(), + ), + ) + .await + .expect( + format!("create_new {} failed", EXAMPLE_VALUE_PATH.as_str()).as_str(), + ); + None + } + }; + + let (result_value, result_value_u64) = { + /** + * prev_state_id是一个MAP的操作对象,形式待定,可能就是一个SingleOpEnv,但最好支持多级路径操作 + */ + let prev_value = prev_value.map_or(0, |prev_value| { + let buf = prev_value.data(); + let mut prev_value = [0u8; 8]; + prev_value.copy_from_slice(&buf[..8]); + u64::from_be_bytes(prev_value) + }); + + let delta_buf = proposal.params().as_ref().unwrap().as_slice(); + let mut delta = [0u8; 8]; + delta.copy_from_slice(delta_buf); + let delta = u64::from_be_bytes(delta); + + let value = prev_value + delta; + let result_value = ObjectIdDataBuilder::new() + .data(&value.to_be_bytes()) + .build() + .unwrap(); + (result_value, value) + }; + + let result_state_id = { + let mut obj_map_id = result_value; + for key in EXAMPLE_VALUE_PATH.split('/').rev() { + let key: &str = key.into(); + if key.is_empty() { + continue; + } + + let state_op_env = object_map_processor + .create_single_op_env(Some(RootStateOpEnvAccess { + path: "".to_string(), + access: AccessPermissions::Full, + })) + .await + .expect(format!("create_sub_tree_op_env failed").as_str()); + + state_op_env + .create_new_with_option( + ObjectMapSimpleContentType::Map, + &CreateObjectMapOption::new_with_owner( + proposal.rpath().group_id().clone(), + ), + ) + .await + .expect(format!("create_new {} failed", key).as_str()); + + state_op_env + .insert_with_key(key, &obj_map_id) + .await + .expect(format!("insert with key {} failed", key).as_str()); + + obj_map_id = state_op_env + .commit() + .await + .expect(format!("commit key {} failed", key).as_str()); + } + obj_map_id + }; + + let receipt = { + /** + * 返回给Client的对象,相当于这个请求的结果或者叫回执? + */ + let text = Text::build("value", "header", format!("{}", result_value_u64)) + .no_create_time() + .build(); + Some(NONObjectInfo::new( + text.desc().object_id(), + text.to_vec().unwrap(), + None, + )) + }; + + let context = { + /** + * 执行请求的上下文,运算过程中可能有验证节点无法得到的上下文信息(比如时间戳,随机数) + */ + Some(Vec::from(result_value_u64.to_le_bytes())) + }; + + /** + * (result_state_id, return_object) = prev_value + proposal + context + */ + Ok(ExecuteResult { + context, + result_state_id: Some(result_state_id), + receipt, + }) + } + + pub async fn verify( + &self, + proposal: &GroupProposal, + prev_state_id: &Option, + object_map_processor: &dyn GroupObjectMapProcessor, + execute_result: &ExecuteResult, + ) -> BuckyResult<()> { + /** + * let is_same = (execute_result.result_state_id, execute_result.return_object) + * == prev_state_id + proposal + execute_result.context + */ + + log::info!( + "verify({}) enter, expect: prev-state: {:?}, {:?}/{:?}/{:?}", + self.stack.local_device_id(), + prev_state_id, + execute_result.result_state_id, + execute_result.context.as_ref().map(|v| v.to_hex()), + execute_result.receipt.as_ref().map(|r| r.object_id), + ); + + let result = self + .execute(proposal, prev_state_id, object_map_processor) + .await?; + + log::info!( + "verify({}) expect: prev-state: {:?}, {:?}/{:?}/{:?}, got: {:?}/{:?}/{:?}", + self.stack.local_device_id(), + prev_state_id, + execute_result.result_state_id, + execute_result.context.as_ref().map(|v| v.to_hex()), + execute_result.receipt.as_ref().map(|r| r.object_id), + result.result_state_id, + result.context.as_ref().map(|v| v.to_hex()), + result.receipt.as_ref().map(|r| r.object_id) + ); + + let is_ok = execute_result.result_state_id == result.result_state_id + && execute_result.context == result.context + && execute_result.receipt.as_ref().map(|r| r.object_id) + == result.receipt.as_ref().map(|r| r.object_id); + + if is_ok { + Ok(()) + } else { + Err(BuckyError::new(BuckyErrorCode::Reject, "result unmatch")) + } + } + } + + #[async_trait::async_trait] + impl RPathDelegate for MyRPathDelegate { + async fn on_execute( + &self, + proposal: &GroupProposal, + prev_state_id: &Option, + object_map_processor: &dyn GroupObjectMapProcessor, + ) -> BuckyResult { + log::info!( + "execute({}) enter, proposal: {}, prev-state: {:?}", + self.stack.local_device_id(), + proposal.desc().object_id(), + prev_state_id, + ); + + let result = self + .execute(proposal, prev_state_id, object_map_processor) + .await?; + + log::info!( + "execute({}), proposal: {}, prev-state: {:?}, result: {:?}", + self.stack.local_device_id(), + proposal.desc().object_id(), + prev_state_id, + result.result_state_id, + ); + + Ok(result) + } + + async fn on_verify( + &self, + proposal: &GroupProposal, + prev_state_id: &Option, + execute_result: &ExecuteResult, + object_map_processor: &dyn GroupObjectMapProcessor, + ) -> BuckyResult<()> { + self.verify( + proposal, + prev_state_id, + object_map_processor, + execute_result, + ) + .await + } + + async fn on_commited( + &self, + prev_state_id: &Option, + block: &GroupConsensusBlock, + object_map_processor: &dyn GroupObjectMapProcessor, + ) { + // 提交到共识链上了,可能有些善后事宜 + + let prev_value = match prev_state_id { + Some(prev_state_id) => { + let state_op_env = object_map_processor + .create_sub_tree_op_env(Some(RootStateOpEnvAccess { + path: "".to_string(), + access: AccessPermissions::Full, + })) + .await + .expect(format!("create_sub_tree_op_env failed").as_str()); + state_op_env + .load(*prev_state_id) + .await + .expect(format!("load {} failed", prev_state_id).as_str()); + state_op_env + .get_by_path(EXAMPLE_VALUE_PATH.as_str()) + .await + .expect( + format!("get_by_path {:?} failed", EXAMPLE_VALUE_PATH.as_str()) + .as_str(), + ) + } + None => None, + } + .map_or(0, |prev_state_id| { + let buf = prev_state_id.data(); + let mut prev_value = [0u8; 8]; + prev_value.copy_from_slice(&buf[..8]); + u64::from_be_bytes(prev_value) + }); + + let result_value = match block.result_state_id() { + Some(result_state_id) => { + let state_op_env = object_map_processor + .create_sub_tree_op_env(Some(RootStateOpEnvAccess { + path: "".to_string(), + access: AccessPermissions::Full, + })) + .await + .expect(format!("create_sub_tree_op_env failed").as_str()); + state_op_env + .load(result_state_id.clone()) + .await + .expect(format!("load {} failed", result_state_id).as_str()); + state_op_env + .get_by_path(EXAMPLE_VALUE_PATH.as_str()) + .await + .expect( + format!("get_by_path {:?} failed", EXAMPLE_VALUE_PATH.as_str()) + .as_str(), + ) + } + None => None, + } + .map_or(0, |result_id| { + let buf = result_id.data(); + let mut result_value = [0u8; 8]; + result_value.copy_from_slice(&buf[..8]); + u64::from_be_bytes(result_value) + }); + + let proposal_infos = + futures::future::join_all(block.proposals().iter().map(|proposal_info| async { + let proposal = self + .stack + .non_service() + .get_object(NONGetObjectOutputRequest { + common: NONOutputRequestCommon { + req_path: None, + source: None, + dec_id: Some(self.dec_id), + level: NONAPILevel::Router, + target: Some(block.owner().clone()), + flags: 0, + }, + object_id: proposal_info.proposal, + inner_path: None, + }) + .await + .unwrap(); + let proposal = proposal.object; + let (proposal, _remain) = + GroupProposal::raw_decode(proposal.object_raw.as_slice()).unwrap(); + + let delta_buf = proposal.params().as_ref().unwrap().as_slice(); + let mut delta = [0u8; 8]; + delta.copy_from_slice(delta_buf); + let delta = u64::from_be_bytes(delta); + (proposal_info.proposal, delta) + })) + .await; + + log::info!( + "proposal commited: height: {}/{}, delta: {:?}, result: {} -> {}, proposal: {:?}, block: {}, local: {}", + block.height(), block.round(), + proposal_infos.iter().map(|(_, delta)| *delta).collect::>(), + prev_value, + result_value, + proposal_infos.iter().map(|(id, _)| *id).collect::>(), + block.block_id(), + self.local_name + ); + + let mut finished_proposals = self.finished_proposals.lock().await; + block.proposals().iter().for_each(|proposal_info| { + let is_new_finished = finished_proposals.insert(proposal_info.proposal); + assert!(is_new_finished); + }); + } + } +} + +fn create_proposal( + delta: u64, + owner: ObjectId, + group_id: ObjectId, + dec_id: ObjectId, +) -> GroupProposal { + GroupProposal::create( + GroupRPath::new(group_id, dec_id, EXAMPLE_RPATH.to_string()), + "add".to_string(), + Some(Vec::from(delta.to_be_bytes())), + None, + None, + owner, + None, + None, + None, + ) + .build() +} + +async fn main_run() { + log::info!("main_run"); + + // async_std::task::sleep(Duration::from_millis(10000)).await; + + cyfs_debug::CyfsLoggerBuilder::new_app(EXAMPLE_APP_NAME.as_str()) + .level("debug") + .console("debug") + .enable_bdt(Some("debug"), Some("debug")) + .build() + .unwrap() + .start(); + + cyfs_debug::PanicBuilder::new(EXAMPLE_APP_NAME.as_str(), EXAMPLE_APP_NAME.as_str()) + .exit_on_panic(true) + .dingtalk_bug_report("any value to disable") + .build() + .start(); + + cyfs_debug::ProcessDeadHelper::instance().enable_exit_on_task_system_dead(None); + + log::info!("will open stacks"); + + let admins = init_admins().await; + let members = init_members().await; + let group = init_group( + admins.iter().map(|m| (&m.0 .0, &m.0 .1)).collect(), + members.iter().map(|m| (&m.0 .0, &m.0 .1)).collect(), + admins.iter().map(|m| &m.1 .0).collect(), + ) + .await; + let group_id = group.desc().object_id(); + let dec_app = DecApp::create( + admins.get(0).unwrap().0 .0.desc().object_id(), + EXAMPLE_APP_NAME.as_str(), + ); + let dec_app_id = DecAppId::try_from(dec_app.desc().object_id()).unwrap(); + + let mut admin_stacks: Vec<(Box, Box)> = vec![]; + let mut admin_group_mgrs: Vec = vec![]; + let mut member_stacks: Vec<(Box, Box)> = vec![]; + let mut member_group_mgrs: Vec = vec![]; + let mut rpc_port = 32217_u16; + let mut ws_port = 33217_u16; + for ((admin, _), (device, private_key)) in admins.iter() { + let (cyfs_stack, shared_stack) = create_stack( + admin, + private_key, + device, + admins + .iter() + .map(|m| (m.0 .0.clone(), m.1 .0.clone())) + .collect(), + members + .iter() + .map(|m| (m.0 .0.clone(), m.1 .0.clone())) + .collect(), + &group, + &dec_app, + rpc_port, + ws_port, + ) + .await; + admin_stacks.push((cyfs_stack, shared_stack)); + rpc_port += 1; + ws_port += 1; + } + + log::info!("stacks for admins has opened."); + + for ((member, _), (device, private_key)) in members.iter() { + let (cyfs_stack, shared_stack) = create_stack( + member, + private_key, + device, + admins + .iter() + .map(|m| (m.0 .0.clone(), m.1 .0.clone())) + .collect(), + members + .iter() + .map(|m| (m.0 .0.clone(), m.1 .0.clone())) + .collect(), + &group, + &dec_app, + rpc_port, + ws_port, + ) + .await; + member_stacks.push((cyfs_stack, shared_stack)); + rpc_port += 1; + ws_port += 1; + } + + log::info!("stacks for members has opened."); + + async_std::task::sleep(Duration::from_millis(10000)).await; + + for i in 0..admin_stacks.len() { + let (_, shared_stack) = admin_stacks.get(i).unwrap(); + let ((admin, _), (admin_device, _)) = admins.get(i).unwrap(); + let local_name = admin.name().unwrap(); + + log::info!( + "will start service, admin: {}, name: {}, device: {}", + admin.desc().object_id(), + local_name, + admin_device.desc().object_id() + ); + + let group_mgr = DecService::run( + &shared_stack, + local_name.to_string(), + dec_app_id.clone(), + group_id, + EXAMPLE_RPATH.as_str(), + ) + .await; + + admin_group_mgrs.push(group_mgr); + } + + log::info!("test dec-service for admins has opened."); + + for i in 0..member_stacks.len() { + let (_, shared_stack) = member_stacks.get(i).unwrap(); + let ((member, _), _) = members.get(i).unwrap(); + let group_mgr = GroupManager::open_as_client( + shared_stack.as_ref().clone(), + &CyfsStackRequestorType::WebSocket, + ) + .await + .unwrap(); + + member_group_mgrs.push(group_mgr); + } + + log::info!("test dec-client for members has opened."); + + // async_std::task::sleep(Duration::from_millis(10000)).await; + + let mut proposals: Vec = vec![]; + + log::info!("proposals will be prepared."); + + let PROPOSAL_COUNT = 20000usize; + for i in 1..PROPOSAL_COUNT { + let (_, stack) = member_stacks.get(i % member_stacks.len()).unwrap(); + let group_mgr = member_group_mgrs.get(i % member_group_mgrs.len()).unwrap(); + let owner = &members.get(i % members.len()).unwrap().0 .0; + let proposal = create_proposal( + i as u64, + owner.desc().object_id(), + group_id, + dec_app_id.object_id().clone(), + ); + + let noc = stack.non_service().clone(); + + let buf = proposal.to_vec().unwrap(); + let proposal_any = Arc::new(AnyNamedObject::Core( + TypelessCoreObject::clone_from_slice(buf.as_slice()).unwrap(), + )); + + let req = NONPutObjectOutputRequest { + common: NONOutputRequestCommon { + req_path: None, + source: None, + dec_id: None, + level: cyfs_lib::NONAPILevel::NOC, + target: None, + flags: 0, + }, + object: NONObjectInfo::new(proposal.desc().object_id(), buf, Some(proposal_any)), + access: Some(AccessString::full()), + }; + noc.put_object(req).await; + proposals.push(proposal); + + let proposal = proposals.get(i - 1).unwrap().clone(); + let stack = member_stacks.get(i % member_stacks.len()).unwrap(); + let group_mgr = member_group_mgrs.get(i % member_group_mgrs.len()).unwrap(); + let ((member, _), _) = members.get(i % members.len()).unwrap(); + let local_name = member.name().map(|n| n.to_string()); + + let client = group_mgr + .rpath_client(group_id, dec_app_id.clone(), &EXAMPLE_RPATH) + .await; + + async_std::task::spawn(async move { + log::info!( + "client {:?} will post proposal {}", + local_name, + proposal.desc().object_id(), + ); + + let result = client.post_proposal(&proposal).await; + let result_text = result.as_ref().map(|obj| { + obj.as_ref().map(|obj| { + Text::raw_decode(obj.object_raw.as_slice()) + .map(|(txt, _)| txt.value().to_string()) + }) + }); + log::info!( + "client {:?} post proposal {}, result: {:?}, result-text: {:?}", + local_name, + proposal.desc().object_id(), + result.as_ref().map(|o| o.as_ref().map(|o| o.object_id)), + result_text + ); + }); + + if i % 1 == 0 { + async_std::task::sleep(Duration::from_millis(4000)).await; + log::info!("will push new proposals, i: {}", i); + } + } + + // futures::future::join_all(prepare_futures).await; + + log::info!("proposals prepared."); + + // for i in 1..PROPOSAL_COUNT {} + + async_std::task::sleep(Duration::from_millis(20000)).await; + + // let client = admin_group_mgrs + // .get(0) + // .unwrap() + // .rpath_client( + // &group.desc().object_id(), + // dec_app_id.object_id(), + // &EXAMPLE_RPATH, + // ) + // .await; + + // let value_obj = client + // .get_by_path(EXAMPLE_VALUE_PATH.as_str()) + // .await + // .unwrap(); + // let buf = value_obj.as_ref().unwrap().object_id.data(); + // let mut value = [0u8; 8]; + // value.copy_from_slice(&buf[..8]); + + // log::info!("value from client is: {}", u64::from_be_bytes(value)); +} + +fn main() { + log::info!("main"); + + cyfs_debug::ProcessDeadHelper::patch_task_min_thread(); + + log::info!("will main-run"); + + let fut = Box::pin(main_run()); + async_std::task::block_on(async move { fut.await }) +} diff --git a/src/tools/cyfs-meta-client/src/main.rs b/src/tools/cyfs-meta-client/src/main.rs index b8cad84ed..ae9178604 100644 --- a/src/tools/cyfs-meta-client/src/main.rs +++ b/src/tools/cyfs-meta-client/src/main.rs @@ -12,7 +12,12 @@ use cyfs_meta_lib::{MetaMinerTarget, MetaClient}; use std::str::FromStr; #[async_std::main] -async fn main() ->BuckyResult<()> { +async fn main() { + let runner_fut = Box::pin(main_run()); + let _ = runner_fut.await; +} + +async fn main_run() ->BuckyResult<()> { simple_logger::SimpleLogger::new().with_level(LevelFilter::Debug).init().unwrap(); let default_target = MetaMinerTarget::default().to_string(); diff --git a/src/tools/desc-tool/Cargo.toml b/src/tools/desc-tool/Cargo.toml index 1a13f5d2d..3cb0aa400 100644 --- a/src/tools/desc-tool/Cargo.toml +++ b/src/tools/desc-tool/Cargo.toml @@ -19,3 +19,4 @@ hex = "0.4" log = "0.4" simple_logger = "2.1" chrono = "0.4" +itertools = '0.10' \ No newline at end of file diff --git a/src/tools/desc-tool/src/create.rs b/src/tools/desc-tool/src/create.rs index 80e3f762f..b00612d7a 100644 --- a/src/tools/desc-tool/src/create.rs +++ b/src/tools/desc-tool/src/create.rs @@ -1,17 +1,30 @@ -use clap::{ArgMatches, App, SubCommand, Arg}; use crate::desc; -use crate::util::{get_objids_from_matches, get_deviceids_from_matches}; -use std::path::{Path}; +use crate::desc::create_people_desc; +use crate::util::{ + get_deviceids_from_matches, get_group_members_from_matches, +}; +use clap::{App, Arg, ArgMatches, SubCommand}; +use cyfs_base::{ + sign_and_set_named_object_desc, Area, DeviceCategory, FileEncoder, + NamedObject, ObjectDesc, ObjectId, RsaCPUObjectSigner, SignatureSource, + SIGNATURE_SOURCE_REFINDEX_OWNER, SIGNATURE_SOURCE_REFINDEX_SELF, BuckyResult, +}; use log::*; -use cyfs_base::{Area, FileEncoder, NamedObject, ObjectDesc, AnyNamedObject, FileDecoder, PublicKeyRef, DeviceCategory, ObjectId, RsaCPUObjectSigner, sign_and_set_named_object_desc, SignatureSource, SIGNATURE_SOURCE_REFINDEX_SELF, SIGNATURE_SOURCE_REFINDEX_OWNER}; -use std::str::FromStr; use std::io::Write; -use crate::desc::create_people_desc; +use std::path::Path; +use std::str::FromStr; +// .\desc-tool.exe create group -F=5r4MYfFfTakY1h6vdEuMurpkawk4MZpB5RmY9CFqSj99 -A=5r4MYfFfTakY1h6vdEuMurpkawk4MZpB5RmY9CFqSj99:;5r4MYfFPPRDNNcJdvve4XVx3FE355PUDpqaA5Mm9UcFh:;5r4MYfFAiXjbEkHZvc1NtHgJkZ4A7LJQcrY7cJeMz5YB:;5r4MYfFKmpMT2u2P13p3bLC6KtGEVsp42X85h5e2onhZ:; -M=5r4MYfF5r9cUfL9JemVXwLWJjufXETYSjfXqEsR3Qwn5:;5r4MYfF8ZaksbXfnZbdjiYJuJv8U4FfvyBgdHq7RiPhY:;5r4MYfFQxUB7okJMvia5yGksrkMBzPrUrwCFgja4Djv3:;5r4MYfFXjPJ9BBYvvdP5QHudAWLNrMzuzZpNpr45pYEc:;5r4MYfFXuCNgbhRPaqtUKsNvNH1RNGF5prFXg7UqiWDS:;5r4MYfFJHxPCYqwLWrHQ24jjv3ZvCbK4dPhBCNn8r3aE:;5r4MYfFXAtLvsW52oCRRAALEt7rEJB7qUdRDEEKAgPJJ:;5r4MYfFdFYt8ytAw9noVjg1aXfeQvHWpaChax73wWKwJ:;5r4MYfFbDWG8jibePJhSoL25mv6tv6ZMDaMZHRzKVEEB:; -l=5aSixgN8tVt1SAM4xBfc1dYvdrU7d5fVeZrzNFpx8FiB;5aSixgMxgNuMQFcG41fW1CN7MTsKMqEuVjW16BnJWrGW;5aSixgN64mtdhmNvKZ681P3iPZbnQPyQsezTFNB2HSdx;5aSixgNS8ij1mkjjNe2UWHVgVYFhr4dJF5BuxxpTb1m8; -n="group" -I="icon" -d="description" -a=0:0:0:0 -O --savepath="./" --idfile="./group.id.txt" pub fn create_subcommand<'a, 'b>() -> App<'a, 'b> { - let id_file_arg = Arg::with_name("id_file").long("idfile").takes_value(true).help("write object id to file"); - let save_path = Arg::with_name("save_path").long("savepath").takes_value(true).help("save file path"); + let id_file_arg = Arg::with_name("id_file") + .long("idfile") + .takes_value(true) + .help("write object id to file"); + let save_path = Arg::with_name("save_path") + .long("savepath") + .takes_value(true) + .help("save file path"); SubCommand::with_name("create").about("create desc") .subcommand(SubCommand::with_name("people").about("create people desc and sec") .arg(Arg::with_name("owner").long("owner").short("o").takes_value(true) @@ -24,15 +37,25 @@ pub fn create_subcommand<'a, 'b>() -> App<'a, 'b> { .arg(Arg::with_name("area").long("area").short("a").takes_value(true) .help("Object area info, if not set,will calc base ip. format [county:carrier:city:inner]")) .arg(id_file_arg.clone()).arg(save_path.clone())) - .subcommand(SubCommand::with_name("sgroup").about("create sgroup desc") - .arg(Arg::with_name("threshold").long("threshold").default_value("1").required(true) - .help("threshold in simple group")) - .arg(Arg::with_name("members").long("members").short("m").value_delimiter(";") - .help("members in simple group")) + .subcommand(SubCommand::with_name("group").about("create group desc") + .arg(Arg::with_name("founder").long("founder").short("F").takes_value(true) + .help("founder of group")) + .arg(Arg::with_name("admins").required(true).long("admins").short("A").value_delimiter(";") + .help("admins in group. format [PeopleId:title]")) + .arg(Arg::with_name("members").long("members").short("M").value_delimiter(";") + .help("members in group. format [PeopleId:title]")) .arg(Arg::with_name("ood_list").long("oodlist").short("l").value_delimiter(";") .help("oods in group")) - .arg(Arg::with_name("owners").long("owners").short("o").value_delimiter(";") - .help("group owners, must have desc file in same path")) + .arg(Arg::with_name("name").long("name").short("n").takes_value(true) + .help("name of group")) + .arg(Arg::with_name("icon").long("icon").short("I").takes_value(true) + .help("icon of group")) + .arg(Arg::with_name("description").long("description").short("d").takes_value(true) + .help("description of group")) + .arg(Arg::with_name("area").required(true).long("area").short("a").takes_value(true) + .help("Object area info. format [county:carrier:city:inner]")) + .arg(Arg::with_name("org").long("org").short("O").takes_value(false) + .help("create a group as organization that administrators is changable.")) .arg(id_file_arg.clone()).arg(save_path.clone())) .subcommand(SubCommand::with_name("device").about("create device desc and sec") .arg(Arg::with_name("area").long("area").short("a").takes_value(true) @@ -71,14 +94,23 @@ fn write_id_file(matches: &ArgMatches, id: &ObjectId) { } fn get_area(matches: &ArgMatches) -> Option { - matches.value_of("area").map(|str_area| { - match Area::from_str(str_area) { + matches + .value_of("area") + .map(|str_area| match Area::from_str(str_area) { Ok(area) => area, Err(_) => { error!("decode area from {} fail, use default", str_area); Area::default() - }, - } + } + }) +} + +fn get_area_no_default(matches: &ArgMatches) -> BuckyResult> { + matches.value_of("area").map_or(Ok(None), |str_area| { + Area::from_str(str_area).map(|a| Some(a)).map_err(|e| { + error!("decode area from {} fail", str_area); + e + }) }) } @@ -88,17 +120,17 @@ fn get_key_bits(matches: &ArgMatches) -> usize { "rsa2048" => 2048, "rsa3072" => 3072, "secp" => 1, - _ => 0 + _ => 0, } } pub async fn create_desc(matches: &ArgMatches<'_>) { match matches.subcommand() { ("device", Some(matches)) => { - let owner = matches.value_of("owner").map(|str| { - ObjectId::from_str(str).unwrap() - }); - let sn_list = get_deviceids_from_matches(matches,"snlist").unwrap_or(vec![]); + let owner = matches + .value_of("owner") + .map(|str| ObjectId::from_str(str).unwrap()); + let sn_list = get_deviceids_from_matches(matches, "snlist").unwrap_or(vec![]); let eps = matches.values_of_lossy("eps").unwrap_or(vec![]); let str_unique_id = matches.value_of("deviceid").unwrap(); @@ -116,78 +148,41 @@ pub async fn create_desc(matches: &ArgMatches<'_>) { "pc" => DeviceCategory::PC, "server" => DeviceCategory::Server, "browser" => DeviceCategory::Browser, - _ => {unreachable!()} + _ => { + unreachable!() + } }; let area = get_area(matches); let save_path = matches.value_of("save_path").unwrap_or("").to_owned(); - if let Some((device, _)) = desc::create_device_desc(area, category, key_bits, str_unique_id, owner, eps, sn_list, Some(save_path)) { + if let Some((device, _)) = desc::create_device_desc( + area, + category, + key_bits, + str_unique_id, + owner, + eps, + sn_list, + Some(save_path), + ) { write_id_file(matches, &device.desc().calculate_id()); } return; } - ("sgroup", Some(matches)) => { - match matches.value_of("threshold").unwrap().parse::() { - Ok(threshold) => { - let owners = if let Some(strs) = matches.values_of_lossy("owners") { - let mut owners = vec![]; - for str in &strs { - if let Ok((obj, _)) = AnyNamedObject::decode_from_file(&Path::new(str).with_extension("desc"), &mut vec![]) { - if let Some(pk) = obj.public_key() { - match pk { - PublicKeyRef::Single(pk) => {owners.push(pk.clone())} - PublicKeyRef::MN((_, pks)) => { - owners.append(&mut pks.clone()) - } - } - } else { - warn!("desc {} not have pubkey, ignore", str); - } - } else { - error!("decode desc file {} fail", str); - } - } - owners - } else { - vec![] - }; - - if threshold as usize > owners.len() { - error!("threshold must small owners count, detail info use --help"); - return; - } - - let group_desc = desc::create_simple_group_desc(threshold - , owners - , get_objids_from_matches(matches, "members") - , get_deviceids_from_matches(matches, "ood_list")); - - let groupid = group_desc.desc().calculate_id(); - let desc_file = Path::new(matches.value_of("save_path").unwrap_or("")).join(&groupid.to_string()).with_extension("desc"); - if let Err(e) = group_desc.encode_to_file(&desc_file, true) { - error!("write imple group desc file failed, err {}", e); - } else { - info!("write simple group desc file succ to {}", desc_file.display()); - write_id_file(matches, &groupid); - }; - }, - Err(_e) => { - error!("threshold must number, detail info use --help"); - return; - } - } - } + ("group", Some(matches)) => create_group_desc(matches).await, ("people", Some(matches)) => { - let owner_id = matches.value_of("owner").map(|str| { - ObjectId::from_str(str).unwrap() - }); + let owner_id = matches + .value_of("owner") + .map(|str| ObjectId::from_str(str).unwrap()); let ood_list = get_deviceids_from_matches(matches, "ood_list").unwrap_or(vec![]); let key_bits = get_key_bits(matches); let area = get_area(matches); let (people, secret) = create_people_desc(area, key_bits, owner_id, ood_list); let objid = people.desc().calculate_id(); - let file_path = Path::new(matches.value_of("save_path").unwrap_or("")).join(&objid.to_string()).with_extension("desc"); + let file_path = Path::new(matches.value_of("save_path").unwrap_or("")) + .join(&objid.to_string()) + .with_extension("desc"); if let Err(e) = people.encode_to_file(&file_path, true) { error!("write people file failed, err {}", e); } else { @@ -208,24 +203,66 @@ pub async fn create_desc(matches: &ArgMatches<'_>) { let people_id = people.desc().calculate_id(); // 再创建ood,使用people为owner - let (mut ood_desc, ood_sec) = desc::create_device_desc(area.clone(), DeviceCategory::OOD, key_bits, "ood", Some(people_id.clone()) - , vec![], vec![], None).unwrap(); + let (mut ood_desc, ood_sec) = desc::create_device_desc( + area.clone(), + DeviceCategory::OOD, + key_bits, + "ood", + Some(people_id.clone()), + vec![], + vec![], + None, + ) + .unwrap(); // 修改people的ood_list people.ood_list_mut().push(ood_desc.desc().device_id()); // 再创建client,使用people为owner - let (mut client_desc, client_sec) = desc::create_device_desc(area, DeviceCategory::PC, key_bits, "client", Some(people_id.clone()) - , vec![], vec![], None).unwrap(); + let (mut client_desc, client_sec) = desc::create_device_desc( + area, + DeviceCategory::PC, + key_bits, + "client", + Some(people_id.clone()), + vec![], + vec![], + None, + ) + .unwrap(); let signer = RsaCPUObjectSigner::new(people_sec.public(), people_sec.clone()); // 给desc签名 - sign_and_set_named_object_desc(&signer, &mut people, &SignatureSource::RefIndex(SIGNATURE_SOURCE_REFINDEX_SELF)).await.unwrap(); - sign_and_set_named_object_desc(&signer, &mut ood_desc, &SignatureSource::RefIndex(SIGNATURE_SOURCE_REFINDEX_OWNER)).await.unwrap(); - sign_and_set_named_object_desc(&signer, &mut client_desc, &SignatureSource::RefIndex(SIGNATURE_SOURCE_REFINDEX_OWNER)).await.unwrap(); + sign_and_set_named_object_desc( + &signer, + &mut people, + &SignatureSource::RefIndex(SIGNATURE_SOURCE_REFINDEX_SELF), + ) + .await + .unwrap(); + sign_and_set_named_object_desc( + &signer, + &mut ood_desc, + &SignatureSource::RefIndex(SIGNATURE_SOURCE_REFINDEX_OWNER), + ) + .await + .unwrap(); + sign_and_set_named_object_desc( + &signer, + &mut client_desc, + &SignatureSource::RefIndex(SIGNATURE_SOURCE_REFINDEX_OWNER), + ) + .await + .unwrap(); let file_path = Path::new(matches.value_of("save_path").unwrap_or("")); // 存储这些对象 let mut postfix = String::from(""); - if Path::new(&file_path.join(format!("device{}", &postfix)).with_extension("desc")).exists() { + if Path::new( + &file_path + .join(format!("device{}", &postfix)) + .with_extension("desc"), + ) + .exists() + { postfix = chrono::Local::now().format("-%F-%H-%M-%S").to_string(); } let people_file = file_path.join(format!("people{}", &postfix)); @@ -254,11 +291,96 @@ pub async fn create_desc(matches: &ArgMatches<'_>) { if let Err(e) = client_sec.encode_to_file(&client_file.with_extension("sec"), false) { error!("write client sec failed, err {}", e); } - } v @ _ => { error!("not support create type {}", v.0); return; } } -} \ No newline at end of file +} + +pub async fn create_group_desc(matches: &ArgMatches<'_>) { + let admins = match get_group_members_from_matches(matches, "admins") { + Ok(admins) => { + match admins { + Some(admins) if admins.len() > 0 => admins, + _ => { + log::error!("empty admins."); + return; + } + } + } + Err(e) => { + log::error!("invalid admins: {}", e.msg()); + return; + } + }; + + let area = match get_area_no_default(matches) { + Ok(area) => match area { + Some(area) => area, + None => { + log::error!("area is expected, detail info use --help"); + return; + } + }, + Err(_) => return, + }; + + let founder = match matches.value_of("founder") { + Some(str) => match ObjectId::from_str(str) { + Ok(id) => Some(id), + Err(_) => { + log::error!("invalid founder: {}", str); + return; + } + }, + None => None, + }; + + let members = match get_group_members_from_matches(matches, "members") { + Ok(members) => members.unwrap_or(vec![]), + Err(e) => { + log::error!("invalid members: {}", e.msg()); + return; + } + }; + + let ood_list = get_deviceids_from_matches(matches, "ood_list").unwrap_or(vec![]); + if ood_list.len() == 0 { + log::error!("no valid ood found."); + return; + } + + let name = matches.value_of("name").map(|s| s.into()); + let icon = matches.value_of("icon").map(|s| s.into()); + let description = matches.value_of("description").map(|s| s.into()); + let is_org = matches.is_present("org"); + + let group_desc = desc::create_group_desc( + founder, + admins, + members, + ood_list, + area, + name, + icon, + description, + is_org, + ); + + let groupid = group_desc.desc().calculate_id(); + let desc_file = Path::new(matches.value_of("save_path").unwrap_or("")) + .join(&groupid.to_string()) + .with_extension("desc"); + if let Err(e) = group_desc.encode_to_file(&desc_file, true) { + error!("write group desc file({:?}) failed, err {}", desc_file, e); + } else { + info!( + "write group({}) desc file succ to {}", + groupid, + desc_file.display() + ); + write_id_file(matches, &groupid); + }; +} diff --git a/src/tools/desc-tool/src/desc.rs b/src/tools/desc-tool/src/desc.rs index 3b43df3ab..42b971d19 100644 --- a/src/tools/desc-tool/src/desc.rs +++ b/src/tools/desc-tool/src/desc.rs @@ -1,16 +1,40 @@ -use std::str::FromStr; -use std::path::Path; -use log::*; use cyfs_base::*; +use log::*; +use std::path::Path; +use std::str::FromStr; - -pub fn create_simple_group_desc(threshold: u8, owners: Vec, members: Option>, ood_list: Option>) -> SimpleGroup -{ - let area_info = Area::default(); - SimpleGroup::new(threshold, owners, members.unwrap_or(vec![]), OODWorkMode::Standalone, ood_list.unwrap_or(vec![]), area_info).build() +pub fn create_group_desc( + founder_id: Option, + admins: Vec, + members: Vec, + oods: Vec, + area: Area, + name: Option, + icon: Option, + description: Option, + is_org: bool, +) -> Group { + let mut group = if is_org { + let mut group = Group::new_org(founder_id, area).build(); + group.check_org_body_content_mut().set_admins(admins); + group + } else { + Group::new_simple_group(founder_id, admins, area).build() + }; + group.set_members(members); + group.set_ood_list(oods); + group.set_name(name); + group.set_icon(icon); + group.set_description(description); + group } -pub fn create_people_desc(area: Option, key_bits: usize, owner: Option, ood_list: Vec) -> (People, PrivateKey) { +pub fn create_people_desc( + area: Option, + key_bits: usize, + owner: Option, + ood_list: Vec, +) -> (People, PrivateKey) { let area_code = match area { Some(v) => v, None => { @@ -25,10 +49,22 @@ pub fn create_people_desc(area: Option, key_bits: usize, owner: Option, category: DeviceCategory, key_bits: usize, unique_id: &str, owner_id: Option, eps: Vec, sn_list: Vec, save_path: Option)->Option<(Device, PrivateKey)> { +pub fn create_device_desc( + area: Option, + category: DeviceCategory, + key_bits: usize, + unique_id: &str, + owner_id: Option, + eps: Vec, + sn_list: Vec, + save_path: Option, +) -> Option<(Device, PrivateKey)> { let area_code = match area { Some(v) => v, None => { @@ -42,11 +78,11 @@ pub fn create_device_desc(area: Option, category: DeviceCategory, key_bits match Endpoint::from_str(s) { Ok(ep) => { ep_objs.push(ep); - }, + } Err(_e) => { error!("ep {} format error", s); return None; - }, + } } } @@ -59,38 +95,47 @@ pub fn create_device_desc(area: Option, category: DeviceCategory, key_bits secret = PrivateKey::generate_rsa(key_bits).unwrap(); } let pubkey = secret.public(); - let peer_desc = Device::new(owner_id, unique, ep_objs, sn_list, vec![], pubkey, area_code, category).build(); + let peer_desc = Device::new( + owner_id, + unique, + ep_objs, + sn_list, + vec![], + pubkey, + area_code, + category, + ) + .build(); let peer_id = peer_desc.desc().calculate_id(); if save_path.is_none() { - return Some((peer_desc, secret)); + return Some((peer_desc, secret)); } - let file_base = Path::new(save_path.unwrap_or("".to_owned()).as_str()).join(&peer_id.to_string()); + let file_base = + Path::new(save_path.unwrap_or("".to_owned()).as_str()).join(&peer_id.to_string()); let secret_file_path = file_base.with_extension("sec"); let desc_file = file_base.with_extension("desc"); match secret.encode_to_file(secret_file_path.as_ref(), true) { Ok(_) => { info!("succ encode secret to {}", secret_file_path.display()); - }, + } Err(e) => { error!("encode secret to file failed, err {}", e); return None; - }, + } } match peer_desc.encode_to_file(desc_file.as_ref(), true) { Ok(_) => { info!("success encode peerdesc to {}", desc_file.display()); Some((peer_desc, secret)) - }, + } Err(e) => { error!("encode peerdesc to file failed, err {}", e); None - }, + } } - - -} \ No newline at end of file +} diff --git a/src/tools/desc-tool/src/main.rs b/src/tools/desc-tool/src/main.rs index 0a12f0f93..901f17fe5 100644 --- a/src/tools/desc-tool/src/main.rs +++ b/src/tools/desc-tool/src/main.rs @@ -25,11 +25,11 @@ fn calc_nonce(peer_desc_file: &str, _bits: u32) { // p.const_info.calc_pow(bits, &mut nonce) Ok(()) }, - StandardObject::SimpleGroup(_p) => { - // TODO: 加calc pow - // p.const_info.calc_pow(bits, &mut nonce) - Ok(()) - }, + // StandardObject::SimpleGroup(_p) => { + // // TODO: 加calc pow + // // p.const_info.calc_pow(bits, &mut nonce) + // Ok(()) + // }, _ => { error!("not support object type"); Err(BuckyError::new(BuckyErrorCode::NotSupport, "")) diff --git a/src/tools/desc-tool/src/modify.rs b/src/tools/desc-tool/src/modify.rs index 53799aaaa..c165e5233 100644 --- a/src/tools/desc-tool/src/modify.rs +++ b/src/tools/desc-tool/src/modify.rs @@ -1,25 +1,153 @@ -use clap::{App, SubCommand, Arg, ArgMatches}; -use crate::util::{get_objids_from_matches, get_eps_from_matches, get_deviceids_from_matches}; +use crate::util::{ + get_deviceids_from_matches, get_eps_from_matches, get_group_members_from_matches, + get_objids_from_matches, +}; +use clap::{App, Arg, ArgMatches, SubCommand}; +use cyfs_base::{ + bucky_time_now, AnyNamedObject, BuckyError, BuckyErrorCode, BuckyResult, DeviceId, FileDecoder, + FileEncoder, FileId, Group, NamedObject, ObjectDesc, ObjectId, OwnerObjectDesc, + StandardObject, RawEncode, +}; +use cyfs_core::{ + AppList, AppListObj, AppStatus, AppStatusObj, CoreObjectType, DecApp, DecAppId, DecAppObj, +}; use log::*; -use cyfs_base::{StandardObject, FileDecoder, FileEncoder, NamedObject, AnyNamedObject, ObjectDesc, ObjectId, OwnerObjectDesc, bucky_time_now}; -use cyfs_core::{CoreObjectType, DecApp, DecAppObj, AppList, AppStatus, AppListObj, AppStatusObj, DecAppId}; +use std::collections::{HashSet}; use std::convert::TryFrom; use std::str::FromStr; pub fn modify_subcommand<'a, 'b>() -> App<'a, 'b> { - SubCommand::with_name("modify").about("modify desc") - .arg(Arg::with_name("desc").required(true).index(1).help("desc file to modify")) - .arg(Arg::with_name("sn").short("s").long("sn").value_delimiter(";").help("new sn list")) - .arg(Arg::with_name("eps").long("eps").short("e").value_delimiter(";").help("new endpoint list")) - .arg(Arg::with_name("members").long("members").short("m").value_delimiter(";").help("members set to simple group")) - .arg(Arg::with_name("add_members").long("add").short("a").value_delimiter(";").help("members append to simple group")) - .arg(Arg::with_name("add_oods").long("add_ood").short("o").value_delimiter(";").help("device id append to people")) - .arg(Arg::with_name("ood_lists").long("ood_lists").short("l").value_delimiter(";").help("device id set to people")) - .arg(Arg::with_name("name").short("n").long("name").takes_value(true).help("people name")) - .arg(Arg::with_name("source").long("source").value_delimiter(";").help("add source to app, {ver}:{id}")) - .arg(Arg::with_name("app_id").long("appid").takes_value(true).help("app id add to app list")) - .arg(Arg::with_name("app_ver").long("appver").takes_value(true).help("app ver add to app list")) - .arg(Arg::with_name("app_status").long("appstart").help("start app, default false")) + SubCommand::with_name("modify") + .about("modify desc") + .arg( + Arg::with_name("desc") + .required(true) + .index(1) + .help("desc file to modify"), + ) + .arg( + Arg::with_name("sn") + .short("s") + .long("sn") + .value_delimiter(";") + .help("new sn list"), + ) + .arg( + Arg::with_name("eps") + .long("eps") + .short("e") + .value_delimiter(";") + .help("new endpoint list"), + ) + .arg( + Arg::with_name("admins") + .long("admins") + .short("A") + .value_delimiter(";") + .help("set administrators to group. format [PeopleId:title]"), + ) + .arg( + Arg::with_name("add_admins") + .long("add_admin") + .value_delimiter(";") + .help("append administrators to group. format [PeopleId:title]"), + ) + .arg( + Arg::with_name("remove_admins") + .long("rm_admin") + .value_delimiter(";") + .help("remove administrators from group. format [PeopleId]"), + ) + .arg( + Arg::with_name("members") + .long("members") + .short("m") + .value_delimiter(";") + .help("set members to group. format [PeopleId:title]"), + ) + .arg( + Arg::with_name("add_members") + .long("add_member") + .value_delimiter(";") + .help("append members to group. format [PeopleId:title]"), + ) + .arg( + Arg::with_name("remove_members") + .long("rm_member") + .value_delimiter(";") + .help("remove members from group. format [PeopleId]"), + ) + .arg( + Arg::with_name("description") + .short("d") + .long("description") + .takes_value(true) + .help("description of group"), + ) + .arg( + Arg::with_name("version") + .short("v") + .long("version") + .takes_value(true) + .help("version of group"), + ) + .arg( + Arg::with_name("prev_shell_id") + .long("prev_shell") + .takes_value(true) + .help("prev-shell-id of group"), + ) + .arg( + Arg::with_name("add_oods") + .long("add_ood") + .short("o") + .value_delimiter(";") + .help("device id append to people or group"), + ) + .arg( + Arg::with_name("ood_lists") + .long("ood_lists") + .short("l") + .value_delimiter(";") + .help("device id set to people or group"), + ) + .arg( + Arg::with_name("name") + .short("n") + .long("name") + .takes_value(true) + .help("name of people or group"), + ) + .arg( + Arg::with_name("icon") + .short("I") + .long("icon") + .takes_value(true) + .help("icon of people or group"), + ) + .arg( + Arg::with_name("source") + .long("source") + .value_delimiter(";") + .help("add source to app, {ver}:{id}"), + ) + .arg( + Arg::with_name("app_id") + .long("appid") + .takes_value(true) + .help("app id add to app list"), + ) + .arg( + Arg::with_name("app_ver") + .long("appver") + .takes_value(true) + .help("app ver add to app list"), + ) + .arg( + Arg::with_name("app_status") + .long("appstart") + .help("start app, default false"), + ) } pub fn modify_desc(matches: &ArgMatches) { @@ -40,48 +168,45 @@ pub fn modify_desc(matches: &ArgMatches) { } body.increase_update_time(bucky_time_now()); - p.encode_to_file(path.as_ref(), false).expect("write desc file err"); - info!("modify success"); - }, - StandardObject::SimpleGroup(mut g) => { - let content = g.body_mut().as_mut().unwrap().content_mut(); - if let Some(members) = get_objids_from_matches(matches, "members") { - content.members_mut().clone_from(&members); - } - - if let Some(members) = get_objids_from_matches(matches, "add_members") { - for member in members { - if !content.members_mut().contains(&member) { - content.members_mut().push(member); - } else { - info!("obj {} already in group, skip.", &member); - } - } - } + p.encode_to_file(path.as_ref(), false) + .expect("write desc file err"); + info!("modify success"); + } + StandardObject::Group(mut g) => { + if modify_group_desc(&mut g, matches).is_ok() { + g.encode_to_file(path.as_ref(), false) + .expect("write desc file err"); + info!("modify success"); + } + } + StandardObject::People(mut p) => { + let content = p.body_mut().as_mut().unwrap().content_mut(); + if let Some(oods) = get_deviceids_from_matches(matches, "ood_lists") { + content.ood_list_mut().clone_from(&oods); + } - g.body_mut().as_mut().unwrap().increase_update_time(bucky_time_now()); + if let Some(oods) = get_deviceids_from_matches(matches, "add_oods") { + for ood in oods { + if !content.ood_list_mut().contains(&ood) { + content.ood_list_mut().push(ood); + } else { + info!("obj {} already exist, skip.", &ood); + } + } + } - g.encode_to_file(path.as_ref(), false).expect("write desc file err"); - } - StandardObject::People(mut p) => { - let content = p.body_mut().as_mut().unwrap().content_mut(); - if let Some(oods) = get_deviceids_from_matches(matches, "ood_lists") { - content.ood_list_mut().clone_from(&oods); - } - - if let Some(oods) = get_deviceids_from_matches(matches, "add_oods") { - for ood in oods { - if !content.ood_list_mut().contains(&ood) { - content.ood_list_mut().push(ood); - } else { - info!("obj {} already in group, skip.", &ood); - } - } - } + if let Some(name) = matches.value_of("name") { + content.set_name(name.to_owned()); + } - if let Some(name) = matches.value_of("name") { - content.set_name(name.to_owned()); - } + if let Some(icon) = matches.value_of("icon") { + match FileId::from_str(icon) { + Ok(icon) => content.set_icon(icon), + Err(_) => { + warn!("invalid icon {}", icon); + } + } + } p.body_mut().as_mut().unwrap().increase_update_time(bucky_time_now()); @@ -91,49 +216,226 @@ pub fn modify_desc(matches: &ArgMatches) { error!("unsupport desc type"); } } - } - AnyNamedObject::Core(obj) => { - match CoreObjectType::from(obj.desc().obj_type()) { - CoreObjectType::DecApp => { - let mut app = DecApp::try_from(obj).unwrap(); - if let Some(values) = matches.values_of_lossy("source") { - for value in &values { - let sources: Vec<&str> = value.split(":").collect(); - app.set_source(sources[0].to_owned(), ObjectId::from_str(sources[1]).unwrap(), None); - } - } + } + AnyNamedObject::Core(obj) => match CoreObjectType::from(obj.desc().obj_type()) { + CoreObjectType::DecApp => { + let mut app = DecApp::try_from(obj).unwrap(); + if let Some(values) = matches.values_of_lossy("source") { + for value in &values { + let sources: Vec<&str> = value.split(":").collect(); + app.set_source( + sources[0].to_owned(), + ObjectId::from_str(sources[1]).unwrap(), + None, + ); + } + } - app.body_mut().as_mut().unwrap().increase_update_time(bucky_time_now()); - - app.encode_to_file(path.as_ref(), false).expect("write desc file err"); - }, - CoreObjectType::AppList => { - let mut list = AppList::try_from(obj).unwrap(); - let owner = list.desc().owner().unwrap(); - if let Some(id_str) = matches.value_of("app_id") { - let dec_id = DecAppId::from_str(id_str).unwrap(); - let version = matches.value_of("app_ver").unwrap().to_owned(); - let app_status = matches.is_present("app_status"); - let status = AppStatus::create(owner, dec_id, version, app_status); - - list.put(status); - } else { - list.clear(); - } + app.body_mut().as_mut().unwrap().increase_update_time(bucky_time_now()); - list.body_mut().as_mut().unwrap().increase_update_time(bucky_time_now()); + app.encode_to_file(path.as_ref(), false).expect("write desc file err"); - list.encode_to_file(path.as_ref(), false).expect("write desc file err"); - } - _ => {} - } - } - AnyNamedObject::DECApp(_) => {} - } + } + CoreObjectType::AppList => { + let mut list = AppList::try_from(obj).unwrap(); + let owner = list.desc().owner().unwrap(); + if let Some(id_str) = matches.value_of("app_id") { + let dec_id = DecAppId::from_str(id_str).unwrap(); + let version = matches.value_of("app_ver").unwrap().to_owned(); + let app_status = matches.is_present("app_status"); + let status = AppStatus::create(owner, dec_id, version, app_status); + list.put(status); + } else { + list.clear(); + } + + list.body_mut().as_mut().unwrap().increase_update_time(bucky_time_now()); + + list.encode_to_file(path.as_ref(), false).expect("write desc file err"); + } + _ => {} + }, + AnyNamedObject::DECApp(_) => {} + } }, Err(e) => { error!("read desc from file {} failed, err {}", path, e); - }, + } + } +} + +fn modify_group_desc(group: &mut Group, matches: &ArgMatches) -> BuckyResult<()> { + let group_id = group.desc().object_id(); + let body_hash = group.body().as_ref().unwrap().raw_hash_encode()?; + + match get_group_members_from_matches(matches, "members") { + Ok(members) => { + if let Some(members) = members { + group.set_members(members); + } + } + Err(err) => { + log::error!("update group({}) failed for invalid member.", group_id); + return Err(err); + } + } + + match get_group_members_from_matches(matches, "add_members") { + Ok(additional_members) => { + if let Some(additional_members) = additional_members { + let mut members = group.members().clone(); + additional_members.into_iter().for_each(|m| { + members.insert(m.id, m); + }); + group.set_members(members.into_iter().map(|(_, m)| m).collect()); + } + } + Err(err) => { + log::error!("update group({}) failed for invalid member.", group_id); + return Err(err); + } + } + + if let Some(remove_members) = get_objids_from_matches(matches, "remove_members") { + let mut members = group.members().clone(); + remove_members.iter().for_each(|m| { + members.remove(m); + }); + group.set_members(members.into_iter().map(|(_, m)| m).collect()); + } + + match get_group_members_from_matches(matches, "admins") { + Ok(admins) => { + if let Some(admins) = admins { + if group.is_simple_group() { + let msg = format!("update group({}) failed for the administrators of simple-group is immutable.", group_id); + log::error!("{}", msg); + return Err(BuckyError::new(BuckyErrorCode::Failed, msg)); + } + let org = group.check_org_body_content_mut(); + org.set_admins(admins); + } + } + Err(err) => { + log::error!( + "update group({}) failed for invalid administrator.", + group_id + ); + return Err(err); + } + } + + match get_group_members_from_matches(matches, "add_admins") { + Ok(additional_admins) => { + if let Some(additional_admins) = additional_admins { + if group.is_simple_group() { + let msg = format!("update group({}) failed for the administrators of simple-group is immutable.", group_id); + log::error!("{}", msg); + return Err(BuckyError::new(BuckyErrorCode::Failed, msg)); + } + let org = group.check_org_body_content_mut(); + let mut admins = org.admins().clone(); + additional_admins.into_iter().for_each(|m| { + admins.insert(m.id, m); + }); + org.set_admins(admins.into_iter().map(|(_, m)| m).collect()); + } + } + Err(err) => { + log::error!( + "update group({}) failed for invalid administrator.", + group_id + ); + return Err(err); + } + } + + if let Some(remove_members) = get_objids_from_matches(matches, "remove_admins") { + let org = group.check_org_body_content_mut(); + let mut admins = org.admins().clone(); + remove_members.iter().for_each(|m| { + admins.remove(m); + }); + org.set_admins( + admins + .into_iter() + .map(|(_, m)| m) + .collect(), + ); } -} \ No newline at end of file + + if let Some(oods) = get_deviceids_from_matches(matches, "ood_lists") { + group.set_ood_list(oods); + } + if let Some(additional_oods) = get_deviceids_from_matches(matches, "add_oods") { + let mut oods = HashSet::::from_iter(group.ood_list().iter().map(|id| id.clone())); + additional_oods.into_iter().for_each(|id| { + oods.insert(id); + }); + group.set_ood_list(oods.into_iter().collect()); + } + + if let Some(description) = matches.value_of("description") { + group.set_description(Some(description.to_string())); + } + + if let Some(icon) = matches.value_of("icon") { + group.set_icon(Some(icon.to_string())); + } + + if let Some(name) = matches.value_of("name") { + group.set_name(Some(name.to_string())); + } + + if let Some(version) = matches.value_of("version") { + let version = match version.parse::() { + Ok(v) => v, + Err(e) => { + let msg = format!( + "update group({}) failed for invalid version {}, err: {:?}", + group_id, version, e + ); + log::error!("{}", msg); + return Err(BuckyError::new(BuckyErrorCode::InvalidFormat, msg)); + } + }; + group.set_version(version); + } + + if let Some(prev_shell_id) = matches.value_of("prev_shell_id") { + let prev_shell_id = match ObjectId::from_str(prev_shell_id) { + Ok(prev_shell_id) => prev_shell_id, + Err(_) => { + let msg = format!( + "update group({}) failed for invalid prev-shell-id {}", + group_id, prev_shell_id + ); + log::error!("{}", msg); + return Err(BuckyError::new(BuckyErrorCode::InvalidFormat, msg)); + } + }; + group.set_prev_shell_id(Some(prev_shell_id)); + } + + if group.admins().is_empty() { + let msg = format!("update group({}) failed for no administrators", group_id); + log::error!("{}", msg); + return Err(BuckyError::new(BuckyErrorCode::InvalidInput, msg)); + } + + if body_hash == group.body().as_ref().unwrap().raw_hash_encode()? { + log::info!("success with no change"); + return Ok(()); + } + + group + .body_mut() + .as_mut() + .unwrap() + .increase_update_time(bucky_time_now()); + + group.signs_mut().clear_body_signs(); + + Ok(()) +} diff --git a/src/tools/desc-tool/src/show.rs b/src/tools/desc-tool/src/show.rs index 0c8507cf7..00c609e7a 100644 --- a/src/tools/desc-tool/src/show.rs +++ b/src/tools/desc-tool/src/show.rs @@ -1,6 +1,13 @@ use clap::{App, Arg, ArgMatches, SubCommand}; -use cyfs_base::{AnyNamedObject, ChunkList, Dir, DirBodyContent, File, FileDecoder, InnerNode, NDNObjectInfo, NamedObject, ObjectDesc, RawEncode, RawFrom, SignatureSource, SingleKeyObjectDesc, StandardObject, AreaObjectDesc}; -use cyfs_core::{AppList, AppListObj, AppStatusObj, CoreObjectType, DecApp, DecAppObj}; +use cyfs_base::{ + AnyNamedObject, AreaObjectDesc, ChunkList, Dir, DirBodyContent, File, FileDecoder, Group, + InnerNode, NDNObjectInfo, NamedObject, ObjectDesc, RawEncode, RawFrom, SignatureSource, + SingleKeyObjectDesc, StandardObject, +}; +use cyfs_core::{ + AppList, AppListObj, AppStatusObj, CoreObjectType, DecApp, DecAppObj, ToGroupShell, +}; +use itertools::Itertools; use std::convert::TryFrom; use std::path::Path; @@ -41,13 +48,19 @@ pub fn show_desc_subcommand<'a, 'b>() -> App<'a, 'b> { Arg::with_name("show_members") .short("m") .long("member") - .help("show members in simple group"), + .help("show members in group"), + ) + .arg( + Arg::with_name("show_admins") + .short("A") + .long("admins") + .help("show administrators in group"), ) .arg( Arg::with_name("show_oodlist") .short("l") .long("ood_list") - .help("show ood_list in people"), + .help("show ood_list in people or group"), ) .arg( Arg::with_name("all") @@ -159,6 +172,78 @@ fn show_dir(dir: &Dir, matches: &ArgMatches) { } } +fn show_group(group: &Group, matches: &ArgMatches) { + println!("desc type: Group"); + let is_all = matches.is_present("all"); + + if is_all { + println!("name: {}", group.name().as_ref().map_or("", |n| n.as_str())); + println!( + "founder: {}", + group.founder_id().map_or("".to_string(), |f| f.to_string()) + ); + println!( + "icon: {}", + group.icon().as_ref().map_or("", |icon| icon.as_str()) + ); + println!( + "description: {}", + group.description().as_ref().map_or("", |d| d.as_str()) + ); + println!( + "area: {}", + group + .desc() + .area() + .as_ref() + .map_or("None".to_string(), |a| a.to_string()) + ); + println!("version: {}", group.version()); + println!( + "prev-shell-id: {}", + group + .prev_shell_id() + .map_or("None".to_string(), |prev| prev.to_string()) + ); + println!("shell-id: {}", group.to_shell().shell_id()); + println!( + "body-hash: {}", + group.body().as_ref().unwrap().raw_hash_value().unwrap() + ); + } + + if is_all || matches.is_present("show_admins") { + print!( + "administrators({}): [", + if group.is_org() { + "mutable" + } else { + "immutable" + } + ); + for (_, member) in group.admins().iter().sorted_by(|l, r| l.0.cmp(r.0)) { + print!("{};", member.to_string()); + } + println!("]"); + } + + if is_all || matches.is_present("show_members") { + print!("members: ["); + for (_, member) in group.members().iter().sorted_by(|l, r| l.0.cmp(r.0)) { + print!("{};", member.to_string()); + } + println!("]"); + } + + if is_all || matches.is_present("show_oodlist") { + print!("ood_list: ["); + for ood in group.ood_list() { + print!("{};", ood); + } + println!("]"); + } +} + pub fn show_desc(matches: &ArgMatches) { let path = Path::new(matches.value_of("desc_file").unwrap()); let mut file_buf = vec![]; @@ -216,7 +301,6 @@ pub fn show_desc(matches: &ArgMatches) { if let Some(area) = p.desc().area() { println!("area: {}", area) } - } if matches.is_present("all") || matches.is_present("show_endpoint") { print!("endpoint: ["); @@ -237,7 +321,9 @@ pub fn show_desc(matches: &ArgMatches) { let pubkey = p.desc().public_key(); let mut buf = vec![]; buf.resize(pubkey.raw_measure(&None).unwrap(), 0); - pubkey.raw_encode(&mut buf, &None).expect("encode pubkey err"); + pubkey + .raw_encode(&mut buf, &None) + .expect("encode pubkey err"); println!("pubkey: {}", hex::encode(&buf)); println!("createtime: {}", p.desc().create_time()); println!("device catelogy: {}", p.category().unwrap()) @@ -251,16 +337,7 @@ pub fn show_desc(matches: &ArgMatches) { ua.desc().content().right() ); } - StandardObject::SimpleGroup(g) => { - println!("desc type: Group"); - if matches.is_present("all") || matches.is_present("show_members") { - print!("members: ["); - for owner in g.body().as_ref().unwrap().content().members() { - print!("{}, ", owner); - } - println!("]"); - } - } + StandardObject::Group(g) => show_group(g, matches), StandardObject::File(f) => { show_file(f, matches); } diff --git a/src/tools/desc-tool/src/sign.rs b/src/tools/desc-tool/src/sign.rs index d86ea4543..e44d42722 100644 --- a/src/tools/desc-tool/src/sign.rs +++ b/src/tools/desc-tool/src/sign.rs @@ -1,98 +1,200 @@ -use clap::{App, SubCommand, Arg, ArgMatches}; +use clap::{App, Arg, ArgMatches, SubCommand}; -use cyfs_base::{PrivateKey, FileDecoder, AnyNamedObject, RsaCPUObjectSigner, SignatureSource, ObjectId, StandardObject, FileEncoder, ObjectLink, SIGNATURE_SOURCE_REFINDEX_OWNER, SIGNATURE_SOURCE_REFINDEX_SELF}; -use cyfs_base::{sign_and_push_named_object_desc, sign_and_push_named_object_body, sign_and_set_named_object_body, sign_and_set_named_object_desc}; +use cyfs_base::{ + sign_and_push_named_object_body, sign_and_push_named_object_desc, + sign_and_set_named_object_body, sign_and_set_named_object_desc, +}; +use cyfs_base::{ + AnyNamedObject, FileDecoder, FileEncoder, ObjectId, ObjectLink, PrivateKey, RsaCPUObjectSigner, + SignatureSource, StandardObject, SIGNATURE_SOURCE_REFINDEX_OWNER, + SIGNATURE_SOURCE_REFINDEX_SELF, +}; use log::*; use std::str::FromStr; +// .\desc-tool sign ${desc-path} -s=${signer-secret-path} -t=${signer-desc-path} -dba + pub fn sign_subcommand<'a, 'b>() -> App<'a, 'b> { - SubCommand::with_name("sign").about("sign desc") - .arg(Arg::with_name("desc").takes_value(true).index(1).required(true) - .help("desc file to sign")) - .arg(Arg::with_name("secret").takes_value(true).short("s").long("secret").required(true) - .help("secret file for sign")) - .arg(Arg::with_name("sign_desc").short("d").long("sign_desc") - .help("add sign for desc")) - .arg(Arg::with_name("sign_body").short("b").long("sign_body") - .help("add sign for body")) - .arg(Arg::with_name("sign_source").short("t").long("sign_source").takes_value(true) - .help("sign type, default single")) - .arg(Arg::with_name("append").short("a").long("append") - .help("append sign, otherwise replace all signs")) + SubCommand::with_name("sign") + .about("sign desc") + .arg( + Arg::with_name("desc") + .takes_value(true) + .index(1) + .required(true) + .help("desc file to sign"), + ) + .arg( + Arg::with_name("secret") + .takes_value(true) + .short("s") + .long("secret") + .required(true) + .help("secret file for sign"), + ) + .arg( + Arg::with_name("sign_desc") + .short("d") + .long("sign_desc") + .help("add sign for desc"), + ) + .arg( + Arg::with_name("sign_body") + .short("b") + .long("sign_body") + .help("add sign for body"), + ) + .arg( + Arg::with_name("sign_source") + .short("t") + .long("sign_source") + .takes_value(true) + .help("sign type, default single"), + ) + .arg( + Arg::with_name("append") + .short("a") + .long("append") + .help("append sign, otherwise replace all signs"), + ) } macro_rules! match_any_obj_mut { ($on:ident, $o:ident, $body:tt, $chunk_id:ident, $chunk_body:tt) => { match &mut $on { - AnyNamedObject::Standard(o) => { - match o { - StandardObject::Device($o) => {$body}, - StandardObject::People($o) => {$body}, - StandardObject::SimpleGroup($o) => {$body}, - StandardObject::Contract($o) => {$body}, - StandardObject::UnionAccount($o) => {$body}, - StandardObject::ChunkId($chunk_id) => {$chunk_body}, - StandardObject::File($o) => {$body}, - StandardObject::Org($o) => {$body}, - StandardObject::AppGroup($o) => {$body}, - StandardObject::Dir($o) => {$body}, - StandardObject::Diff($o) => {$body}, - StandardObject::ProofOfService($o) => {$body}, - StandardObject::Tx($o) => {$body}, - StandardObject::Action($o) => {$body}, - StandardObject::ObjectMap($o) => {$body}, - } - }, - AnyNamedObject::Core($o) => { - $body - }, - AnyNamedObject::DECApp($o) => { - $body + AnyNamedObject::Standard(o) => match o { + StandardObject::Device($o) => $body, + StandardObject::People($o) => $body, + StandardObject::Group($o) => $body, + StandardObject::Contract($o) => $body, + StandardObject::UnionAccount($o) => $body, + StandardObject::ChunkId($chunk_id) => $chunk_body, + StandardObject::File($o) => $body, + StandardObject::AppGroup($o) => $body, + StandardObject::Dir($o) => $body, + StandardObject::Diff($o) => $body, + StandardObject::ProofOfService($o) => $body, + StandardObject::Tx($o) => $body, + StandardObject::Action($o) => $body, + StandardObject::ObjectMap($o) => $body, }, + AnyNamedObject::Core($o) => $body, + AnyNamedObject::DECApp($o) => $body, } - } + }; } pub async fn sign_desc(matches: &ArgMatches<'_>) { - if let Ok((private, _)) = PrivateKey::decode_from_file(matches.value_of("secret").unwrap().as_ref(), &mut vec![]) { - if let Ok((mut obj, _)) = AnyNamedObject::decode_from_file(matches.value_of("desc").unwrap().as_ref(), &mut vec![]) { + if let Ok((private, _)) = + PrivateKey::decode_from_file(matches.value_of("secret").unwrap().as_ref(), &mut vec![]) + { + if let Ok((mut obj, _)) = AnyNamedObject::decode_from_file( + matches.value_of("desc").unwrap().as_ref(), + &mut vec![], + ) { let signer = RsaCPUObjectSigner::new(private.public(), private); - let sign_type = matches.value_of("sign_source").map(|str|{ - match str { + let sign_type = matches + .value_of("sign_source") + .map(|str| match str { "self" => SignatureSource::RefIndex(SIGNATURE_SOURCE_REFINDEX_SELF), "owner" => SignatureSource::RefIndex(SIGNATURE_SOURCE_REFINDEX_OWNER), str @ _ => { if let Ok(obj) = ObjectId::from_str(str) { - SignatureSource::Object(ObjectLink { obj_id: obj, obj_owner: None }) + SignatureSource::Object(ObjectLink { + obj_id: obj, + obj_owner: None, + }) } else if let Ok(index) = str.parse::() { SignatureSource::RefIndex(index) + } else if let Ok((obj, _)) = + AnyNamedObject::decode_from_file(str.as_ref(), &mut vec![]) + { + SignatureSource::Object(ObjectLink { + obj_id: obj.object_id(), + obj_owner: None, + }) } else { SignatureSource::RefIndex(SIGNATURE_SOURCE_REFINDEX_OWNER) } } - } - - }).unwrap_or(SignatureSource::RefIndex(SIGNATURE_SOURCE_REFINDEX_OWNER)); + }) + .unwrap_or(SignatureSource::RefIndex(SIGNATURE_SOURCE_REFINDEX_OWNER)); let mut signed = false; let append = matches.is_present("append"); if matches.is_present("sign_desc") { if append { - match_any_obj_mut!(obj, o, {sign_and_push_named_object_desc(&signer, o, &sign_type).await.unwrap(); signed = true;}, _id, {error!("not support sign for chunkid");}); + match_any_obj_mut!( + obj, + o, + { + sign_and_push_named_object_desc(&signer, o, &sign_type) + .await + .unwrap(); + signed = true; + }, + _id, + { + error!("not support sign for chunkid"); + } + ); } else { - match_any_obj_mut!(obj, o, {sign_and_set_named_object_desc(&signer, o, &sign_type).await.unwrap(); signed = true;}, _id, {error!("not support sign for chunkid");}); + match_any_obj_mut!( + obj, + o, + { + sign_and_set_named_object_desc(&signer, o, &sign_type) + .await + .unwrap(); + signed = true; + }, + _id, + { + error!("not support sign for chunkid"); + } + ); } - }; if matches.is_present("sign_body") { if append { - match_any_obj_mut!(obj, o, {sign_and_push_named_object_body(&signer, o, &sign_type).await.unwrap(); signed = true;}, _id, {error!("not support sign for chunkid");}); + match_any_obj_mut!( + obj, + o, + { + sign_and_push_named_object_body(&signer, o, &sign_type) + .await + .unwrap(); + signed = true; + }, + _id, + { + error!("not support sign for chunkid"); + } + ); } else { - match_any_obj_mut!(obj, o, {sign_and_set_named_object_body(&signer, o, &sign_type).await.unwrap(); signed = true;}, _id, {error!("not support sign for chunkid");}); + match_any_obj_mut!( + obj, + o, + { + sign_and_set_named_object_body(&signer, o, &sign_type) + .await + .unwrap(); + signed = true; + }, + _id, + { + error!("not support sign for chunkid"); + } + ); } } if signed { - obj.encode_to_file(matches.value_of("desc").unwrap().as_ref(), true).unwrap(); + obj.encode_to_file(matches.value_of("desc").unwrap().as_ref(), true) + .unwrap(); + + info!("signature success."); + } else { + error!("signature failed!"); } } else { error!("invalid desc file"); diff --git a/src/tools/desc-tool/src/util.rs b/src/tools/desc-tool/src/util.rs index 6111220dd..44d4d9a9c 100644 --- a/src/tools/desc-tool/src/util.rs +++ b/src/tools/desc-tool/src/util.rs @@ -1,20 +1,18 @@ use clap::ArgMatches; +use cyfs_base::{DeviceId, Endpoint, GroupMember, ObjectId, BuckyResult}; use log::*; -use std::str::FromStr; -use cyfs_base::{ObjectId, Endpoint, DeviceId}; use std::convert::TryFrom; +use std::str::FromStr; pub fn get_objids_from_matches(matches: &ArgMatches, name: &str) -> Option> { if let Some(strs) = matches.values_of_lossy(name) { let mut ret = vec![]; for str in &strs { match ObjectId::from_str(str) { - Ok(obj) => { - ret.push(obj) - }, + Ok(obj) => ret.push(obj), Err(_) => { error!("{} not valid objid, ignore", str); - }, + } } } Some(ret) @@ -24,7 +22,7 @@ pub fn get_objids_from_matches(matches: &ArgMatches, name: &str) -> Option Option> { - get_objids_from_matches(matches, name).map(|objs|{ + get_objids_from_matches(matches, name).map(|objs| { let mut ret = vec![]; for obj in &objs { if let Ok(device_id) = DeviceId::try_from(obj) { @@ -42,16 +40,29 @@ pub fn get_eps_from_matches(matches: &ArgMatches, name: &str) -> Option { - ret.push(obj) - }, + Ok(obj) => ret.push(obj), Err(_) => { error!("{} not valid endpoint, ignore", str); - }, + } } } Some(ret) } else { None } -} \ No newline at end of file +} + +pub fn get_group_members_from_matches( + matches: &ArgMatches, + name: &str, +) -> BuckyResult>> { + if let Some(strs) = matches.values_of_lossy(name) { + let mut ret = vec![]; + for str in &strs { + ret.push(GroupMember::from_str(str)?); + } + Ok(Some(ret)) + } else { + Ok(None) + } +}