├── .gitignore ├── .rustfmt.toml ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── README.md ├── client ├── Cargo.toml ├── http-server │ ├── Cargo.toml │ └── src │ │ ├── helpers.rs │ │ ├── lib.rs │ │ ├── traits.rs │ │ └── user_data.rs ├── simple-cache │ ├── Cargo.toml │ └── src │ │ ├── disk.rs │ │ ├── lib.rs │ │ ├── lru_cache.rs │ │ └── memory.rs └── src │ ├── cache.rs │ ├── config.rs │ ├── lfs_id.rs │ ├── lib.rs │ ├── network.rs │ ├── network │ └── bitswap.rs │ └── rpc.rs ├── demo ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── example_data │ ├── personal_site │ │ ├── LICENSE.txt │ │ ├── assets │ │ │ ├── css │ │ │ │ ├── fontawesome-all.min.css │ │ │ │ ├── images │ │ │ │ │ └── overlay.png │ │ │ │ ├── main.css │ │ │ │ └── noscript.css │ │ │ └── webfonts │ │ │ │ ├── fa-brands-400.eot │ │ │ │ ├── fa-brands-400.svg │ │ │ │ ├── fa-brands-400.ttf │ │ │ │ ├── fa-brands-400.woff │ │ │ │ ├── fa-brands-400.woff2 │ │ │ │ ├── fa-regular-400.eot │ │ │ │ ├── fa-regular-400.svg │ │ │ │ ├── fa-regular-400.ttf │ │ │ │ ├── fa-regular-400.woff │ │ │ │ ├── fa-regular-400.woff2 │ │ │ │ ├── fa-solid-900.eot │ │ │ │ ├── fa-solid-900.svg │ │ │ │ ├── fa-solid-900.ttf │ │ │ │ ├── fa-solid-900.woff │ │ │ │ └── fa-solid-900.woff2 │ │ ├── images │ │ │ ├── avatar.png │ │ │ └── bg.jpg │ │ └── index.html │ └── website │ │ ├── LICENSE.txt │ │ ├── assets │ │ ├── css │ │ │ ├── fontawesome-all.min.css │ │ │ ├── images │ │ │ │ ├── arrow.svg │ │ │ │ ├── banner.svg │ │ │ │ ├── loader.gif │ │ │ │ ├── overlay.png │ │ │ │ ├── poptrox-closer.svg │ │ │ │ └── poptrox-nav.svg │ │ │ ├── main.css │ │ │ └── noscript.css │ │ ├── js │ │ │ ├── breakpoints.min.js │ │ │ ├── browser.min.js │ │ │ ├── jquery.min.js │ │ │ ├── jquery.poptrox.min.js │ │ │ ├── jquery.scrolly.min.js │ │ │ ├── main.js │ │ │ └── util.js │ │ └── webfonts │ │ │ ├── fa-brands-400.eot │ │ │ ├── fa-brands-400.svg │ │ │ ├── fa-brands-400.ttf │ │ │ ├── fa-brands-400.woff │ │ │ ├── fa-brands-400.woff2 │ │ │ ├── fa-regular-400.eot │ │ │ ├── fa-regular-400.svg │ │ │ ├── fa-regular-400.ttf │ │ │ ├── fa-regular-400.woff │ │ │ ├── fa-regular-400.woff2 │ │ │ ├── fa-solid-900.eot │ │ │ ├── fa-solid-900.svg │ │ │ ├── fa-solid-900.ttf │ │ │ ├── fa-solid-900.woff │ │ │ └── fa-solid-900.woff2 │ │ ├── images │ │ ├── bg-alt.jpg │ │ ├── bg.jpg │ │ ├── fulls │ │ │ ├── 01.jpg │ │ │ ├── 02.jpg │ │ │ ├── 03.jpg │ │ │ ├── 04.jpg │ │ │ ├── 05.jpg │ │ │ ├── 06.jpg │ │ │ ├── 07.jpg │ │ │ └── 08.jpg │ │ ├── pic01.jpg │ │ ├── pic02.jpg │ │ └── thumbs │ │ │ ├── 01.jpg │ │ │ ├── 02.jpg │ │ │ ├── 03.jpg │ │ │ ├── 04.jpg │ │ │ ├── 05.jpg │ │ │ ├── 06.jpg │ │ │ ├── 07.jpg │ │ │ └── 08.jpg │ │ └── index.html ├── rpc-client │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── runtime │ ├── Cargo.toml │ ├── build.rs │ └── src │ │ └── lib.rs ├── scripts │ └── init.sh └── src │ ├── chain_spec.rs │ ├── cli.rs │ ├── command.rs │ ├── main.rs │ └── service.rs ├── pallets ├── Cargo.toml ├── README.md ├── src │ └── lib.rs └── user-data │ ├── Cargo.toml │ └── src │ └── lib.rs └── primitives ├── cache ├── Cargo.toml └── src │ ├── lib.rs │ └── shared.rs └── core ├── Cargo.toml └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | **/*.rs.bk 3 | .local 4 | .cargo 5 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | hard_tabs = true 2 | format_strings = true -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | cache: 3 | cargo: true 4 | timeout: 1000 5 | 6 | rust: 7 | - nightly 8 | 9 | script: 10 | - rustup target install wasm32-unknown-unknown 11 | - rustup component add rustfmt 12 | - cargo fmt -- --check 13 | - cargo build --verbose --workspace 14 | - cargo test --verbose --workspace 15 | 16 | notifications: 17 | email: 18 | on_success: never 19 | on_failure: never 20 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | # primitives 4 | "primitives/core", 5 | "primitives/cache", 6 | 7 | # client 8 | "client/simple-cache", 9 | "client/http-server", 10 | "client", 11 | 12 | # frame core pallet 13 | "pallets", 14 | # custom frame palletts 15 | "pallets/user-data", 16 | 17 | # demo implementation 18 | "demo/runtime", 19 | "demo/rpc-client", 20 | "demo", 21 | ] 22 | 23 | default-members = ["demo"] 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # substrate-lfs 2 | [Substrate Large File Storage](https://github.com/paritytech/substrate-lfs/) 3 | 4 | A substrate extension to allow storing static data (like assets) along side your chain. 5 | 6 | TODO: license 7 | 8 | ## Demo 9 | 10 | There is an avatar demo runtime and node available in `demo`. You can try our default setup by running `cargo run --release -- --dev` from the root directory. 11 | 12 | The demo has support for `UserData` and hosting of homepages through it. Once the server is running, you can the homepage for alice by running: `cargo run --release -p lfs-demo-rpc-client -- upload-dir --prefix "" --replace-index demo/example_data/personal_site/`. This demo client will read the directory and all its files, uploads each one via rpc to the `node` and then submits them as a batch as the home page for `Alice`. Once the offchain worker confirm the availability of the data, you can browse the website with the http-server included in the demo-node under `http://localhost:8080/5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY/` . 13 | 14 | The rpc-client as further features, you can read all about them by passing `--help`. Among others, the uploader can be used to set the global hompage via the `--root` flag. If you, for example, run the `cargo run --release -p lfs-demo-rpc-client -- --root upload-dir --prefix "" --replace-index demo/example_data/website`, you can surf the example website on `http://localhost:8080` \o/ . -------------------------------------------------------------------------------- /client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sc-lfs" 3 | version = "0.1.0" 4 | authors = ["Parity Technologies "] 5 | description = "Large File Storage for Substrate" 6 | edition = "2018" 7 | 8 | [dependencies.sp-lfs-cache] 9 | path = "../primitives/cache" 10 | features = ['std'] 11 | 12 | [dependencies.sc-lfs-simple-cache] 13 | path = "./simple-cache" 14 | 15 | [dependencies.sp-lfs-core] 16 | path = "../primitives/core" 17 | features = ["std"] 18 | 19 | [dependencies] 20 | libp2p = "0.13.1" 21 | parking_lot = "0.10.0" 22 | base64 = "0.11" 23 | codec = { default-features = false, package = "parity-scale-codec", version = "1.1.2" } 24 | sc-client-api = { default-features = false, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 25 | sp-runtime-interface = { default-features = false, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 26 | sp-externalities = { default-features = false, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 27 | sp-core = { default-features = false, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 28 | 29 | # std 30 | toml = { version = "0.5.5", optional = true } 31 | serde = { version = "1.0", optional = true } 32 | 33 | # feature: jsonrpc 34 | jsonrpc-core = { version = "14.0.5", optional = true } 35 | jsonrpc-core-client = { version = "14.0.5", optional = true } 36 | jsonrpc-derive = { version = "14.0.5", optional = true } 37 | 38 | # feature: with-blake3 39 | blake3 = { version = "0.1.0", optional = true } 40 | 41 | [features] 42 | default = ["std", "jsonrpc"] 43 | unstable = ["with-blake3"] 44 | 45 | std = [ 46 | "sp-lfs-core/std", 47 | "sp-runtime-interface/std", 48 | "codec/std", 49 | "blake3/std", 50 | "toml", 51 | "serde", 52 | ] 53 | 54 | jsonrpc = [ 55 | "jsonrpc-core", 56 | "jsonrpc-core-client", 57 | "jsonrpc-derive", 58 | ] 59 | 60 | with-blake3 = [ 61 | "blake3" 62 | ] -------------------------------------------------------------------------------- /client/http-server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sc-lfs-http-server" 3 | version = "0.1.0" 4 | authors = ["Benjamin Kampmann "] 5 | edition = "2018" 6 | 7 | 8 | [dependencies.sp-lfs-cache] 9 | path = "../../primitives/cache" 10 | 11 | [dependencies.sp-lfs-core] 12 | path = "../../primitives/core" 13 | 14 | [dependencies.pallet-lfs-user-data] 15 | path = "../../pallets/user-data" 16 | optional = true 17 | 18 | [dependencies] 19 | hyper = "0.13" 20 | base64 = "0.11" 21 | futures = "0.3.1" 22 | codec = { package = "parity-scale-codec", version = "1.1.2" } 23 | sc-client = { version = "0.8.0", optional = true, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 24 | sc-client-api = { version = "2.0.0", optional = true, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 25 | sp-core = { version = "2.0.0", optional = true, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 26 | sp-runtime = { version = "2.0.0", optional = true, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 27 | frame-system = { version = "2.0.0", optional = true, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 28 | frame-support = { version = "2.0.0", optional = true, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 29 | 30 | 31 | [features] 32 | default = [] 33 | user-data = [ 34 | "sc-client", 35 | "sc-client-api", 36 | "sp-runtime/std", 37 | "sp-core/std", 38 | "frame-system/std", 39 | "frame-support/std", 40 | "pallet-lfs-user-data/std" 41 | ] -------------------------------------------------------------------------------- /client/http-server/src/helpers.rs: -------------------------------------------------------------------------------- 1 | use base64; 2 | use codec::{Decode, Encode}; 3 | 4 | /// helper to decode a base64 encoded string to Lfs 5 | pub fn b64decode<'a, D: Decode>(input: &'a [u8]) -> Option { 6 | base64::decode_config(input, base64::URL_SAFE) 7 | .ok() 8 | .and_then(|input| D::decode(&mut input.as_ref()).ok()) 9 | } 10 | 11 | /// helper to encode to a base64 12 | pub fn b64encode<'a, E: Encode>(input: E) -> String { 13 | base64::encode_config(&input.encode(), base64::URL_SAFE) 14 | } 15 | -------------------------------------------------------------------------------- /client/http-server/src/lib.rs: -------------------------------------------------------------------------------- 1 | use futures::future; 2 | use hyper::service::Service; 3 | use hyper::{header, http, Body, Request, Response, Server, StatusCode}; 4 | use sp_lfs_cache::Cache; 5 | use std::marker::PhantomData; 6 | use std::task::{Context, Poll}; 7 | 8 | mod helpers; 9 | mod traits; 10 | #[cfg(feature = "user-data")] 11 | pub mod user_data; 12 | 13 | pub use helpers::{b64decode, b64encode}; 14 | pub use traits::Resolver; 15 | 16 | fn not_found() -> Response { 17 | Response::builder() 18 | .status(StatusCode::NOT_FOUND) 19 | .body(Body::from("404 - Not found")) 20 | .expect("Building this simple response doesn't fail. qed") 21 | } 22 | 23 | struct LfsServer { 24 | cache: C, 25 | resolver: R, 26 | _marker: PhantomData, 27 | } 28 | 29 | impl LfsServer { 30 | fn new(cache: C, resolver: R) -> Self { 31 | Self { 32 | cache, 33 | resolver, 34 | _marker: Default::default(), 35 | } 36 | } 37 | } 38 | 39 | impl Service> for LfsServer 40 | where 41 | C: Cache, 42 | R: Resolver, 43 | LfsId: sp_lfs_core::LfsId, 44 | { 45 | type Response = Response; 46 | type Error = http::Error; 47 | type Future = future::Ready>; 48 | 49 | fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { 50 | Poll::Ready(Ok(())) 51 | } 52 | 53 | fn call(&mut self, req: Request) -> Self::Future { 54 | if let Some(it) = self.resolver.resolve(req.uri().clone()) { 55 | if let Some(key) = it 56 | .filter(|key| self.cache.exists(key).unwrap_or(false)) 57 | .next() 58 | { 59 | if Some(key.clone()) 60 | == req 61 | .headers() 62 | .get(header::IF_NONE_MATCH) 63 | .and_then(|l| b64decode::(l.as_bytes())) 64 | { 65 | return future::ok( 66 | Response::builder() 67 | .status(StatusCode::NOT_MODIFIED) 68 | .body(Body::empty()) 69 | .expect("Empty doesn't fail"), 70 | ); 71 | } 72 | return future::ok(match self.cache.get(&key) { 73 | Ok(data) => Response::builder() 74 | .status(StatusCode::OK) 75 | .header(header::ETAG, b64encode(key)) 76 | .body(data.into()) 77 | .expect("Building this simple response doesn't fail. qed"), 78 | Err(_) => Response::builder() 79 | .status(StatusCode::INTERNAL_SERVER_ERROR) 80 | .body(Body::from(format!( 81 | "Internal Server error key {:?} found, but couldn't be read.", 82 | key 83 | ))) 84 | .expect("Building this simple response doesn't fail. qed"), 85 | }); 86 | } 87 | } 88 | future::ok(not_found()) 89 | } 90 | } 91 | 92 | struct MakeSvc(C, R, PhantomData); 93 | impl MakeSvc { 94 | fn new(cache: C, resolver: R) -> Self { 95 | Self(cache, resolver, Default::default()) 96 | } 97 | } 98 | 99 | impl Service for MakeSvc 100 | where 101 | C: Cache + Clone + Send, 102 | R: Resolver + Clone + Send, 103 | L: sp_lfs_core::LfsId + Send, 104 | { 105 | type Response = LfsServer; 106 | type Error = std::io::Error; 107 | type Future = future::Ready>; 108 | 109 | fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { 110 | Ok(()).into() 111 | } 112 | 113 | fn call(&mut self, _: T) -> Self::Future { 114 | future::ok(LfsServer::new(self.0.clone(), self.1.clone())) 115 | } 116 | } 117 | 118 | pub async fn start_server(cache: C, resolver: R) -> () 119 | where 120 | C: Cache + Clone + 'static + Send, 121 | R: Resolver + 'static + Send, 122 | LfsId: sp_lfs_core::LfsId + 'static, 123 | { 124 | // This is our socket address... 125 | let addr = ([127, 0, 0, 1], 8080).into(); 126 | let service = MakeSvc::new(cache, resolver); 127 | 128 | let server = Server::bind(&addr).serve(service); 129 | if let Err(e) = server.await { 130 | println!("server error: {}", e); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /client/http-server/src/traits.rs: -------------------------------------------------------------------------------- 1 | use crate::helpers::b64decode; 2 | use hyper::Uri; 3 | use sp_lfs_core::LfsId; 4 | 5 | /// This can resolve a path into a set of 6 | /// LfsIds we'd like to check for 7 | pub trait Resolver: Clone { 8 | /// The iterator this resolves to, must yield `LfsdId`s 9 | type Iterator: core::iter::Iterator; 10 | 11 | /// Given the path, yield the `LfsId`s to look up 12 | fn resolve(&self, uri: Uri) -> Option; 13 | } 14 | 15 | /// Default implementation just takes the entire path, 16 | /// excluding the starting slash, and attempts to base64 decode that 17 | impl Resolver for () { 18 | type Iterator = std::vec::IntoIter; 19 | fn resolve(&self, uri: Uri) -> Option { 20 | let (_, pure_path) = uri.path().split_at(1); 21 | b64decode::(pure_path.as_bytes()).map(|id| vec![id].into_iter()) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /client/http-server/src/user_data.rs: -------------------------------------------------------------------------------- 1 | use codec::Decode; 2 | use frame_support::storage::generator::StorageDoubleMap; 3 | use hyper::Uri; 4 | use pallet_lfs_user_data as pallet; 5 | use sc_client::Client; 6 | use sc_client_api::{backend, CallExecutor}; 7 | use sp_core::crypto::Ss58Codec; 8 | use sp_core::storage::StorageKey; 9 | use sp_lfs_core::{LfsId, LfsReference}; 10 | use sp_runtime::{generic::BlockId, traits::Block as BlockT}; 11 | use std::marker::PhantomData; 12 | use std::sync::Arc; 13 | 14 | use crate::traits::Resolver; 15 | 16 | #[derive(Clone, Debug)] 17 | enum NextResolveStep { 18 | UserData, 19 | RootData, 20 | Glob, 21 | NotFound, 22 | End, 23 | } 24 | 25 | impl NextResolveStep { 26 | fn next(&self) -> Self { 27 | match self { 28 | NextResolveStep::UserData => NextResolveStep::RootData, 29 | NextResolveStep::RootData => NextResolveStep::Glob, 30 | NextResolveStep::Glob => NextResolveStep::NotFound, 31 | NextResolveStep::NotFound => NextResolveStep::End, 32 | NextResolveStep::End => NextResolveStep::End, 33 | } 34 | } 35 | } 36 | 37 | pub struct UserDataResolveIterator { 38 | client: Arc>, 39 | best_block: BlockId, 40 | root_key: T::AccountId, 41 | _marker: PhantomData<(T, L)>, 42 | uri: Uri, 43 | step: NextResolveStep, 44 | } 45 | 46 | impl UserDataResolveIterator 47 | where 48 | B: backend::Backend, 49 | E: CallExecutor, 50 | Block: BlockT, 51 | L: LfsId, 52 | T: pallet::Trait, 53 | { 54 | fn new( 55 | client: Arc>, 56 | best_block: BlockId, 57 | root_key: T::AccountId, 58 | uri: Uri, 59 | ) -> Self { 60 | Self { 61 | client, 62 | best_block, 63 | root_key, 64 | uri, 65 | step: NextResolveStep::UserData, 66 | _marker: Default::default(), 67 | } 68 | } 69 | 70 | fn lookup(&self, key: &StorageKey) -> Option { 71 | self.client 72 | .storage(&self.best_block, key) 73 | .map(|o| { 74 | o.map(|d| { 75 | // user data is stored as an opaque LFS reference 76 | LfsReference::decode(&mut d.0.as_slice()) 77 | // which we then convert into an LFSid 78 | .map(|i| L::try_from(i).ok()) 79 | .map_err(|_| { 80 | println!("UserData Entry {:?} holds a non-key: {:?}.", key, d.0) 81 | }) 82 | .ok()? 83 | })? 84 | }) 85 | .ok()? 86 | } 87 | } 88 | 89 | impl core::iter::Iterator for UserDataResolveIterator 90 | where 91 | B: backend::Backend, 92 | E: CallExecutor, 93 | Block: BlockT, 94 | L: LfsId, 95 | T: pallet::Trait, 96 | T::AccountId: Ss58Codec, 97 | { 98 | type Item = L; 99 | 100 | fn next(&mut self) -> Option { 101 | loop { 102 | let key = match self.step { 103 | NextResolveStep::UserData => { 104 | let mut splitter = self.uri.path().splitn(3, "/").filter(|s| s.len() > 0); 105 | let user_key = splitter.next(); 106 | user_key 107 | .and_then(|mut u| T::AccountId::from_string(&mut u).ok()) 108 | .map(|key| { 109 | pallet::UserData::::storage_double_map_final_key( 110 | &key, 111 | // the rest is the key we want to look up 112 | // fallback is to check for `""` 113 | splitter.next().unwrap_or("").as_bytes().to_vec(), 114 | ) 115 | }) 116 | } 117 | NextResolveStep::RootData => { 118 | let path = self.uri.path().split_at(1).1; 119 | Some(pallet::UserData::::storage_double_map_final_key( 120 | &self.root_key, 121 | path.as_bytes(), // drop leading `/` 122 | )) 123 | } 124 | NextResolveStep::Glob => Some(pallet::UserData::::storage_double_map_final_key( 125 | &self.root_key, 126 | b".*".to_vec(), 127 | )), 128 | NextResolveStep::NotFound => { 129 | Some(pallet::UserData::::storage_double_map_final_key( 130 | &self.root_key, 131 | b"_404".to_vec(), 132 | )) 133 | } 134 | NextResolveStep::End => { 135 | // we are done. 136 | break; 137 | } 138 | }; 139 | 140 | self.step = self.step.next(); 141 | if let Some(l) = key.and_then(|k| self.lookup(&StorageKey(k))) { 142 | return Some(l); 143 | } 144 | } 145 | 146 | None 147 | } 148 | } 149 | 150 | /// Resolve uri via on-chain UserData 151 | pub struct UserDataResolver { 152 | client: Arc>, 153 | _marker: PhantomData, 154 | } 155 | 156 | impl Clone for UserDataResolver 157 | where 158 | B: backend::Backend, 159 | E: CallExecutor, 160 | Block: BlockT, 161 | T: pallet::Trait, 162 | { 163 | fn clone(&self) -> Self { 164 | Self { 165 | client: self.client.clone(), 166 | _marker: Default::default(), 167 | } 168 | } 169 | } 170 | 171 | impl UserDataResolver 172 | where 173 | B: backend::Backend, 174 | E: CallExecutor, 175 | Block: BlockT, 176 | T: pallet::Trait, 177 | { 178 | pub fn new(client: Arc>) -> Self { 179 | UserDataResolver { 180 | client, 181 | _marker: Default::default(), 182 | } 183 | } 184 | } 185 | 186 | impl Resolver for UserDataResolver 187 | where 188 | B: backend::Backend, 189 | E: CallExecutor, 190 | Block: BlockT, 191 | T: pallet::Trait, 192 | L: LfsId, 193 | T::AccountId: Ss58Codec, 194 | { 195 | /// The iterator this resolves to, must yield `LfsdId`s 196 | type Iterator = Box>; 197 | 198 | /// Given the uri, yield the `LfsId`s to look up 199 | fn resolve(&self, uri: Uri) -> Option { 200 | Some(Box::new(UserDataResolveIterator::new( 201 | self.client.clone(), 202 | BlockId::Hash(self.client.chain_info().best_hash), 203 | T::AccountId::default(), 204 | uri, 205 | ))) 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /client/simple-cache/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sc-lfs-simple-cache" 3 | version = "0.1.0" 4 | authors = ["Benjamin Kampmann "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies.sp-lfs-cache] 10 | path = "../../primitives/cache" 11 | 12 | [dependencies.sp-lfs-core] 13 | path = "../../primitives/core" 14 | 15 | [dependencies] 16 | base64 = "0.11.0" 17 | parking_lot = "0.10.0" 18 | lru = "0.4.3" 19 | -------------------------------------------------------------------------------- /client/simple-cache/src/disk.rs: -------------------------------------------------------------------------------- 1 | use sp_lfs_cache::Cache; 2 | use sp_lfs_core::LfsId; 3 | use std::fs; 4 | use std::path::PathBuf; 5 | 6 | /// a super simplistic disk cache 7 | pub struct SimpleDiskCache { 8 | path: PathBuf, 9 | } 10 | 11 | impl SimpleDiskCache { 12 | pub fn new(path: PathBuf) -> Result { 13 | if !path.as_path().is_dir() { 14 | return Err(format!( 15 | "{:?} is not an accessible directory", 16 | path.as_path() 17 | )); 18 | } 19 | Ok(SimpleDiskCache { path }) 20 | } 21 | fn make_local_path(&self, key: &Key) -> PathBuf { 22 | let encoded = base64::encode_config(&key.encode(), base64::URL_SAFE); 23 | let mut path = self.path.clone(); 24 | path.push(encoded); 25 | path 26 | } 27 | } 28 | 29 | impl Cache for SimpleDiskCache 30 | where 31 | Key: LfsId, 32 | { 33 | fn exists(&self, key: &Key) -> Result { 34 | let path = self.make_local_path(key); 35 | Ok(path.as_path().exists()) 36 | } 37 | 38 | fn get(&self, key: &Key) -> Result, ()> { 39 | let path = self.make_local_path(key); 40 | fs::read(path).map_err(|_| ()) 41 | } 42 | 43 | fn insert(&self, key: &Key, data: &Vec) -> Result<(), ()> { 44 | let path = self.make_local_path(key); 45 | fs::write(path, data).map_err(|_| ()) 46 | } 47 | 48 | fn drop(&self, key: &Key) -> Result<(), ()> { 49 | let path = self.make_local_path(key); 50 | fs::remove_file(path).map_err(|_| ()) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /client/simple-cache/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod disk; 2 | mod lru_cache; 3 | mod memory; 4 | 5 | pub use disk::SimpleDiskCache; 6 | pub use lru_cache::Cache as LruCache; 7 | pub use memory::InMemoryCache; 8 | -------------------------------------------------------------------------------- /client/simple-cache/src/lru_cache.rs: -------------------------------------------------------------------------------- 1 | use lru::LruCache; 2 | use parking_lot::Mutex; 3 | use sp_lfs_core::LfsId; 4 | 5 | /// a simple in-memory HashMap caching system 6 | pub struct Cache { 7 | inner: Mutex>>, 8 | } 9 | 10 | impl Cache { 11 | pub fn new(cap: usize) -> Self { 12 | Cache { 13 | inner: Mutex::new(LruCache::new(cap)), 14 | } 15 | } 16 | } 17 | 18 | impl sp_lfs_cache::Cache for Cache { 19 | fn exists(&self, key: &Key) -> Result { 20 | Ok(self.inner.lock().contains(key)) 21 | } 22 | 23 | fn get(&self, key: &Key) -> Result, ()> { 24 | self.inner.lock().get(key).ok_or(()).map(|v| v.clone()) 25 | } 26 | 27 | fn insert(&self, key: &Key, data: &Vec) -> Result<(), ()> { 28 | self.inner 29 | .lock() 30 | .put(key.clone(), data.to_vec()) 31 | .ok_or(()) 32 | .map(|_| ()) 33 | } 34 | 35 | fn drop(&self, key: &Key) -> Result<(), ()> { 36 | self.inner.lock().pop(key).ok_or(()).map(|_| ()) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /client/simple-cache/src/memory.rs: -------------------------------------------------------------------------------- 1 | use parking_lot::Mutex; 2 | use sp_lfs_cache::Cache; 3 | use sp_lfs_core::LfsId; 4 | use std::collections::HashMap; 5 | 6 | /// a simple in-memory HashMap caching system 7 | pub struct InMemoryCache { 8 | inner: Mutex>>, 9 | } 10 | 11 | impl InMemoryCache { 12 | pub fn new() -> Self { 13 | InMemoryCache { 14 | inner: Mutex::new(HashMap::new()), 15 | } 16 | } 17 | } 18 | 19 | impl Cache for InMemoryCache { 20 | fn exists(&self, key: &Key) -> Result { 21 | Ok(self.inner.lock().contains_key(key)) 22 | } 23 | 24 | fn get(&self, key: &Key) -> Result, ()> { 25 | self.inner.lock().get(key).ok_or(()).map(|v| v.clone()) 26 | } 27 | 28 | fn insert(&self, key: &Key, data: &Vec) -> Result<(), ()> { 29 | self.inner 30 | .lock() 31 | .insert(key.clone(), data.to_vec()) 32 | .ok_or(()) 33 | .map(|_| ()) 34 | } 35 | 36 | fn drop(&self, key: &Key) -> Result<(), ()> { 37 | self.inner.lock().remove(key).ok_or(()).map(|_| ()) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /client/src/cache.rs: -------------------------------------------------------------------------------- 1 | use crate::config::LfsConfig; 2 | use crate::lfs_id::LfsId; 3 | use sc_lfs_simple_cache::{LruCache, SimpleDiskCache}; 4 | use sp_lfs_cache::{shared::SharedCache, FrontedCache}; 5 | use std::path::PathBuf; 6 | 7 | pub type ClientCache = SharedCache, SimpleDiskCache>>; 8 | 9 | pub fn from_config(cfg: &LfsConfig, path_reverter: F) -> Result 10 | where 11 | F: Fn(PathBuf) -> Result, 12 | { 13 | let path_buf = { 14 | let path = cfg.cache.path.clone(); 15 | if path.is_relative() { 16 | path_reverter(path)? 17 | } else { 18 | path 19 | } 20 | }; 21 | let path = path_buf.as_path(); 22 | 23 | if !path.exists() { 24 | std::fs::create_dir_all(path) 25 | .map_err(|e| format!("Creating lfs directory failed: {}", e))?; 26 | } 27 | 28 | let disk = SimpleDiskCache::new(path_buf)?; 29 | 30 | Ok(SharedCache::new(FrontedCache::new( 31 | LruCache::::new(cfg.cache.mem_limit), 32 | disk, 33 | ))) 34 | } 35 | -------------------------------------------------------------------------------- /client/src/config.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::fs; 3 | use std::path::{Path, PathBuf}; 4 | use toml; 5 | 6 | const DEFAULT_MEM_LIMIT: usize = 1024; 7 | 8 | /// Configuration for the LFS cache 9 | #[derive(Serialize, Deserialize, Debug)] 10 | pub struct CacheConfig { 11 | /// Where to store data locally 12 | pub(crate) path: PathBuf, 13 | /// Memory cache 14 | pub(crate) mem_limit: usize, 15 | } 16 | 17 | /// Our lfs configuration file 18 | #[derive(Serialize, Deserialize, Debug)] 19 | pub struct LfsConfig { 20 | pub cache: CacheConfig, 21 | } 22 | 23 | impl core::default::Default for CacheConfig { 24 | fn default() -> CacheConfig { 25 | CacheConfig { 26 | path: PathBuf::from("./lfs"), 27 | mem_limit: DEFAULT_MEM_LIMIT, 28 | } 29 | } 30 | } 31 | 32 | impl std::default::Default for LfsConfig { 33 | fn default() -> LfsConfig { 34 | LfsConfig { 35 | cache: Default::default(), 36 | } 37 | } 38 | } 39 | 40 | pub fn load_config(config_file_path: &Path) -> Result { 41 | if !config_file_path.is_file() { 42 | // make sure the folder exists 43 | if let Some(dir) = config_file_path.parent() { 44 | if !dir.is_dir() { 45 | std::fs::create_dir_all(dir) 46 | .map_err(|e| format!("could not create config dir: {}", e))? 47 | } 48 | } 49 | // we write the defaults to the config 50 | fs::write( 51 | config_file_path, 52 | toml::to_string(&LfsConfig::default()).expect("Handcrafted to never fail"), 53 | ) 54 | .expect("Writing the LFS configuration failed"); 55 | } 56 | 57 | let content = fs::read(config_file_path) 58 | .map_err(|e| format!("failed to open LFS configuration: {}", e))?; 59 | toml::from_slice::(&content) 60 | .map_err(|e| format!("Error parsing LFS configuration : {}", e)) 61 | } 62 | -------------------------------------------------------------------------------- /client/src/lfs_id.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "std")] 2 | use serde::{Deserialize, Serialize}; 3 | use sp_core::hashing::{blake2_256, keccak_256, sha2_256}; 4 | pub use sp_lfs_core::{LfsId as LfsIdT, LfsReference}; 5 | 6 | use codec::{Decode, Encode}; 7 | 8 | #[cfg(feature = "with-blake3")] 9 | use blake3; 10 | 11 | type Hash256 = [u8; 32]; 12 | 13 | #[derive(Debug, Encode, Decode, Clone, Hash, Eq)] 14 | /// Our Large File System ID 15 | #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] 16 | pub enum LfsId { 17 | /// Raw directly showing the data 18 | /// below a certain length (< 32 bytes), it doesn't make any sense to hash them 19 | #[codec(index = "0")] 20 | Raw(Vec), 21 | 22 | #[codec(index = "10")] 23 | Blake2(Hash256), 24 | #[cfg(feature = "with-blake3")] 25 | #[codec(index = "11")] 26 | Blake3(Hash256), 27 | #[codec(index = "20")] 28 | Sha2(Hash256), 29 | #[codec(index = "21")] 30 | Sha3(Hash256), 31 | } 32 | 33 | impl LfsId { 34 | #[cfg(feature = "with-blake3")] 35 | pub fn default(data: &Vec) -> Self { 36 | Self::blake3(data) 37 | } 38 | #[cfg(not(feature = "with-blake3"))] 39 | pub fn default(data: &Vec) -> Self { 40 | Self::blake2(data) 41 | } 42 | pub fn blake2(data: &Vec) -> Self { 43 | LfsId::Blake2(blake2_256(data)) 44 | } 45 | pub fn sha2(data: &Vec) -> Self { 46 | LfsId::Sha2(sha2_256(data)) 47 | } 48 | pub fn sha3(data: &Vec) -> Self { 49 | LfsId::Sha3(keccak_256(data)) 50 | } 51 | #[cfg(feature = "with-blake3")] 52 | pub fn blake3(data: &Vec) -> Self { 53 | LfsId::Blake3(*blake3::hash(data).as_bytes()) 54 | } 55 | } 56 | 57 | impl std::fmt::Display for LfsId { 58 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 59 | write!(f, "LFSid<{}>", base64::encode(&self.encode())) 60 | } 61 | } 62 | 63 | impl sp_runtime_interface::pass_by::PassBy for LfsId { 64 | type PassBy = sp_runtime_interface::pass_by::Codec; 65 | } 66 | 67 | impl core::cmp::PartialEq for LfsId { 68 | fn eq(&self, other: &Self) -> bool { 69 | match (self, other) { 70 | (LfsId::Raw(ref s), LfsId::Raw(ref o)) => s == o, 71 | (LfsId::Blake2(s), LfsId::Blake2(o)) => s == o, 72 | (LfsId::Sha2(s), LfsId::Sha2(o)) => s == o, 73 | (LfsId::Sha3(s), LfsId::Sha3(o)) => s == o, 74 | #[cfg(feature = "with-blake3")] 75 | (LfsId::Blake3(s), LfsId::Blake3(o)) => s == o, 76 | _ => false, 77 | } 78 | } 79 | } 80 | 81 | impl core::convert::TryFrom for LfsId { 82 | type Error = String; 83 | 84 | fn try_from(value: LfsReference) -> Result { 85 | Self::decode(&mut value.as_slice()) 86 | .map_err(|e| format!("Decoding LFS Reference failed: {:}", e)) 87 | } 88 | } 89 | impl core::convert::Into for LfsId { 90 | fn into(self) -> LfsReference { 91 | self.encode() 92 | } 93 | } 94 | 95 | impl LfsIdT for LfsId { 96 | fn for_data(data: &Vec) -> Result { 97 | if data.len() <= 32 { 98 | Ok(LfsId::Raw(data.clone())) 99 | } else { 100 | Ok(Self::default(data)) 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /client/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | 3 | pub mod lfs_id; 4 | 5 | #[cfg(feature = "std")] 6 | pub mod cache; 7 | #[cfg(feature = "std")] 8 | pub mod config; 9 | 10 | #[cfg(feature = "jsonrpc")] 11 | pub mod rpc; 12 | 13 | #[cfg(feature = "std")] 14 | pub struct DefaultClient { 15 | cache: cache::ClientCache, 16 | } 17 | 18 | impl DefaultClient { 19 | /// get a reference to the inner client cache 20 | pub fn cache(&self) -> &cache::ClientCache { 21 | &self.cache 22 | } 23 | } 24 | pub use sp_lfs_cache::lfs_cache_interface; 25 | 26 | pub struct LfsExtensionsFactory(cache::ClientCache); 27 | impl sc_client_api::execution_extensions::ExtensionsFactory for LfsExtensionsFactory { 28 | fn extensions_for( 29 | &self, 30 | capabilities: sp_core::offchain::Capabilities, 31 | ) -> sp_externalities::Extensions { 32 | let mut exts = sp_externalities::Extensions::new(); 33 | if capabilities != sp_core::offchain::Capabilities::none() { 34 | // only offer feature in offchain workers 35 | let inner: sp_lfs_cache::RuntimeCacheInterfaceWrapper<_, _> = self.0.clone().into(); 36 | exts.register(sp_lfs_cache::LfsCacheExt::new(Box::new(inner))); 37 | } 38 | exts 39 | } 40 | } 41 | 42 | #[cfg(feature = "std")] 43 | impl DefaultClient { 44 | pub fn from_config Result>( 45 | cfg: &config::LfsConfig, 46 | converter: F, 47 | ) -> Result { 48 | Ok(DefaultClient { 49 | cache: cache::from_config(cfg, converter)?, 50 | }) 51 | } 52 | 53 | #[cfg(feature = "jsonrpc")] 54 | pub fn make_rpc(&self) -> rpc::LfsRpc { 55 | rpc::LfsRpc::new(self.cache.clone()) 56 | } 57 | 58 | pub fn make_externalities_extension_factory(&self) -> Box { 59 | Box::new(LfsExtensionsFactory(self.cache.clone())) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /client/src/network.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /client/src/network/bitswap.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/client/src/network/bitswap.rs -------------------------------------------------------------------------------- /client/src/rpc.rs: -------------------------------------------------------------------------------- 1 | use jsonrpc_core::types::error::Error as ApiError; 2 | use jsonrpc_core::Result; 3 | use jsonrpc_derive::rpc; 4 | 5 | use crate::lfs_id::LfsId; 6 | use sp_lfs_cache::Cache; 7 | 8 | pub use self::gen_client::Client as LfsClient; 9 | 10 | /// Substrate LFS RPC API 11 | #[rpc] 12 | pub trait LfsApi { 13 | #[rpc(name = "lfs_get")] 14 | fn get(&self, id: Key) -> Result>; 15 | 16 | #[rpc(name = "lfs_upload")] 17 | fn upload(&self, data: Vec) -> Result; 18 | } 19 | 20 | /// An implementation of System-specific RPC methods. 21 | pub struct LfsRpc { 22 | cache: C, 23 | } 24 | 25 | impl LfsRpc { 26 | /// Create new `LFS` interface given the cache. 27 | pub fn new(cache: C) -> Self { 28 | LfsRpc { cache } 29 | } 30 | } 31 | 32 | impl LfsApi for LfsRpc 33 | where 34 | C: Cache + Sync + Send + Clone + 'static, 35 | { 36 | fn get(&self, id: LfsId) -> Result> { 37 | if let LfsId::Raw(data) = id { 38 | return Ok(data); 39 | } 40 | 41 | self.cache 42 | .clone() // FIXME: why do we have to clone here? 43 | .get(&id) 44 | .map_err(|_| ApiError::invalid_params("Key not found")) 45 | } 46 | 47 | fn upload(&self, data: Vec) -> Result { 48 | self.cache 49 | .clone() // FIXME: why do we have to clone here? 50 | .store(&data) 51 | .map_err(|_| ApiError::invalid_params("Data could not be stored")) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /demo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lfs-demo" 3 | version = "2.0.0" 4 | authors = ["Anonymous"] 5 | build = "build.rs" 6 | edition = "2018" 7 | default-run = "lfs-demo" 8 | publish = false 9 | 10 | [[bin]] 11 | name = "lfs-demo" 12 | path = "src/main.rs" 13 | 14 | [dependencies.lfs-demo-runtime] 15 | path = "./runtime" 16 | 17 | [dependencies.sc-lfs] 18 | path = "../client" 19 | features = ["default", "unstable"] 20 | 21 | [dependencies.sc-lfs-http-server] 22 | path = "../client/http-server" 23 | features = ["default", "user-data"] 24 | 25 | 26 | [dependencies] 27 | futures = "0.3.1" 28 | futures01 = { package = "futures", version = "0.1.29" } 29 | ctrlc = { version = "3.1.3", features = ["termination"] } 30 | log = "0.4.8" 31 | tokio = "0.1.22" 32 | parking_lot = "0.9.0" 33 | codec = { package = "parity-scale-codec", version = "1.0.0" } 34 | trie-root = "0.15.2" 35 | sp-io = { version = "2.0.0", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 36 | sc-cli = { version = "0.8.0", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 37 | sp-core = { version = "2.0.0", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 38 | sc-executor = { version = "0.8.0", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 39 | sc-service = { version = "0.8.0", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 40 | sp-inherents = { version = "2.0.0", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 41 | sc-transaction-pool = { version = "2.0.0", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 42 | sp-transaction-pool = { version = "2.0.0", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 43 | sc-network = { version = "0.8", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 44 | sc-consensus-aura = { version = "0.8", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 45 | sp-consensus-aura = { version = "0.8", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 46 | sp-consensus = { version = "0.8", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 47 | grandpa = { version = "0.8.0", package = "sc-finality-grandpa", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 48 | grandpa-primitives = { version = "2.0.0", package = "sp-finality-grandpa", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 49 | sc-client = { version = "0.8.0", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 50 | sp-runtime = { version = "2.0.0", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 51 | sc-basic-authorship = { git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 52 | 53 | structopt = "0.3.8" 54 | jsonrpc-core = "14.0.5" 55 | 56 | [build-dependencies] 57 | vergen = "3.0.4" 58 | build-script-utils = { version = "2.0.0", package = "substrate-build-script-utils", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 59 | -------------------------------------------------------------------------------- /demo/LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | # Substrate LFS Demo Node 2 | 3 | ## Build 4 | 5 | Install Rust: 6 | 7 | ```bash 8 | curl https://sh.rustup.rs -sSf | sh 9 | ``` 10 | 11 | Initialize your Wasm Build environment: 12 | 13 | ```bash 14 | ./scripts/init.sh 15 | ``` 16 | 17 | ## Run 18 | 19 | ```bash 20 | cargo run 21 | ``` -------------------------------------------------------------------------------- /demo/build.rs: -------------------------------------------------------------------------------- 1 | use vergen::{generate_cargo_keys, ConstantsFlags}; 2 | 3 | const ERROR_MSG: &str = "Failed to generate metadata files"; 4 | 5 | fn main() { 6 | generate_cargo_keys(ConstantsFlags::SHA_SHORT).expect(ERROR_MSG); 7 | 8 | build_script_utils::rerun_if_git_head_changed(); 9 | } 10 | -------------------------------------------------------------------------------- /demo/example_data/personal_site/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Attribution 3.0 Unported 2 | http://creativecommons.org/licenses/by/3.0/ 3 | 4 | License 5 | 6 | THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. 7 | 8 | BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. 9 | 10 | 1. Definitions 11 | 12 | 1. "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License. 13 | 2. "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined above) for the purposes of this License. 14 | 3. "Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership. 15 | 4. "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License. 16 | 5. "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast. 17 | 6. "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work. 18 | 7. "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. 19 | 8. "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. 20 | 9. "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. 21 | 22 | 2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. 23 | 24 | 3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: 25 | 26 | 1. to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; 27 | 2. to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified."; 28 | 3. to Distribute and Publicly Perform the Work including as incorporated in Collections; and, 29 | 4. to Distribute and Publicly Perform Adaptations. 30 | 5. 31 | 32 | For the avoidance of doubt: 33 | 1. Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; 34 | 2. Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and, 35 | 3. Voluntary License Schemes. The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License. 36 | 37 | The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved. 38 | 39 | 4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: 40 | 41 | 1. You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(b), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(b), as requested. 42 | 2. If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv) , consistent with Section 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4 (b) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. 43 | 3. Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise. 44 | 45 | 5. Representations, Warranties and Disclaimer 46 | 47 | UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. 48 | 49 | 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 50 | 51 | 7. Termination 52 | 53 | 1. This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. 54 | 2. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. 55 | 56 | 8. Miscellaneous 57 | 58 | 1. Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. 59 | 2. Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. 60 | 3. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. 61 | 4. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. 62 | 5. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. 63 | 6. The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law. 64 | -------------------------------------------------------------------------------- /demo/example_data/personal_site/assets/css/images/overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/personal_site/assets/css/images/overlay.png -------------------------------------------------------------------------------- /demo/example_data/personal_site/assets/css/noscript.css: -------------------------------------------------------------------------------- 1 | /* 2 | Identity by HTML5 UP 3 | html5up.net | @ajlkn 4 | Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | */ 6 | 7 | /* Basic */ 8 | 9 | body:after { 10 | display: none; 11 | } 12 | 13 | /* Main */ 14 | 15 | #main { 16 | -moz-transform: none !important; 17 | -webkit-transform: none !important; 18 | -ms-transform: none !important; 19 | transform: none !important; 20 | opacity: 1 !important; 21 | } -------------------------------------------------------------------------------- /demo/example_data/personal_site/assets/webfonts/fa-brands-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/personal_site/assets/webfonts/fa-brands-400.eot -------------------------------------------------------------------------------- /demo/example_data/personal_site/assets/webfonts/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/personal_site/assets/webfonts/fa-brands-400.ttf -------------------------------------------------------------------------------- /demo/example_data/personal_site/assets/webfonts/fa-brands-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/personal_site/assets/webfonts/fa-brands-400.woff -------------------------------------------------------------------------------- /demo/example_data/personal_site/assets/webfonts/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/personal_site/assets/webfonts/fa-brands-400.woff2 -------------------------------------------------------------------------------- /demo/example_data/personal_site/assets/webfonts/fa-regular-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/personal_site/assets/webfonts/fa-regular-400.eot -------------------------------------------------------------------------------- /demo/example_data/personal_site/assets/webfonts/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/personal_site/assets/webfonts/fa-regular-400.ttf -------------------------------------------------------------------------------- /demo/example_data/personal_site/assets/webfonts/fa-regular-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/personal_site/assets/webfonts/fa-regular-400.woff -------------------------------------------------------------------------------- /demo/example_data/personal_site/assets/webfonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/personal_site/assets/webfonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /demo/example_data/personal_site/assets/webfonts/fa-solid-900.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/personal_site/assets/webfonts/fa-solid-900.eot -------------------------------------------------------------------------------- /demo/example_data/personal_site/assets/webfonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/personal_site/assets/webfonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /demo/example_data/personal_site/assets/webfonts/fa-solid-900.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/personal_site/assets/webfonts/fa-solid-900.woff -------------------------------------------------------------------------------- /demo/example_data/personal_site/assets/webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/personal_site/assets/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /demo/example_data/personal_site/images/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/personal_site/images/avatar.png -------------------------------------------------------------------------------- /demo/example_data/personal_site/images/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/personal_site/images/bg.jpg -------------------------------------------------------------------------------- /demo/example_data/personal_site/index.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | Identity by HTML5 UP 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 |
22 |
23 | 24 |

Alice

25 |

Senior Crypto Engineer

26 |
27 | 64 | 71 |
72 | 73 | 74 |
75 | 78 |
79 | 80 |
81 | 82 | 83 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /demo/example_data/website/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Attribution 3.0 Unported 2 | http://creativecommons.org/licenses/by/3.0/ 3 | 4 | License 5 | 6 | THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. 7 | 8 | BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. 9 | 10 | 1. Definitions 11 | 12 | 1. "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License. 13 | 2. "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined above) for the purposes of this License. 14 | 3. "Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership. 15 | 4. "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License. 16 | 5. "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast. 17 | 6. "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work. 18 | 7. "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. 19 | 8. "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. 20 | 9. "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. 21 | 22 | 2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. 23 | 24 | 3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: 25 | 26 | 1. to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; 27 | 2. to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified."; 28 | 3. to Distribute and Publicly Perform the Work including as incorporated in Collections; and, 29 | 4. to Distribute and Publicly Perform Adaptations. 30 | 5. 31 | 32 | For the avoidance of doubt: 33 | 1. Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; 34 | 2. Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and, 35 | 3. Voluntary License Schemes. The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License. 36 | 37 | The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved. 38 | 39 | 4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: 40 | 41 | 1. You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(b), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(b), as requested. 42 | 2. If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv) , consistent with Section 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4 (b) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. 43 | 3. Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise. 44 | 45 | 5. Representations, Warranties and Disclaimer 46 | 47 | UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. 48 | 49 | 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 50 | 51 | 7. Termination 52 | 53 | 1. This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. 54 | 2. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. 55 | 56 | 8. Miscellaneous 57 | 58 | 1. Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. 59 | 2. Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. 60 | 3. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. 61 | 4. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. 62 | 5. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. 63 | 6. The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law. 64 | -------------------------------------------------------------------------------- /demo/example_data/website/assets/css/images/arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /demo/example_data/website/assets/css/images/banner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /demo/example_data/website/assets/css/images/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/assets/css/images/loader.gif -------------------------------------------------------------------------------- /demo/example_data/website/assets/css/images/overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/assets/css/images/overlay.png -------------------------------------------------------------------------------- /demo/example_data/website/assets/css/images/poptrox-closer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /demo/example_data/website/assets/css/images/poptrox-nav.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /demo/example_data/website/assets/css/noscript.css: -------------------------------------------------------------------------------- 1 | /* 2 | Overflow by HTML5 UP 3 | html5up.net | @ajlkn 4 | Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | */ 6 | 7 | /* Header */ 8 | 9 | body.is-preload #header { 10 | opacity: 1; 11 | } 12 | 13 | body.is-preload #header footer { 14 | -moz-transform: none; 15 | -webkit-transform: none; 16 | -ms-transform: none; 17 | transform: none; 18 | opacity: 1; 19 | } -------------------------------------------------------------------------------- /demo/example_data/website/assets/js/breakpoints.min.js: -------------------------------------------------------------------------------- 1 | /* breakpoints.js v1.0 | @ajlkn | MIT licensed */ 2 | var breakpoints=function(){"use strict";function e(e){t.init(e)}var t={list:null,media:{},events:[],init:function(e){t.list=e,window.addEventListener("resize",t.poll),window.addEventListener("orientationchange",t.poll),window.addEventListener("load",t.poll),window.addEventListener("fullscreenchange",t.poll)},active:function(e){var n,a,s,i,r,d,c;if(!(e in t.media)){if(">="==e.substr(0,2)?(a="gte",n=e.substr(2)):"<="==e.substr(0,2)?(a="lte",n=e.substr(2)):">"==e.substr(0,1)?(a="gt",n=e.substr(1)):"<"==e.substr(0,1)?(a="lt",n=e.substr(1)):"!"==e.substr(0,1)?(a="not",n=e.substr(1)):(a="eq",n=e),n&&n in t.list)if(i=t.list[n],Array.isArray(i)){if(r=parseInt(i[0]),d=parseInt(i[1]),isNaN(r)){if(isNaN(d))return;c=i[1].substr(String(d).length)}else c=i[0].substr(String(r).length);if(isNaN(r))switch(a){case"gte":s="screen";break;case"lte":s="screen and (max-width: "+d+c+")";break;case"gt":s="screen and (min-width: "+(d+1)+c+")";break;case"lt":s="screen and (max-width: -1px)";break;case"not":s="screen and (min-width: "+(d+1)+c+")";break;default:s="screen and (max-width: "+d+c+")"}else if(isNaN(d))switch(a){case"gte":s="screen and (min-width: "+r+c+")";break;case"lte":s="screen";break;case"gt":s="screen and (max-width: -1px)";break;case"lt":s="screen and (max-width: "+(r-1)+c+")";break;case"not":s="screen and (max-width: "+(r-1)+c+")";break;default:s="screen and (min-width: "+r+c+")"}else switch(a){case"gte":s="screen and (min-width: "+r+c+")";break;case"lte":s="screen and (max-width: "+d+c+")";break;case"gt":s="screen and (min-width: "+(d+1)+c+")";break;case"lt":s="screen and (max-width: "+(r-1)+c+")";break;case"not":s="screen and (max-width: "+(r-1)+c+"), screen and (min-width: "+(d+1)+c+")";break;default:s="screen and (min-width: "+r+c+") and (max-width: "+d+c+")"}}else s="("==i.charAt(0)?"screen and "+i:i;t.media[e]=!!s&&s}return t.media[e]!==!1&&window.matchMedia(t.media[e]).matches},on:function(e,n){t.events.push({query:e,handler:n,state:!1}),t.active(e)&&n()},poll:function(){var e,n;for(e=0;e0:!!("ontouchstart"in window),e.mobile="wp"==e.os||"android"==e.os||"ios"==e.os||"bb"==e.os}};return e.init(),e}();!function(e,n){"function"==typeof define&&define.amd?define([],n):"object"==typeof exports?module.exports=n():e.browser=n()}(this,function(){return browser}); 3 | -------------------------------------------------------------------------------- /demo/example_data/website/assets/js/jquery.poptrox.min.js: -------------------------------------------------------------------------------- 1 | /* jquery.poptrox.js v2.5.2-dev | (c) @ajlkn | github.com/ajlkn/jquery.poptrox | MIT licensed */ 2 | !function(e){e.fn.poptrox_disableSelection=function(){return e(this).css("user-select","none").css("-khtml-user-select","none").css("-moz-user-select","none").css("-o-user-select","none").css("-webkit-user-select","none")},e.fn.poptrox=function(o){if(0==this.length)return e(this);if(this.length>1){for(var t=0;t'),l=e(window),u=[],d=0,h=!1,g=new Array,f=function(){p=l.width(),i=l.height()+s.windowHeightPad;var e=Math.abs(x.width()-x.outerWidth()),o=Math.abs(x.height()-x.outerHeight()),t=p-2*s.windowMargin-e,r=i-2*s.windowMargin-o;x.css("min-width",s.popupWidth).css("min-height",s.popupHeight),v.children().css("max-width",t).css("max-height",r)};s.usePopupLoader||(s.popupLoaderSelector=null),s.usePopupCloser||(s.popupCloserSelector=null),s.usePopupCaption||(s.popupCaptionSelector=null),s.usePopupNav||(s.popupNavPreviousSelector=null,s.popupNavNextSelector=null);var x;x=e(s.popupSelector?s.popupSelector:'
'+(s.popupLoaderSelector?'
'+s.popupLoaderText+"
":"")+'
'+(s.popupCaptionSelector?'
':"")+(s.popupCloserSelector?''+s.popupCloserText+"":"")+(s.popupNavPreviousSelector?'':"")+(s.popupNavNextSelector?'':"")+"
");var v=x.find(".pic"),w=e(),b=x.find(s.popupLoaderSelector),m=x.find(s.popupCaptionSelector),C=x.find(s.popupCloserSelector),y=x.find(s.popupNavNextSelector),S=x.find(s.popupNavPreviousSelector),P=y.add(S);if(s.usePopupDefaultStyling&&(x.css("background",s.popupBackgroundColor).css("color",s.popupTextColor).css("padding",s.popupPadding+"px"),m.length>0&&(x.css("padding-bottom",s.popupCaptionHeight+"px"),m.css("position","absolute").css("left","0").css("bottom","0").css("width","100%").css("text-align","center").css("height",s.popupCaptionHeight+"px").css("line-height",s.popupCaptionHeight+"px"),s.popupCaptionTextSize&&m.css("font-size",popupCaptionTextSize)),C.length>0&&C.html(s.popupCloserText).css("font-size",s.popupCloserTextSize).css("background",s.popupCloserBackgroundColor).css("color",s.popupCloserTextColor).css("display","block").css("width","40px").css("height","40px").css("line-height","40px").css("text-align","center").css("position","absolute").css("text-decoration","none").css("outline","0").css("top","0").css("right","-40px"),b.length>0&&b.html("").css("position","relative").css("font-size",s.popupLoaderTextSize).on("startSpinning",function(o){var t=e("
"+s.popupLoaderText+"
");t.css("height",Math.floor(s.popupHeight/2)+"px").css("overflow","hidden").css("line-height",Math.floor(s.popupHeight/2)+"px").css("text-align","center").css("margin-top",Math.floor((x.height()-t.height()+(m.length>0?m.height():0))/2)).css("color",s.popupTextColor?s.popupTextColor:"").on("xfin",function(){t.fadeTo(300,.5,function(){t.trigger("xfout")})}).on("xfout",function(){t.fadeTo(300,.05,function(){t.trigger("xfin")})}).trigger("xfin"),b.append(t)}).on("stopSpinning",function(e){var o=b.find("div");o.remove()}),2==P.length)){P.css("font-size","75px").css("text-align","center").css("color","#fff").css("text-shadow","none").css("height","100%").css("position","absolute").css("top","0").css("opacity","0.35").css("cursor","pointer").css("box-shadow","inset 0px 0px 10px 0px rgba(0,0,0,0)").poptrox_disableSelection();var k,T;s.usePopupEasyClose?(k="100px",T="100px"):(k="75%",T="25%"),y.css("right","0").css("width",k).html('
>
'),S.css("left","0").css("width",T).html('
<
')}return l.on("resize orientationchange",function(){f()}),m.on("update",function(e,o){o&&0!=o.length||(o=s.popupBlankCaptionText),m.html(o)}),C.css("cursor","pointer").on("click",function(e){return e.preventDefault(),e.stopPropagation(),x.trigger("poptrox_close"),!0}),y.on("click",function(e){e.stopPropagation(),e.preventDefault(),x.trigger("poptrox_next")}),S.on("click",function(e){e.stopPropagation(),e.preventDefault(),x.trigger("poptrox_previous")}),a.css("position","fixed").css("left",0).css("top",0).css("z-index",s.baseZIndex).css("width","100%").css("height","100%").css("text-align","center").css("cursor","pointer").appendTo(s.parent).prepend('
').append('
').hide().on("touchmove",function(e){return!1}).on("click",function(e){e.preventDefault(),e.stopPropagation(),x.trigger("poptrox_close")}),x.css("display","inline-block").css("vertical-align","middle").css("position","relative").css("z-index",1).css("cursor","auto").appendTo(a).hide().on("poptrox_next",function(){var e=d+1;e>=u.length&&(e=0),x.trigger("poptrox_switch",[e])}).on("poptrox_previous",function(){var e=d-1;e<0&&(e=u.length-1),x.trigger("poptrox_switch",[e])}).on("poptrox_reset",function(){f(),x.data("width",s.popupWidth).data("height",s.popupHeight),b.hide().trigger("stopSpinning"),m.hide(),C.hide(),P.hide(),v.hide(),w.attr("src","").detach()}).on("poptrox_open",function(e,o){return!!h||(h=!0,s.useBodyOverflow&&n.css("overflow","hidden"),s.onPopupOpen&&s.onPopupOpen(),x.addClass("loading"),void a.fadeTo(s.fadeSpeed,1,function(){x.trigger("poptrox_switch",[o,!0])}))}).on("poptrox_switch",function(o,t,p){var i;if(!p&&h)return!0;if(h=!0,x.addClass("loading").css("width",x.data("width")).css("height",x.data("height")),m.hide(),w.attr("src")&&w.attr("src",""),w.detach(),i=u[t],w=i.object,w.off("load"),v.css("text-indent","-9999px").show().append(w),"ajax"==i.type?e.get(i.src,function(e){w.html(e),w.trigger("load")}):w.attr("src",i.src),"image"!=i.type){var r,n;r=i.width,n=i.height,"%"==r.slice(-1)&&(r=parseInt(r.substring(0,r.length-1))/100*l.width()),"%"==n.slice(-1)&&(n=parseInt(n.substring(0,n.length-1))/100*l.height()),w.css("position","relative").css("outline","0").css("z-index",s.baseZIndex+100).width(r).height(n)}b.trigger("startSpinning").fadeIn(300),x.show(),s.popupIsFixed?(x.removeClass("loading").width(s.popupWidth).height(s.popupHeight),w.on("load",function(){w.off("load"),b.hide().trigger("stopSpinning"),m.trigger("update",[i.captionText]).fadeIn(s.fadeSpeed),C.fadeIn(s.fadeSpeed),v.css("text-indent",0).hide().fadeIn(s.fadeSpeed,function(){h=!1}),d=t,P.fadeIn(s.fadeSpeed)})):w.on("load",function(){f(),w.off("load"),b.hide().trigger("stopSpinning");var e=w.width(),o=w.height(),p=function(){m.trigger("update",[i.captionText]).fadeIn(s.fadeSpeed),C.fadeIn(s.fadeSpeed),v.css("text-indent",0).hide().fadeIn(s.fadeSpeed,function(){h=!1}),d=t,P.fadeIn(s.fadeSpeed),x.removeClass("loading").data("width",e).data("height",o).css("width","auto").css("height","auto")};e==x.data("width")&&o==x.data("height")?p():x.animate({width:e,height:o},s.popupSpeed,"swing",p)}),"image"!=i.type&&w.trigger("load")}).on("poptrox_close",function(){return!(!h||s.usePopupForceClose)||(h=!0,x.hide().trigger("poptrox_reset"),s.onPopupClose&&s.onPopupClose(),void a.fadeOut(s.fadeSpeed,function(){s.useBodyOverflow&&n.css("overflow","auto"),h=!1}))}).trigger("poptrox_reset"),s.usePopupEasyClose?(m.on("click","a",function(e){e.stopPropagation()}),x.css("cursor","pointer").on("click",function(e){e.stopPropagation(),e.preventDefault(),x.trigger("poptrox_close")})):x.on("click",function(e){e.stopPropagation()}),l.keydown(function(e){if(x.is(":visible"))switch(e.keyCode){case 37:case 32:if(s.usePopupNav)return x.trigger("poptrox_previous"),!1;break;case 39:if(s.usePopupNav)return x.trigger("poptrox_next"),!1;break;case 27:return x.trigger("poptrox_close"),!1}}),r.find(s.selector).each(function(o){var t,p,i=e(this),r=i.find("img"),n=i.data("poptrox");if("ignore"!=n&&i.attr("href")){if(t={src:i.attr("href"),captionText:r.attr("title"),width:null,height:null,type:null,object:null,options:null},s.caption){if("function"==typeof s.caption)c=s.caption(i);else if("selector"in s.caption){var a;a=i.find(s.caption.selector),"attribute"in s.caption?c=a.attr(s.caption.attribute):(c=a.html(),s.caption.remove===!0&&a.remove())}}else c=r.attr("title");if(t.captionText=c,n){var l=n.split(",");0 in l&&(t.type=l[0]),1 in l&&(p=l[1].match(/([0-9%]+)x([0-9%]+)/),p&&3==p.length&&(t.width=p[1],t.height=p[2])),2 in l&&(t.options=l[2])}if(!t.type)switch(p=t.src.match(/\/\/([a-z0-9\.]+)\/.*/),(!p||p.length<2)&&(p=[!1]),p[1]){case"api.soundcloud.com":t.type="soundcloud";break;case"youtu.be":t.type="youtube";break;case"vimeo.com":t.type="vimeo";break;case"wistia.net":t.type="wistia";break;case"bcove.me":t.type="bcove";break;default:t.type="image"}switch(p=t.src.match(/\/\/[a-z0-9\.]+\/(.*)/),t.type){case"iframe":t.object=e(''),t.object.on("click",function(e){e.stopPropagation()}).css("cursor","auto"),t.width&&t.height||(t.width="600",t.height="400");break;case"ajax":t.object=e('
'),t.object.on("click",function(e){e.stopPropagation()}).css("cursor","auto").css("overflow","auto"),t.width&&t.height||(t.width="600",t.height="400");break;case"soundcloud":t.object=e(''),t.src="//w.soundcloud.com/player/?url="+escape(t.src)+(t.options?"&"+t.options:""),t.width="600",t.height="166";break;case"youtube":t.object=e(''),t.src="//www.youtube.com/embed/"+p[1]+(t.options?"?"+t.options:""),t.width&&t.height||(t.width="800",t.height="480");break;case"vimeo":t.object=e(''),t.src="//player.vimeo.com/video/"+p[1]+(t.options?"?"+t.options:""),t.width&&t.height||(t.width="800",t.height="480");break;case"wistia":t.object=e(''),t.src="//fast.wistia.net/"+p[1]+(t.options?"?"+t.options:""),t.width&&t.height||(t.width="800",t.height="480");break;case"bcove":t.object=e(''),t.src="//bcove.me/"+p[1]+(t.options?"?"+t.options:""),t.width&&t.height||(t.width="640",t.height="360");break;default:if(t.object=e(''),s.preload){var p=document.createElement("img");p.src=t.src,g.push(p)}t.width=i.attr("width"),t.height=i.attr("height")}"file:"==window.location.protocol&&t.src.match(/^\/\//)&&(t.src="http:"+t.src),u.push(t),r.removeAttr("title"),i.removeAttr("href").css("cursor","pointer").css("outline",0).on("click",function(e){e.preventDefault(),e.stopPropagation(),x.trigger("poptrox_open",[o])})}}),r.prop("_poptrox",s),r}}(jQuery); 3 | -------------------------------------------------------------------------------- /demo/example_data/website/assets/js/jquery.scrolly.min.js: -------------------------------------------------------------------------------- 1 | /* jquery.scrolly v1.0.0-dev | (c) @ajlkn | MIT licensed */ 2 | (function(e){function u(s,o){var u,a,f;if((u=e(s))[t]==0)return n;a=u[i]()[r];switch(o.anchor){case"middle":f=a-(e(window).height()-u.outerHeight())/2;break;default:case r:f=Math.max(a,0)}return typeof o[i]=="function"?f-=o[i]():f-=o[i],f}var t="length",n=null,r="top",i="offset",s="click.scrolly",o=e(window);e.fn.scrolly=function(i){var o,a,f,l,c=e(this);if(this[t]==0)return c;if(this[t]>1){for(o=0;o' + 26 | '' + 27 | $this.text() + 28 | '' 29 | ); 30 | 31 | }); 32 | 33 | return b.join(''); 34 | 35 | }; 36 | 37 | /** 38 | * Panel-ify an element. 39 | * @param {object} userConfig User config. 40 | * @return {jQuery} jQuery object. 41 | */ 42 | $.fn.panel = function(userConfig) { 43 | 44 | // No elements? 45 | if (this.length == 0) 46 | return $this; 47 | 48 | // Multiple elements? 49 | if (this.length > 1) { 50 | 51 | for (var i=0; i < this.length; i++) 52 | $(this[i]).panel(userConfig); 53 | 54 | return $this; 55 | 56 | } 57 | 58 | // Vars. 59 | var $this = $(this), 60 | $body = $('body'), 61 | $window = $(window), 62 | id = $this.attr('id'), 63 | config; 64 | 65 | // Config. 66 | config = $.extend({ 67 | 68 | // Delay. 69 | delay: 0, 70 | 71 | // Hide panel on link click. 72 | hideOnClick: false, 73 | 74 | // Hide panel on escape keypress. 75 | hideOnEscape: false, 76 | 77 | // Hide panel on swipe. 78 | hideOnSwipe: false, 79 | 80 | // Reset scroll position on hide. 81 | resetScroll: false, 82 | 83 | // Reset forms on hide. 84 | resetForms: false, 85 | 86 | // Side of viewport the panel will appear. 87 | side: null, 88 | 89 | // Target element for "class". 90 | target: $this, 91 | 92 | // Class to toggle. 93 | visibleClass: 'visible' 94 | 95 | }, userConfig); 96 | 97 | // Expand "target" if it's not a jQuery object already. 98 | if (typeof config.target != 'jQuery') 99 | config.target = $(config.target); 100 | 101 | // Panel. 102 | 103 | // Methods. 104 | $this._hide = function(event) { 105 | 106 | // Already hidden? Bail. 107 | if (!config.target.hasClass(config.visibleClass)) 108 | return; 109 | 110 | // If an event was provided, cancel it. 111 | if (event) { 112 | 113 | event.preventDefault(); 114 | event.stopPropagation(); 115 | 116 | } 117 | 118 | // Hide. 119 | config.target.removeClass(config.visibleClass); 120 | 121 | // Post-hide stuff. 122 | window.setTimeout(function() { 123 | 124 | // Reset scroll position. 125 | if (config.resetScroll) 126 | $this.scrollTop(0); 127 | 128 | // Reset forms. 129 | if (config.resetForms) 130 | $this.find('form').each(function() { 131 | this.reset(); 132 | }); 133 | 134 | }, config.delay); 135 | 136 | }; 137 | 138 | // Vendor fixes. 139 | $this 140 | .css('-ms-overflow-style', '-ms-autohiding-scrollbar') 141 | .css('-webkit-overflow-scrolling', 'touch'); 142 | 143 | // Hide on click. 144 | if (config.hideOnClick) { 145 | 146 | $this.find('a') 147 | .css('-webkit-tap-highlight-color', 'rgba(0,0,0,0)'); 148 | 149 | $this 150 | .on('click', 'a', function(event) { 151 | 152 | var $a = $(this), 153 | href = $a.attr('href'), 154 | target = $a.attr('target'); 155 | 156 | if (!href || href == '#' || href == '' || href == '#' + id) 157 | return; 158 | 159 | // Cancel original event. 160 | event.preventDefault(); 161 | event.stopPropagation(); 162 | 163 | // Hide panel. 164 | $this._hide(); 165 | 166 | // Redirect to href. 167 | window.setTimeout(function() { 168 | 169 | if (target == '_blank') 170 | window.open(href); 171 | else 172 | window.location.href = href; 173 | 174 | }, config.delay + 10); 175 | 176 | }); 177 | 178 | } 179 | 180 | // Event: Touch stuff. 181 | $this.on('touchstart', function(event) { 182 | 183 | $this.touchPosX = event.originalEvent.touches[0].pageX; 184 | $this.touchPosY = event.originalEvent.touches[0].pageY; 185 | 186 | }) 187 | 188 | $this.on('touchmove', function(event) { 189 | 190 | if ($this.touchPosX === null 191 | || $this.touchPosY === null) 192 | return; 193 | 194 | var diffX = $this.touchPosX - event.originalEvent.touches[0].pageX, 195 | diffY = $this.touchPosY - event.originalEvent.touches[0].pageY, 196 | th = $this.outerHeight(), 197 | ts = ($this.get(0).scrollHeight - $this.scrollTop()); 198 | 199 | // Hide on swipe? 200 | if (config.hideOnSwipe) { 201 | 202 | var result = false, 203 | boundary = 20, 204 | delta = 50; 205 | 206 | switch (config.side) { 207 | 208 | case 'left': 209 | result = (diffY < boundary && diffY > (-1 * boundary)) && (diffX > delta); 210 | break; 211 | 212 | case 'right': 213 | result = (diffY < boundary && diffY > (-1 * boundary)) && (diffX < (-1 * delta)); 214 | break; 215 | 216 | case 'top': 217 | result = (diffX < boundary && diffX > (-1 * boundary)) && (diffY > delta); 218 | break; 219 | 220 | case 'bottom': 221 | result = (diffX < boundary && diffX > (-1 * boundary)) && (diffY < (-1 * delta)); 222 | break; 223 | 224 | default: 225 | break; 226 | 227 | } 228 | 229 | if (result) { 230 | 231 | $this.touchPosX = null; 232 | $this.touchPosY = null; 233 | $this._hide(); 234 | 235 | return false; 236 | 237 | } 238 | 239 | } 240 | 241 | // Prevent vertical scrolling past the top or bottom. 242 | if (($this.scrollTop() < 0 && diffY < 0) 243 | || (ts > (th - 2) && ts < (th + 2) && diffY > 0)) { 244 | 245 | event.preventDefault(); 246 | event.stopPropagation(); 247 | 248 | } 249 | 250 | }); 251 | 252 | // Event: Prevent certain events inside the panel from bubbling. 253 | $this.on('click touchend touchstart touchmove', function(event) { 254 | event.stopPropagation(); 255 | }); 256 | 257 | // Event: Hide panel if a child anchor tag pointing to its ID is clicked. 258 | $this.on('click', 'a[href="#' + id + '"]', function(event) { 259 | 260 | event.preventDefault(); 261 | event.stopPropagation(); 262 | 263 | config.target.removeClass(config.visibleClass); 264 | 265 | }); 266 | 267 | // Body. 268 | 269 | // Event: Hide panel on body click/tap. 270 | $body.on('click touchend', function(event) { 271 | $this._hide(event); 272 | }); 273 | 274 | // Event: Toggle. 275 | $body.on('click', 'a[href="#' + id + '"]', function(event) { 276 | 277 | event.preventDefault(); 278 | event.stopPropagation(); 279 | 280 | config.target.toggleClass(config.visibleClass); 281 | 282 | }); 283 | 284 | // Window. 285 | 286 | // Event: Hide on ESC. 287 | if (config.hideOnEscape) 288 | $window.on('keydown', function(event) { 289 | 290 | if (event.keyCode == 27) 291 | $this._hide(event); 292 | 293 | }); 294 | 295 | return $this; 296 | 297 | }; 298 | 299 | /** 300 | * Apply "placeholder" attribute polyfill to one or more forms. 301 | * @return {jQuery} jQuery object. 302 | */ 303 | $.fn.placeholder = function() { 304 | 305 | // Browser natively supports placeholders? Bail. 306 | if (typeof (document.createElement('input')).placeholder != 'undefined') 307 | return $(this); 308 | 309 | // No elements? 310 | if (this.length == 0) 311 | return $this; 312 | 313 | // Multiple elements? 314 | if (this.length > 1) { 315 | 316 | for (var i=0; i < this.length; i++) 317 | $(this[i]).placeholder(); 318 | 319 | return $this; 320 | 321 | } 322 | 323 | // Vars. 324 | var $this = $(this); 325 | 326 | // Text, TextArea. 327 | $this.find('input[type=text],textarea') 328 | .each(function() { 329 | 330 | var i = $(this); 331 | 332 | if (i.val() == '' 333 | || i.val() == i.attr('placeholder')) 334 | i 335 | .addClass('polyfill-placeholder') 336 | .val(i.attr('placeholder')); 337 | 338 | }) 339 | .on('blur', function() { 340 | 341 | var i = $(this); 342 | 343 | if (i.attr('name').match(/-polyfill-field$/)) 344 | return; 345 | 346 | if (i.val() == '') 347 | i 348 | .addClass('polyfill-placeholder') 349 | .val(i.attr('placeholder')); 350 | 351 | }) 352 | .on('focus', function() { 353 | 354 | var i = $(this); 355 | 356 | if (i.attr('name').match(/-polyfill-field$/)) 357 | return; 358 | 359 | if (i.val() == i.attr('placeholder')) 360 | i 361 | .removeClass('polyfill-placeholder') 362 | .val(''); 363 | 364 | }); 365 | 366 | // Password. 367 | $this.find('input[type=password]') 368 | .each(function() { 369 | 370 | var i = $(this); 371 | var x = $( 372 | $('
') 373 | .append(i.clone()) 374 | .remove() 375 | .html() 376 | .replace(/type="password"/i, 'type="text"') 377 | .replace(/type=password/i, 'type=text') 378 | ); 379 | 380 | if (i.attr('id') != '') 381 | x.attr('id', i.attr('id') + '-polyfill-field'); 382 | 383 | if (i.attr('name') != '') 384 | x.attr('name', i.attr('name') + '-polyfill-field'); 385 | 386 | x.addClass('polyfill-placeholder') 387 | .val(x.attr('placeholder')).insertAfter(i); 388 | 389 | if (i.val() == '') 390 | i.hide(); 391 | else 392 | x.hide(); 393 | 394 | i 395 | .on('blur', function(event) { 396 | 397 | event.preventDefault(); 398 | 399 | var x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]'); 400 | 401 | if (i.val() == '') { 402 | 403 | i.hide(); 404 | x.show(); 405 | 406 | } 407 | 408 | }); 409 | 410 | x 411 | .on('focus', function(event) { 412 | 413 | event.preventDefault(); 414 | 415 | var i = x.parent().find('input[name=' + x.attr('name').replace('-polyfill-field', '') + ']'); 416 | 417 | x.hide(); 418 | 419 | i 420 | .show() 421 | .focus(); 422 | 423 | }) 424 | .on('keypress', function(event) { 425 | 426 | event.preventDefault(); 427 | x.val(''); 428 | 429 | }); 430 | 431 | }); 432 | 433 | // Events. 434 | $this 435 | .on('submit', function() { 436 | 437 | $this.find('input[type=text],input[type=password],textarea') 438 | .each(function(event) { 439 | 440 | var i = $(this); 441 | 442 | if (i.attr('name').match(/-polyfill-field$/)) 443 | i.attr('name', ''); 444 | 445 | if (i.val() == i.attr('placeholder')) { 446 | 447 | i.removeClass('polyfill-placeholder'); 448 | i.val(''); 449 | 450 | } 451 | 452 | }); 453 | 454 | }) 455 | .on('reset', function(event) { 456 | 457 | event.preventDefault(); 458 | 459 | $this.find('select') 460 | .val($('option:first').val()); 461 | 462 | $this.find('input,textarea') 463 | .each(function() { 464 | 465 | var i = $(this), 466 | x; 467 | 468 | i.removeClass('polyfill-placeholder'); 469 | 470 | switch (this.type) { 471 | 472 | case 'submit': 473 | case 'reset': 474 | break; 475 | 476 | case 'password': 477 | i.val(i.attr('defaultValue')); 478 | 479 | x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]'); 480 | 481 | if (i.val() == '') { 482 | i.hide(); 483 | x.show(); 484 | } 485 | else { 486 | i.show(); 487 | x.hide(); 488 | } 489 | 490 | break; 491 | 492 | case 'checkbox': 493 | case 'radio': 494 | i.attr('checked', i.attr('defaultValue')); 495 | break; 496 | 497 | case 'text': 498 | case 'textarea': 499 | i.val(i.attr('defaultValue')); 500 | 501 | if (i.val() == '') { 502 | i.addClass('polyfill-placeholder'); 503 | i.val(i.attr('placeholder')); 504 | } 505 | 506 | break; 507 | 508 | default: 509 | i.val(i.attr('defaultValue')); 510 | break; 511 | 512 | } 513 | }); 514 | 515 | }); 516 | 517 | return $this; 518 | 519 | }; 520 | 521 | /** 522 | * Moves elements to/from the first positions of their respective parents. 523 | * @param {jQuery} $elements Elements (or selector) to move. 524 | * @param {bool} condition If true, moves elements to the top. Otherwise, moves elements back to their original locations. 525 | */ 526 | $.prioritize = function($elements, condition) { 527 | 528 | var key = '__prioritize'; 529 | 530 | // Expand $elements if it's not already a jQuery object. 531 | if (typeof $elements != 'jQuery') 532 | $elements = $($elements); 533 | 534 | // Step through elements. 535 | $elements.each(function() { 536 | 537 | var $e = $(this), $p, 538 | $parent = $e.parent(); 539 | 540 | // No parent? Bail. 541 | if ($parent.length == 0) 542 | return; 543 | 544 | // Not moved? Move it. 545 | if (!$e.data(key)) { 546 | 547 | // Condition is false? Bail. 548 | if (!condition) 549 | return; 550 | 551 | // Get placeholder (which will serve as our point of reference for when this element needs to move back). 552 | $p = $e.prev(); 553 | 554 | // Couldn't find anything? Means this element's already at the top, so bail. 555 | if ($p.length == 0) 556 | return; 557 | 558 | // Move element to top of parent. 559 | $e.prependTo($parent); 560 | 561 | // Mark element as moved. 562 | $e.data(key, $p); 563 | 564 | } 565 | 566 | // Moved already? 567 | else { 568 | 569 | // Condition is true? Bail. 570 | if (condition) 571 | return; 572 | 573 | $p = $e.data(key); 574 | 575 | // Move element back to its original location (using our placeholder). 576 | $e.insertAfter($p); 577 | 578 | // Unmark element as moved. 579 | $e.removeData(key); 580 | 581 | } 582 | 583 | }); 584 | 585 | }; 586 | 587 | })(jQuery); -------------------------------------------------------------------------------- /demo/example_data/website/assets/webfonts/fa-brands-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/assets/webfonts/fa-brands-400.eot -------------------------------------------------------------------------------- /demo/example_data/website/assets/webfonts/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/assets/webfonts/fa-brands-400.ttf -------------------------------------------------------------------------------- /demo/example_data/website/assets/webfonts/fa-brands-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/assets/webfonts/fa-brands-400.woff -------------------------------------------------------------------------------- /demo/example_data/website/assets/webfonts/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/assets/webfonts/fa-brands-400.woff2 -------------------------------------------------------------------------------- /demo/example_data/website/assets/webfonts/fa-regular-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/assets/webfonts/fa-regular-400.eot -------------------------------------------------------------------------------- /demo/example_data/website/assets/webfonts/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/assets/webfonts/fa-regular-400.ttf -------------------------------------------------------------------------------- /demo/example_data/website/assets/webfonts/fa-regular-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/assets/webfonts/fa-regular-400.woff -------------------------------------------------------------------------------- /demo/example_data/website/assets/webfonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/assets/webfonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /demo/example_data/website/assets/webfonts/fa-solid-900.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/assets/webfonts/fa-solid-900.eot -------------------------------------------------------------------------------- /demo/example_data/website/assets/webfonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/assets/webfonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /demo/example_data/website/assets/webfonts/fa-solid-900.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/assets/webfonts/fa-solid-900.woff -------------------------------------------------------------------------------- /demo/example_data/website/assets/webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/assets/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /demo/example_data/website/images/bg-alt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/images/bg-alt.jpg -------------------------------------------------------------------------------- /demo/example_data/website/images/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/images/bg.jpg -------------------------------------------------------------------------------- /demo/example_data/website/images/fulls/01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/images/fulls/01.jpg -------------------------------------------------------------------------------- /demo/example_data/website/images/fulls/02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/images/fulls/02.jpg -------------------------------------------------------------------------------- /demo/example_data/website/images/fulls/03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/images/fulls/03.jpg -------------------------------------------------------------------------------- /demo/example_data/website/images/fulls/04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/images/fulls/04.jpg -------------------------------------------------------------------------------- /demo/example_data/website/images/fulls/05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/images/fulls/05.jpg -------------------------------------------------------------------------------- /demo/example_data/website/images/fulls/06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/images/fulls/06.jpg -------------------------------------------------------------------------------- /demo/example_data/website/images/fulls/07.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/images/fulls/07.jpg -------------------------------------------------------------------------------- /demo/example_data/website/images/fulls/08.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/images/fulls/08.jpg -------------------------------------------------------------------------------- /demo/example_data/website/images/pic01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/images/pic01.jpg -------------------------------------------------------------------------------- /demo/example_data/website/images/pic02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/images/pic02.jpg -------------------------------------------------------------------------------- /demo/example_data/website/images/thumbs/01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/images/thumbs/01.jpg -------------------------------------------------------------------------------- /demo/example_data/website/images/thumbs/02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/images/thumbs/02.jpg -------------------------------------------------------------------------------- /demo/example_data/website/images/thumbs/03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/images/thumbs/03.jpg -------------------------------------------------------------------------------- /demo/example_data/website/images/thumbs/04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/images/thumbs/04.jpg -------------------------------------------------------------------------------- /demo/example_data/website/images/thumbs/05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/images/thumbs/05.jpg -------------------------------------------------------------------------------- /demo/example_data/website/images/thumbs/06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/images/thumbs/06.jpg -------------------------------------------------------------------------------- /demo/example_data/website/images/thumbs/07.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/images/thumbs/07.jpg -------------------------------------------------------------------------------- /demo/example_data/website/images/thumbs/08.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/substrate-lfs/6806e6ffec16a630f60f444f7a65331aadb38d97/demo/example_data/website/images/thumbs/08.jpg -------------------------------------------------------------------------------- /demo/example_data/website/index.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | Substrate LFS Example Site 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 27 | 28 | 29 | 40 | 41 | 42 |
43 | 44 |
45 |
46 |

Lorem ipsum
47 | dolor sit amet

48 |
49 |

Tortor faucibus ullamcorper nec tempus purus sed penatibus. Lacinia pellentesque eleifend vitae est elit tristique velit tempus etiam.

50 |
51 |
52 | 53 | 54 |
55 | 56 |
57 |
58 |

Mollis posuere
59 | lectus lacus

60 |
61 |

Rhoncus mattis egestas sed fusce sodales rutrum et etiam ullamcorper. Etiam egestas scelerisque ac duis magna lorem ipsum dolor.

62 |
63 |
64 | 65 | 66 |
67 |
68 |

Magnis parturient

69 |

Justo phasellus et aenean dignissim
70 | placerat cubilia purus lectus.

71 |
72 | 86 |
87 | 88 | 89 |
90 |
91 |

Nisl sed ultricies

92 |

Diam dignissim lectus eu ornare volutpat orci.

93 |
94 |
95 |
96 |
97 |
98 |
99 | 100 |
101 |
102 |
    103 |
  • 104 |
105 |
106 |
107 |
108 |
109 | 110 | 111 | 258 | 259 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | -------------------------------------------------------------------------------- /demo/rpc-client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lfs-demo-rpc-client" 3 | version = "2.0.0" 4 | authors = ["Parity Technologies "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | env_logger = "0.7.0" 9 | walkdir = "2.3.1" 10 | futures = "0.1.29" 11 | hyper = "0.12.35" 12 | structopt = "0.3.9" 13 | jsonrpc-core-client = { version = "14.0.3", features = ["http", "ws"] } 14 | log = "0.4.8" 15 | lfs-demo-runtime = { version = "2.0.0", path = "../runtime" } 16 | parity-scale-codec = "1.1.2" 17 | frame-system = { version = "2.0.0", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 18 | frame-support = { version = "2.0.0", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 19 | sp-storage = { version = "2.0.0", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 20 | sp-runtime = { version = "2.0.0", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 21 | sp-core = { version = "2.0.0", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 22 | sp-rpc = { version = "2.0.0", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 23 | sc-rpc = { version = "2.0.0", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 24 | sp-keyring = { version = "2.0.0", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 25 | pallet-sudo = { version = "2.0.0", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 26 | pallet-transaction-payment = { version = "2.0.0", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 27 | pallet-utility = { version = "2.0.0", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 28 | 29 | pallet-lfs-user-data = { path = "../../pallets/user-data" } 30 | sc-lfs = { path = "../../client", features = ["default", "unstable"] } -------------------------------------------------------------------------------- /demo/rpc-client/src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2020 Parity Technologies (UK) Ltd. 2 | // This file is part of Substrate. 3 | 4 | // Substrate is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Substrate is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Substrate. If not, see . 16 | 17 | #![warn(missing_docs)] 18 | 19 | //! Example substrate RPC client code. 20 | //! 21 | //! This module shows how you can write a Rust RPC client that connects to a running 22 | //! substrate node and use staticly typed RPC wrappers. 23 | 24 | use frame_support::storage::generator::StorageMap; 25 | use futures::{future::join_all, Future}; 26 | use hyper::rt; 27 | use jsonrpc_core_client::{transports::http, RpcChannel}; 28 | use lfs_demo_runtime::{ 29 | BlockNumber as Number, Hash, Header, Runtime, Signature, SignedBlock, SignedExtra, 30 | SignedPayload, UncheckedExtrinsic, VERSION, 31 | }; 32 | use pallet_lfs_user_data as user_data; 33 | use pallet_sudo as sudo; 34 | use parity_scale_codec::{Decode, Encode}; 35 | use sc_lfs::{lfs_id::LfsId, rpc::LfsClient}; 36 | use sc_rpc::{author::AuthorClient, chain::ChainClient, state::StateClient}; 37 | use sp_core::crypto::Pair; 38 | use sp_keyring::AccountKeyring; 39 | use sp_rpc::{list::ListOrValue, number::NumberOrHex}; 40 | use sp_runtime::{ 41 | generic::Era, 42 | traits::{IdentifyAccount, Verify}, 43 | }; 44 | use sp_storage::{StorageData, StorageKey}; 45 | 46 | use std::path::PathBuf; 47 | use structopt::StructOpt; 48 | 49 | fn parse_key(s: &str) -> Result { 50 | match s { 51 | "alice" | "Alice" => Ok(AccountKeyring::Alice), 52 | "bob" | "Bob" => Ok(AccountKeyring::Bob), 53 | "charlie" | "Charlie" => Ok(AccountKeyring::Charlie), 54 | _ => Err(format!("Unknown key {:}", s)), 55 | } 56 | } 57 | 58 | #[derive(Debug, StructOpt)] 59 | enum Commands { 60 | /// Upload the given file and store it under the filename or given name 61 | Set { 62 | /// Store under `name` rather than the name of the file 63 | #[structopt(long)] 64 | name: Option, 65 | 66 | /// Input file to upload and set to 67 | #[structopt(name = "FILE")] 68 | path: PathBuf, 69 | }, 70 | 71 | /// Upload all files and folders (recursively) in the folder 72 | UploadDir { 73 | /// Use this prefix rather than supplied path 74 | #[structopt(long)] 75 | prefix: Option, 76 | 77 | /// Replace the name of the `index.html` with `` 78 | #[structopt(long)] 79 | replace_index: bool, 80 | 81 | /// Input folder 82 | #[structopt(name = "DIRECTORY")] 83 | root_dir: PathBuf, 84 | }, 85 | } 86 | 87 | #[derive(Debug, StructOpt)] 88 | #[structopt( 89 | name = "lfs-demo-rpc-client", 90 | about = "Let's submit some user data to our chain" 91 | )] 92 | struct Opt { 93 | /// RPC Server to use 94 | #[structopt(long, default_value = "http://localhost:9933")] 95 | server: String, 96 | 97 | /// Use the this Key 98 | #[structopt(short = "k", long = "key", parse(try_from_str = parse_key), default_value = "Alice")] 99 | key: AccountKeyring, 100 | 101 | /// Set as root, not for the given account 102 | #[structopt(long)] 103 | root: bool, 104 | 105 | #[structopt(subcommand)] 106 | cmd: Commands, 107 | } 108 | 109 | fn main() { 110 | env_logger::init(); 111 | let m = Opt::from_args(); 112 | 113 | let uri = m.server; 114 | let key = m.key; 115 | let root = m.root; 116 | 117 | let files = match m.cmd { 118 | Commands::Set { name, path } => vec![( 119 | name.map(|s| s.as_str().to_owned()) 120 | .or_else(|| { 121 | if let Some(Some(s)) = path.as_path().file_name().map(|s| s.to_str()) { 122 | return Some(s.to_owned()); 123 | } 124 | return None; 125 | }) 126 | .expect("Not a proper file."), 127 | path, 128 | )], 129 | Commands::UploadDir { 130 | prefix, 131 | replace_index, 132 | root_dir, 133 | } => walkdir::WalkDir::new(root_dir.clone()) 134 | .contents_first(true) 135 | .into_iter() 136 | .filter_map(|e| e.ok()) 137 | .filter_map(move |entry| { 138 | if entry.path().is_dir() { 139 | return None; 140 | } // nothing to be done for folders directly. 141 | let path = entry.into_path(); 142 | // is always a sub part 143 | let mut upload_key = path.strip_prefix(root_dir.clone()).map(|p| p.to_path_buf()); 144 | 145 | if let Some(ref p) = prefix { 146 | upload_key = upload_key.map(|r| p.join(r)) 147 | } 148 | 149 | if replace_index { 150 | upload_key = upload_key.map(|local_name| { 151 | if let Some("index.html") = local_name.file_name().and_then(|s| s.to_str()) 152 | { 153 | local_name.with_file_name("") 154 | } else { 155 | local_name 156 | } 157 | }) 158 | } 159 | Some(( 160 | upload_key 161 | .map(|u| { 162 | u.to_str() 163 | .map(|s| String::from(s)) 164 | .expect("Can't read file name") 165 | }) 166 | .expect("Key generation failed"), 167 | path, 168 | )) 169 | }) 170 | .collect(), 171 | }; 172 | 173 | rt::run(rt::lazy(move || { 174 | http::connect(&uri) 175 | .and_then(move |channel: RpcChannel| { 176 | // let's upload the image via RPC 177 | let client = LfsClient::::new(channel.clone()); 178 | join_all(files.into_iter().map(move |(name, path)| { 179 | client 180 | .upload(std::fs::read(path.clone()).expect("Could not read file ")) 181 | .map(move |r| { 182 | println!("File {:?} uploaded via RPC: {:}", path, r); 183 | (name, r) 184 | }) 185 | })) 186 | .map(|v: Vec<(String, LfsId)>| (channel, v)) 187 | }) 188 | .map(move |(channel, to_set)| { 189 | // get the current nonce via RPC 190 | let nonce_key = frame_system::AccountNonce::::storage_map_final_key( 191 | key.clone().to_account_id(), 192 | ); 193 | let nonce = ::Index::decode( 194 | &mut &StateClient::::new(channel.clone()) 195 | .storage(StorageKey(nonce_key), None) 196 | .wait() 197 | .expect("RPC doesn't fail") 198 | .unwrap_or(StorageData(vec![0, 0, 0, 0])) 199 | .0[..], 200 | ) 201 | .expect("Nonce is always an Index"); 202 | 203 | let genesis_hash = { 204 | if let ListOrValue::Value(Some(h)) = 205 | ChainClient::::new(channel.clone()) 206 | .block_hash(Some(ListOrValue::Value(NumberOrHex::Number(0)))) 207 | .wait() 208 | .expect("Genesis Block exists") 209 | { 210 | h 211 | } else { 212 | panic!("No genesis hash found on remote chain!"); 213 | } 214 | }; 215 | 216 | (channel, to_set, genesis_hash, nonce) 217 | }) 218 | .map(move |(channel, to_set, genesis_hash, nonce)| { 219 | // submit the reference ID as our avatar entry 220 | let calls = if root { 221 | to_set 222 | .iter() 223 | .map(|(name, remote_id)| { 224 | println!("Setting (root) {:} to {:}", name, remote_id); 225 | sudo::Call::::sudo(Box::new( 226 | user_data::Call::::root_update( 227 | name.as_bytes().to_vec(), 228 | remote_id.clone().into(), 229 | ) 230 | .into(), 231 | )) 232 | .into() 233 | }) 234 | .collect() 235 | } else { 236 | to_set 237 | .iter() 238 | .map(|(name, remote_id)| { 239 | println!("Setting ({}) {:} to {:}", key, name, remote_id); 240 | user_data::Call::::update( 241 | name.as_bytes().to_vec(), 242 | remote_id.clone().into(), 243 | ) 244 | .into() 245 | }) 246 | .collect() 247 | }; 248 | 249 | let tip = 0; 250 | let extra: SignedExtra = ( 251 | frame_system::CheckVersion::::new(), 252 | frame_system::CheckGenesis::::new(), 253 | frame_system::CheckEra::::from(Era::Immortal), 254 | frame_system::CheckNonce::::from(nonce), 255 | frame_system::CheckWeight::::new(), 256 | pallet_transaction_payment::ChargeTransactionPayment::::from(tip), 257 | ); 258 | let raw_payload = SignedPayload::from_raw( 259 | pallet_utility::Call::::batch(calls).into(), 260 | extra, 261 | ( 262 | VERSION.spec_version, 263 | genesis_hash.clone(), 264 | genesis_hash, 265 | (), 266 | (), 267 | (), 268 | ), // additional extras 269 | ); 270 | let signature = raw_payload.using_encoded(|payload| key.pair().sign(payload)); 271 | let (call, extra, _) = raw_payload.deconstruct(); 272 | let account = ::Signer::from(key.public()).into_account(); 273 | 274 | let extrinsic = 275 | UncheckedExtrinsic::new_signed(call, account.into(), signature.into(), extra); 276 | 277 | let client = AuthorClient::::new(channel.clone()); 278 | client 279 | .submit_extrinsic(extrinsic.encode().into()) 280 | .wait() 281 | .map_err(|e| { 282 | println!("Error: {:?}", e); 283 | }) 284 | .map(|hash| println!("Submitted in {:}", hash)) 285 | }) 286 | .map(|_| { 287 | println!("------ All submitted"); 288 | }) 289 | .map_err(|e| { 290 | println!("Error: {:?}", e); 291 | }) 292 | })) 293 | } 294 | -------------------------------------------------------------------------------- /demo/runtime/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lfs-demo-runtime" 3 | version = "2.0.0" 4 | authors = ["Anonymous"] 5 | edition = "2018" 6 | 7 | [dependencies.pallet-lfs] 8 | path = "../../pallets" 9 | default-features = false 10 | 11 | [dependencies.pallet-lfs-user-data] 12 | path = "../../pallets/user-data" 13 | default-features = false 14 | 15 | [dependencies.sp-lfs-core] 16 | path = "../../primitives/core" 17 | default-features = false 18 | 19 | [dependencies] 20 | aura = { version = "2.0.0", default-features = false, package = "pallet-aura", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 21 | balances = { version = "2.0.0", default-features = false, package = "pallet-balances", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 22 | frame-support = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 23 | grandpa = { version = "2.0.0", default-features = false, package = "pallet-grandpa", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 24 | indices = { version = "2.0.0", default-features = false, package = "pallet-indices", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 25 | randomness-collective-flip = { version = "2.0.0", default-features = false, package = "pallet-randomness-collective-flip", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 26 | sudo = { version = "2.0.0", default-features = false, package = "pallet-sudo", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 27 | system = { version = "2.0.0", default-features = false, package = "frame-system", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 28 | timestamp = { version = "2.0.0", default-features = false, package = "pallet-timestamp", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 29 | transaction-payment = { version = "2.0.0", default-features = false, package = "pallet-transaction-payment", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 30 | utility = { package = "pallet-utility", default-features = false, version = "2.0.0", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 31 | 32 | codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] } 33 | frame-executive = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 34 | safe-mix = { version = "1.0.0", default-features = false } 35 | serde = { version = "1.0.101", optional = true, features = ["derive"] } 36 | sp-api = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 37 | sp-block-builder = { default-features = false, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 38 | sp-consensus-aura = { version = "0.8", default-features = false, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 39 | sp-core = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 40 | sp-inherents = { default-features = false, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e"} 41 | sp-io = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 42 | sp-offchain = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 43 | sp-runtime = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 44 | sp-session = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 45 | sp-std = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 46 | sp-transaction-pool = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 47 | sp-version = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 48 | 49 | [build-dependencies] 50 | wasm-builder-runner = { version = "1.0.4", package = "substrate-wasm-builder-runner", git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 51 | 52 | [features] 53 | default = ["std"] 54 | std = [ 55 | "aura/std", 56 | "balances/std", 57 | "codec/std", 58 | "frame-executive/std", 59 | "frame-support/std", 60 | "grandpa/std", 61 | "indices/std", 62 | "randomness-collective-flip/std", 63 | "safe-mix/std", 64 | "utility/std", 65 | "serde", 66 | "sp-api/std", 67 | "sp-block-builder/std", 68 | "sp-consensus-aura/std", 69 | "sp-core/std", 70 | "sp-inherents/std", 71 | "sp-io/std", 72 | "sp-offchain/std", 73 | "sp-runtime/std", 74 | "sp-session/std", 75 | "sp-std/std", 76 | "sp-transaction-pool/std", 77 | "sp-version/std", 78 | "sudo/std", 79 | "system/std", 80 | "timestamp/std", 81 | "transaction-payment/std", 82 | "pallet-lfs/std", 83 | "pallet-lfs-user-data/std", 84 | ] 85 | -------------------------------------------------------------------------------- /demo/runtime/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Parity Technologies (UK) Ltd. 2 | // This file is part of Substrate. 3 | 4 | // Substrate is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Substrate is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Substrate. If not, see . 16 | 17 | use wasm_builder_runner::WasmBuilder; 18 | 19 | fn main() { 20 | WasmBuilder::new() 21 | .with_current_project() 22 | .with_wasm_builder_from_crates("1.0.9") 23 | .export_heap_base() 24 | .import_memory() 25 | .build() 26 | } 27 | -------------------------------------------------------------------------------- /demo/runtime/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! The Substrate Node Template runtime. This can be compiled with `#[no_std]`, ready for Wasm. 2 | 3 | #![cfg_attr(not(feature = "std"), no_std)] 4 | // `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. 5 | #![recursion_limit = "256"] 6 | 7 | // Make the WASM binary available. 8 | #[cfg(feature = "std")] 9 | include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); 10 | 11 | use grandpa::fg_primitives; 12 | use grandpa::AuthorityList as GrandpaAuthorityList; 13 | use sp_api::impl_runtime_apis; 14 | use sp_consensus_aura::sr25519::AuthorityId as AuraId; 15 | use sp_core::OpaqueMetadata; 16 | use sp_runtime::traits::{ 17 | self, BlakeTwo256, Block as BlockT, ConvertInto, IdentifyAccount, SaturatedConversion, 18 | StaticLookup, Verify, 19 | }; 20 | use sp_runtime::{ 21 | create_runtime_str, generic, impl_opaque_keys, transaction_validity::TransactionValidity, 22 | ApplyExtrinsicResult, MultiSignature, 23 | }; 24 | use sp_std::prelude::*; 25 | #[cfg(feature = "std")] 26 | use sp_version::NativeVersion; 27 | use sp_version::RuntimeVersion; 28 | 29 | // A few exports that help ease life for downstream crates. 30 | pub use balances::Call as BalancesCall; 31 | pub use frame_support::{ 32 | construct_runtime, parameter_types, traits::Randomness, weights::Weight, StorageValue, 33 | }; 34 | #[cfg(any(feature = "std", test))] 35 | pub use sp_runtime::BuildStorage; 36 | pub use sp_runtime::{Perbill, Permill}; 37 | use system::offchain::TransactionSubmitter; 38 | pub use timestamp::Call as TimestampCall; 39 | 40 | /// An index to a block. 41 | pub type BlockNumber = u32; 42 | 43 | /// Alias to 512-bit hash when used in the context of a transaction signature on the chain. 44 | pub type Signature = MultiSignature; 45 | 46 | /// Some way of identifying an account on the chain. We intentionally make it equivalent 47 | /// to the public key of our transaction signing scheme. 48 | pub type AccountId = <::Signer as IdentifyAccount>::AccountId; 49 | 50 | /// The type for looking up accounts. We don't expect more than 4 billion of them, but you 51 | /// never know... 52 | pub type AccountIndex = u32; 53 | 54 | /// Balance of an account. 55 | pub type Balance = u128; 56 | 57 | /// Index of a transaction in the chain. 58 | pub type Index = u32; 59 | 60 | /// A hash of some data used by the chain. 61 | pub type Hash = sp_core::H256; 62 | 63 | /// Digest item type. 64 | pub type DigestItem = generic::DigestItem; 65 | 66 | pub const LFS_APP_KEY_TYPE: sp_runtime::KeyTypeId = pallet_lfs::KEY_TYPE; 67 | pub type LfsAppKeyPublic = pallet_lfs::sr25519::Public; 68 | #[cfg(feature = "std")] 69 | pub type LfsAppKeyPair = pallet_lfs::sr25519::Pair; 70 | 71 | /// Opaque types. These are used by the CLI to instantiate machinery that don't need to know 72 | /// the specifics of the runtime. They can then be made to be agnostic over specific formats 73 | /// of data like extrinsics, allowing for them to continue syncing the network through upgrades 74 | /// to even the core datastructures. 75 | pub mod opaque { 76 | use super::*; 77 | 78 | pub use sp_runtime::OpaqueExtrinsic as UncheckedExtrinsic; 79 | 80 | /// Opaque block header type. 81 | pub type Header = generic::Header; 82 | /// Opaque block type. 83 | pub type Block = generic::Block; 84 | /// Opaque block identifier type. 85 | pub type BlockId = generic::BlockId; 86 | 87 | impl_opaque_keys! { 88 | pub struct SessionKeys { 89 | pub aura: Aura, 90 | pub grandpa: Grandpa, 91 | } 92 | } 93 | } 94 | 95 | /// This runtime version. 96 | pub const VERSION: RuntimeVersion = RuntimeVersion { 97 | spec_name: create_runtime_str!("lfs-demo"), 98 | impl_name: create_runtime_str!("lfs-demo"), 99 | authoring_version: 1, 100 | spec_version: 1, 101 | impl_version: 1, 102 | apis: RUNTIME_API_VERSIONS, 103 | }; 104 | 105 | pub const MILLISECS_PER_BLOCK: u64 = 6000; 106 | 107 | pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; 108 | 109 | // These time units are defined in number of blocks. 110 | pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber); 111 | pub const HOURS: BlockNumber = MINUTES * 60; 112 | pub const DAYS: BlockNumber = HOURS * 24; 113 | 114 | pub const MILLICENTS: Balance = 1_000_000_000; 115 | pub const CENTS: Balance = 1_000 * MILLICENTS; // assume this is worth about a cent. 116 | pub const DOLLARS: Balance = 100 * CENTS; 117 | 118 | /// The version infromation used to identify this runtime when compiled natively. 119 | #[cfg(feature = "std")] 120 | pub fn native_version() -> NativeVersion { 121 | NativeVersion { 122 | runtime_version: VERSION, 123 | can_author_with: Default::default(), 124 | } 125 | } 126 | 127 | parameter_types! { 128 | pub const BlockHashCount: BlockNumber = 250; 129 | pub const MaximumBlockWeight: Weight = 1_000_000; 130 | pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75); 131 | pub const MaximumBlockLength: u32 = 5 * 1024 * 1024; 132 | pub const Version: RuntimeVersion = VERSION; 133 | } 134 | 135 | impl system::Trait for Runtime { 136 | /// The identifier used to distinguish between accounts. 137 | type AccountId = AccountId; 138 | /// The aggregated dispatch type that is available for extrinsics. 139 | type Call = Call; 140 | /// The lookup mechanism to get account ID from whatever is passed in dispatchers. 141 | type Lookup = Indices; 142 | /// The index type for storing how many extrinsics an account has signed. 143 | type Index = Index; 144 | /// The index type for blocks. 145 | type BlockNumber = BlockNumber; 146 | /// The type for hashing blocks and tries. 147 | type Hash = Hash; 148 | /// The hashing algorithm used. 149 | type Hashing = BlakeTwo256; 150 | /// The header type. 151 | type Header = generic::Header; 152 | /// The ubiquitous event type. 153 | type Event = Event; 154 | /// The ubiquitous origin type. 155 | type Origin = Origin; 156 | /// Maximum number of block number to block hash mappings to keep (oldest pruned first). 157 | type BlockHashCount = BlockHashCount; 158 | /// Maximum weight of each block. 159 | type MaximumBlockWeight = MaximumBlockWeight; 160 | /// Maximum size of all encoded transactions (in bytes) that are allowed in one block. 161 | type MaximumBlockLength = MaximumBlockLength; 162 | /// Portion of the block weight that is available to all normal transactions. 163 | type AvailableBlockRatio = AvailableBlockRatio; 164 | /// Version of the runtime. 165 | type Version = Version; 166 | /// Converts a module to the index of the module in `construct_runtime!`. 167 | /// 168 | /// This type is being generated by `construct_runtime!`. 169 | type ModuleToIndex = ModuleToIndex; 170 | } 171 | 172 | impl aura::Trait for Runtime { 173 | type AuthorityId = AuraId; 174 | } 175 | 176 | impl grandpa::Trait for Runtime { 177 | type Event = Event; 178 | } 179 | 180 | impl indices::Trait for Runtime { 181 | /// The type for recording indexing into the account enumeration. If this ever overflows, there 182 | /// will be problems! 183 | type AccountIndex = AccountIndex; 184 | /// Use the standard means of resolving an index hint from an id. 185 | type ResolveHint = indices::SimpleResolveHint; 186 | /// Determine whether an account is dead. 187 | type IsDeadAccount = Balances; 188 | /// The ubiquitous event type. 189 | type Event = Event; 190 | } 191 | 192 | parameter_types! { 193 | pub const MinimumPeriod: u64 = SLOT_DURATION / 2; 194 | } 195 | 196 | impl timestamp::Trait for Runtime { 197 | /// A timestamp: milliseconds since the unix epoch. 198 | type Moment = u64; 199 | type OnTimestampSet = Aura; 200 | type MinimumPeriod = MinimumPeriod; 201 | } 202 | 203 | parameter_types! { 204 | pub const ExistentialDeposit: u128 = 500; 205 | pub const TransferFee: u128 = 0; 206 | pub const CreationFee: u128 = 0; 207 | } 208 | 209 | impl balances::Trait for Runtime { 210 | /// The type for recording an account's balance. 211 | type Balance = Balance; 212 | /// What to do if an account's free balance gets zeroed. 213 | type OnFreeBalanceZero = (); 214 | /// What to do if an account is fully reaped from the system. 215 | type OnReapAccount = System; 216 | /// What to do if a new account is created. 217 | type OnNewAccount = Indices; 218 | /// The ubiquitous event type. 219 | type Event = Event; 220 | type DustRemoval = (); 221 | type TransferPayment = (); 222 | type ExistentialDeposit = ExistentialDeposit; 223 | type TransferFee = TransferFee; 224 | type CreationFee = CreationFee; 225 | } 226 | 227 | parameter_types! { 228 | pub const TransactionBaseFee: Balance = 0; 229 | pub const TransactionByteFee: Balance = 1; 230 | } 231 | 232 | impl transaction_payment::Trait for Runtime { 233 | type Currency = balances::Module; 234 | type OnTransactionPayment = (); 235 | type TransactionBaseFee = TransactionBaseFee; 236 | type TransactionByteFee = TransactionByteFee; 237 | type WeightToFee = ConvertInto; 238 | type FeeMultiplierUpdate = (); 239 | } 240 | 241 | impl sudo::Trait for Runtime { 242 | type Event = Event; 243 | type Proposal = Call; 244 | } 245 | 246 | parameter_types! { 247 | // One storage item; value is size 4+4+16+32 bytes = 56 bytes. 248 | pub const MultisigDepositBase: Balance = 30 * CENTS; 249 | // Additional storage item size of 32 bytes. 250 | pub const MultisigDepositFactor: Balance = 5 * CENTS; 251 | pub const MaxSignatories: u16 = 100; 252 | } 253 | 254 | /// So we can dispatch multiple calls in one extrinsic 255 | impl utility::Trait for Runtime { 256 | type Event = Event; 257 | type Call = Call; 258 | type Currency = Balances; 259 | type MultisigDepositBase = MultisigDepositBase; 260 | type MultisigDepositFactor = MultisigDepositFactor; 261 | type MaxSignatories = MaxSignatories; 262 | } 263 | 264 | /// Used for the module template in `./template.rs` 265 | impl pallet_lfs_user_data::Trait for Runtime { 266 | type Event = Event; 267 | type Callback = Call; 268 | type KeyGuard = ( 269 | pallet_lfs_user_data::guard::DefaultUserKeys, 270 | pallet_lfs_user_data::guard::Homepage, 271 | ); 272 | } 273 | 274 | type LfsTransactionSubmitter = TransactionSubmitter; 275 | 276 | /// Setup 277 | impl pallet_lfs::Trait for Runtime { 278 | type Event = Event; 279 | type OcwCall = Call; 280 | type Callback = Call; 281 | type SubmitTransaction = LfsTransactionSubmitter; 282 | } 283 | 284 | construct_runtime!( 285 | pub enum Runtime where 286 | Block = Block, 287 | NodeBlock = opaque::Block, 288 | UncheckedExtrinsic = UncheckedExtrinsic 289 | { 290 | System: system::{Module, Call, Storage, Config, Event}, 291 | Utility: utility::{Module, Call, Storage, Event}, 292 | Timestamp: timestamp::{Module, Call, Storage, Inherent}, 293 | Aura: aura::{Module, Config, Inherent(Timestamp)}, 294 | Grandpa: grandpa::{Module, Call, Storage, Config, Event}, 295 | Indices: indices, 296 | Balances: balances, 297 | TransactionPayment: transaction_payment::{Module, Storage}, 298 | Sudo: sudo, 299 | Lfs: pallet_lfs::{Module, Call, Storage, Event, Config}, 300 | UserData: pallet_lfs_user_data::{Module, Call, Storage, Event}, 301 | RandomnessCollectiveFlip: randomness_collective_flip::{Module, Call, Storage}, 302 | } 303 | ); 304 | 305 | /// The address format for describing accounts. 306 | pub type Address = ::Source; 307 | /// Block header type as expected by this runtime. 308 | pub type Header = generic::Header; 309 | /// Block type as expected by this runtime. 310 | pub type Block = generic::Block; 311 | /// A Block signed with a Justification 312 | pub type SignedBlock = generic::SignedBlock; 313 | /// BlockId type as expected by this runtime. 314 | pub type BlockId = generic::BlockId; 315 | /// The SignedExtension to the basic transaction logic. 316 | pub type SignedExtra = ( 317 | system::CheckVersion, 318 | system::CheckGenesis, 319 | system::CheckEra, 320 | system::CheckNonce, 321 | system::CheckWeight, 322 | transaction_payment::ChargeTransactionPayment, 323 | ); 324 | /// Unchecked extrinsic type as expected by this runtime. 325 | pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; 326 | /// The payload being signed in transactions. 327 | pub type SignedPayload = generic::SignedPayload; 328 | /// Extrinsic type that has already been checked. 329 | pub type CheckedExtrinsic = generic::CheckedExtrinsic; 330 | /// Executive: handles dispatch to the various modules. 331 | pub type Executive = 332 | frame_executive::Executive, Runtime, AllModules>; 333 | 334 | impl system::offchain::CreateTransaction for Runtime { 335 | type Public = ::Signer; 336 | type Signature = Signature; 337 | 338 | fn create_transaction>( 339 | call: Call, 340 | public: Self::Public, 341 | account: AccountId, 342 | index: Index, 343 | ) -> Option<( 344 | Call, 345 | ::SignaturePayload, 346 | )> { 347 | // take the biggest period possible. 348 | let period = BlockHashCount::get() 349 | .checked_next_power_of_two() 350 | .map(|c| c / 2) 351 | .unwrap_or(2) as u64; 352 | let current_block = System::block_number() 353 | .saturated_into::() 354 | // make sure we actually construct on top of the parent block. 355 | .saturating_sub(1); 356 | let tip = 0; 357 | let extra: SignedExtra = ( 358 | system::CheckVersion::::new(), 359 | system::CheckGenesis::::new(), 360 | system::CheckEra::::from(generic::Era::mortal(period, current_block)), 361 | system::CheckNonce::::from(index), 362 | system::CheckWeight::::new(), 363 | transaction_payment::ChargeTransactionPayment::::from(tip), 364 | ); 365 | let raw_payload = SignedPayload::new(call, extra).ok()?; 366 | let signature = TSigner::sign(public, &raw_payload)?; 367 | let address = Indices::unlookup(account); 368 | let (call, extra, _) = raw_payload.deconstruct(); 369 | Some((call, (address, signature, extra))) 370 | } 371 | } 372 | 373 | impl_runtime_apis! { 374 | impl sp_api::Core for Runtime { 375 | fn version() -> RuntimeVersion { 376 | VERSION 377 | } 378 | 379 | fn execute_block(block: Block) { 380 | Executive::execute_block(block) 381 | } 382 | 383 | fn initialize_block(header: &::Header) { 384 | Executive::initialize_block(header) 385 | } 386 | } 387 | 388 | impl sp_api::Metadata for Runtime { 389 | fn metadata() -> OpaqueMetadata { 390 | Runtime::metadata().into() 391 | } 392 | } 393 | 394 | impl sp_block_builder::BlockBuilder for Runtime { 395 | fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { 396 | Executive::apply_extrinsic(extrinsic) 397 | } 398 | 399 | fn finalize_block() -> ::Header { 400 | Executive::finalize_block() 401 | } 402 | 403 | fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<::Extrinsic> { 404 | data.create_extrinsics() 405 | } 406 | 407 | fn check_inherents( 408 | block: Block, 409 | data: sp_inherents::InherentData, 410 | ) -> sp_inherents::CheckInherentsResult { 411 | data.check_extrinsics(&block) 412 | } 413 | 414 | fn random_seed() -> ::Hash { 415 | RandomnessCollectiveFlip::random_seed() 416 | } 417 | } 418 | 419 | impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { 420 | fn validate_transaction(tx: ::Extrinsic) -> TransactionValidity { 421 | Executive::validate_transaction(tx) 422 | } 423 | } 424 | 425 | impl sp_offchain::OffchainWorkerApi for Runtime { 426 | fn offchain_worker(header: &::Header) { 427 | Executive::offchain_worker(header) 428 | } 429 | } 430 | 431 | impl sp_consensus_aura::AuraApi for Runtime { 432 | fn slot_duration() -> u64 { 433 | Aura::slot_duration() 434 | } 435 | 436 | fn authorities() -> Vec { 437 | Aura::authorities() 438 | } 439 | } 440 | 441 | impl sp_session::SessionKeys for Runtime { 442 | fn generate_session_keys(seed: Option>) -> Vec { 443 | opaque::SessionKeys::generate(seed) 444 | } 445 | 446 | fn decode_session_keys( 447 | encoded: Vec, 448 | ) -> Option, sp_core::crypto::KeyTypeId)>> { 449 | opaque::SessionKeys::decode_into_raw_public_keys(&encoded) 450 | } 451 | } 452 | 453 | impl fg_primitives::GrandpaApi for Runtime { 454 | fn grandpa_authorities() -> GrandpaAuthorityList { 455 | Grandpa::grandpa_authorities() 456 | } 457 | } 458 | } 459 | -------------------------------------------------------------------------------- /demo/scripts/init.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | echo "*** Initializing WASM build environment" 6 | 7 | if [ -z $CI_PROJECT_NAME ] ; then 8 | rustup update nightly 9 | rustup update stable 10 | fi 11 | 12 | rustup target add wasm32-unknown-unknown --toolchain nightly 13 | -------------------------------------------------------------------------------- /demo/src/chain_spec.rs: -------------------------------------------------------------------------------- 1 | use grandpa_primitives::AuthorityId as GrandpaId; 2 | use lfs_demo_runtime::{ 3 | AccountId, AuraConfig, BalancesConfig, GenesisConfig, GrandpaConfig, IndicesConfig, LfsConfig, 4 | Signature, SudoConfig, SystemConfig, WASM_BINARY, 5 | }; 6 | use sc_service; 7 | use sp_consensus_aura::sr25519::AuthorityId as AuraId; 8 | use sp_core::{sr25519, Pair, Public}; 9 | use sp_runtime::traits::{IdentifyAccount, Verify}; 10 | 11 | // Note this is the URL for the telemetry server 12 | //const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/"; 13 | 14 | /// Specialized `ChainSpec`. This is a specialization of the general Substrate ChainSpec type. 15 | pub type ChainSpec = sc_service::ChainSpec; 16 | 17 | /// The chain specification option. This is expected to come in from the CLI and 18 | /// is little more than one of a number of alternatives which can easily be converted 19 | /// from a string (`--chain=...`) into a `ChainSpec`. 20 | #[derive(Clone, Debug)] 21 | pub enum Alternative { 22 | /// Whatever the current runtime is, with just Alice as an auth. 23 | Development, 24 | /// Whatever the current runtime is, with simple Alice/Bob auths. 25 | LocalTestnet, 26 | } 27 | 28 | /// Helper function to generate a crypto pair from seed 29 | pub fn get_from_seed(seed: &str) -> ::Public { 30 | TPublic::Pair::from_string(&format!("//{}", seed), None) 31 | .expect("static values are valid; qed") 32 | .public() 33 | } 34 | 35 | type AccountPublic = ::Signer; 36 | 37 | /// Helper function to generate an account ID from seed 38 | pub fn get_account_id_from_seed(seed: &str) -> AccountId 39 | where 40 | AccountPublic: From<::Public>, 41 | { 42 | AccountPublic::from(get_from_seed::(seed)).into_account() 43 | } 44 | 45 | /// Helper function to generate an authority key for Aura 46 | pub fn get_authority_keys_from_seed(s: &str) -> (AuraId, GrandpaId) { 47 | (get_from_seed::(s), get_from_seed::(s)) 48 | } 49 | 50 | impl Alternative { 51 | /// Get an actual chain config from one of the alternatives. 52 | pub(crate) fn load(self) -> Result { 53 | Ok(match self { 54 | Alternative::Development => ChainSpec::from_genesis( 55 | "Development", 56 | "dev", 57 | || { 58 | testnet_genesis( 59 | vec![get_authority_keys_from_seed("Alice")], 60 | get_account_id_from_seed::("Alice"), 61 | vec![ 62 | get_account_id_from_seed::("Alice"), 63 | get_account_id_from_seed::("Bob"), 64 | get_account_id_from_seed::("Alice//stash"), 65 | get_account_id_from_seed::("Bob//stash"), 66 | ], 67 | true, 68 | ) 69 | }, 70 | vec![], 71 | None, 72 | None, 73 | None, 74 | None, 75 | ), 76 | Alternative::LocalTestnet => ChainSpec::from_genesis( 77 | "Local Testnet", 78 | "local_testnet", 79 | || { 80 | testnet_genesis( 81 | vec![ 82 | get_authority_keys_from_seed("Alice"), 83 | get_authority_keys_from_seed("Bob"), 84 | ], 85 | get_account_id_from_seed::("Alice"), 86 | vec![ 87 | get_account_id_from_seed::("Alice"), 88 | get_account_id_from_seed::("Bob"), 89 | get_account_id_from_seed::("Charlie"), 90 | get_account_id_from_seed::("Dave"), 91 | get_account_id_from_seed::("Eve"), 92 | get_account_id_from_seed::("Ferdie"), 93 | get_account_id_from_seed::("Alice//stash"), 94 | get_account_id_from_seed::("Bob//stash"), 95 | get_account_id_from_seed::("Charlie//stash"), 96 | get_account_id_from_seed::("Dave//stash"), 97 | get_account_id_from_seed::("Eve//stash"), 98 | get_account_id_from_seed::("Ferdie//stash"), 99 | ], 100 | true, 101 | ) 102 | }, 103 | vec![], 104 | None, 105 | None, 106 | None, 107 | None, 108 | ), 109 | }) 110 | } 111 | 112 | pub(crate) fn from(s: &str) -> Option { 113 | match s { 114 | "dev" => Some(Alternative::Development), 115 | "" | "local" => Some(Alternative::LocalTestnet), 116 | _ => None, 117 | } 118 | } 119 | } 120 | 121 | fn testnet_genesis( 122 | initial_authorities: Vec<(AuraId, GrandpaId)>, 123 | root_key: AccountId, 124 | endowed_accounts: Vec, 125 | _enable_println: bool, 126 | ) -> GenesisConfig { 127 | GenesisConfig { 128 | system: Some(SystemConfig { 129 | code: WASM_BINARY.to_vec(), 130 | changes_trie_config: Default::default(), 131 | }), 132 | indices: Some(IndicesConfig { 133 | ids: endowed_accounts.clone(), 134 | }), 135 | balances: Some(BalancesConfig { 136 | balances: endowed_accounts 137 | .iter() 138 | .cloned() 139 | .map(|k| (k, 1 << 60)) 140 | .collect(), 141 | vesting: vec![], 142 | }), 143 | sudo: Some(SudoConfig { key: root_key }), 144 | aura: Some(AuraConfig { 145 | authorities: initial_authorities.iter().map(|x| (x.0.clone())).collect(), 146 | }), 147 | grandpa: Some(GrandpaConfig { 148 | authorities: initial_authorities 149 | .iter() 150 | .map(|x| (x.1.clone(), 1)) 151 | .collect(), 152 | }), 153 | pallet_lfs: Some(LfsConfig { 154 | authorities: endowed_accounts.clone(), 155 | }), 156 | } 157 | } 158 | 159 | pub fn load_spec(id: &str) -> Result, String> { 160 | Ok(match Alternative::from(id) { 161 | Some(spec) => Some(spec.load()?), 162 | None => None, 163 | }) 164 | } 165 | -------------------------------------------------------------------------------- /demo/src/cli.rs: -------------------------------------------------------------------------------- 1 | use sc_cli::{RunCmd, Subcommand}; 2 | use structopt::StructOpt; 3 | 4 | #[derive(Debug, StructOpt)] 5 | pub struct Cli { 6 | #[structopt(subcommand)] 7 | pub subcommand: Option, 8 | 9 | #[structopt(flatten)] 10 | pub run: RunCmd, 11 | } 12 | -------------------------------------------------------------------------------- /demo/src/command.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2020 Parity Technologies (UK) Ltd. 2 | // This file is part of Substrate. 3 | 4 | // Substrate is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Substrate is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Substrate. If not, see . 16 | 17 | use crate::chain_spec; 18 | use crate::cli::Cli; 19 | use crate::service; 20 | use sc_cli::{error, VersionInfo}; 21 | use sp_consensus_aura::sr25519::AuthorityPair as AuraPair; 22 | 23 | /// Parse and run command line arguments 24 | pub fn run(version: VersionInfo) -> error::Result<()> { 25 | let opt = sc_cli::from_args::(&version); 26 | 27 | let mut config = sc_service::Configuration::default(); 28 | config.impl_name = "lfs-demo"; 29 | 30 | match opt.subcommand { 31 | Some(subcommand) => sc_cli::run_subcommand( 32 | config, 33 | subcommand, 34 | chain_spec::load_spec, 35 | |config: _| Ok(new_full_start!(config).0), 36 | &version, 37 | ), 38 | None => sc_cli::run( 39 | config, 40 | opt.run, 41 | service::new_light, 42 | service::new_full, 43 | chain_spec::load_spec, 44 | &version, 45 | ), 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /demo/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Substrate Node Demo Cli 2 | #![warn(missing_docs)] 3 | 4 | mod chain_spec; 5 | #[macro_use] 6 | mod service; 7 | mod cli; 8 | mod command; 9 | 10 | pub use sc_cli::{error, VersionInfo}; 11 | 12 | fn main() -> Result<(), error::Error> { 13 | let version = VersionInfo { 14 | name: "Substrate LFS Demo Node", 15 | commit: env!("VERGEN_SHA_SHORT"), 16 | version: env!("CARGO_PKG_VERSION"), 17 | executable_name: "lfs-demo", 18 | author: "Anonymous", 19 | description: "Template Node", 20 | support_url: "support.anonymous.an", 21 | copyright_start_year: 2019, 22 | }; 23 | 24 | command::run(version) 25 | } 26 | -------------------------------------------------------------------------------- /demo/src/service.rs: -------------------------------------------------------------------------------- 1 | //! Service and ServiceFactory implementation. Specialized wrapper over substrate service. 2 | 3 | use grandpa::{self, FinalityProofProvider as GrandpaFinalityProofProvider}; 4 | use lfs_demo_runtime::{self, opaque::Block, GenesisConfig, RuntimeApi}; 5 | use sc_client::LongestChain; 6 | use sc_executor::native_executor_instance; 7 | pub use sc_executor::NativeExecutor; 8 | use sc_network::construct_simple_protocol; 9 | use sc_service::{error::Error as ServiceError, AbstractService, Configuration, ServiceBuilder}; 10 | use sp_consensus_aura::sr25519::AuthorityPair as AuraPair; 11 | use sp_inherents::InherentDataProviders; 12 | use std::sync::Arc; 13 | use std::time::Duration; 14 | 15 | // LFS 16 | use sc_lfs::{ 17 | config::load_config as load_lfs_config, lfs_cache_interface, DefaultClient as LfsClient, 18 | }; 19 | 20 | // Our native executor instance. 21 | native_executor_instance!( 22 | pub Executor, 23 | lfs_demo_runtime::api::dispatch, 24 | lfs_demo_runtime::native_version, 25 | lfs_cache_interface::HostFunctions, 26 | ); 27 | 28 | construct_simple_protocol! { 29 | /// Demo protocol attachment for substrate. 30 | pub struct NodeProtocol where Block = Block { } 31 | } 32 | 33 | /// Starts a `ServiceBuilder` for a full service. 34 | /// 35 | /// Use this macro if you don't actually need the full service, but just the builder in order to 36 | /// be able to perform chain operations. 37 | macro_rules! new_full_start { 38 | ($config:expr) => {{ 39 | let mut import_setup = None; 40 | let inherent_data_providers = sp_inherents::InherentDataProviders::new(); 41 | 42 | let builder = sc_service::ServiceBuilder::new_full::< 43 | lfs_demo_runtime::opaque::Block, 44 | lfs_demo_runtime::RuntimeApi, 45 | crate::service::Executor, 46 | >($config)? 47 | .with_select_chain(|_config, backend| Ok(sc_client::LongestChain::new(backend.clone())))? 48 | .with_transaction_pool(|config, client, _fetcher| { 49 | let pool_api = sc_transaction_pool::FullChainApi::new(client.clone()); 50 | let pool = sc_transaction_pool::BasicPool::new(config, std::sync::Arc::new(pool_api)); 51 | Ok(pool) 52 | })? 53 | .with_import_queue(|_config, client, mut select_chain, transaction_pool| { 54 | let select_chain = select_chain 55 | .take() 56 | .ok_or_else(|| sc_service::Error::SelectChainRequired)?; 57 | 58 | let (grandpa_block_import, grandpa_link) = 59 | grandpa::block_import::<_, _, _, lfs_demo_runtime::RuntimeApi, _>( 60 | client.clone(), 61 | &*client, 62 | select_chain, 63 | )?; 64 | 65 | let aura_block_import = sc_consensus_aura::AuraBlockImport::<_, _, _, AuraPair>::new( 66 | grandpa_block_import.clone(), 67 | client.clone(), 68 | ); 69 | 70 | let import_queue = sc_consensus_aura::import_queue::<_, _, _, AuraPair, _>( 71 | sc_consensus_aura::SlotDuration::get_or_compute(&*client)?, 72 | aura_block_import, 73 | Some(Box::new(grandpa_block_import.clone())), 74 | None, 75 | client, 76 | inherent_data_providers.clone(), 77 | Some(transaction_pool), 78 | )?; 79 | 80 | import_setup = Some((grandpa_block_import, grandpa_link)); 81 | 82 | Ok(import_queue) 83 | })?; 84 | 85 | (builder, import_setup, inherent_data_providers) 86 | }}; 87 | } 88 | 89 | /// Builds a new service for a full client. 90 | pub fn new_full( 91 | config: Configuration, 92 | ) -> Result { 93 | let is_authority = config.roles.is_authority(); 94 | let force_authoring = config.force_authoring; 95 | let name = config.name.clone(); 96 | let disable_grandpa = config.disable_grandpa; 97 | let dev_seed = config.dev_key_seed.clone(); 98 | 99 | let lfs = LfsClient::from_config( 100 | &load_lfs_config( 101 | config 102 | .in_chain_config_dir("lfs.toml") 103 | .expect("We always have a path") 104 | .as_path(), 105 | )?, 106 | |p| { 107 | p.as_path() 108 | .to_str() 109 | .map(|s| { 110 | config 111 | .in_chain_config_dir(s) 112 | .expect("Chain configuration directory is always defined.") 113 | }) 114 | .ok_or(format!( 115 | "Could not convert LFS configuration path '{:?}' into OS string", 116 | p 117 | )) 118 | }, 119 | )?; 120 | 121 | // sentry nodes announce themselves as authorities to the network 122 | // and should run the same protocols authorities do, but it should 123 | // never actively participate in any consensus process. 124 | let participates_in_consensus = is_authority && !config.sentry_mode; 125 | 126 | let (builder, mut import_setup, inherent_data_providers) = new_full_start!(config); 127 | 128 | let (block_import, grandpa_link) = import_setup.take().expect( 129 | "Link Half and Block Import are present for Full Services or setup failed before. qed", 130 | ); 131 | 132 | let service = builder 133 | .with_network_protocol(|_| Ok(NodeProtocol::new()))? 134 | .with_finality_proof_provider(|client, backend| { 135 | Ok(Arc::new(GrandpaFinalityProofProvider::new(backend, client)) as _) 136 | })? 137 | .with_execution_extensions_factory(lfs.make_externalities_extension_factory())? 138 | .with_rpc_extensions(|_client, _pool, _backend, _, _| { 139 | use sc_lfs::rpc::LfsApi; 140 | let mut io = jsonrpc_core::IoHandler::default(); 141 | io.extend_with(LfsApi::to_delegate(lfs.make_rpc())); 142 | Ok(io) 143 | })? 144 | .build()?; 145 | 146 | if participates_in_consensus { 147 | let proposer = sc_basic_authorship::ProposerFactory { 148 | client: service.client(), 149 | transaction_pool: service.transaction_pool(), 150 | }; 151 | 152 | let client = service.client(); 153 | let select_chain = service 154 | .select_chain() 155 | .ok_or(ServiceError::SelectChainRequired)?; 156 | 157 | let can_author_with = 158 | sp_consensus::CanAuthorWithNativeVersion::new(client.executor().clone()); 159 | 160 | let aura = sc_consensus_aura::start_aura::<_, _, _, _, _, AuraPair, _, _, _>( 161 | sc_consensus_aura::SlotDuration::get_or_compute(&*client)?, 162 | client, 163 | select_chain, 164 | block_import, 165 | proposer, 166 | service.network(), 167 | inherent_data_providers.clone(), 168 | force_authoring, 169 | service.keystore(), 170 | can_author_with, 171 | )?; 172 | 173 | // the AURA authoring task is considered essential, i.e. if it 174 | // fails we take down the service with it. 175 | service.spawn_essential_task("aura", aura); 176 | } 177 | 178 | let user_data_resolver = sc_lfs_http_server::user_data::UserDataResolver::< 179 | _, 180 | _, 181 | _, 182 | _, 183 | lfs_demo_runtime::Runtime, 184 | >::new(service.client()); 185 | 186 | service.spawn_task( 187 | "http-server", 188 | sc_lfs_http_server::start_server(lfs.cache().clone(), user_data_resolver), 189 | ); 190 | 191 | // if the node isn't actively participating in consensus then it doesn't 192 | // need a keystore, regardless of which protocol we use below. 193 | let keystore = if participates_in_consensus { 194 | Some(service.keystore()) 195 | } else { 196 | None 197 | }; 198 | 199 | if let Some(seed) = dev_seed { 200 | service 201 | .keystore() 202 | .write() 203 | .insert_ephemeral_from_seed_by_type::( 204 | &seed, 205 | lfs_demo_runtime::LFS_APP_KEY_TYPE, 206 | ) 207 | .expect("Dev Seed always succeeds"); 208 | } 209 | 210 | let grandpa_config = grandpa::Config { 211 | // FIXME #1578 make this available through chainspec 212 | gossip_duration: Duration::from_millis(333), 213 | justification_period: 512, 214 | name: Some(name), 215 | observer_enabled: true, 216 | keystore, 217 | is_authority, 218 | }; 219 | 220 | match (is_authority, disable_grandpa) { 221 | (false, false) => { 222 | // start the lightweight GRANDPA observer 223 | service.spawn_task( 224 | "grandpa-observer", 225 | grandpa::run_grandpa_observer( 226 | grandpa_config, 227 | grandpa_link, 228 | service.network(), 229 | service.on_exit(), 230 | service.spawn_task_handle(), 231 | )?, 232 | ); 233 | } 234 | (true, false) => { 235 | // start the full GRANDPA voter 236 | let voter_config = grandpa::GrandpaParams { 237 | config: grandpa_config, 238 | link: grandpa_link, 239 | network: service.network(), 240 | inherent_data_providers: inherent_data_providers.clone(), 241 | on_exit: service.on_exit(), 242 | telemetry_on_connect: Some(service.telemetry_on_connect_stream()), 243 | voting_rule: grandpa::VotingRulesBuilder::default().build(), 244 | executor: service.spawn_task_handle(), 245 | }; 246 | 247 | // the GRANDPA voter task is considered infallible, i.e. 248 | // if it fails we take down the service with it. 249 | service.spawn_essential_task("grandpa", grandpa::run_grandpa_voter(voter_config)?); 250 | } 251 | (_, true) => { 252 | grandpa::setup_disabled_grandpa( 253 | service.client(), 254 | &inherent_data_providers, 255 | service.network(), 256 | )?; 257 | } 258 | } 259 | 260 | Ok(service) 261 | } 262 | 263 | /// Builds a new service for a light client. 264 | pub fn new_light( 265 | config: Configuration, 266 | ) -> Result { 267 | let inherent_data_providers = InherentDataProviders::new(); 268 | 269 | ServiceBuilder::new_light::(config)? 270 | .with_select_chain(|_config, backend| Ok(LongestChain::new(backend.clone())))? 271 | .with_transaction_pool(|config, client, fetcher| { 272 | let fetcher = fetcher 273 | .ok_or_else(|| "Trying to start light transaction pool without active fetcher")?; 274 | 275 | let pool_api = sc_transaction_pool::LightChainApi::new(client.clone(), fetcher.clone()); 276 | let pool = sc_transaction_pool::BasicPool::with_revalidation_type( 277 | config, 278 | Arc::new(pool_api), 279 | sc_transaction_pool::RevalidationType::Light, 280 | ); 281 | Ok(pool) 282 | })? 283 | .with_import_queue_and_fprb( 284 | |_config, client, backend, fetcher, _select_chain, _tx_pool| { 285 | let fetch_checker = fetcher 286 | .map(|fetcher| fetcher.checker().clone()) 287 | .ok_or_else(|| { 288 | "Trying to start light import queue without active fetch checker" 289 | })?; 290 | let grandpa_block_import = grandpa::light_block_import::<_, _, _, RuntimeApi>( 291 | client.clone(), 292 | backend, 293 | &*client.clone(), 294 | Arc::new(fetch_checker), 295 | )?; 296 | let finality_proof_import = grandpa_block_import.clone(); 297 | let finality_proof_request_builder = 298 | finality_proof_import.create_finality_proof_request_builder(); 299 | 300 | let import_queue = sc_consensus_aura::import_queue::<_, _, _, AuraPair, ()>( 301 | sc_consensus_aura::SlotDuration::get_or_compute(&*client)?, 302 | grandpa_block_import, 303 | None, 304 | Some(Box::new(finality_proof_import)), 305 | client, 306 | inherent_data_providers.clone(), 307 | None, 308 | )?; 309 | 310 | Ok((import_queue, finality_proof_request_builder)) 311 | }, 312 | )? 313 | .with_network_protocol(|_| Ok(NodeProtocol::new()))? 314 | .with_finality_proof_provider(|client, backend| { 315 | Ok(Arc::new(GrandpaFinalityProofProvider::new(backend, client)) as _) 316 | })? 317 | .build() 318 | } 319 | -------------------------------------------------------------------------------- /pallets/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pallet-lfs" 3 | version = "0.1.0" 4 | authors = ["Anonymous"] 5 | edition = "2018" 6 | 7 | [dependencies.sp-lfs-core] 8 | path = "../primitives/core" 9 | default-features = false 10 | 11 | [dependencies.sp-lfs-cache] 12 | path = "../primitives/cache" 13 | default-features = false 14 | 15 | [dependencies] 16 | serde = { version = "1.0", optional = true } 17 | codec = { default-features = false, features = ['derive'], package = 'parity-scale-codec', version = '1.1.2' } 18 | support = { default-features = false, git = 'https://github.com/paritytech/substrate.git', package = 'frame-support', rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 19 | system = { default-features = false, git = 'https://github.com/paritytech/substrate.git', package = 'frame-system', rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 20 | sp-std = { default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 21 | sp-io = { default-features = false, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 22 | sp-runtime = { default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e"} 23 | 24 | [features] 25 | default = ['std'] 26 | std = [ 27 | 'serde', 28 | 'codec/std', 29 | 'support/std', 30 | 'system/std', 31 | 'sp-std/std', 32 | 'sp-io/std', 33 | 'sp-runtime/std', 34 | 'sp-lfs-cache/std', 35 | 'sp-lfs-core/std', 36 | ] 37 | -------------------------------------------------------------------------------- /pallets/README.md: -------------------------------------------------------------------------------- 1 | # LFS pallets 2 | 3 | Pallets using LFS: 4 | 5 | ## core 6 | `./` // `./src` 7 | 8 | The core pallet to manage LFS through. 9 | -------------------------------------------------------------------------------- /pallets/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | 3 | use codec::{Codec, Decode, Encode}; 4 | use sp_runtime::app_crypto::KeyTypeId; 5 | use sp_runtime::{ 6 | traits::{Dispatchable, StaticLookup}, 7 | DispatchError, 8 | }; 9 | use sp_std::prelude::*; 10 | use support::{ 11 | decl_event, decl_module, decl_storage, dispatch::DispatchResult, Parameter, StorageValue, 12 | }; 13 | use system::offchain::SubmitSignedTransaction; 14 | use system::{ensure_root, ensure_signed}; 15 | 16 | use sp_lfs_core::LfsReference; 17 | 18 | pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"lfs0"); 19 | 20 | pub mod sr25519 { 21 | use super::KEY_TYPE; 22 | use sp_runtime::app_crypto::{app_crypto, sr25519}; 23 | app_crypto!(sr25519, KEY_TYPE); 24 | } 25 | 26 | pub mod ed25519 { 27 | use super::KEY_TYPE; 28 | use sp_runtime::app_crypto::{app_crypto, ed25519}; 29 | app_crypto!(ed25519, KEY_TYPE); 30 | } 31 | 32 | /// The module's configuration trait. 33 | pub trait Trait: system::Trait { 34 | /// The overarching event type. 35 | type Event: From> + Into<::Event>; 36 | 37 | /// Offchain Worker Call 38 | type OcwCall: From>; 39 | 40 | /// The callback type to call 41 | type Callback: Parameter + Dispatchable + Codec + Eq; 42 | 43 | /// Let's define the helper we use to create signed transactions with 44 | type SubmitTransaction: SubmitSignedTransaction::OcwCall>; 45 | } 46 | 47 | #[derive(Encode, Decode)] 48 | /// Calls triggered to the offchain worker 49 | pub enum LfsOffchainEvent { 50 | /// Represents a query issued 51 | Query(LfsReference), 52 | /// Inform the Offchain Worker that the entry has been resolved 53 | /// meaning, they probably do not want to waste resources responding again 54 | Resolved(LfsReference), 55 | /// This entry has been dropped from the internal listing 56 | Dropped(LfsReference), 57 | } 58 | 59 | #[derive(Encode, Decode)] 60 | /// The LFS state 61 | enum LfsEntryState { 62 | /// This entry is pending and hasn't been resolved yet 63 | Pending { 64 | /// Since when the Block is pending 65 | since: T::BlockNumber, 66 | /// callbacks to call once resolved 67 | listeners: Vec<( 68 | ::Callback, 69 | Option<::Source>, 70 | )>, 71 | }, 72 | Resolved { 73 | /// first confirmed to exist 74 | since: T::BlockNumber, 75 | /// latest confirmation it exists 76 | latest: T::BlockNumber, 77 | /// how many internally still refer to this entry 78 | ref_count: u32, 79 | }, 80 | } 81 | 82 | // This module's storage items. 83 | decl_storage! { 84 | trait Store for Module as LFS { 85 | /// our record of offchain calls triggered in this block 86 | OcwEvents get(fn ocw_events): Vec; 87 | /// The current set of keys that may submit pongs 88 | Authorities get(fn authorities) config(authorities): Vec; 89 | /// The specific LFS entries and states 90 | Entries: map hasher(blake2_256) LfsReference => Option>; 91 | } 92 | } 93 | 94 | // The module's dispatchable functions. 95 | decl_module! { 96 | /// The module declaration. 97 | pub struct Module for enum Call where origin: T::Origin { 98 | // Initializing events 99 | // this is needed only if you are using events in your module 100 | fn deposit_event() = default; 101 | 102 | fn on_initialize(_now: T::BlockNumber) { 103 | // clean offchain calls on every block start 104 | ::OcwEvents::kill(); 105 | } 106 | // Respond to an lfs entry query 107 | pub fn respond(origin, key: LfsReference) -> DispatchResult { 108 | let author = ensure_signed(origin)?; 109 | if !Self::is_authority(&author) { 110 | // No known authority, ignore 111 | return Ok(()) 112 | }; 113 | 114 | let now = >::block_number(); 115 | 116 | if let Some(entry) = Entries::::get(&key) { 117 | let replace = match entry { 118 | LfsEntryState::Pending { listeners, .. } => { 119 | // inform the outer OcwEventss about this 120 | ::OcwEvents::mutate(|v| v.push(LfsOffchainEvent::Resolved(key.clone()))); 121 | let mut ref_count = 0u32; 122 | // inform the listeners 123 | for callback in listeners { 124 | if Self::callback(callback) { 125 | ref_count += 1; 126 | } 127 | } 128 | // replace with resolved 129 | if ref_count > 0 { 130 | LfsEntryState::Resolved { 131 | ref_count, 132 | since: now.clone(), 133 | latest: now, 134 | } 135 | } else { 136 | // we were able to resolve, but the result didn't lead to any references staying around 137 | Entries::::remove(&key); 138 | return Ok(()); 139 | } 140 | } 141 | LfsEntryState::Resolved { ref_count, since, .. } => { 142 | LfsEntryState::Resolved { 143 | since, 144 | ref_count, 145 | latest: now, 146 | } 147 | } 148 | }; 149 | 150 | // replace our entry with the updated version 151 | Entries::::insert(&key, replace); 152 | } 153 | 154 | Ok(()) 155 | } 156 | 157 | fn offchain_worker(_now: T::BlockNumber) { 158 | if T::SubmitTransaction::can_sign() { 159 | let _ = Self::offchain(); 160 | } 161 | } 162 | 163 | // Simple authority management: add a new authority to the set of keys that 164 | // are allowed to respond to lfs queries 165 | pub fn add_authority(origin, who: ::Source ) -> DispatchResult { 166 | let _me = ensure_root(origin)?; 167 | let account = T::Lookup::lookup(who)?; 168 | 169 | if !Self::is_authority(&account){ 170 | >::mutate(|l| l.push(account)); 171 | } 172 | 173 | Ok(()) 174 | } 175 | 176 | // Simple authority management: remove an authority from the set of keys that 177 | // are allowed to respond to lfs queries 178 | pub fn drop_authority(origin, who: ::Source ) -> DispatchResult { 179 | // In practice this should be a bit cleverer, but for this example it is enough 180 | // that this is protected by a root-call (e.g. through governance like `sudo`). 181 | let _me = ensure_root(origin)?; 182 | let account = T::Lookup::lookup(who)?; 183 | 184 | >::mutate(|l| l.retain(|i| i != &account)); 185 | 186 | Ok(()) 187 | } 188 | } 189 | } 190 | 191 | decl_event!( 192 | pub enum Event 193 | where 194 | AccountId = ::AccountId, 195 | { 196 | /// Triggered on a pong with the corresponding value 197 | Ack(u8, AccountId), 198 | } 199 | ); 200 | 201 | /// The inner functions other modules build upon 202 | impl Module { 203 | /// query for an lfs entry 204 | pub fn query( 205 | key: LfsReference, 206 | callback: ( 207 | ::Callback, 208 | Option<::Source>, 209 | ), 210 | ) -> DispatchResult { 211 | let now = >::block_number(); 212 | let mut issue_query = false; 213 | let new_entry = match Entries::::get(&key) { 214 | None => { 215 | issue_query = true; 216 | LfsEntryState::Pending { 217 | since: now, 218 | listeners: vec![callback], 219 | } 220 | } 221 | Some(mut entry) => { 222 | match entry { 223 | LfsEntryState::Pending { 224 | ref mut listeners, .. 225 | } => { 226 | listeners.push(callback); 227 | } 228 | LfsEntryState::Resolved { 229 | ref mut ref_count, .. 230 | } => { 231 | if Self::callback(callback) { 232 | *ref_count += 1; 233 | } 234 | } 235 | }; 236 | entry 237 | } 238 | }; 239 | 240 | Entries::::insert(&key, new_entry); 241 | 242 | if issue_query { 243 | // Informing the offchain worker 244 | ::OcwEvents::mutate(|v| v.push(LfsOffchainEvent::Query(key))); 245 | } 246 | 247 | Ok(()) 248 | } 249 | 250 | /// indicate that you are not using a previously resolved reference anymore 251 | pub fn drop(key: LfsReference) -> DispatchResult { 252 | if let Some(mut entry) = Entries::::get(&key) { 253 | if let LfsEntryState::Resolved { 254 | ref mut ref_count, .. 255 | } = entry 256 | { 257 | *ref_count -= 1; 258 | if *ref_count == 0 { 259 | Entries::::remove(&key); 260 | // Informing the offchain worker 261 | ::OcwEvents::mutate(|v| { 262 | v.push(LfsOffchainEvent::Dropped(key.clone())) 263 | }); 264 | } else { 265 | Entries::::insert(&key, entry); 266 | } 267 | } 268 | } 269 | Ok(()) 270 | } 271 | 272 | // test 273 | fn callback( 274 | callback: ( 275 | ::Callback, 276 | Option<::Source>, 277 | ), 278 | ) -> bool { 279 | let (cb, who) = callback; 280 | let origin = if let Some(who) = who { 281 | if let Ok(sign) = T::Lookup::lookup(who) { 282 | system::RawOrigin::Signed(sign).into() 283 | } else { 284 | sp_runtime::print("Callback not issued, Lookup failed"); 285 | return false; 286 | } 287 | } else { 288 | system::RawOrigin::Root.into() 289 | }; 290 | 291 | match cb.dispatch(origin) { 292 | Ok(_) => true, 293 | Err(e) => { 294 | let e: DispatchError = e.into(); 295 | sp_runtime::print(e); 296 | false 297 | } 298 | } 299 | } 300 | } 301 | 302 | // We've moved the helper functions outside of the main decleration for briefety. 303 | impl Module { 304 | /// The main entry point, called with account we are supposed to sign with 305 | fn offchain() { 306 | for e in ::OcwEvents::get() { 307 | match e { 308 | LfsOffchainEvent::Query(key) => { 309 | sp_io::misc::print_utf8(b"Received query, sending response"); 310 | match sp_lfs_cache::lfs_cache_interface::exists(&key) { 311 | Ok(true) => { 312 | sp_io::misc::print_utf8(b"Found in local cache"); 313 | let call = Call::respond(key); 314 | let _ = T::SubmitTransaction::submit_signed(call); 315 | } 316 | _ => { 317 | sp_io::misc::print_utf8(b"Not found"); 318 | } 319 | } 320 | } 321 | _ => {} 322 | } 323 | } 324 | } 325 | 326 | /// Helper that confirms whether the given `AccountId` can sign `pong` transactions 327 | fn is_authority(who: &T::AccountId) -> bool { 328 | Self::authorities().into_iter().find(|i| i == who).is_some() 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /pallets/user-data/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pallet-lfs-user-data" 3 | version = "0.1.0" 4 | authors = ["Benjamin Kampmann "] 5 | edition = "2018" 6 | 7 | [dependencies.sp-lfs-core] 8 | path = "../../primitives/core" 9 | default-features = false 10 | 11 | [dependencies.pallet-lfs] 12 | path = "../" 13 | default-features = false 14 | 15 | [dependencies] 16 | codec = { default-features = false, features = ['derive'], package = 'parity-scale-codec', version = '1.1.2' } 17 | frame-support = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 18 | system = { version = "2.0.0", default-features = false, package = 'frame-system', git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 19 | sp-std = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 20 | impl-trait-for-tuples = "0.1.3" 21 | 22 | [features] 23 | default = ['std'] 24 | std = [ 25 | 'codec/std', 26 | 'system/std', 27 | 'frame-support/std', 28 | 'pallet-lfs/std', 29 | 'sp-std/std', 30 | 'sp-lfs-core/std', 31 | ] 32 | -------------------------------------------------------------------------------- /pallets/user-data/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | /// A runtime module to manage user data per accounts, using `LfsReference`s 3 | /// 4 | use frame_support::{decl_event, decl_module, decl_storage, dispatch}; 5 | use pallet_lfs::{Module as LfsModule, Trait as LfsTrait}; 6 | use sp_lfs_core::LfsReference; 7 | use sp_std::prelude::*; 8 | use system::{ensure_root, ensure_signed}; 9 | 10 | /// Local alias for a named storage entry 11 | pub type EntryKey = Vec; 12 | 13 | pub mod guard { 14 | 15 | /// Type which regulates which keys are accepted 16 | pub trait KeyGuardian { 17 | /// Is this key allowed as an entry? 18 | fn is_allowed(_key: &[u8]) -> bool { 19 | false 20 | } 21 | } 22 | impl KeyGuardian for () {} 23 | 24 | impl KeyGuardian for (T, P) 25 | where 26 | T: KeyGuardian, 27 | P: KeyGuardian, 28 | { 29 | fn is_allowed(key: &[u8]) -> bool { 30 | T::is_allowed(key) || P::is_allowed(key) 31 | } 32 | } 33 | 34 | impl KeyGuardian for (A, T, P) 35 | where 36 | A: KeyGuardian, 37 | T: KeyGuardian, 38 | P: KeyGuardian, 39 | { 40 | fn is_allowed(key: &[u8]) -> bool { 41 | A::is_allowed(key) || T::is_allowed(key) || P::is_allowed(key) 42 | } 43 | } 44 | 45 | pub struct DefaultUserKeys; 46 | impl KeyGuardian for DefaultUserKeys { 47 | fn is_allowed(key: &[u8]) -> bool { 48 | match key { 49 | b"settings" | b"avatar" | b"profile" | b"colors" | b"backdrop" => true, 50 | _ => false, 51 | } 52 | } 53 | } 54 | 55 | pub struct Homepage; 56 | impl KeyGuardian for Homepage { 57 | fn is_allowed(key: &[u8]) -> bool { 58 | if key == b"" { 59 | return true; 60 | } 61 | if key.starts_with(b"/") { 62 | return false; 63 | } 64 | 65 | let mut last_entry = Default::default(); 66 | 67 | for entry in key.split(|c| c == &b"/"[0]) { 68 | if *entry == b".."[..] { 69 | // we do not accept this 70 | return false; 71 | } 72 | last_entry = entry; 73 | } 74 | 75 | if key.ends_with(b"/") { 76 | return true; 77 | } 78 | 79 | if let Some(ext) = last_entry.rsplitn(2, |c| c == &b"."[0]).next() { 80 | match ext { 81 | b"css" | b"js" | b"html" // regular web stuff 82 | | b"png" | b"jpg" | b"svg" | b"gif" // allowed images 83 | | b"txt" | b"rtf" | b"md" | b"adoc" // common text formats 84 | | b"eot" | b"ttf" | b"woff" | b"woff2" // webfonts 85 | => return true, 86 | _ => {} 87 | } 88 | } 89 | return false; 90 | } 91 | } 92 | } 93 | 94 | use crate::guard::KeyGuardian; 95 | 96 | /// The module's configuration trait. 97 | pub trait Trait: system::Trait + LfsTrait { 98 | /// The overarching event type. 99 | type Event: From> + Into<::Event>; 100 | /// Generating callback 101 | type Callback: From> + Into<::Callback>; 102 | /// The type that regulates, which keys are accepted 103 | type KeyGuard: KeyGuardian; 104 | } 105 | 106 | // This module's storage items. 107 | decl_storage! { 108 | trait Store for Module as UserDataModule { 109 | // We store the LfsId as the Avatar for any AccountId 110 | pub UserData get(fn user_data): double_map hasher(twox_128) T::AccountId, hasher(blake2_256) EntryKey => Option; 111 | UserDataChangeNonce get(fn nonce): double_map hasher(twox_128) T::AccountId, hasher(blake2_256) EntryKey => Option; 112 | } 113 | } 114 | 115 | // The module's dispatchable functions. 116 | decl_module! { 117 | /// The module declaration. 118 | pub struct Module for enum Call where origin: T::Origin { 119 | // Initializing events 120 | // this is needed only if you are using events in your module 121 | fn deposit_event() = default; 122 | 123 | pub fn update(origin, key: EntryKey, lfs_entry: LfsReference) -> dispatch::DispatchResult { 124 | let who = ensure_signed(origin)?; 125 | if T::KeyGuard::is_allowed(&key) { 126 | Self::request_to_update(who, key, lfs_entry) 127 | } else { 128 | // we still eat your tokes for trying! 129 | Err("Key not allowed".into()) 130 | } 131 | } 132 | 133 | pub fn root_update(origin, key: EntryKey, lfs_entry: LfsReference) -> dispatch::DispatchResult { 134 | let _ = ensure_root(origin)?; 135 | Self::request_to_update(T::AccountId::default(), key, lfs_entry) 136 | } 137 | 138 | // callback called once the LFS is confirmedLfsReference 139 | fn data_changed( 140 | origin, 141 | who: T::AccountId, 142 | key: EntryKey, 143 | nonce: u32, 144 | lfs_entry: LfsReference, 145 | ) -> dispatch::DispatchResult { 146 | let _ = ensure_root(origin)?; 147 | 148 | if Some(nonce) == Self::nonce(&who, &key) { 149 | if let Some(old_lfs_entry) = UserData::::get(&who, &key) { 150 | // There was an entry stored, inform LFS to drop the lfs_entryerence (count) 151 | let _ = LfsModule::::drop(old_lfs_entry); 152 | } 153 | // then overwrite the entry with the new value 154 | UserData::::insert(&who, &key, lfs_entry); 155 | // and inform the public, that the users avatar changed 156 | Self::deposit_event(RawEvent::UserDataChanged(who, key)) 157 | } else { 158 | // not the correct one, drop the entry from our list as we won't be using it 159 | let _ = LfsModule::::drop(lfs_entry); 160 | } 161 | Ok(()) 162 | } 163 | } 164 | } 165 | 166 | /// The inner functions other modules build upon 167 | impl Module { 168 | fn request_to_update( 169 | who: T::AccountId, 170 | key: EntryKey, 171 | lfs_entry: LfsReference, 172 | ) -> dispatch::DispatchResult { 173 | let nonce = Self::nonce(&who, &key).unwrap_or(0) + 1; 174 | let call: ::Callback = 175 | Call::data_changed(who.clone(), key.clone(), nonce, lfs_entry.clone()).into(); 176 | 177 | // store first 178 | UserDataChangeNonce::::insert(&who, &key, nonce); 179 | // this maybe fire directly, if the lfs_entry is already known! 180 | LfsModule::::query(lfs_entry, (call.into(), None))?; 181 | 182 | Ok(()) 183 | } 184 | } 185 | 186 | decl_event!( 187 | pub enum Event 188 | where 189 | AccountId = ::AccountId, 190 | { 191 | UserDataChanged(AccountId, EntryKey), 192 | } 193 | ); 194 | -------------------------------------------------------------------------------- /primitives/cache/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sp-lfs-cache" 3 | version = "0.1.0" 4 | authors = ["Benjamin Kampmann "] 5 | edition = "2018" 6 | 7 | [dependencies.sp-lfs-core] 8 | path = "../core" 9 | default-features = false 10 | 11 | [dependencies] 12 | sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 13 | sp-externalities = { optional = true, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 14 | sp-runtime-interface = { default-features = false, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 15 | 16 | 17 | [features] 18 | default = ["std"] 19 | std = [ 20 | "sp-externalities", 21 | "sp-std/std", 22 | "sp-runtime-interface/std", 23 | "sp-lfs-core/std", 24 | ] 25 | -------------------------------------------------------------------------------- /primitives/cache/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | use sp_lfs_core::{LfsId, LfsReference}; 3 | use sp_runtime_interface::runtime_interface; 4 | use sp_std::prelude::*; 5 | 6 | #[cfg(feature = "std")] 7 | use sp_externalities::{decl_extension, ExternalitiesExt}; 8 | 9 | #[cfg(feature = "std")] 10 | pub mod shared; 11 | 12 | /// Node-side caching interface 13 | pub trait Cache: Send + Sync { 14 | /// this cache knows of `key` 15 | fn exists(&self, key: &Key) -> Result; 16 | /// Fetch the data for `key` 17 | fn get(&self, key: &Key) -> Result, ()>; 18 | // insert the data at `key` 19 | fn insert(&self, key: &Key, data: &Vec) -> Result<(), ()>; 20 | /// store data, receive the resulting key 21 | fn store(&self, data: &Vec) -> Result { 22 | let key = Key::for_data(data)?; 23 | self.insert(&key, data).map(|_| key) 24 | } 25 | // mark the following key to be okay to drop 26 | fn drop(&self, key: &Key) -> Result<(), ()>; 27 | } 28 | 29 | pub struct FrontedCache(F, B); 30 | 31 | impl FrontedCache { 32 | pub fn new(front: F, back: B) -> Self { 33 | FrontedCache(front, back) 34 | } 35 | } 36 | 37 | impl Cache for FrontedCache 38 | where 39 | Key: LfsId, 40 | F: Cache, 41 | B: Cache, 42 | { 43 | fn exists(&self, key: &Key) -> Result { 44 | if self.0.exists(key).unwrap_or(false) { 45 | return Ok(true); 46 | } 47 | self.1.exists(key) 48 | } 49 | 50 | fn get(&self, key: &Key) -> Result, ()> { 51 | self.0.get(key).or_else(|_| match self.1.get(key) { 52 | Ok(d) => { 53 | let _ = self.0.insert(key, &d); 54 | Ok(d) 55 | } 56 | Err(e) => Err(e), 57 | }) 58 | } 59 | 60 | fn insert(&self, key: &Key, data: &Vec) -> Result<(), ()> { 61 | let _ = self.0.insert(key, data); 62 | self.1.insert(key, data) 63 | } 64 | fn drop(&self, key: &Key) -> Result<(), ()> { 65 | let _ = self.0.drop(key); 66 | self.1.drop(key) 67 | } 68 | } 69 | 70 | pub trait RuntimeCacheInterface: Send + Sync { 71 | /// this cache knows of `key` 72 | fn exists(&self, key: &LfsReference) -> Result; 73 | /// Fetch the data for `key` 74 | fn get(&self, key: &LfsReference) -> Result, ()>; 75 | // insert the data at `key` 76 | fn insert(&self, key: &LfsReference, data: &Vec) -> Result<(), ()>; 77 | // mark the following key to be okay to drop 78 | fn drop(&self, key: &LfsReference) -> Result<(), ()>; 79 | } 80 | 81 | pub struct RuntimeCacheInterfaceWrapper(C, core::marker::PhantomData); 82 | 83 | impl core::convert::From for RuntimeCacheInterfaceWrapper 84 | where 85 | C: Cache, 86 | Key: LfsId, 87 | { 88 | fn from(cache: C) -> Self { 89 | Self(cache, core::marker::PhantomData) 90 | } 91 | } 92 | 93 | impl RuntimeCacheInterface for RuntimeCacheInterfaceWrapper 94 | where 95 | C: Cache, 96 | Key: LfsId, 97 | { 98 | fn exists(&self, key: &LfsReference) -> Result { 99 | let k = Key::try_from(key.to_vec()).map_err(|_| ())?; 100 | self.0.exists(&k) 101 | } 102 | 103 | fn get(&self, key: &LfsReference) -> Result, ()> { 104 | let k = Key::try_from(key.to_vec()).map_err(|_| ())?; 105 | self.0.get(&k) 106 | } 107 | 108 | fn insert(&self, key: &LfsReference, data: &Vec) -> Result<(), ()> { 109 | let k = Key::try_from(key.to_vec()).map_err(|_| ())?; 110 | self.0.insert(&k, data) 111 | } 112 | 113 | fn drop(&self, key: &LfsReference) -> Result<(), ()> { 114 | let k = Key::try_from(key.to_vec()).map_err(|_| ())?; 115 | self.0.drop(&k) 116 | } 117 | } 118 | 119 | #[cfg(feature = "std")] 120 | decl_extension! { 121 | pub struct LfsCacheExt(Box); 122 | } 123 | 124 | #[cfg(feature = "std")] 125 | impl LfsCacheExt { 126 | pub fn new(cache: Box) -> Self { 127 | LfsCacheExt(cache) 128 | } 129 | } 130 | 131 | #[runtime_interface] 132 | pub trait LfsCacheInterface { 133 | /// Fetch the data for `key` 134 | fn get(&mut self, key: &LfsReference) -> Result, ()> { 135 | self.extension::() 136 | .expect("LFSCacheExtension must be present") 137 | .0 138 | .get(key) 139 | } 140 | fn exists(&mut self, key: &LfsReference) -> Result { 141 | self.extension::() 142 | .expect("LFSCacheExtension must be present") 143 | .0 144 | .exists(key) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /primitives/cache/src/shared.rs: -------------------------------------------------------------------------------- 1 | use crate::Cache; 2 | use sp_lfs_core::LfsId; 3 | use std::ops::Deref; 4 | use std::sync::Arc; 5 | 6 | pub struct SharedCache(Arc); 7 | 8 | impl SharedCache { 9 | pub fn new(cache: C) -> Self { 10 | SharedCache(Arc::new(cache)) 11 | } 12 | } 13 | 14 | impl std::clone::Clone for SharedCache { 15 | fn clone(&self) -> Self { 16 | SharedCache(self.0.clone()) 17 | } 18 | } 19 | 20 | impl Cache for SharedCache 21 | where 22 | C: Cache, 23 | Key: LfsId, 24 | { 25 | fn exists(&self, key: &Key) -> Result { 26 | self.0.exists(key) 27 | } 28 | fn get(&self, key: &Key) -> Result, ()> { 29 | self.0.get(key) 30 | } 31 | fn insert(&self, key: &Key, data: &Vec) -> Result<(), ()> { 32 | self.0.insert(key, data) 33 | } 34 | fn drop(&self, key: &Key) -> Result<(), ()> { 35 | self.0.deref().drop(key) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /primitives/core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sp-lfs-core" 3 | version = "0.1.0" 4 | authors = ["Benjamin Kampmann "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | codec = { default-features = false, package = "parity-scale-codec", version = "1.1.2" } 9 | sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 10 | sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", rev = "e65957e5efceea82d88f8b4e1248171eb3c2167e" } 11 | 12 | [features] 13 | default = ["std"] 14 | std = [ 15 | "codec/std", 16 | "sp-std/std", 17 | "sp-runtime/std", 18 | ] -------------------------------------------------------------------------------- /primitives/core/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | 3 | use codec::{Decode, Encode, EncodeLike}; 4 | use core::{ 5 | convert::{Into, TryFrom}, 6 | hash::Hash, 7 | }; 8 | use sp_std::{fmt::Debug, prelude::*}; 9 | 10 | /// To the runtime LFS References are just an opaque encoded value 11 | pub type LfsReference = Vec; 12 | 13 | /// Represent a Large File System Reference 14 | pub trait LfsId: 15 | Encode 16 | + EncodeLike 17 | + Debug 18 | + Decode 19 | + TryFrom 20 | + Into 21 | + Hash 22 | + Eq 23 | + Clone 24 | + Sync 25 | + Send 26 | { 27 | /// Generate the LfsId for the given data 28 | fn for_data(data: &Vec) -> Result; 29 | } 30 | --------------------------------------------------------------------------------