data_directory.rs 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. use crate::storage::data_object_type_registry::Trait as DOTRTrait;
  2. use crate::traits::{ContentIdExists, IsActiveDataObjectType, Members};
  3. use parity_codec::Codec;
  4. use parity_codec_derive::{Decode, Encode};
  5. use primitives::ed25519::Signature as Ed25519Signature;
  6. use rstd::prelude::*;
  7. use runtime_primitives::traits::{
  8. As, MaybeDebug, MaybeSerializeDebug, Member, SimpleArithmetic, Verify,
  9. };
  10. use srml_support::{
  11. decl_event, decl_module, decl_storage, dispatch, ensure, Parameter, StorageMap, StorageValue,
  12. };
  13. use system::{self, ensure_signed};
  14. pub type Ed25519AuthorityId = <Ed25519Signature as Verify>::Signer;
  15. pub trait Trait: timestamp::Trait + system::Trait + DOTRTrait + MaybeDebug {
  16. type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
  17. type ContentId: Parameter
  18. + Member
  19. + SimpleArithmetic
  20. + Codec
  21. + Default
  22. + Copy
  23. + As<usize>
  24. + As<u64>
  25. + MaybeSerializeDebug
  26. + PartialEq;
  27. type Members: Members<Self>;
  28. type IsActiveDataObjectType: IsActiveDataObjectType<Self>;
  29. }
  30. static MSG_CID_NOT_FOUND: &str = "Content with this ID not found.";
  31. static MSG_LIAISON_REQUIRED: &str = "Only the liaison for the content may modify its status.";
  32. static MSG_CREATOR_MUST_BE_MEMBER: &str = "Only active members may create content.";
  33. static MSG_DO_TYPE_MUST_BE_ACTIVE: &str =
  34. "Cannot create content for inactive or missing data object type.";
  35. const DEFAULT_FIRST_CONTENT_ID: u64 = 1;
  36. #[derive(Clone, Encode, Decode, PartialEq)]
  37. #[cfg_attr(feature = "std", derive(Debug))]
  38. pub enum LiaisonJudgement {
  39. Pending,
  40. Rejected,
  41. Accepted,
  42. }
  43. impl Default for LiaisonJudgement {
  44. fn default() -> Self {
  45. LiaisonJudgement::Pending
  46. }
  47. }
  48. #[derive(Clone, Encode, Decode, PartialEq)]
  49. #[cfg_attr(feature = "std", derive(Debug))]
  50. pub struct DataObject<T: Trait> {
  51. pub data_object_type: <T as DOTRTrait>::DataObjectTypeId,
  52. pub signing_key: Option<Ed25519AuthorityId>,
  53. pub size: u64,
  54. pub added_at_block: T::BlockNumber,
  55. pub added_at_time: T::Moment,
  56. pub owner: T::AccountId,
  57. pub liaison: T::AccountId,
  58. pub liaison_judgement: LiaisonJudgement,
  59. }
  60. decl_storage! {
  61. trait Store for Module<T: Trait> as DataDirectory {
  62. // Start at this value
  63. pub FirstContentId get(first_content_id) config(first_content_id): T::ContentId = T::ContentId::sa(DEFAULT_FIRST_CONTENT_ID);
  64. // Increment
  65. pub NextContentId get(next_content_id) build(|config: &GenesisConfig<T>| config.first_content_id): T::ContentId = T::ContentId::sa(DEFAULT_FIRST_CONTENT_ID);
  66. // Mapping of Content ID to Data Object
  67. pub Contents get(contents): map T::ContentId => Option<DataObject<T>>;
  68. }
  69. }
  70. decl_event! {
  71. pub enum Event<T> where
  72. <T as Trait>::ContentId,
  73. <T as system::Trait>::AccountId
  74. {
  75. // The account is the Liaison that was selected
  76. ContentAdded(ContentId, AccountId),
  77. // The account is the liaison again - only they can reject or accept
  78. ContentAccepted(ContentId, AccountId),
  79. ContentRejected(ContentId, AccountId),
  80. }
  81. }
  82. impl<T: Trait> ContentIdExists<T> for Module<T> {
  83. fn has_content(which: &T::ContentId) -> bool {
  84. Self::contents(which.clone()).is_some()
  85. }
  86. fn get_data_object(which: &T::ContentId) -> Result<DataObject<T>, &'static str> {
  87. match Self::contents(which.clone()) {
  88. None => Err(MSG_CID_NOT_FOUND),
  89. Some(data) => Ok(data),
  90. }
  91. }
  92. }
  93. decl_module! {
  94. pub struct Module<T: Trait> for enum Call where origin: T::Origin {
  95. fn deposit_event<T>() = default;
  96. pub fn add_content(origin, data_object_type_id: <T as DOTRTrait>::DataObjectTypeId,
  97. size: u64, signing_key: Option<Ed25519AuthorityId>) {
  98. // Origin has to be a member
  99. let who = ensure_signed(origin)?;
  100. ensure!(T::Members::is_active_member(&who), MSG_CREATOR_MUST_BE_MEMBER);
  101. // Data object type has to be active
  102. ensure!(T::IsActiveDataObjectType::is_active_data_object_type(&data_object_type_id), MSG_DO_TYPE_MUST_BE_ACTIVE);
  103. // The liaison is something we need to take from staked roles. The idea
  104. // is to select the liaison, for now randomly.
  105. // FIXME without that module, we're currently hardcoding it, to the
  106. // origin, which is wrong on many levels.
  107. let liaison = who.clone();
  108. // Let's create the entry then
  109. let new_id = Self::next_content_id();
  110. let data: DataObject<T> = DataObject {
  111. data_object_type: data_object_type_id,
  112. signing_key: signing_key,
  113. size: size,
  114. added_at_block: <system::Module<T>>::block_number(),
  115. added_at_time: <timestamp::Module<T>>::now(),
  116. owner: who,
  117. liaison: liaison.clone(),
  118. liaison_judgement: LiaisonJudgement::Pending,
  119. };
  120. // If we've constructed the data, we can store it and send an event.
  121. <Contents<T>>::insert(new_id, data);
  122. <NextContentId<T>>::mutate(|n| { *n += T::ContentId::sa(1); });
  123. Self::deposit_event(RawEvent::ContentAdded(new_id, liaison));
  124. }
  125. // The LiaisonJudgement can be updated, but only by the liaison.
  126. fn accept_content(origin, id: T::ContentId) {
  127. let who = ensure_signed(origin)?;
  128. Self::update_content_judgement(&who, id.clone(), LiaisonJudgement::Accepted)?;
  129. Self::deposit_event(RawEvent::ContentAccepted(id, who));
  130. }
  131. fn reject_content(origin, id: T::ContentId) {
  132. let who = ensure_signed(origin)?;
  133. Self::update_content_judgement(&who, id.clone(), LiaisonJudgement::Rejected)?;
  134. Self::deposit_event(RawEvent::ContentRejected(id, who));
  135. }
  136. }
  137. }
  138. impl<T: Trait> Module<T> {
  139. fn update_content_judgement(
  140. who: &T::AccountId,
  141. id: T::ContentId,
  142. judgement: LiaisonJudgement,
  143. ) -> dispatch::Result {
  144. // Find the data
  145. let mut data = Self::contents(&id).ok_or(MSG_CID_NOT_FOUND)?;
  146. // Make sure the liaison matches
  147. ensure!(data.liaison == *who, MSG_LIAISON_REQUIRED);
  148. // At this point we can update the data.
  149. data.liaison_judgement = judgement;
  150. // Update and send event.
  151. <Contents<T>>::insert(id, data);
  152. Ok(())
  153. }
  154. }
  155. #[cfg(test)]
  156. mod tests {
  157. use crate::storage::mock::*;
  158. #[test]
  159. fn succeed_adding_content() {
  160. with_default_mock_builder(|| {
  161. // Register a content with 1234 bytes of type 1, which should be recognized.
  162. let res = TestDataDirectory::add_content(Origin::signed(1), 1, 1234, None);
  163. assert!(res.is_ok());
  164. });
  165. }
  166. #[test]
  167. fn accept_content_as_liaison() {
  168. with_default_mock_builder(|| {
  169. let res = TestDataDirectory::add_content(Origin::signed(1), 1, 1234, None);
  170. assert!(res.is_ok());
  171. // An appropriate event should have been fired.
  172. let (content_id, liaison) = match System::events().last().unwrap().event {
  173. MetaEvent::data_directory(data_directory::RawEvent::ContentAdded(
  174. content_id,
  175. liaison,
  176. )) => (content_id, liaison),
  177. _ => (0u64, 0xdeadbeefu64), // invalid value, unlikely to match
  178. };
  179. assert_ne!(liaison, 0xdeadbeefu64);
  180. // Accepting content should not work with some random origin
  181. let res = TestDataDirectory::accept_content(Origin::signed(42), content_id);
  182. assert!(res.is_err());
  183. // However, with the liaison as origin it should.
  184. let res = TestDataDirectory::accept_content(Origin::signed(liaison), content_id);
  185. assert!(res.is_ok());
  186. });
  187. }
  188. #[test]
  189. fn reject_content_as_liaison() {
  190. with_default_mock_builder(|| {
  191. let res = TestDataDirectory::add_content(Origin::signed(1), 1, 1234, None);
  192. assert!(res.is_ok());
  193. // An appropriate event should have been fired.
  194. let (content_id, liaison) = match System::events().last().unwrap().event {
  195. MetaEvent::data_directory(data_directory::RawEvent::ContentAdded(
  196. content_id,
  197. liaison,
  198. )) => (content_id, liaison),
  199. _ => (0u64, 0xdeadbeefu64), // invalid value, unlikely to match
  200. };
  201. assert_ne!(liaison, 0xdeadbeefu64);
  202. // Rejecting content should not work with some random origin
  203. let res = TestDataDirectory::reject_content(Origin::signed(42), content_id);
  204. assert!(res.is_err());
  205. // However, with the liaison as origin it should.
  206. let res = TestDataDirectory::reject_content(Origin::signed(liaison), content_id);
  207. assert!(res.is_ok());
  208. });
  209. }
  210. }