|
@@ -1,47 +1,112 @@
|
|
|
-use crate::data_object_type_registry::Trait as DOTRTrait;
|
|
|
-use crate::traits::{ContentIdExists, IsActiveDataObjectType};
|
|
|
-use codec::{Codec, Decode, Encode};
|
|
|
-use roles::actors;
|
|
|
-use roles::traits::Roles;
|
|
|
+//! # Data directory module
|
|
|
+//! Data directory module for the Joystream platform manages IPFS content id, storage providers,
|
|
|
+//! owners of the content. It allows to add and accept or reject the content in the system.
|
|
|
+//!
|
|
|
+//! ## Comments
|
|
|
+//!
|
|
|
+//! Data object type registry module uses bureaucracy module to authorize actions.
|
|
|
+//!
|
|
|
+//! ## Supported extrinsics
|
|
|
+//!
|
|
|
+//! ### Public extrinsic
|
|
|
+//! - [add_content](./struct.Module.html#method.add_content) - Adds the content to the system.
|
|
|
+//!
|
|
|
+//! ### Private extrinsics
|
|
|
+//! - accept_content - Storage provider accepts a content.
|
|
|
+//! - reject_content - Storage provider rejects a content.
|
|
|
+//! - remove_known_content_id - Removes the content id from the list of known content ids. Requires root privileges.
|
|
|
+//! - set_known_content_id - Sets the content id from the list of known content ids. Requires root privileges.
|
|
|
+//!
|
|
|
+
|
|
|
+// Do not delete! Cannot be uncommented by default, because of Parity decl_module! issue.
|
|
|
+//#![warn(missing_docs)]
|
|
|
+
|
|
|
+use codec::{Decode, Encode};
|
|
|
use rstd::prelude::*;
|
|
|
-use sr_primitives::traits::{MaybeSerialize, Member, SimpleArithmetic};
|
|
|
-use srml_support::{decl_event, decl_module, decl_storage, dispatch, ensure, Parameter};
|
|
|
-use system::{self, ensure_root, ensure_signed};
|
|
|
-
|
|
|
-pub trait Trait: timestamp::Trait + system::Trait + DOTRTrait + membership::members::Trait {
|
|
|
+use sr_primitives::traits::{MaybeSerialize, Member};
|
|
|
+use srml_support::{decl_error, decl_event, decl_module, decl_storage, ensure, Parameter};
|
|
|
+use system::{self, ensure_root};
|
|
|
+
|
|
|
+use common::origin_validator::ActorOriginValidator;
|
|
|
+pub(crate) use common::BlockAndTime;
|
|
|
+
|
|
|
+use crate::data_object_type_registry;
|
|
|
+use crate::data_object_type_registry::IsActiveDataObjectType;
|
|
|
+use crate::{MemberId, StorageBureaucracy, StorageProviderId};
|
|
|
+
|
|
|
+/// The _Data directory_ main _Trait_.
|
|
|
+pub trait Trait:
|
|
|
+ timestamp::Trait
|
|
|
+ + system::Trait
|
|
|
+ + data_object_type_registry::Trait
|
|
|
+ + membership::members::Trait
|
|
|
+ + bureaucracy::Trait<bureaucracy::Instance2>
|
|
|
+{
|
|
|
+ /// _Data directory_ event type.
|
|
|
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
|
|
|
|
|
|
+ /// Content id.
|
|
|
type ContentId: Parameter + Member + MaybeSerialize + Copy + Ord + Default;
|
|
|
|
|
|
- type SchemaId: Parameter
|
|
|
- + Member
|
|
|
- + SimpleArithmetic
|
|
|
- + Codec
|
|
|
- + Default
|
|
|
- + Copy
|
|
|
- + MaybeSerialize
|
|
|
- + PartialEq;
|
|
|
-
|
|
|
- type Roles: Roles<Self>;
|
|
|
- type IsActiveDataObjectType: IsActiveDataObjectType<Self>;
|
|
|
+ /// Provides random storage provider id.
|
|
|
+ type StorageProviderHelper: StorageProviderHelper<Self>;
|
|
|
+
|
|
|
+ ///Active data object type validator.
|
|
|
+ type IsActiveDataObjectType: data_object_type_registry::IsActiveDataObjectType<Self>;
|
|
|
+
|
|
|
+ /// Validates member id and origin combination.
|
|
|
+ type MemberOriginValidator: ActorOriginValidator<Self::Origin, MemberId<Self>, Self::AccountId>;
|
|
|
}
|
|
|
|
|
|
-static MSG_CID_NOT_FOUND: &str = "Content with this ID not found.";
|
|
|
-static MSG_LIAISON_REQUIRED: &str = "Only the liaison for the content may modify its status.";
|
|
|
-static MSG_CREATOR_MUST_BE_MEMBER: &str = "Only active members may create content.";
|
|
|
-static MSG_DO_TYPE_MUST_BE_ACTIVE: &str =
|
|
|
- "Cannot create content for inactive or missing data object type.";
|
|
|
+decl_error! {
|
|
|
+ /// _Data object storage registry_ module predefined errors.
|
|
|
+ pub enum Error {
|
|
|
+ /// Content with this ID not found.
|
|
|
+ CidNotFound,
|
|
|
|
|
|
-#[derive(Clone, Encode, Decode, PartialEq, Debug)]
|
|
|
-pub struct BlockAndTime<T: Trait> {
|
|
|
- pub block: T::BlockNumber,
|
|
|
- pub time: T::Moment,
|
|
|
+ /// Only the liaison for the content may modify its status.
|
|
|
+ LiaisonRequired,
|
|
|
+
|
|
|
+ /// Cannot create content for inactive or missing data object type.
|
|
|
+ DataObjectTypeMustBeActive,
|
|
|
+
|
|
|
+ /// "Data object already added under this content id".
|
|
|
+ DataObjectAlreadyAdded,
|
|
|
+
|
|
|
+ /// Require root origin in extrinsics.
|
|
|
+ RequireRootOrigin,
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
+impl From<system::Error> for Error {
|
|
|
+ fn from(error: system::Error) -> Self {
|
|
|
+ match error {
|
|
|
+ system::Error::Other(msg) => Error::Other(msg),
|
|
|
+ system::Error::RequireRootOrigin => Error::RequireRootOrigin,
|
|
|
+ _ => Error::Other(error.into()),
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl From<bureaucracy::Error> for Error {
|
|
|
+ fn from(error: bureaucracy::Error) -> Self {
|
|
|
+ match error {
|
|
|
+ bureaucracy::Error::Other(msg) => Error::Other(msg),
|
|
|
+ _ => Error::Other(error.into()),
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/// The decision of the storage provider when it acts as liaison.
|
|
|
#[derive(Clone, Encode, Decode, PartialEq, Debug)]
|
|
|
pub enum LiaisonJudgement {
|
|
|
+ /// Content awaits for a judgment.
|
|
|
Pending,
|
|
|
+
|
|
|
+ /// Content accepted.
|
|
|
Accepted,
|
|
|
+
|
|
|
+ /// Content rejected.
|
|
|
Rejected,
|
|
|
}
|
|
|
|
|
@@ -51,137 +116,162 @@ impl Default for LiaisonJudgement {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/// Manages content ids, type and storage provider decision about it.
|
|
|
#[derive(Clone, Encode, Decode, PartialEq, Debug)]
|
|
|
pub struct DataObject<T: Trait> {
|
|
|
- pub owner: T::AccountId,
|
|
|
- pub added_at: BlockAndTime<T>,
|
|
|
- pub type_id: <T as DOTRTrait>::DataObjectTypeId,
|
|
|
+ /// Content owner.
|
|
|
+ pub owner: MemberId<T>,
|
|
|
+
|
|
|
+ /// Content added at.
|
|
|
+ pub added_at: BlockAndTime<T::BlockNumber, T::Moment>,
|
|
|
+
|
|
|
+ /// Content type id.
|
|
|
+ pub type_id: <T as data_object_type_registry::Trait>::DataObjectTypeId,
|
|
|
+
|
|
|
+ /// Content size in bytes.
|
|
|
pub size: u64,
|
|
|
- pub liaison: T::AccountId,
|
|
|
- pub liaison_judgement: LiaisonJudgement,
|
|
|
- pub ipfs_content_id: Vec<u8>, // shoule we use rust multi-format crate?
|
|
|
- // TODO signing_key: public key supplied by the uploader,
|
|
|
- // they sigh the content with this key
|
|
|
|
|
|
- // TODO add support for this field (Some if judgment == Rejected)
|
|
|
- // pub rejection_reason: Option<Vec<u8>>,
|
|
|
-}
|
|
|
+ /// Storage provider id of the liaison.
|
|
|
+ pub liaison: StorageProviderId<T>,
|
|
|
|
|
|
-#[derive(Clone, Encode, Decode, PartialEq, Debug)]
|
|
|
-pub enum ContentVisibility {
|
|
|
- Draft, // TODO rename to Unlisted?
|
|
|
- Public,
|
|
|
-}
|
|
|
+ /// Storage provider as liaison judgment.
|
|
|
+ pub liaison_judgement: LiaisonJudgement,
|
|
|
|
|
|
-impl Default for ContentVisibility {
|
|
|
- fn default() -> Self {
|
|
|
- ContentVisibility::Draft // TODO make Public by default?
|
|
|
- }
|
|
|
+ /// IPFS content id.
|
|
|
+ pub ipfs_content_id: Vec<u8>,
|
|
|
}
|
|
|
|
|
|
decl_storage! {
|
|
|
trait Store for Module<T: Trait> as DataDirectory {
|
|
|
+ /// List of ids known to the system.
|
|
|
+ pub KnownContentIds get(known_content_ids): Vec<T::ContentId> = Vec::new();
|
|
|
|
|
|
- // TODO default_liaison = Joystream storage account id.
|
|
|
-
|
|
|
- // TODO this list of ids should be moved off-chain once we have Content Indexer.
|
|
|
- // TODO deprecated, moved tp storage relationship
|
|
|
- pub KnownContentIds get(known_content_ids): Vec<T::ContentId> = vec![];
|
|
|
-
|
|
|
+ /// Maps data objects by their content id.
|
|
|
pub DataObjectByContentId get(data_object_by_content_id):
|
|
|
map T::ContentId => Option<DataObject<T>>;
|
|
|
-
|
|
|
- // Default storage provider account id, overrides all active storage providers as liason if set
|
|
|
- pub PrimaryLiaisonAccountId get(primary_liaison_account_id): Option<T::AccountId>;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
decl_event! {
|
|
|
+ /// _Data directory_ events
|
|
|
pub enum Event<T> where
|
|
|
<T as Trait>::ContentId,
|
|
|
- <T as system::Trait>::AccountId
|
|
|
+ MemberId = MemberId<T>,
|
|
|
+ StorageProviderId = StorageProviderId<T>
|
|
|
{
|
|
|
- // The account is the one who uploaded the content.
|
|
|
- ContentAdded(ContentId, AccountId),
|
|
|
-
|
|
|
- // The account is the liaison - only they can reject or accept
|
|
|
- ContentAccepted(ContentId, AccountId),
|
|
|
- ContentRejected(ContentId, AccountId),
|
|
|
+ /// Emits on adding of the content.
|
|
|
+ /// Params:
|
|
|
+ /// - Id of the relationship.
|
|
|
+ /// - Id of the member.
|
|
|
+ ContentAdded(ContentId, MemberId),
|
|
|
+
|
|
|
+ /// Emits when the storage provider accepts a content.
|
|
|
+ /// Params:
|
|
|
+ /// - Id of the relationship.
|
|
|
+ /// - Id of the storage provider.
|
|
|
+ ContentAccepted(ContentId, StorageProviderId),
|
|
|
+
|
|
|
+ /// Emits when the storage provider rejects a content.
|
|
|
+ /// Params:
|
|
|
+ /// - Id of the relationship.
|
|
|
+ /// - Id of the storage provider.
|
|
|
+ ContentRejected(ContentId, StorageProviderId),
|
|
|
}
|
|
|
}
|
|
|
|
|
|
decl_module! {
|
|
|
+ /// _Data directory_ substrate module.
|
|
|
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
|
|
|
+ /// Default deposit_event() handler
|
|
|
fn deposit_event() = default;
|
|
|
|
|
|
- // TODO send file_name as param so we could create a Draft metadata in this fn
|
|
|
+ /// Predefined errors.
|
|
|
+ type Error = Error;
|
|
|
+
|
|
|
+
|
|
|
+ /// Adds the content to the system. Member id should match its origin. The created DataObject
|
|
|
+ /// awaits liaison to accept or reject it.
|
|
|
pub fn add_content(
|
|
|
origin,
|
|
|
+ member_id: MemberId<T>,
|
|
|
content_id: T::ContentId,
|
|
|
- type_id: <T as DOTRTrait>::DataObjectTypeId,
|
|
|
+ type_id: <T as data_object_type_registry::Trait>::DataObjectTypeId,
|
|
|
size: u64,
|
|
|
ipfs_content_id: Vec<u8>
|
|
|
) {
|
|
|
- let who = ensure_signed(origin)?;
|
|
|
- ensure!(<membership::members::Module<T>>::is_member_account(&who), MSG_CREATOR_MUST_BE_MEMBER);
|
|
|
+ T::MemberOriginValidator::ensure_actor_origin(
|
|
|
+ origin,
|
|
|
+ member_id,
|
|
|
+ )?;
|
|
|
|
|
|
ensure!(T::IsActiveDataObjectType::is_active_data_object_type(&type_id),
|
|
|
- MSG_DO_TYPE_MUST_BE_ACTIVE);
|
|
|
+ Error::DataObjectTypeMustBeActive);
|
|
|
|
|
|
ensure!(!<DataObjectByContentId<T>>::exists(content_id),
|
|
|
- "Data object aready added under this content id");
|
|
|
+ Error::DataObjectAlreadyAdded);
|
|
|
|
|
|
- let liaison = match Self::primary_liaison_account_id() {
|
|
|
- // Select primary liaison if set
|
|
|
- Some(primary_liaison) => primary_liaison,
|
|
|
-
|
|
|
- // Select liaison from staked roles if available
|
|
|
- _ => T::Roles::random_account_for_role(actors::Role::StorageProvider)?
|
|
|
- };
|
|
|
+ let liaison = T::StorageProviderHelper::get_random_storage_provider()?;
|
|
|
|
|
|
// Let's create the entry then
|
|
|
let data: DataObject<T> = DataObject {
|
|
|
type_id,
|
|
|
size,
|
|
|
- added_at: Self::current_block_and_time(),
|
|
|
- owner: who.clone(),
|
|
|
+ added_at: common::current_block_time::<T>(),
|
|
|
+ owner: member_id,
|
|
|
liaison,
|
|
|
liaison_judgement: LiaisonJudgement::Pending,
|
|
|
ipfs_content_id,
|
|
|
};
|
|
|
|
|
|
+ //
|
|
|
+ // == MUTATION SAFE ==
|
|
|
+ //
|
|
|
+
|
|
|
<DataObjectByContentId<T>>::insert(&content_id, data);
|
|
|
- Self::deposit_event(RawEvent::ContentAdded(content_id, who));
|
|
|
+ Self::deposit_event(RawEvent::ContentAdded(content_id, member_id));
|
|
|
}
|
|
|
|
|
|
- // The LiaisonJudgement can be updated, but only by the liaison.
|
|
|
- fn accept_content(origin, content_id: T::ContentId) {
|
|
|
- let who = ensure_signed(origin)?;
|
|
|
- Self::update_content_judgement(&who, content_id, LiaisonJudgement::Accepted)?;
|
|
|
+ /// Storage provider accepts a content. Requires signed storage provider account and its id.
|
|
|
+ /// The LiaisonJudgement can be updated, but only by the liaison.
|
|
|
+ pub(crate) fn accept_content(
|
|
|
+ origin,
|
|
|
+ storage_provider_id: StorageProviderId<T>,
|
|
|
+ content_id: T::ContentId
|
|
|
+ ) {
|
|
|
+ <StorageBureaucracy<T>>::ensure_worker_signed(origin, &storage_provider_id)?;
|
|
|
+
|
|
|
+ // == MUTATION SAFE ==
|
|
|
+
|
|
|
+ Self::update_content_judgement(&storage_provider_id, content_id, LiaisonJudgement::Accepted)?;
|
|
|
+
|
|
|
<KnownContentIds<T>>::mutate(|ids| ids.push(content_id));
|
|
|
- Self::deposit_event(RawEvent::ContentAccepted(content_id, who));
|
|
|
- }
|
|
|
|
|
|
- fn reject_content(origin, content_id: T::ContentId) {
|
|
|
- let who = ensure_signed(origin)?;
|
|
|
- Self::update_content_judgement(&who, content_id, LiaisonJudgement::Rejected)?;
|
|
|
- Self::deposit_event(RawEvent::ContentRejected(content_id, who));
|
|
|
+ Self::deposit_event(RawEvent::ContentAccepted(content_id, storage_provider_id));
|
|
|
}
|
|
|
|
|
|
- // Sudo methods
|
|
|
+ /// Storage provider rejects a content. Requires signed storage provider account and its id.
|
|
|
+ /// The LiaisonJudgement can be updated, but only by the liaison.
|
|
|
+ pub(crate) fn reject_content(
|
|
|
+ origin,
|
|
|
+ storage_provider_id: StorageProviderId<T>,
|
|
|
+ content_id: T::ContentId
|
|
|
+ ) {
|
|
|
+ <StorageBureaucracy<T>>::ensure_worker_signed(origin, &storage_provider_id)?;
|
|
|
|
|
|
- fn set_primary_liaison_account_id(origin, account: T::AccountId) {
|
|
|
- ensure_root(origin)?;
|
|
|
- <PrimaryLiaisonAccountId<T>>::put(account);
|
|
|
- }
|
|
|
+ // == MUTATION SAFE ==
|
|
|
|
|
|
- fn unset_primary_liaison_account_id(origin) {
|
|
|
- ensure_root(origin)?;
|
|
|
- <PrimaryLiaisonAccountId<T>>::take();
|
|
|
+ Self::update_content_judgement(&storage_provider_id, content_id, LiaisonJudgement::Rejected)?;
|
|
|
+ Self::deposit_event(RawEvent::ContentRejected(content_id, storage_provider_id));
|
|
|
}
|
|
|
|
|
|
+ // Sudo methods
|
|
|
+
|
|
|
+ /// Removes the content id from the list of known content ids. Requires root privileges.
|
|
|
fn remove_known_content_id(origin, content_id: T::ContentId) {
|
|
|
ensure_root(origin)?;
|
|
|
+
|
|
|
+ // == MUTATION SAFE ==
|
|
|
+
|
|
|
let upd_content_ids: Vec<T::ContentId> = Self::known_content_ids()
|
|
|
.into_iter()
|
|
|
.filter(|&id| id != content_id)
|
|
@@ -189,43 +279,27 @@ decl_module! {
|
|
|
<KnownContentIds<T>>::put(upd_content_ids);
|
|
|
}
|
|
|
|
|
|
+ /// Sets the content id from the list of known content ids. Requires root privileges.
|
|
|
fn set_known_content_id(origin, content_ids: Vec<T::ContentId>) {
|
|
|
ensure_root(origin)?;
|
|
|
- <KnownContentIds<T>>::put(content_ids);
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
|
|
|
-impl<T: Trait> ContentIdExists<T> for Module<T> {
|
|
|
- fn has_content(content_id: &T::ContentId) -> bool {
|
|
|
- Self::data_object_by_content_id(*content_id).is_some()
|
|
|
- }
|
|
|
+ // == MUTATION SAFE ==
|
|
|
|
|
|
- fn get_data_object(content_id: &T::ContentId) -> Result<DataObject<T>, &'static str> {
|
|
|
- match Self::data_object_by_content_id(*content_id) {
|
|
|
- Some(data) => Ok(data),
|
|
|
- None => Err(MSG_CID_NOT_FOUND),
|
|
|
+ <KnownContentIds<T>>::put(content_ids);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
impl<T: Trait> Module<T> {
|
|
|
- fn current_block_and_time() -> BlockAndTime<T> {
|
|
|
- BlockAndTime {
|
|
|
- block: <system::Module<T>>::block_number(),
|
|
|
- time: <timestamp::Module<T>>::now(),
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
fn update_content_judgement(
|
|
|
- who: &T::AccountId,
|
|
|
+ storage_provider_id: &StorageProviderId<T>,
|
|
|
content_id: T::ContentId,
|
|
|
judgement: LiaisonJudgement,
|
|
|
- ) -> dispatch::Result {
|
|
|
- let mut data = Self::data_object_by_content_id(&content_id).ok_or(MSG_CID_NOT_FOUND)?;
|
|
|
+ ) -> Result<(), Error> {
|
|
|
+ let mut data = Self::data_object_by_content_id(&content_id).ok_or(Error::CidNotFound)?;
|
|
|
|
|
|
// Make sure the liaison matches
|
|
|
- ensure!(data.liaison == *who, MSG_LIAISON_REQUIRED);
|
|
|
+ ensure!(data.liaison == *storage_provider_id, Error::LiaisonRequired);
|
|
|
|
|
|
data.liaison_judgement = judgement;
|
|
|
<DataObjectByContentId<T>>::insert(content_id, data);
|
|
@@ -234,93 +308,30 @@ impl<T: Trait> Module<T> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-#[cfg(test)]
|
|
|
-mod tests {
|
|
|
- use crate::mock::*;
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn succeed_adding_content() {
|
|
|
- with_default_mock_builder(|| {
|
|
|
- let sender = 1 as u64;
|
|
|
- // Register a content with 1234 bytes of type 1, which should be recognized.
|
|
|
- let res = TestDataDirectory::add_content(
|
|
|
- Origin::signed(sender),
|
|
|
- 1,
|
|
|
- 1234,
|
|
|
- 0,
|
|
|
- vec![1, 3, 3, 7],
|
|
|
- );
|
|
|
- assert!(res.is_ok());
|
|
|
- });
|
|
|
- }
|
|
|
+/// Provides random storage provider id. We use it when assign the content to the storage provider.
|
|
|
+pub trait StorageProviderHelper<T: Trait> {
|
|
|
+ /// Provides random storage provider id.
|
|
|
+ fn get_random_storage_provider() -> Result<StorageProviderId<T>, &'static str>;
|
|
|
+}
|
|
|
|
|
|
- #[test]
|
|
|
- fn accept_content_as_liaison() {
|
|
|
- with_default_mock_builder(|| {
|
|
|
- let sender = 1 as u64;
|
|
|
- let res = TestDataDirectory::add_content(
|
|
|
- Origin::signed(sender),
|
|
|
- 1,
|
|
|
- 1234,
|
|
|
- 0,
|
|
|
- vec![1, 2, 3, 4],
|
|
|
- );
|
|
|
- assert!(res.is_ok());
|
|
|
-
|
|
|
- // An appropriate event should have been fired.
|
|
|
- let (content_id, creator) = match System::events().last().unwrap().event {
|
|
|
- MetaEvent::data_directory(data_directory::RawEvent::ContentAdded(
|
|
|
- content_id,
|
|
|
- creator,
|
|
|
- )) => (content_id, creator),
|
|
|
- _ => (0u64, 0xdeadbeefu64), // invalid value, unlikely to match
|
|
|
- };
|
|
|
- assert_ne!(creator, 0xdeadbeefu64);
|
|
|
- assert_eq!(creator, sender);
|
|
|
-
|
|
|
- // Accepting content should not work with some random origin
|
|
|
- let res = TestDataDirectory::accept_content(Origin::signed(1), content_id);
|
|
|
- assert!(res.is_err());
|
|
|
-
|
|
|
- // However, with the liaison as origin it should.
|
|
|
- let res =
|
|
|
- TestDataDirectory::accept_content(Origin::signed(TEST_MOCK_LIAISON), content_id);
|
|
|
- assert!(res.is_ok());
|
|
|
- });
|
|
|
+/// Content access helper.
|
|
|
+pub trait ContentIdExists<T: Trait> {
|
|
|
+ /// Verifies the content existence.
|
|
|
+ fn has_content(id: &T::ContentId) -> bool;
|
|
|
+
|
|
|
+ /// Returns the data object for the provided content id.
|
|
|
+ fn get_data_object(id: &T::ContentId) -> Result<DataObject<T>, &'static str>;
|
|
|
+}
|
|
|
+
|
|
|
+impl<T: Trait> ContentIdExists<T> for Module<T> {
|
|
|
+ fn has_content(content_id: &T::ContentId) -> bool {
|
|
|
+ Self::data_object_by_content_id(*content_id).is_some()
|
|
|
}
|
|
|
|
|
|
- #[test]
|
|
|
- fn reject_content_as_liaison() {
|
|
|
- with_default_mock_builder(|| {
|
|
|
- let sender = 1 as u64;
|
|
|
- let res = TestDataDirectory::add_content(
|
|
|
- Origin::signed(sender),
|
|
|
- 1,
|
|
|
- 1234,
|
|
|
- 0,
|
|
|
- vec![1, 2, 3, 4],
|
|
|
- );
|
|
|
- assert!(res.is_ok());
|
|
|
-
|
|
|
- // An appropriate event should have been fired.
|
|
|
- let (content_id, creator) = match System::events().last().unwrap().event {
|
|
|
- MetaEvent::data_directory(data_directory::RawEvent::ContentAdded(
|
|
|
- content_id,
|
|
|
- creator,
|
|
|
- )) => (content_id, creator),
|
|
|
- _ => (0u64, 0xdeadbeefu64), // invalid value, unlikely to match
|
|
|
- };
|
|
|
- assert_ne!(creator, 0xdeadbeefu64);
|
|
|
- assert_eq!(creator, sender);
|
|
|
-
|
|
|
- // Rejecting content should not work with some random origin
|
|
|
- let res = TestDataDirectory::reject_content(Origin::signed(1), content_id);
|
|
|
- assert!(res.is_err());
|
|
|
-
|
|
|
- // However, with the liaison as origin it should.
|
|
|
- let res =
|
|
|
- TestDataDirectory::reject_content(Origin::signed(TEST_MOCK_LIAISON), content_id);
|
|
|
- assert!(res.is_ok());
|
|
|
- });
|
|
|
+ fn get_data_object(content_id: &T::ContentId) -> Result<DataObject<T>, &'static str> {
|
|
|
+ match Self::data_object_by_content_id(*content_id) {
|
|
|
+ Some(data) => Ok(data),
|
|
|
+ None => Err(Error::LiaisonRequired.into()),
|
|
|
+ }
|
|
|
}
|
|
|
}
|