diff --git a/src/lib.rs b/src/lib.rs index 4600041b62..73e8646f10 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -252,6 +252,7 @@ impl storage::data_directory::Trait for Runtime { type Event = Event; type ContentId = ContentId; type Members = Members; + type Roles = Actors; type IsActiveDataObjectType = DataObjectTypeRegistry; } @@ -265,6 +266,7 @@ impl storage::data_object_storage_registry::Trait for Runtime { type Event = Event; type DataObjectStorageRelationshipId = u64; type Members = Members; + type Roles = Actors; type ContentIdExists = DataDirectory; } diff --git a/src/roles/actors.rs b/src/roles/actors.rs index cda9927230..3b2cd9000f 100644 --- a/src/roles/actors.rs +++ b/src/roles/actors.rs @@ -12,6 +12,8 @@ use system::{self, ensure_signed}; use crate::traits::{Members, Roles}; +static MSG_NO_ACTOR_FOR_ROLE: &str = "For the specified role, no actor is currently staked."; + #[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, Debug)] pub enum Role { Storage, @@ -193,6 +195,24 @@ impl Roles for Module { fn is_role_account(account_id: &T::AccountId) -> bool { >::exists(account_id) || >::exists(account_id) } + + fn account_has_role(account_id: &T::AccountId, role: Role) -> bool { + Self::actor_by_account_id(account_id).map_or(false, |actor| actor.role == role) + } + + fn random_account_for_role(role: Role) -> Result { + let ids = Self::account_ids_by_role(role); + if 0 == ids.len() { + return Err(MSG_NO_ACTOR_FOR_ROLE); + } + let seed = >::random_seed(); + let mut rand: u64 = 0; + for offset in 0..8 { + rand += (seed.as_ref()[offset] as u64) << offset; + } + let idx = (rand as usize) % ids.len(); + return Ok(ids[idx].clone()); + } } decl_module! { diff --git a/src/storage/data_directory.rs b/src/storage/data_directory.rs index ee1037ddae..a345f40a49 100644 --- a/src/storage/data_directory.rs +++ b/src/storage/data_directory.rs @@ -1,7 +1,8 @@ #![cfg_attr(not(feature = "std"), no_std)] +use crate::roles::actors; use crate::storage::data_object_type_registry::Trait as DOTRTrait; -use crate::traits::{ContentIdExists, IsActiveDataObjectType, Members}; +use crate::traits::{ContentIdExists, IsActiveDataObjectType, Members, Roles}; use parity_codec::Codec; use parity_codec_derive::{Decode, Encode}; use primitives::Ed25519AuthorityId; @@ -27,6 +28,7 @@ pub trait Trait: timestamp::Trait + system::Trait + DOTRTrait + MaybeDebug { + PartialEq; type Members: Members; + type Roles: Roles; type IsActiveDataObjectType: IsActiveDataObjectType; } @@ -120,9 +122,7 @@ decl_module! { // The liaison is something we need to take from staked roles. The idea // is to select the liaison, for now randomly. - // FIXME without that module, we're currently hardcoding it, to the - // origin, which is wrong on many levels. - let liaison = who.clone(); + let liaison = T::Roles::random_account_for_role(actors::Role::Storage)?; // Let's create the entry then let new_id = Self::next_content_id(); @@ -209,9 +209,10 @@ mod tests { _ => (0u64, 0xdeadbeefu64), // invalid value, unlikely to match }; assert_ne!(liaison, 0xdeadbeefu64); + assert_eq!(liaison, TEST_MOCK_LIAISON); // Accepting content should not work with some random origin - let res = TestDataDirectory::accept_content(Origin::signed(42), content_id); + let res = TestDataDirectory::accept_content(Origin::signed(1), content_id); assert!(res.is_err()); // However, with the liaison as origin it should. @@ -235,9 +236,10 @@ mod tests { _ => (0u64, 0xdeadbeefu64), // invalid value, unlikely to match }; assert_ne!(liaison, 0xdeadbeefu64); + assert_eq!(liaison, TEST_MOCK_LIAISON); // Rejecting content should not work with some random origin - let res = TestDataDirectory::reject_content(Origin::signed(42), content_id); + let res = TestDataDirectory::reject_content(Origin::signed(1), content_id); assert!(res.is_err()); // However, with the liaison as origin it should. diff --git a/src/storage/data_object_storage_registry.rs b/src/storage/data_object_storage_registry.rs index 61b81defbd..c55a22e8c2 100644 --- a/src/storage/data_object_storage_registry.rs +++ b/src/storage/data_object_storage_registry.rs @@ -1,7 +1,8 @@ #![cfg_attr(not(feature = "std"), no_std)] +use crate::roles::actors; use crate::storage::data_directory::Trait as DDTrait; -use crate::traits::{ContentHasStorage, ContentIdExists, Members}; +use crate::traits::{ContentHasStorage, ContentIdExists, Members, Roles}; use parity_codec::Codec; use parity_codec_derive::{Decode, Encode}; use rstd::prelude::*; @@ -26,11 +27,14 @@ pub trait Trait: timestamp::Trait + system::Trait + DDTrait + MaybeDebug { + PartialEq; type Members: Members; + type Roles: Roles; type ContentIdExists: ContentIdExists; } static MSG_CID_NOT_FOUND: &str = "Content with this ID not found."; static MSG_DOSR_NOT_FOUND: &str = "No data object storage relationship found for this ID."; +static MSG_ONLY_STORAGE_PROVIDER_MAY_CREATE_DOSR: &str = + "Only storage providers can create data object storage relationships."; static MSG_ONLY_STORAGE_PROVIDER_MAY_CLAIM_READY: &str = "Only the storage provider in a DOSR can decide whether they're ready."; @@ -104,10 +108,9 @@ decl_module! { pub fn add_relationship(origin, cid: T::ContentId) { // Origin has to be a storage provider let who = ensure_signed(origin)?; - // TODO check for being staked as a storage provider - // if !T::Members::is_active_member(&who) { - // return Err(MSG_CREATOR_MUST_BE_MEMBER); - // } + + // Check that the origin is a storage provider + ensure!(::Roles::account_has_role(&who, actors::Role::Storage), MSG_ONLY_STORAGE_PROVIDER_MAY_CREATE_DOSR); // Content ID must exist ensure!(T::ContentIdExists::has_content(&cid), MSG_CID_NOT_FOUND); @@ -190,8 +193,11 @@ mod tests { #[test] fn test_add_relationship() { with_default_mock_builder(|| { - // The content needs to exist - in our mock, that's with the content ID 42 - let res = TestDataObjectStorageRegistry::add_relationship(Origin::signed(1), 42); + // The content needs to exist - in our mock, that's with the content ID TEST_MOCK_EXISTING_CID + let res = TestDataObjectStorageRegistry::add_relationship( + Origin::signed(TEST_MOCK_LIAISON), + TEST_MOCK_EXISTING_CID, + ); assert!(res.is_ok()); }); } @@ -208,7 +214,10 @@ mod tests { fn test_toggle_ready() { with_default_mock_builder(|| { // Create a DOSR - let res = TestDataObjectStorageRegistry::add_relationship(Origin::signed(1), 42); + let res = TestDataObjectStorageRegistry::add_relationship( + Origin::signed(TEST_MOCK_LIAISON), + TEST_MOCK_EXISTING_CID, + ); assert!(res.is_ok()); // Grab DOSR ID from event @@ -231,14 +240,16 @@ mod tests { // Toggling with the wrong ID should fail. let res = TestDataObjectStorageRegistry::set_relationship_ready( - Origin::signed(1), + Origin::signed(TEST_MOCK_LIAISON), dosr_id + 1, ); assert!(res.is_err()); // Toggling with the correct ID and origin should succeed - let res = - TestDataObjectStorageRegistry::set_relationship_ready(Origin::signed(1), dosr_id); + let res = TestDataObjectStorageRegistry::set_relationship_ready( + Origin::signed(TEST_MOCK_LIAISON), + dosr_id, + ); assert!(res.is_ok()); assert_eq!(System::events().last().unwrap().event, MetaEvent::data_object_storage_registry(data_object_storage_registry::RawEvent::DataObjectStorageRelationshipReadyUpdated( diff --git a/src/storage/mock.rs b/src/storage/mock.rs index ffb95ab550..0e7c12cf9a 100644 --- a/src/storage/mock.rs +++ b/src/storage/mock.rs @@ -3,6 +3,8 @@ pub use super::{ content_directory, data_directory, data_object_storage_registry, data_object_type_registry, }; +use crate::governance::GovernanceCurrency; +use crate::roles::actors; use crate::traits; use runtime_io::with_externalities; pub use system; @@ -26,9 +28,19 @@ impl_outer_event! { data_directory, data_object_storage_registry, content_directory, + actors, + balances, } } +pub const TEST_FIRST_DATA_OBJECT_TYPE_ID: u64 = 1000; +pub const TEST_FIRST_CONTENT_ID: u64 = 2000; +pub const TEST_FIRST_RELATIONSHIP_ID: u64 = 3000; +pub const TEST_FIRST_METADATA_ID: u64 = 4000; + +pub const TEST_MOCK_LIAISON: u64 = 0xd00du64; +pub const TEST_MOCK_EXISTING_CID: u64 = 42; + pub struct MockMembers {} impl traits::Members for MockMembers { type Id = u64; @@ -46,6 +58,27 @@ impl traits::Members for MockMembers { } } +pub struct MockRoles {} +impl traits::Roles for MockRoles { + fn is_role_account(_account_id: &::AccountId) -> bool { + false + } + + fn account_has_role( + account_id: &::AccountId, + _role: actors::Role, + ) -> bool { + *account_id == TEST_MOCK_LIAISON + } + + fn random_account_for_role( + _role: actors::Role, + ) -> Result<::AccountId, &'static str> { + // We "randomly" select an account Id. + Ok(TEST_MOCK_LIAISON) + } +} + pub struct AnyDataObjectTypeIsActive {} impl traits::IsActiveDataObjectType for AnyDataObjectTypeIsActive @@ -58,21 +91,21 @@ impl traits::IsActiveDataObjectType pub struct MockContent {} impl traits::ContentIdExists for MockContent { fn has_content(which: &::ContentId) -> bool { - *which == 42 + *which == TEST_MOCK_EXISTING_CID } fn get_data_object( which: &::ContentId, ) -> Result, &'static str> { match *which { - 42 => Ok(data_directory::DataObject { + TEST_MOCK_EXISTING_CID => Ok(data_directory::DataObject { data_object_type: 1, signing_key: None, size: 1234, added_at_block: 10, added_at_time: 1024, owner: 1, - liaison: 1, // TODO change to another later + liaison: TEST_MOCK_LIAISON, liaison_judgement: data_directory::LiaisonJudgement::Pending, }), _ => Err("nope, missing"), @@ -108,6 +141,7 @@ impl data_directory::Trait for Test { type Event = MetaEvent; type ContentId = u64; type Members = MockMembers; + type Roles = MockRoles; type IsActiveDataObjectType = AnyDataObjectTypeIsActive; } @@ -115,6 +149,7 @@ impl data_object_storage_registry::Trait for Test { type Event = MetaEvent; type DataObjectStorageRelationshipId = u64; type Members = MockMembers; + type Roles = MockRoles; type ContentIdExists = MockContent; } @@ -125,6 +160,11 @@ impl content_directory::Trait for Test { type Members = MockMembers; } +impl actors::Trait for Test { + type Event = MetaEvent; + type Members = MockMembers; +} + impl timestamp::Trait for Test { type Moment = u64; type OnTimestampSet = (); @@ -136,6 +176,29 @@ impl consensus::Trait for Test { type Log = DigestItem; } +impl balances::Trait for Test { + type Event = MetaEvent; + + /// The balance of an account. + type Balance = u32; + + /// A function which is invoked when the free-balance has fallen below the existential deposit and + /// has been reduced to zero. + /// + /// Gives a chance to clean up resources associated with the given account. + type OnFreeBalanceZero = (); + + /// Handler for when a new account is created. + type OnNewAccount = (); + + /// A function that returns true iff a given account can transfer its funds to another account. + type EnsureAccountLiquid = (); +} + +impl GovernanceCurrency for Test { + type Currency = balances::Module; +} + pub struct ExtBuilder { first_data_object_type_id: u64, first_content_id: u64, @@ -224,11 +287,8 @@ pub type TestDataDirectory = data_directory::Module; // pub type TestDataObject = data_directory::DataObject; pub type TestDataObjectStorageRegistry = data_object_storage_registry::Module; pub type TestContentDirectory = content_directory::Module; +pub type TestActors = actors::Module; -pub const TEST_FIRST_DATA_OBJECT_TYPE_ID: u64 = 1000; -pub const TEST_FIRST_CONTENT_ID: u64 = 2000; -pub const TEST_FIRST_RELATIONSHIP_ID: u64 = 3000; -pub const TEST_FIRST_METADATA_ID: u64 = 4000; pub fn with_default_mock_builder R>(f: F) -> R { with_externalities( &mut ExtBuilder::default() @@ -237,6 +297,11 @@ pub fn with_default_mock_builder R>(f: F) -> R { .first_relationship_id(TEST_FIRST_RELATIONSHIP_ID) .first_metadata_id(TEST_FIRST_METADATA_ID) .build(), - || f(), + || { + let roles: Vec = vec![actors::Role::Storage]; + assert!(TestActors::set_available_roles(roles).is_ok(), ""); + + f() + }, ) } diff --git a/src/traits.rs b/src/traits.rs index 3bfea937b6..c2eba3136a 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,5 +1,6 @@ #![cfg_attr(not(feature = "std"), no_std)] +use crate::roles::actors; use crate::storage::{data_directory, data_object_storage_registry, data_object_type_registry}; use parity_codec::Codec; use runtime_primitives::traits::{As, MaybeSerializeDebug, Member, SimpleArithmetic}; @@ -42,12 +43,25 @@ impl Members for () { // Roles pub trait Roles { fn is_role_account(account_id: &T::AccountId) -> bool; + + fn account_has_role(account_id: &T::AccountId, role: actors::Role) -> bool; + + // If available, return a random account ID for the given role. + fn random_account_for_role(role: actors::Role) -> Result; } impl Roles for () { fn is_role_account(_who: &T::AccountId) -> bool { false } + + fn account_has_role(_account_id: &T::AccountId, _role: actors::Role) -> bool { + false + } + + fn random_account_for_role(_role: actors::Role) -> Result { + Err("not implemented") + } } // Storage