Browse Source

Merge pull request #31 from jfinkhaeuser/staked-storage-provider

Add use of staked storage provider roles to storage subsystem.
Mokhtar Naamani 6 years ago
parent
commit
afba5aad0a
6 changed files with 139 additions and 25 deletions
  1. 2 0
      src/lib.rs
  2. 20 0
      src/roles/actors.rs
  3. 8 6
      src/storage/data_directory.rs
  4. 22 11
      src/storage/data_object_storage_registry.rs
  5. 73 8
      src/storage/mock.rs
  6. 14 0
      src/traits.rs

+ 2 - 0
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;
 }
 

+ 20 - 0
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<T: Trait> Roles<T> for Module<T> {
     fn is_role_account(account_id: &T::AccountId) -> bool {
         <ActorByAccountId<T>>::exists(account_id) || <Bondage<T>>::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<T::AccountId, &'static str> {
+        let ids = Self::account_ids_by_role(role);
+        if 0 == ids.len() {
+            return Err(MSG_NO_ACTOR_FOR_ROLE);
+        }
+        let seed = <system::Module<T>>::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! {

+ 8 - 6
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<Self>;
+    type Roles: Roles<Self>;
     type IsActiveDataObjectType: IsActiveDataObjectType<Self>;
 }
 
@@ -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.

+ 22 - 11
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<Self>;
+    type Roles: Roles<Self>;
     type ContentIdExists: ContentIdExists<Self>;
 }
 
 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!(<T as Trait>::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(

+ 73 - 8
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<T>,
         data_object_storage_registry<T>,
         content_directory<T>,
+        actors<T>,
+        balances<T>,
     }
 }
 
+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<T: system::Trait> traits::Members<T> for MockMembers {
     type Id = u64;
@@ -46,6 +58,27 @@ impl<T: system::Trait> traits::Members<T> for MockMembers {
     }
 }
 
+pub struct MockRoles {}
+impl traits::Roles<Test> for MockRoles {
+    fn is_role_account(_account_id: &<Test as system::Trait>::AccountId) -> bool {
+        false
+    }
+
+    fn account_has_role(
+        account_id: &<Test as system::Trait>::AccountId,
+        _role: actors::Role,
+    ) -> bool {
+        *account_id == TEST_MOCK_LIAISON
+    }
+
+    fn random_account_for_role(
+        _role: actors::Role,
+    ) -> Result<<Test as system::Trait>::AccountId, &'static str> {
+        // We "randomly" select an account Id.
+        Ok(TEST_MOCK_LIAISON)
+    }
+}
+
 pub struct AnyDataObjectTypeIsActive {}
 impl<T: data_object_type_registry::Trait> traits::IsActiveDataObjectType<T>
     for AnyDataObjectTypeIsActive
@@ -58,21 +91,21 @@ impl<T: data_object_type_registry::Trait> traits::IsActiveDataObjectType<T>
 pub struct MockContent {}
 impl traits::ContentIdExists<Test> for MockContent {
     fn has_content(which: &<Test as data_directory::Trait>::ContentId) -> bool {
-        *which == 42
+        *which == TEST_MOCK_EXISTING_CID
     }
 
     fn get_data_object(
         which: &<Test as data_directory::Trait>::ContentId,
     ) -> Result<data_directory::DataObject<Test>, &'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<Self>;
+}
+
 pub struct ExtBuilder {
     first_data_object_type_id: u64,
     first_content_id: u64,
@@ -224,11 +287,8 @@ pub type TestDataDirectory = data_directory::Module<Test>;
 // pub type TestDataObject = data_directory::DataObject<Test>;
 pub type TestDataObjectStorageRegistry = data_object_storage_registry::Module<Test>;
 pub type TestContentDirectory = content_directory::Module<Test>;
+pub type TestActors = actors::Module<Test>;
 
-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: FnOnce() -> R>(f: F) -> R {
     with_externalities(
         &mut ExtBuilder::default()
@@ -237,6 +297,11 @@ pub fn with_default_mock_builder<R, F: FnOnce() -> R>(f: F) -> R {
             .first_relationship_id(TEST_FIRST_RELATIONSHIP_ID)
             .first_metadata_id(TEST_FIRST_METADATA_ID)
             .build(),
-        || f(),
+        || {
+            let roles: Vec<actors::Role> = vec![actors::Role::Storage];
+            assert!(TestActors::set_available_roles(roles).is_ok(), "");
+
+            f()
+        },
     )
 }

+ 14 - 0
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<T: system::Trait> Members<T> for () {
 // Roles
 pub trait Roles<T: system::Trait> {
     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<T::AccountId, &'static str>;
 }
 
 impl<T: system::Trait> Roles<T> 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<T::AccountId, &'static str> {
+        Err("not implemented")
+    }
 }
 
 // Storage