Browse Source

Merge pull request #72 from Joystream/development

Acropolis Release
Mokhtar Naamani 5 years ago
parent
commit
7338beab41

+ 3 - 0
.gitignore

@@ -8,6 +8,9 @@
 # These are backup files generated by rustfmt
 **/*.rs.bk
 
+# runtime built with docker build script
+joystream_runtime.wasm
+
 # JetBrains IDEs
 .idea
 

+ 12 - 0
.travis.yml

@@ -0,0 +1,12 @@
+language: rust
+rust:
+  - 1.34.0
+cache:
+  - cargo
+before_script:
+  - rustup component add rustfmt
+  - ./setup.sh
+script:
+  - cargo fmt --all -- --check
+  - ./build.sh
+  - cargo test --verbose --all

+ 13 - 2
Cargo.toml

@@ -2,7 +2,11 @@
 authors = ['Joystream']
 edition = '2018'
 name = 'joystream-node-runtime'
-version = '5.3.0'
+version = '5.4.0'
+
+[dependencies]
+# restrict version of syn, see https://github.com/paritytech/substrate/issues/2326
+syn = { version = ">= 0.15.26, < 0.15.32", features = ["full"] }
 
 [features]
 default = ['std']
@@ -33,6 +37,7 @@ std = [
     'consensus-authorities/std',
     'grandpa/std',
     'finality-tracker/std',
+    'forum/std'
 ]
 [dependencies.aura]
 default_features = false
@@ -182,4 +187,10 @@ rev = '6dfc3e8b057bb00322136251a0f10305fbb1ad8f'
 default_features = false
 git = 'https://github.com/joystream/substrate.git'
 package = 'srml-finality-tracker'
-rev = '6dfc3e8b057bb00322136251a0f10305fbb1ad8f'
+rev = '6dfc3e8b057bb00322136251a0f10305fbb1ad8f'
+
+[dependencies.forum]
+default_features = false
+git = 'https://github.com/joystream/substrate-forum-module'
+package = 'substrate-forum-module'
+tag = "v1.0.0"

+ 0 - 8
Dockerfile

@@ -1,8 +0,0 @@
-# Build the image from https://github.com/joystream/docker-files/
-FROM rust-wasm
-
-WORKDIR /runtime
-
-COPY . /runtime
-
-RUN ./build.sh

+ 20 - 0
build-with-docker.sh

@@ -0,0 +1,20 @@
+#!/bin/bash
+
+# Build the toolchain image - contains only the compiler environmet for building runtime
+# This is then pushed to docker hub: https://cloud.docker.com/repository/docker/mokhtargz/wasm-toolchain/general
+# docker build --tag mokhtargz/wasm-toolchain --file ./wasm_dockerfile .
+
+# Build the runtime in a new image
+docker build --tag runtime-build --file ./runtime_dockerfile .
+
+# Create a non running container from the runtime build image
+docker create --name runtime-container runtime-build
+# Copy the compiled wasm blob from the docker container to our host
+docker cp runtime-container:/runtime/wasm/target/wasm32-unknown-unknown/release/joystream_node_runtime_wasm.compact.wasm joystream_runtime.wasm
+docker rm runtime-container
+
+# compute blake2_256 hash of the wasm blob - this should match the hash computed when the runtime file is
+# used to create a runtime upgrade proposal.
+# osx with: brew install b2sum; b2sum -b blake2b -l 256 joystream_runtime.wasm
+# ubuntu 17.0+ with: apt-get install coreutils; b2sum -l 256 joystream_runtime.wasm
+b2sum -l 256 joystream_runtime.wasm

+ 17 - 0
runtime_dockerfile

@@ -0,0 +1,17 @@
+# use toolchain image from: https://cloud.docker.com/repository/docker/mokhtargz/wasm-toolchain/general
+FROM mokhtargz/wasm-toolchain
+LABEL description="Joystream substrate runtime build"
+
+WORKDIR /runtime
+COPY . /runtime
+ENV TERM=xterm
+
+RUN rustup show
+
+# Ensure clean build
+RUN cargo clean
+RUN rm -fr target/
+RUN rm -fr wasm/target/
+
+# Build the runtime
+RUN cargo test && ./build.sh

+ 3 - 0
setup.sh

@@ -2,6 +2,9 @@
 
 set -e
 
+# Install OS dependencies
+curl https://getsubstrate.io -sSf | bash -s -- --fast
+
 echo "*** Initialising WASM build environment"
 
 if [ -z $CI_PROJECT_NAME ] ; then

+ 102 - 5
src/lib.rs

@@ -22,9 +22,14 @@ use storage::{data_directory, data_object_storage_registry, data_object_type_reg
 mod membership;
 mod memo;
 mod traits;
+pub use forum;
 use membership::members;
+
 mod migration;
 mod roles;
+mod service_discovery;
+use service_discovery::discovery;
+
 use client::{
     block_builder::api::{self as block_builder_api, CheckInherentsResult, InherentData},
     impl_runtime_apis, runtime_api as client_api,
@@ -55,7 +60,7 @@ pub use consensus::Call as ConsensusCall;
 #[cfg(any(feature = "std", test))]
 pub use runtime_primitives::BuildStorage;
 pub use runtime_primitives::{Perbill, Permill};
-pub use srml_support::{construct_runtime, StorageValue};
+pub use srml_support::{construct_runtime, StorageMap, StorageValue};
 pub use staking::StakerStatus;
 pub use timestamp::BlockPeriod;
 pub use timestamp::Call as TimestampCall;
@@ -125,7 +130,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
     spec_name: create_runtime_str!("joystream-node"),
     impl_name: create_runtime_str!("joystream-node"),
     authoring_version: 5,
-    spec_version: 3,
+    spec_version: 4,
     impl_version: 0,
     apis: RUNTIME_API_VERSIONS,
 };
@@ -266,7 +271,7 @@ impl storage::data_directory::Trait for Runtime {
     type ContentId = ContentId;
     type SchemaId = u64;
     type Members = Members;
-    type Roles = Actors;
+    type Roles = LookupRoles;
     type IsActiveDataObjectType = DataObjectTypeRegistry;
 }
 
@@ -280,16 +285,93 @@ impl storage::data_object_storage_registry::Trait for Runtime {
     type Event = Event;
     type DataObjectStorageRelationshipId = u64;
     type Members = Members;
-    type Roles = Actors;
+    type Roles = LookupRoles;
     type ContentIdExists = DataDirectory;
 }
 
+fn random_index(upper_bound: usize) -> usize {
+    let seed = <system::Module<Runtime>>::random_seed();
+    let mut rand: u64 = 0;
+    for offset in 0..8 {
+        rand += (seed.as_ref()[offset] as u64) << offset;
+    }
+    (rand as usize) % upper_bound
+}
+
+pub struct LookupRoles {}
+impl traits::Roles<Runtime> for LookupRoles {
+    fn is_role_account(account_id: &<Runtime as system::Trait>::AccountId) -> bool {
+        <actors::Module<Runtime>>::is_role_account(account_id)
+    }
+
+    fn account_has_role(
+        account_id: &<Runtime as system::Trait>::AccountId,
+        role: actors::Role,
+    ) -> bool {
+        <actors::Module<Runtime>>::account_has_role(account_id, role)
+    }
+
+    fn random_account_for_role(
+        role: actors::Role,
+    ) -> Result<<Runtime as system::Trait>::AccountId, &'static str> {
+        let ids = <actors::AccountIdsByRole<Runtime>>::get(role);
+
+        let live_ids: Vec<<Runtime as system::Trait>::AccountId> = ids
+            .into_iter()
+            .filter(|id| !<discovery::Module<Runtime>>::is_account_info_expired(id))
+            .collect();
+
+        if live_ids.len() == 0 {
+            Err("no staked account found")
+        } else {
+            let index = random_index(live_ids.len());
+            Ok(live_ids[index].clone())
+        }
+    }
+}
+
 impl members::Trait for Runtime {
     type Event = Event;
     type MemberId = u64;
     type PaidTermId = u64;
     type SubscriptionId = u64;
-    type Roles = Actors;
+    type Roles = LookupRoles;
+}
+
+/*
+ * Forum module integration
+ *
+ * ForumUserRegistry could have been implemented directly on
+ * the membership module, and likewise ForumUser on Profile,
+ * however this approach is more loosley coupled.
+ *
+ * Further exploration required to decide what the long
+ * run convention should be.
+ */
+
+/// Shim registry which will proxy ForumUserRegistry behaviour to the members module
+pub struct ShimMembershipRegistry {}
+
+impl forum::ForumUserRegistry<AccountId> for ShimMembershipRegistry {
+    fn get_forum_user(id: &AccountId) -> Option<forum::ForumUser<AccountId>> {
+        if let Some(_profile) = members::Module::<Runtime>::get_profile(id) {
+            // For now the profile is not used for anything,
+            // but in the future we may need it to read out more
+            // information possibly required to construct a
+            // ForumUser.
+
+            // Now convert member profile to a forum user
+
+            Some(forum::ForumUser { id: id.clone() })
+        } else {
+            None
+        }
+    }
+}
+
+impl forum::Trait for Runtime {
+    type Event = Event;
+    type MembershipRegistry = ShimMembershipRegistry;
 }
 
 impl migration::Trait for Runtime {
@@ -299,6 +381,19 @@ impl migration::Trait for Runtime {
 impl actors::Trait for Runtime {
     type Event = Event;
     type Members = Members;
+    type OnActorRemoved = HandleActorRemoved;
+}
+
+pub struct HandleActorRemoved {}
+impl actors::ActorRemoved<Runtime> for HandleActorRemoved {
+    fn actor_removed(actor: &<Runtime as system::Trait>::AccountId) {
+        Discovery::remove_account_info(actor);
+    }
+}
+
+impl discovery::Trait for Runtime {
+    type Event = Event;
+    type Roles = LookupRoles;
 }
 
 impl grandpa::Trait for Runtime {
@@ -333,12 +428,14 @@ construct_runtime!(
 		Council: council::{Module, Call, Storage, Event<T>, Config<T>},
 		Memo: memo::{Module, Call, Storage, Event<T>},
 		Members: members::{Module, Call, Storage, Event<T>, Config<T>},
+        Forum: forum::{Module, Call, Storage, Event<T>, Config<T>},
 		Migration: migration::{Module, Call, Storage, Event<T>},
 		Actors: actors::{Module, Call, Storage, Event<T>, Config<T>},
 		DataObjectTypeRegistry: data_object_type_registry::{Module, Call, Storage, Event<T>, Config<T>},
 		DataDirectory: data_directory::{Module, Call, Storage, Event<T>},
 		DataObjectStorageRegistry: data_object_storage_registry::{Module, Call, Storage, Event<T>, Config<T>},
 		DownloadSessions: downloads::{Module, Call, Storage, Event<T>, Config<T>},
+        Discovery: discovery::{Module, Call, Storage, Event<T>},
 	}
 );
 

+ 9 - 0
src/membership/members.rs

@@ -331,6 +331,15 @@ decl_module! {
 }
 
 impl<T: Trait> Module<T> {
+    pub fn get_profile(id: &T::AccountId) -> Option<Profile<T>> {
+        if let Some(member_id) = Self::ensure_is_member(id).ok() {
+            // This option _must_ be set
+            Self::member_profile(&member_id)
+        } else {
+            None
+        }
+    }
+
     fn ensure_not_member(who: &T::AccountId) -> dispatch::Result {
         ensure!(
             !<MemberIdByAccountId<T>>::exists(who),

+ 69 - 5
src/migration.rs

@@ -1,8 +1,10 @@
-use crate::membership::members;
-use crate::roles::actors;
+use crate::forum;
+use crate::storage;
 use crate::VERSION;
+use rstd::prelude::*;
 use runtime_io::print;
-use srml_support::{decl_event, decl_module, decl_storage, StorageValue};
+use srml_support::{decl_event, decl_module, decl_storage, StorageMap, StorageValue};
+use sudo;
 use system;
 
 // When preparing a new major runtime release version bump this value to match it and update
@@ -10,7 +12,7 @@ use system;
 // the runtime doesn't need to maintain any logic for old migrations. All knowledge about state of the chain and runtime
 // prior to the new runtime taking over is implicit in the migration code implementation. If assumptions are incorrect
 // behaviour is undefined.
-const MIGRATION_FOR_SPEC_VERSION: u32 = 2;
+const MIGRATION_FOR_SPEC_VERSION: u32 = 4;
 
 impl<T: Trait> Module<T> {
     fn runtime_initialization() {
@@ -24,14 +26,76 @@ impl<T: Trait> Module<T> {
         // add initialization of other modules introduced in this runtime
         // ...
 
+        Self::initialize_forum_module();
+
+        Self::initialize_storage_module();
+
         Self::deposit_event(RawEvent::Migrated(
             <system::Module<T>>::block_number(),
             VERSION.spec_version,
         ));
     }
+
+    fn initialize_forum_module() {
+        // next id's
+        <forum::NextCategoryId<T>>::put(1);
+        <forum::NextThreadId<T>>::put(1);
+        <forum::NextPostId<T>>::put(1);
+
+        // sudo key will be used as initial forum sudo key
+        <forum::ForumSudo<T>>::put(<sudo::Module<T>>::key().clone());
+
+        // input validation constraints
+        <forum::CategoryTitleConstraint<T>>::put(Self::create_input_validation_length_constraint(
+            10, 90,
+        ));
+        <forum::CategoryDescriptionConstraint<T>>::put(
+            Self::create_input_validation_length_constraint(10, 490),
+        );
+        <forum::ThreadTitleConstraint<T>>::put(Self::create_input_validation_length_constraint(
+            10, 90,
+        ));
+        <forum::PostTextConstraint<T>>::put(Self::create_input_validation_length_constraint(
+            10, 990,
+        ));
+        <forum::ThreadModerationRationaleConstraint<T>>::put(
+            Self::create_input_validation_length_constraint(10, 290),
+        );
+        <forum::PostModerationRationaleConstraint<T>>::put(
+            Self::create_input_validation_length_constraint(10, 290),
+        );
+    }
+
+    fn create_input_validation_length_constraint(
+        min: u16,
+        max_min_diff: u16,
+    ) -> forum::InputValidationLengthConstraint {
+        return forum::InputValidationLengthConstraint { min, max_min_diff };
+    }
+
+    fn initialize_storage_module() {
+        // Remove hardcoded liaison
+        <storage::data_directory::PrimaryLiaisonAccountId<T>>::take();
+
+        // remove all content
+        for content_id in <storage::data_directory::KnownContentIds<T>>::get().iter() {
+            <storage::data_directory::DataObjectByContentId<T>>::remove(content_id);
+            <storage::data_directory::MetadataByContentId<T>>::remove(content_id);
+            <storage::data_object_storage_registry::RelationshipsByContentId<T>>::remove(
+                content_id,
+            );
+        }
+        <storage::data_directory::KnownContentIds<T>>::put(vec![]);
+    }
 }
 
-pub trait Trait: system::Trait + members::Trait + actors::Trait {
+pub trait Trait:
+    system::Trait
+    + storage::data_directory::Trait
+    + storage::data_object_storage_registry::Trait
+    + forum::Trait
+    + sudo::Trait
+{
     type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
 }
 

+ 19 - 23
src/roles/actors.rs

@@ -1,12 +1,15 @@
 use crate::currency::{BalanceOf, GovernanceCurrency};
 use parity_codec_derive::{Decode, Encode};
 use rstd::prelude::*;
+use runtime_io::print;
 use runtime_primitives::traits::{As, Bounded, MaybeDebug, Zero};
-use srml_support::traits::{Currency, LockIdentifier, LockableCurrency, WithdrawReasons, WithdrawReason};
+use srml_support::traits::{
+    Currency, LockIdentifier, LockableCurrency, WithdrawReason, WithdrawReasons,
+};
 use srml_support::{decl_event, decl_module, decl_storage, ensure, StorageMap, StorageValue};
 use system::{self, ensure_signed};
 
-use crate::traits::{Members, Roles};
+use crate::traits::Members;
 
 static MSG_NO_ACTOR_FOR_ROLE: &str = "For the specified role, no actor is currently staked.";
 
@@ -81,10 +84,16 @@ pub struct Actor<T: Trait> {
     pub joined_at: T::BlockNumber,
 }
 
+pub trait ActorRemoved<T: Trait> {
+    fn actor_removed(actor: &T::AccountId);
+}
+
 pub trait Trait: system::Trait + GovernanceCurrency + MaybeDebug {
     type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
 
     type Members: Members<Self>;
+
+    type OnActorRemoved: ActorRemoved<Self>;
 }
 
 pub type MemberId<T> = <<T as Trait>::Members as Members<T>>::Id;
@@ -205,6 +214,8 @@ impl<T: Trait> Module<T> {
         <ActorAccountIds<T>>::put(accounts);
 
         <ActorByAccountId<T>>::remove(&actor_account);
+
+        T::OnActorRemoved::actor_removed(&actor_account);
     }
 
     fn apply_unstake(
@@ -235,30 +246,14 @@ impl<T: Trait> Module<T> {
             WithdrawReasons::all() & !(WithdrawReason::TransactionPayment | WithdrawReason::Fee),
         );
     }
-}
 
-impl<T: Trait> Roles<T> for Module<T> {
-    fn is_role_account(account_id: &T::AccountId) -> bool {
+    pub fn is_role_account(account_id: &T::AccountId) -> bool {
         <ActorByAccountId<T>>::exists(account_id)
     }
 
-    fn account_has_role(account_id: &T::AccountId, role: Role) -> bool {
+    pub 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! {
@@ -418,10 +413,11 @@ decl_module! {
         }
 
         pub fn remove_actor(actor_account: T::AccountId) {
-            let member_id = T::Members::lookup_member_id(&actor_account)?;
-            let actor = Self::ensure_actor_is_member(&actor_account, member_id)?;
+            ensure!(<ActorByAccountId<T>>::exists(&actor_account), "error trying to remove non actor account");
+            let actor = Self::actor_by_account_id(&actor_account).unwrap();
             let role_parameters = Self::ensure_role_parameters(actor.role)?;
-            Self::apply_unstake(actor.account, actor.role, actor.member_id, role_parameters.unbonding_period, role_parameters.min_stake);
+            Self::apply_unstake(actor_account, actor.role, actor.member_id, role_parameters.unbonding_period, role_parameters.min_stake);
+            print("sudo removed actor");
         }
     }
 }

+ 5 - 0
src/roles/mock.rs

@@ -122,6 +122,11 @@ impl Members<Test> for MockMembers {
 impl actors::Trait for Test {
     type Event = ();
     type Members = MockMembers;
+    type OnActorRemoved = ();
+}
+
+impl actors::ActorRemoved<Test> for () {
+    fn actor_removed(_: &u64) {}
 }
 
 pub fn initial_test_ext() -> runtime_io::TestExternalities<Blake2Hasher> {

+ 125 - 0
src/service_discovery/discovery.rs

@@ -0,0 +1,125 @@
+use crate::traits::Roles;
+use rstd::prelude::*;
+use runtime_primitives::traits::As;
+
+use srml_support::{decl_event, decl_module, decl_storage, ensure, StorageMap, StorageValue};
+use system::{self, ensure_root, ensure_signed};
+/*
+  Although there is support for ed25519 keys as the IPNS identity key and we could potentially
+  reuse the same key for the role account and ipns (and make this discovery module obselete)
+  it is probably better to separate concerns.
+  Why not to use a fixed size 32byte -> SHA256 hash of public key: because we would have to force
+  specific key type on ipfs side.
+  pub struct IPNSIdentity(pub [u8; 32]); // we loose the key type!
+  pub type IPNSIdentity(pub u8, pub [u8; 32]); // we could add the keytype?
+  can we use rust library in wasm runtime?
+  https://github.com/multiformats/rust-multihash
+  https://github.com/multiformats/multicodec/
+  https://github.com/multiformats/multihash/
+*/
+/// base58 encoded IPNS identity multihash codec
+pub type IPNSIdentity = Vec<u8>;
+
+/// HTTP Url string to a discovery service endpoint
+pub type Url = Vec<u8>;
+
+pub const MINIMUM_LIFETIME: u64 = 600; // 1hr assuming 6s block times
+pub const DEFAULT_LIFETIME: u64 = MINIMUM_LIFETIME * 24; // 24hr
+
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq)]
+pub struct AccountInfo<BlockNumber> {
+    /// IPNS Identity
+    pub identity: IPNSIdentity,
+    /// Block at which information expires
+    pub expires_at: BlockNumber,
+}
+
+pub trait Trait: system::Trait {
+    type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
+
+    type Roles: Roles<Self>;
+}
+
+decl_storage! {
+    trait Store for Module<T: Trait> as Discovery {
+        /// Bootstrap endpoints maintained by root
+        pub BootstrapEndpoints get(bootstrap_endpoints): Vec<Url>;
+        /// Mapping of service providers' AccountIds to their AccountInfo
+        pub AccountInfoByAccountId get(account_info_by_account_id): map T::AccountId => AccountInfo<T::BlockNumber>;
+        /// Lifetime of an AccountInfo record in AccountInfoByAccountId map
+        pub DefaultLifetime get(default_lifetime) config(): T::BlockNumber = T::BlockNumber::sa(DEFAULT_LIFETIME);
+    }
+}
+
+decl_event! {
+    pub enum Event<T> where <T as system::Trait>::AccountId {
+        AccountInfoUpdated(AccountId, IPNSIdentity),
+        AccountInfoRemoved(AccountId),
+    }
+}
+
+impl<T: Trait> Module<T> {
+    pub fn remove_account_info(accountid: &T::AccountId) {
+        if <AccountInfoByAccountId<T>>::exists(accountid) {
+            <AccountInfoByAccountId<T>>::remove(accountid);
+            Self::deposit_event(RawEvent::AccountInfoRemoved(accountid.clone()));
+        }
+    }
+
+    pub fn is_account_info_expired(accountid: &T::AccountId) -> bool {
+        !<AccountInfoByAccountId<T>>::exists(accountid)
+            || <system::Module<T>>::block_number()
+                > <AccountInfoByAccountId<T>>::get(accountid).expires_at
+    }
+}
+
+decl_module! {
+    pub struct Module<T: Trait> for enum Call where origin: T::Origin {
+        fn deposit_event<T>() = default;
+
+        pub fn set_ipns_id(origin, id: Vec<u8>, lifetime: Option<u64>) {
+            let sender = ensure_signed(origin)?;
+            ensure!(T::Roles::is_role_account(&sender), "only role accounts can set ipns id");
+
+            // TODO: ensure id is a valid base58 encoded IPNS identity
+
+            let ttl = match lifetime {
+                Some(value) => if value >= MINIMUM_LIFETIME {
+                    T::BlockNumber::sa(value)
+                } else {
+                    T::BlockNumber::sa(MINIMUM_LIFETIME)
+                },
+                _ => Self::default_lifetime()
+            };
+
+            <AccountInfoByAccountId<T>>::insert(&sender, AccountInfo {
+                identity: id.clone(),
+                expires_at: <system::Module<T>>::block_number() + ttl,
+            });
+
+            Self::deposit_event(RawEvent::AccountInfoUpdated(sender.clone(), id.clone()));
+        }
+
+        pub fn unset_ipns_id(origin) {
+            let sender = ensure_signed(origin)?;
+            Self::remove_account_info(&sender);
+        }
+
+        // privileged methods
+
+        pub fn set_default_lifetime(origin, lifetime: T::BlockNumber) {
+            // although not strictly required to have an origin parameter and ensure_root
+            // decl_module! macro takes care of it.. its required for unit tests to work correctly
+            // otherwise it complains the method
+            ensure_root(origin)?;
+            ensure!(lifetime >= T::BlockNumber::sa(MINIMUM_LIFETIME), "discovery: default lifetime must be gte minimum lifetime");
+            <DefaultLifetime<T>>::put(lifetime);
+        }
+
+        pub fn set_bootstrap_endpoints(origin, endpoints: Vec<Vec<u8>>) {
+            ensure_root(origin)?;
+            <BootstrapEndpoints<T>>::put(endpoints);
+        }
+    }
+}

+ 82 - 0
src/service_discovery/mock.rs

@@ -0,0 +1,82 @@
+#![cfg(test)]
+
+pub use super::discovery;
+pub use crate::roles::actors;
+pub use crate::traits::Roles;
+
+pub use primitives::{Blake2Hasher, H256};
+pub use runtime_primitives::{
+    testing::{Digest, DigestItem, Header, UintAuthorityId},
+    traits::{BlakeTwo256, IdentityLookup, OnFinalize},
+    BuildStorage,
+};
+
+use srml_support::{impl_outer_event, impl_outer_origin};
+
+impl_outer_origin! {
+    pub enum Origin for Test {}
+}
+
+impl_outer_event! {
+    pub enum MetaEvent for Test {
+        discovery<T>,
+    }
+}
+
+// For testing the module, we construct most of a mock runtime. This means
+// first constructing a configuration type (`Test`) which `impl`s each of the
+// configuration traits of modules we want to use.
+#[derive(Clone, Eq, PartialEq, Debug)]
+pub struct Test;
+impl system::Trait for Test {
+    type Origin = Origin;
+    type Index = u64;
+    type BlockNumber = u64;
+    type Hash = H256;
+    type Hashing = BlakeTwo256;
+    type Digest = Digest;
+    type AccountId = u64;
+    type Header = Header;
+    type Event = MetaEvent;
+    type Log = DigestItem;
+    type Lookup = IdentityLookup<u64>;
+}
+
+pub fn alice_account() -> u64 {
+    1
+}
+pub fn bob_account() -> u64 {
+    2
+}
+
+impl discovery::Trait for Test {
+    type Event = MetaEvent;
+    type Roles = MockRoles;
+}
+
+pub struct MockRoles {}
+impl Roles<Test> for MockRoles {
+    fn is_role_account(account_id: &u64) -> bool {
+        *account_id == alice_account()
+    }
+
+    fn account_has_role(_account_id: &u64, _role: actors::Role) -> bool {
+        false
+    }
+
+    fn random_account_for_role(_role: actors::Role) -> Result<u64, &'static str> {
+        Err("not implemented")
+    }
+}
+
+pub fn initial_test_ext() -> runtime_io::TestExternalities<Blake2Hasher> {
+    let t = system::GenesisConfig::<Test>::default()
+        .build_storage()
+        .unwrap()
+        .0;
+
+    runtime_io::TestExternalities::new(t)
+}
+
+pub type System = system::Module<Test>;
+pub type Discovery = discovery::Module<Test>;

+ 4 - 0
src/service_discovery/mod.rs

@@ -0,0 +1,4 @@
+pub mod discovery;
+
+mod mock;
+mod tests;

+ 136 - 0
src/service_discovery/tests.rs

@@ -0,0 +1,136 @@
+#![cfg(test)]
+
+use super::mock::*;
+
+use runtime_io::with_externalities;
+use srml_support::*;
+use system::{self, EventRecord, Phase};
+
+#[test]
+fn set_ipns_id() {
+    with_externalities(&mut initial_test_ext(), || {
+        let current_block_number = 1000;
+        System::set_block_number(current_block_number);
+
+        let alice = alice_account();
+        let identity = "alice".as_bytes().to_vec();
+        let ttl = discovery::MINIMUM_LIFETIME + 100;
+        assert!(Discovery::set_ipns_id(Origin::signed(alice), identity.clone(), Some(ttl)).is_ok());
+
+        assert!(<discovery::AccountInfoByAccountId<Test>>::exists(&alice));
+        let account_info = Discovery::account_info_by_account_id(&alice);
+        assert_eq!(
+            account_info,
+            discovery::AccountInfo {
+                identity: identity.clone(),
+                expires_at: current_block_number + ttl
+            }
+        );
+
+        assert_eq!(
+            *System::events().last().unwrap(),
+            EventRecord {
+                phase: Phase::ApplyExtrinsic(0),
+                event: MetaEvent::discovery(discovery::RawEvent::AccountInfoUpdated(
+                    alice,
+                    identity.clone()
+                )),
+            }
+        );
+
+        // Non role account trying to set account into should fail
+        let bob = bob_account();
+        assert!(Discovery::set_ipns_id(Origin::signed(bob), identity.clone(), None).is_err());
+        assert!(!<discovery::AccountInfoByAccountId<Test>>::exists(&bob));
+    });
+}
+
+#[test]
+fn unset_ipns_id() {
+    with_externalities(&mut initial_test_ext(), || {
+        let alice = alice_account();
+
+        <discovery::AccountInfoByAccountId<Test>>::insert(
+            &alice,
+            discovery::AccountInfo {
+                expires_at: 1000,
+                identity: "alice".as_bytes().to_vec(),
+            },
+        );
+
+        assert!(<discovery::AccountInfoByAccountId<Test>>::exists(&alice));
+
+        assert!(Discovery::unset_ipns_id(Origin::signed(alice)).is_ok());
+        assert!(!<discovery::AccountInfoByAccountId<Test>>::exists(&alice));
+
+        assert_eq!(
+            *System::events().last().unwrap(),
+            EventRecord {
+                phase: Phase::ApplyExtrinsic(0),
+                event: MetaEvent::discovery(discovery::RawEvent::AccountInfoRemoved(alice)),
+            }
+        );
+    });
+}
+
+#[test]
+fn is_account_info_expired() {
+    with_externalities(&mut initial_test_ext(), || {
+        let alice = alice_account();
+        let expires_at = 1000;
+        let id = "alice".as_bytes().to_vec();
+        <discovery::AccountInfoByAccountId<Test>>::insert(
+            &alice,
+            discovery::AccountInfo {
+                expires_at,
+                identity: id.clone(),
+            },
+        );
+
+        System::set_block_number(expires_at - 10);
+        assert!(!Discovery::is_account_info_expired(&alice));
+
+        System::set_block_number(expires_at + 10);
+        assert!(Discovery::is_account_info_expired(&alice));
+    });
+}
+
+#[test]
+fn set_default_lifetime() {
+    with_externalities(&mut initial_test_ext(), || {
+        let lifetime = discovery::MINIMUM_LIFETIME + 2000;
+        // priviliged method should fail if not from root origin
+        assert!(
+            Discovery::set_default_lifetime(Origin::signed(1), lifetime).is_err(),
+            ""
+        );
+        assert!(
+            Discovery::set_default_lifetime(Origin::ROOT, lifetime).is_ok(),
+            ""
+        );
+        assert_eq!(Discovery::default_lifetime(), lifetime, "");
+
+        // cannot set default lifetime to less than minimum
+        assert!(
+            Discovery::set_default_lifetime(Origin::ROOT, discovery::MINIMUM_LIFETIME - 1).is_err(),
+            ""
+        );
+    });
+}
+
+#[test]
+fn set_bootstrap_endpoints() {
+    with_externalities(&mut initial_test_ext(), || {
+        let endpoints = vec!["endpoint1".as_bytes().to_vec()];
+        // priviliged method should fail if not from root origin
+        assert!(
+            Discovery::set_bootstrap_endpoints(Origin::signed(1), endpoints.clone()).is_err(),
+            ""
+        );
+        assert!(
+            Discovery::set_bootstrap_endpoints(Origin::ROOT, endpoints.clone()).is_ok(),
+            ""
+        );
+        assert_eq!(Discovery::bootstrap_endpoints(), endpoints, "");
+    });
+}

+ 27 - 23
src/storage/data_directory.rs

@@ -69,11 +69,12 @@ pub struct DataObject<T: Trait> {
     pub size: u64,
     pub liaison: T::AccountId,
     pub liaison_judgement: LiaisonJudgement,
-    // TODO signing_key: public key supplied by the uploader,
-    // they sigh the content with this key
+    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>>,
+                                  // TODO add support for this field (Some if judgment == Rejected)
+                                  // pub rejection_reason: Option<Vec<u8>>,
 }
 
 #[derive(Clone, Encode, Decode, PartialEq)]
@@ -117,18 +118,14 @@ decl_storage! {
 
         // TODO this list of ids should be moved off-chain once we have Content Indexer.
         // TODO deprecated, moved tp storage relationship
-        KnownContentIds get(known_content_ids): Vec<T::ContentId> = vec![];
+        pub KnownContentIds get(known_content_ids): Vec<T::ContentId> = vec![];
 
-        DataObjectByContentId get(data_object_by_content_id):
+        pub DataObjectByContentId get(data_object_by_content_id):
             map T::ContentId => Option<DataObject<T>>;
 
-        MetadataByContentId get(metadata_by_content_id):
+        pub MetadataByContentId get(metadata_by_content_id):
             map T::ContentId => Option<ContentMetadata<T>>;
 
-        // Default storage provider address
-        pub StorageProviderAddress get(storage_provider_address): Vec<u8>;
-        // Default storage provider repository id
-        pub StorageProviderRepoId get(storage_provider_repo_id): Vec<u8>;
         // Default storage provider account id, overrides all active storage providers as liason if set
         pub PrimaryLiaisonAccountId get(primary_liaison_account_id): Option<T::AccountId>;
     }
@@ -161,7 +158,8 @@ decl_module! {
             origin,
             content_id: T::ContentId,
             type_id: <T as DOTRTrait>::DataObjectTypeId,
-            size: u64
+            size: u64,
+            ipfs_content_id: Vec<u8>
         ) {
             let who = ensure_signed(origin)?;
             ensure!(T::Members::is_active_member(&who), MSG_CREATOR_MUST_BE_MEMBER);
@@ -188,6 +186,7 @@ decl_module! {
                 owner: who.clone(),
                 liaison: liaison,
                 liaison_judgement: LiaisonJudgement::Pending,
+                ipfs_content_id: ipfs_content_id.clone(),
             };
 
             <DataObjectByContentId<T>>::insert(&content_id, data);
@@ -279,14 +278,6 @@ decl_module! {
 
         // Sudo methods
 
-        fn set_storage_provider_repo_id(repo_id: Vec<u8>) {
-            <StorageProviderRepoId<T>>::put(repo_id);
-        }
-
-        fn set_storage_provider_address(address: Vec<u8>) {
-            <StorageProviderAddress<T>>::put(address);
-        }
-
         fn set_primary_liaison_account_id(account: T::AccountId) {
             <PrimaryLiaisonAccountId<T>>::put(account);
         }
@@ -365,7 +356,8 @@ mod tests {
     fn succeed_adding_content() {
         with_default_mock_builder(|| {
             // Register a content with 1234 bytes of type 1, which should be recognized.
-            let res = TestDataDirectory::add_content(Origin::signed(1), 1, 1234, 0);
+            let res =
+                TestDataDirectory::add_content(Origin::signed(1), 1, 1234, 0, vec![1, 3, 3, 7]);
             assert!(res.is_ok());
         });
     }
@@ -374,7 +366,13 @@ mod tests {
     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);
+            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.
@@ -403,7 +401,13 @@ mod tests {
     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);
+            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.

+ 6 - 0
src/storage/mock.rs

@@ -105,6 +105,7 @@ impl traits::ContentIdExists<Test> for MockContent {
                 owner: 1,
                 liaison: TEST_MOCK_LIAISON,
                 liaison_judgement: data_directory::LiaisonJudgement::Pending,
+                ipfs_content_id: vec![],
             }),
             _ => Err("nope, missing"),
         }
@@ -155,6 +156,11 @@ impl data_object_storage_registry::Trait for Test {
 impl actors::Trait for Test {
     type Event = MetaEvent;
     type Members = MockMembers;
+    type OnActorRemoved = ();
+}
+
+impl actors::ActorRemoved<Test> for () {
+    fn actor_removed(_: &u64) {}
 }
 
 impl timestamp::Trait for Test {

+ 44 - 20
wasm/Cargo.lock

@@ -79,7 +79,7 @@ version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.31 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -519,7 +519,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.31 (registry+https://github.com/rust-lang/crates.io-index)",
  "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -756,7 +756,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "joystream-node-runtime"
-version = "5.2.0"
+version = "5.4.0"
 dependencies = [
  "parity-codec 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "parity-codec-derive 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -783,15 +783,17 @@ dependencies = [
  "substrate-client 1.0.0 (git+https://github.com/joystream/substrate.git?rev=6dfc3e8b057bb00322136251a0f10305fbb1ad8f)",
  "substrate-consensus-aura-primitives 1.0.0 (git+https://github.com/joystream/substrate.git?rev=6dfc3e8b057bb00322136251a0f10305fbb1ad8f)",
  "substrate-consensus-authorities 1.0.0 (git+https://github.com/joystream/substrate.git?rev=6dfc3e8b057bb00322136251a0f10305fbb1ad8f)",
+ "substrate-forum-module 1.0.0 (git+https://github.com/joystream/substrate-forum-module?tag=v1.0.0)",
  "substrate-offchain-primitives 0.1.0 (git+https://github.com/joystream/substrate.git?rev=6dfc3e8b057bb00322136251a0f10305fbb1ad8f)",
  "substrate-primitives 1.0.0 (git+https://github.com/joystream/substrate.git?rev=6dfc3e8b057bb00322136251a0f10305fbb1ad8f)",
+ "syn 0.15.31 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "joystream-node-runtime-wasm"
-version = "5.2.0"
+version = "5.4.0"
 dependencies = [
- "joystream-node-runtime 5.2.0",
+ "joystream-node-runtime 5.4.0",
 ]
 
 [[package]]
@@ -907,7 +909,7 @@ version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.31 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -1405,7 +1407,7 @@ dependencies = [
  "proc-macro-crate 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.31 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -1522,7 +1524,7 @@ dependencies = [
  "proc-macro-hack 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.31 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -1579,7 +1581,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.31 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -1911,7 +1913,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.31 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -2041,7 +2043,7 @@ dependencies = [
  "proc-macro-crate 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.31 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -2312,7 +2314,7 @@ dependencies = [
  "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "sr-api-macros 1.0.0 (git+https://github.com/joystream/substrate.git?rev=6dfc3e8b057bb00322136251a0f10305fbb1ad8f)",
  "srml-support-procedural-tools 1.0.0 (git+https://github.com/joystream/substrate.git?rev=6dfc3e8b057bb00322136251a0f10305fbb1ad8f)",
- "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.31 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -2324,7 +2326,7 @@ dependencies = [
  "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "srml-support-procedural-tools-derive 1.0.0 (git+https://github.com/joystream/substrate.git?rev=6dfc3e8b057bb00322136251a0f10305fbb1ad8f)",
- "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.31 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -2334,7 +2336,7 @@ source = "git+https://github.com/joystream/substrate.git?rev=6dfc3e8b057bb003221
 dependencies = [
  "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.31 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -2405,7 +2407,7 @@ dependencies = [
  "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.31 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -2420,7 +2422,7 @@ dependencies = [
  "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)",
  "sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.31 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -2449,7 +2451,7 @@ dependencies = [
  "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.31 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -2572,6 +2574,27 @@ dependencies = [
  "substrate-primitives 1.0.0 (git+https://github.com/joystream/substrate.git?rev=6dfc3e8b057bb00322136251a0f10305fbb1ad8f)",
 ]
 
+[[package]]
+name = "substrate-forum-module"
+version = "1.0.0"
+source = "git+https://github.com/joystream/substrate-forum-module?tag=v1.0.0#d724d9946ab01c6d3b4307ca6ea9263dabd58d34"
+dependencies = [
+ "hex-literal 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "parity-codec 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "parity-codec-derive 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sr-io 1.0.0 (git+https://github.com/joystream/substrate.git?rev=6dfc3e8b057bb00322136251a0f10305fbb1ad8f)",
+ "sr-primitives 1.0.0 (git+https://github.com/joystream/substrate.git?rev=6dfc3e8b057bb00322136251a0f10305fbb1ad8f)",
+ "sr-std 1.0.0 (git+https://github.com/joystream/substrate.git?rev=6dfc3e8b057bb00322136251a0f10305fbb1ad8f)",
+ "srml-balances 1.0.0 (git+https://github.com/joystream/substrate.git?rev=6dfc3e8b057bb00322136251a0f10305fbb1ad8f)",
+ "srml-support 1.0.0 (git+https://github.com/joystream/substrate.git?rev=6dfc3e8b057bb00322136251a0f10305fbb1ad8f)",
+ "srml-support-procedural 1.0.0 (git+https://github.com/joystream/substrate.git?rev=6dfc3e8b057bb00322136251a0f10305fbb1ad8f)",
+ "srml-system 1.0.0 (git+https://github.com/joystream/substrate.git?rev=6dfc3e8b057bb00322136251a0f10305fbb1ad8f)",
+ "srml-timestamp 1.0.0 (git+https://github.com/joystream/substrate.git?rev=6dfc3e8b057bb00322136251a0f10305fbb1ad8f)",
+ "syn 0.15.31 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "substrate-inherents"
 version = "1.0.0"
@@ -2713,7 +2736,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "syn"
-version = "0.15.29"
+version = "0.15.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2728,7 +2751,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.31 (registry+https://github.com/rust-lang/crates.io-index)",
  "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -3462,6 +3485,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum substrate-consensus-common 1.0.0 (git+https://github.com/joystream/substrate.git?rev=6dfc3e8b057bb00322136251a0f10305fbb1ad8f)" = "<none>"
 "checksum substrate-executor 1.0.0 (git+https://github.com/joystream/substrate.git?rev=6dfc3e8b057bb00322136251a0f10305fbb1ad8f)" = "<none>"
 "checksum substrate-finality-grandpa-primitives 1.0.0 (git+https://github.com/joystream/substrate.git?rev=6dfc3e8b057bb00322136251a0f10305fbb1ad8f)" = "<none>"
+"checksum substrate-forum-module 1.0.0 (git+https://github.com/joystream/substrate-forum-module?tag=v1.0.0)" = "<none>"
 "checksum substrate-inherents 1.0.0 (git+https://github.com/joystream/substrate.git?rev=6dfc3e8b057bb00322136251a0f10305fbb1ad8f)" = "<none>"
 "checksum substrate-keyring 1.0.0 (git+https://github.com/joystream/substrate.git?rev=6dfc3e8b057bb00322136251a0f10305fbb1ad8f)" = "<none>"
 "checksum substrate-offchain-primitives 0.1.0 (git+https://github.com/joystream/substrate.git?rev=6dfc3e8b057bb00322136251a0f10305fbb1ad8f)" = "<none>"
@@ -3473,7 +3497,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum substrate-trie 1.0.0 (git+https://github.com/joystream/substrate.git?rev=6dfc3e8b057bb00322136251a0f10305fbb1ad8f)" = "<none>"
 "checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
 "checksum subtle 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "702662512f3ddeb74a64ce2fbbf3707ee1b6bb663d28bb054e0779bbc720d926"
-"checksum syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1825685f977249735d510a242a6727b46efe914bb67e38d30c071b1b72b1d5c2"
+"checksum syn 0.15.31 (registry+https://github.com/rust-lang/crates.io-index)" = "d2b4cfac95805274c6afdb12d8f770fa2d27c045953e7b630a81801953699a9a"
 "checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015"
 "checksum take_mut 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60"
 "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"

+ 1 - 1
wasm/Cargo.toml

@@ -20,4 +20,4 @@ path = '..'
 authors = ['Joystream']
 edition = '2018'
 name = 'joystream-node-runtime-wasm'
-version = '5.3.0'
+version = '5.4.0'

+ 10 - 0
wasm_dockerfile

@@ -0,0 +1,10 @@
+FROM liuchong/rustup:1.34.0 AS builder
+LABEL description="Joystream wasm toolchain image"
+
+ENV TERM=xterm
+
+WORKDIR /setup
+COPY ./setup.sh /setup/
+ENV TERM=xterm
+
+RUN ./setup.sh