├── .dockerignore ├── .github ├── ISSUE_TEMPLATE │ ├── backlog_item.md │ ├── bug_issue.md │ ├── config.yml │ └── general_issue.md ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── check-licence.yml │ ├── docker.yml │ ├── publish.yml │ └── tests.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── README.md ├── filecoindot-cli ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── src │ ├── lib.rs │ └── main.rs ├── test_run.sh └── tests │ └── test_all.rs ├── filecoindot-io ├── Cargo.toml ├── README.md └── src │ └── lib.rs ├── filecoindot-nft ├── Cargo.toml ├── README.md └── src │ ├── lib.rs │ ├── tests │ ├── mint.rs │ ├── mock.rs │ └── mod.rs │ └── types.rs ├── filecoindot-proofs ├── Cargo.toml └── src │ ├── amt.rs │ ├── benchmarking.rs │ ├── errors.rs │ ├── forest_amt_adaptor.rs │ ├── forest_hamt_adaptor.rs │ ├── generate.rs │ ├── hamt.rs │ ├── lib.rs │ ├── traits.rs │ └── verify.rs ├── filecoindot-rpc ├── Cargo.toml └── src │ ├── lib.rs │ └── result.rs ├── filecoindot ├── Cargo.toml ├── README.md └── src │ ├── benchmarking.rs │ ├── crypto.rs │ ├── lib.rs │ ├── ocw │ ├── api │ │ ├── chain_head.rs │ │ └── mod.rs │ ├── de.rs │ ├── mod.rs │ ├── result.rs │ ├── tests │ │ ├── data.rs │ │ ├── ext │ │ │ ├── db.rs │ │ │ ├── ext.rs │ │ │ ├── mod.rs │ │ │ ├── result.rs │ │ │ └── state.rs │ │ ├── get_tip_set_by_height.rs │ │ └── mod.rs │ └── types.rs │ ├── tests │ ├── mock.rs │ ├── mod.rs │ ├── ocw.rs │ ├── relayer.rs │ ├── verify.rs │ └── vote.rs │ └── types.rs ├── js ├── e2e │ ├── .eslintrc.js │ ├── .gitignore │ ├── README.md │ ├── index.ts │ ├── package.json │ ├── proof.json │ ├── setup.ts │ ├── src │ │ ├── api.ts │ │ ├── index.ts │ │ ├── launch.ts │ │ └── runner.ts │ └── tsconfig.json └── types │ ├── .gitignore │ ├── README.md │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── lerna.json ├── package.json ├── resources ├── header.txt └── types.json ├── scripts ├── setup.json └── setup.sh ├── substrate-node-example ├── .editorconfig ├── LICENSE ├── README.md ├── node │ ├── Cargo.toml │ ├── build.rs │ └── src │ │ ├── chain_spec.rs │ │ ├── cli.rs │ │ ├── command.rs │ │ ├── lib.rs │ │ ├── main.rs │ │ ├── rpc.rs │ │ └── service.rs └── runtime │ ├── Cargo.toml │ ├── build.rs │ └── src │ ├── lib.rs │ └── weights │ ├── filecoindot.rs │ └── mod.rs ├── ui ├── .env.example ├── .eslintrc.json ├── .gitignore ├── .nvmrc ├── README.md ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── manifest.json │ └── robots.txt ├── src │ ├── App.css │ ├── App.test.tsx │ ├── App.tsx │ ├── components │ │ └── layout │ │ │ └── Center.tsx │ ├── constants │ │ └── substrate.ts │ ├── containers │ │ ├── AccountSelect │ │ │ └── index.tsx │ │ ├── Header │ │ │ └── index.tsx │ │ ├── Settings │ │ │ └── index.tsx │ │ └── UserSpace │ │ │ └── index.tsx │ ├── contexts │ │ ├── AccountsContext.tsx │ │ ├── ApiContext.tsx │ │ └── NftContext.tsx │ ├── hooks │ │ ├── usNft.tsx │ │ ├── useBuckets.tsx │ │ └── useDidUpdateEffect.tsx │ ├── index.css │ ├── index.tsx │ ├── interfaces │ │ ├── augment-api-consts.ts │ │ ├── augment-api-errors.ts │ │ ├── augment-api-events.ts │ │ ├── augment-api-query.ts │ │ ├── augment-api-rpc.ts │ │ ├── augment-api-tx.ts │ │ ├── augment-api.ts │ │ ├── augment-types.ts │ │ ├── definitions.ts │ │ ├── index.ts │ │ ├── lookup.ts │ │ ├── types-lookup.ts │ │ └── types.ts │ ├── logo.svg │ ├── logos │ │ ├── Filecoin.png │ │ ├── chainsafe.png │ │ ├── rmrk.png │ │ └── substrate.png │ ├── pages │ │ ├── MintNFT.tsx │ │ └── VerifyBlock.tsx │ ├── proof.ts │ ├── react-app-env.d.ts │ ├── reportWebVitals.ts │ └── setupTests.ts └── tsconfig.json └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | **/target 2 | **/node_modules 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/backlog_item.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Backlog Item 3 | about: Issue that has enough reasoning, research and information to be implemented 4 | title: '' 5 | labels: 'needs triage' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | ## Implementation details 13 | 14 | 15 | ## Testing details 16 | 17 | 18 | ## Acceptance Criteria 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Describe the bug 11 | 12 | 13 | - 14 | 15 | 16 | ## Expected Behavior 17 | 18 | 25 | 26 | - 27 | 28 | 29 | ## Current Behavior 30 | 31 | 39 | 40 | - 41 | 42 | 43 | ## Possible Solution 44 | 45 | 53 | 54 | - 55 | 56 | 57 | ## To Reproduce 58 | Steps to reproduce the behavior: 59 | 60 | 1. 61 | 2. 62 | 3. 63 | 64 | 65 | ## Log output 66 | 67 |
68 | Log Output 69 | 70 | ```Paste log output here 71 | paste log output... 72 | ``` 73 |
74 |
75 | 76 | ## Specification 77 | 78 | 91 | 92 | - rustc version: 93 | - pint version: 94 | - commit tag: 95 | - commit hash: 96 | - operating system: 97 | - additional links: 98 | 99 | 100 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | 2 | blank_issues_enabled: true -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/general_issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: General issue 3 | about: General purpose issue template 4 | title: '' 5 | labels: 'needs triage' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Issue summary 11 | 12 | - 13 | 14 | 15 | ## Other information and links 16 | 17 | - 18 | 19 | 20 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "cargo" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | 9 | 10 | - 11 | - 12 | - 13 | 14 | ## Tests 15 | 16 | 21 | 22 | ``` 23 | 24 | ``` 25 | 26 | ## Issues 27 | 28 | 34 | 35 | - -------------------------------------------------------------------------------- /.github/workflows/check-licence.yml: -------------------------------------------------------------------------------- 1 | name: Check Licence 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | push: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | check-licence: 13 | runs-on: ubuntu-18.04 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up Golang 18 | uses: actions/setup-go@v2 19 | with: 20 | go-version: '^1.16' 21 | - name: Install addlicense 22 | run: | 23 | export PATH=${PATH}:`go env GOPATH`/bin 24 | go get -v -u github.com/google/addlicense 25 | - name: Check license 26 | run: | 27 | export PATH=${PATH}:`go env GOPATH`/bin 28 | addlicense -check -c "ChainSafe Systems" -f ./resources/header.txt -y 2021 $(find $PWD -type f -name '*.rs') 29 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: docker 2 | 3 | on: 4 | push: 5 | branches: main 6 | pull_request: 7 | types: [ opened, synchronize, reopened ] 8 | 9 | jobs: 10 | docker: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | - name: Set up QEMU 16 | uses: docker/setup-qemu-action@v1 17 | - name: Set up Docker Buildx 18 | uses: docker/setup-buildx-action@v1 19 | - name: Login to Github Registry 20 | uses: docker/login-action@v1 21 | with: 22 | registry: ghcr.io 23 | username: ${{ github.actor }} 24 | password: ${{ secrets.GITHUB_TOKEN }} 25 | - name: PrepareReg Names 26 | run: echo IMAGE_REPOSITORY=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV 27 | - name: Build and push 28 | id: docker_build 29 | uses: docker/build-push-action@v2 30 | with: 31 | push: ${{ github.event_name == 'push' }} 32 | tags: ghcr.io/chainsafe/filecoindot-template 33 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | release: 8 | types: [ published ] 9 | 10 | jobs: 11 | publish: 12 | name: Publish 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v2 17 | - name: Setup Nodejs 18 | uses: actions/setup-node@v2 19 | with: 20 | node-version: '16.x' 21 | registry-url: 'https://registry.npmjs.org' 22 | - name: Build 23 | run: yarn build 24 | - name: Publish 25 | run: cd js/types && yarn publish --access public 26 | env: 27 | NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 28 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | pull_request: 5 | types: [ opened, synchronize, reopened ] 6 | 7 | jobs: 8 | test: 9 | name: Test Suite 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions-rs/toolchain@v1 14 | with: 15 | profile: minimal 16 | target: wasm32-unknown-unknown 17 | # https://github.com/rust-osdev/uefi-rs/issues/329 18 | toolchain: nightly-2021-12-07 19 | override: true 20 | - run: cargo b --all-features --release 21 | - run: cargo t --all-features --release 22 | - run: ./target/release/filecoindot-template benchmark -p "*" -e "*" --wasm-execution compiled 23 | - uses: actions/setup-node@v2 24 | with: 25 | node-version: '16' 26 | - run: yarn e2e 27 | 28 | fmt: 29 | name: Rustfmt 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@v2 33 | - uses: actions-rs/toolchain@v1 34 | with: 35 | profile: minimal 36 | target: wasm32-unknown-unknown 37 | # https://github.com/rust-osdev/uefi-rs/issues/329 38 | toolchain: nightly-2021-12-07 39 | override: true 40 | - run: rustup component add rustfmt 41 | - uses: actions-rs/cargo@v1 42 | with: 43 | command: fmt 44 | args: --all -- --check 45 | 46 | clippy: 47 | name: Clippy 48 | runs-on: ubuntu-latest 49 | steps: 50 | - uses: actions/checkout@v2 51 | - uses: actions-rs/toolchain@v1 52 | with: 53 | profile: minimal 54 | target: wasm32-unknown-unknown 55 | # https://github.com/rust-osdev/uefi-rs/issues/329 56 | toolchain: nightly-2021-12-07 57 | override: true 58 | - run: rustup component add clippy 59 | - uses: actions-rs/cargo@v1 60 | with: 61 | command: clippy 62 | args: -- -D warnings 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .log 2 | .idea 3 | target 4 | node_modules 5 | lib 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "filecoindot", 4 | "filecoindot-io", 5 | "filecoindot-proofs", 6 | "filecoindot-rpc", 7 | # node-example 8 | "substrate-node-example/node", 9 | "substrate-node-example/runtime", 10 | "filecoindot-cli", 11 | ] 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:experimental 2 | # 3 | # Copyright 2021 ChainSafe Systems 4 | # SPDX-License-Identifier: LGPL-3.0-only 5 | # 6 | # Building layer 7 | FROM paritytech/ci-linux:production as builder 8 | COPY . . 9 | ENV CARGO_TERM_COLOR=always 10 | RUN --mount=type=cache,target=/usr/local/cargo/git \ 11 | --mount=type=cache,target=/usr/local/cargo/registry \ 12 | --mount=type=cache,sharing=private,target=target \ 13 | cargo +nightly build --release && \ 14 | mv target/release/filecoindot-template /filecoindot-template 15 | 16 | # Release 17 | FROM debian:buster-slim 18 | ENV DEBIAN_FRONTEND=noninteractive 19 | LABEL description="The docker image of filecoindot template" 20 | COPY --from=builder /filecoindot-template /usr/local/bin/ 21 | RUN apt-get update && \ 22 | apt-get install -y --no-install-recommends ca-certificates && \ 23 | apt-get autoremove -y && \ 24 | apt-get clean && \ 25 | rm -rf /var/lib/apt/lists/* && \ 26 | useradd -m -u 1000 -U -s /bin/sh -d /filecoindot filecoindot && \ 27 | mkdir -p /filecoindot/.local/share && \ 28 | mkdir /data && \ 29 | chown -R filecoindot:filecoindot /data && \ 30 | ln -s /data /filecoindot/.local/share/filecoindot-template && \ 31 | rm -rf /usr/bin /usr/sbin 32 | 33 | USER filecoindot 34 | # 30333 for p2p traffic 35 | # 9933 for RPC call 36 | # 9944 for Websocket 37 | # 9615 for Prometheus (metrics) 38 | EXPOSE 30333 9933 9944 9615 39 | VOLUME [ "/data" ] 40 | ENTRYPOINT [ "/usr/local/bin/filecoindot-template" ] 41 | -------------------------------------------------------------------------------- /filecoindot-cli/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /filecoindot-cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "filecoindot-cli" 3 | description = 'Pallet that bridges Filecoin to substrate based chains.' 4 | version = '0.0.1' 5 | license = "GPL-3.0" 6 | homepage = 'https://github.com/ChainSafe/filecoindot' 7 | repository = 'https://github.com/ChainSafe/filecoindot' 8 | edition = "2021" 9 | 10 | [dependencies] 11 | type-cli = "0.0.3" 12 | codec = { package = "parity-scale-codec", version = "2.3.1", default-features = false } 13 | hex = "0.4.3" 14 | anyhow = "1" 15 | thiserror = "1" 16 | filecoindot-proofs = { path = "../filecoindot-proofs" } 17 | cid = { package = "forest_cid", version = "0.3" } 18 | 19 | 20 | [features] 21 | default = ['std'] 22 | std = [ 23 | 'codec/std', 24 | 'hex/std', 25 | ] 26 | -------------------------------------------------------------------------------- /filecoindot-cli/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | #![deny(warnings)] 5 | use codec::{Decode, Encode}; 6 | 7 | #[derive(thiserror::Error, Debug)] 8 | pub enum DecodeError { 9 | #[error("Error decoding from hex: {0}")] 10 | FromHex(#[from] hex::FromHexError), 11 | #[error("codec error: {0}")] 12 | CodecError(#[from] codec::Error), 13 | } 14 | 15 | /// encode a Vec of Vec of bytes into a String using hex encoding 16 | pub fn hex_encode_proof(proof: Vec>) -> String { 17 | hex::encode(proof.encode()) 18 | } 19 | 20 | /// decode a hex String into a Vec of Vec of bytes 21 | pub fn decode_proof_from_hex(hex: &str) -> Result>, DecodeError> { 22 | let p = hex::decode(hex)?; 23 | let decoded = Decode::decode(&mut &*p)?; 24 | Ok(decoded) 25 | } 26 | 27 | #[cfg(test)] 28 | mod test { 29 | use super::*; 30 | 31 | #[test] 32 | fn hex_encode_proof_works() { 33 | let p = vec![ 34 | vec![ 35 | 1, 113, 160, 228, 2, 32, 36, 124, 182, 126, 106, 187, 25, 199, 230, 181, 100, 214, 36 | 154, 77, 62, 109, 17, 9, 120, 21, 205, 111, 102, 96, 38, 79, 186, 148, 178, 110, 37 | 68, 137, 38 | ], 39 | vec![ 40 | 1, 113, 160, 228, 2, 32, 181, 170, 59, 78, 87, 6, 123, 107, 23, 248, 104, 224, 201, 41 | 4, 132, 237, 73, 29, 249, 91, 139, 26, 156, 212, 179, 175, 127, 214, 118, 157, 251, 42 | 48, 43 | ], 44 | ]; 45 | 46 | let expected = "08980171a0e40220247cb67e6abb19c7e6b564d69a4d3e6d11097815cd6f6660264fba94b26e4489980171a0e40220b5aa3b4e57067b6b17f868e0c90484ed491df95b8b1a9cd4b3af7fd6769dfb30"; 47 | let hex_string = hex_encode_proof(p); 48 | assert_eq!(hex_string, expected); 49 | } 50 | 51 | #[test] 52 | fn decode_hex_works() { 53 | let input = "08980171a0e40220247cb67e6abb19c7e6b564d69a4d3e6d11097815cd6f6660264fba94b26e4489980171a0e40220b5aa3b4e57067b6b17f868e0c90484ed491df95b8b1a9cd4b3af7fd6769dfb30"; 54 | 55 | let expected = vec![ 56 | vec![ 57 | 1, 113, 160, 228, 2, 32, 36, 124, 182, 126, 106, 187, 25, 199, 230, 181, 100, 214, 58 | 154, 77, 62, 109, 17, 9, 120, 21, 205, 111, 102, 96, 38, 79, 186, 148, 178, 110, 59 | 68, 137, 60 | ], 61 | vec![ 62 | 1, 113, 160, 228, 2, 32, 181, 170, 59, 78, 87, 6, 123, 107, 23, 248, 104, 224, 201, 63 | 4, 132, 237, 73, 29, 249, 91, 139, 26, 156, 212, 179, 175, 127, 214, 118, 157, 251, 64 | 48, 65 | ], 66 | ]; 67 | let proof = decode_proof_from_hex(input).expect("must not error"); 68 | assert_eq!(proof, expected); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /filecoindot-cli/src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | #![deny(warnings)] 5 | use anyhow::Result; 6 | use cid::Cid; 7 | use filecoindot_cli::{decode_proof_from_hex, DecodeError}; 8 | use filecoindot_proofs::generic_verify; 9 | use std::convert::TryFrom; 10 | use thiserror::Error; 11 | use type_cli::CLI; 12 | 13 | #[derive(CLI)] 14 | enum Filecoindot { 15 | Verify { 16 | #[named] 17 | proof: String, 18 | #[named] 19 | cid: String, 20 | }, 21 | } 22 | 23 | #[derive(Error, Debug)] 24 | enum CliError { 25 | #[error("CID error: {0}")] 26 | Cid(#[from] filecoindot_proofs::cid::Error), 27 | #[error("Verification Error: {0}")] 28 | Verification(#[from] filecoindot_proofs::Error), 29 | #[error("decode error: {0}")] 30 | Decode(#[from] DecodeError), 31 | } 32 | 33 | fn main() -> Result<(), CliError> { 34 | match Filecoindot::process() { 35 | Filecoindot::Verify { proof, cid } => { 36 | let proof = decode_proof_from_hex(&proof)?; 37 | let cid = Cid::try_from(&*cid)?; 38 | generic_verify(proof, &cid)?; 39 | println!("verification success"); 40 | Ok(()) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /filecoindot-io/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "filecoindot-io" 3 | authors = ['tianyi@chainsafe.io'] 4 | description = 'filecoindot runtime interfaces.' 5 | version = "0.0.1" 6 | license = "GPL-3.0" 7 | homepage = 'https://github.com/ChainSafe/filecoindot' 8 | repository = 'https://github.com/ChainSafe/filecoindot' 9 | edition = "2021" 10 | 11 | [dependencies] 12 | sp-std = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.13', default-features = false } 13 | sp-runtime-interface = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.13', default-features = false } 14 | 15 | # fileconidot deps 16 | filecoindot-proofs = { path = "../filecoindot-proofs", default-features = false, optional = true } 17 | 18 | [features] 19 | default = [ "std" ] 20 | std = [ 21 | "sp-runtime-interface/std", 22 | "sp-std/std", 23 | "filecoindot-proofs" 24 | ] 25 | -------------------------------------------------------------------------------- /filecoindot-io/README.md: -------------------------------------------------------------------------------- 1 | ## filecoindot-io 2 | 3 | fileconidot runtime interfaces 4 | -------------------------------------------------------------------------------- /filecoindot-io/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | #![cfg_attr(not(feature = "std"), no_std)] 4 | 5 | use sp_runtime_interface::runtime_interface; 6 | use sp_std::vec::Vec; 7 | 8 | #[runtime_interface] 9 | pub trait ForestProofVerify { 10 | fn verify_receipt(proof: Vec>, cid: Vec) -> Option<()> { 11 | use filecoindot_proofs::{ForestAmtAdaptedNode, ProofVerify, Verify}; 12 | ProofVerify::verify_proof::>(proof, cid).ok() 13 | } 14 | 15 | fn verify_state(proof: Vec>, cid: Vec) -> Option<()> { 16 | use filecoindot_proofs::{HAMTNodeType, ProofVerify, Verify}; 17 | ProofVerify::verify_proof::(proof, cid).ok() 18 | } 19 | 20 | fn verify_message(proof: Vec>, cid: Vec) -> Option<()> { 21 | use filecoindot_proofs::{MessageNodeType, ProofVerify, Verify}; 22 | ProofVerify::verify_proof::(proof, cid).ok() 23 | } 24 | } 25 | 26 | #[runtime_interface] 27 | pub trait Benchmarking { 28 | fn hamt_proof_generation() -> (Vec>, Vec) { 29 | filecoindot_proofs::benchmarking::hamt_proof_generation() 30 | } 31 | 32 | fn amt_proof_generation(n: u64) -> (Vec>, Vec) { 33 | filecoindot_proofs::benchmarking::amt_proof_generation(n as usize) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /filecoindot-nft/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = 'filecoindot-nft' 3 | authors = ['matthias@chainsafe.io'] 4 | description = 'Pallet that bridges Filecoin to substrate based chains.' 5 | version = '0.0.1' 6 | license = "GPL-3.0" 7 | homepage = 'https://github.com/ChainSafe/filecoindot' 8 | repository = 'https://github.com/ChainSafe/filecoindot' 9 | edition = "2021" 10 | 11 | [package.metadata.docs.rs] 12 | targets = ['x86_64-unknown-linux-gnu'] 13 | 14 | [dependencies] 15 | codec = { package = "parity-scale-codec", version = "2.3.1", default-features = false } 16 | serde = { version = "1.0.130", features = [ "derive" ], default-features = false } 17 | serde_json = { version = "1.0", features = [ "alloc" ], default-features = false } 18 | derive_more = "^0.99" 19 | 20 | # substrate dependencies 21 | frame-benchmarking = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.13', default-features = false, optional = true } 22 | frame-support = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.13', default-features = false } 23 | frame-system = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.13', default-features = false } 24 | sp-core = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.13', default-features = false } 25 | scale-info = { version = "1.0", default-features = false, features = ["derive"] } 26 | 27 | # filecoindot related 28 | filecoindot-io = { path = "../filecoindot-io", default-features = false } 29 | 30 | # orml dependencies 31 | orml-nft = { default-features = false, git = "https://github.com/open-web3-stack/open-runtime-module-library.git", branch = "polkadot-v0.9.13" } 32 | 33 | [dev-dependencies] 34 | serde = "1.0.130" 35 | bincode = "1.3.3" 36 | reqwest = { version = "0.11.6", features = [ "json" ] } 37 | parking_lot = "0.11" 38 | futures = "0.3" 39 | tokio = { version = "1.14", features = [ "full" ] } 40 | thiserror = "1.0.30" 41 | dirs = "4.0.0" 42 | 43 | sp-core = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.13', default-features = false } 44 | sp-runtime = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.13', default-features = false } 45 | sp-io = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.13', default-features = false } 46 | sp-keystore = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.13', default-features = false } 47 | ipld_hamt = { git = "https://github.com/willeslau/forest", branch = "willes/filecoindot" } 48 | ipld_amt = { git = "https://github.com/willeslau/forest", branch = "willes/filecoindot" } 49 | ipld_blockstore = { git = "https://github.com/willeslau/forest", branch = "willes/filecoindot" } 50 | cid = { package = "forest_cid", version = "0.3" } 51 | serde_cbor = { version = "0.12", features = ["tags"], package = "cs_serde_cbor" } 52 | filecoindot-proofs = { path = "../filecoindot-proofs", default-features = false } 53 | 54 | [features] 55 | default = ['std'] 56 | runtime-benchmarks = [ 57 | 'frame-benchmarking', 58 | 'frame-support/runtime-benchmarks', 59 | 'frame-system/runtime-benchmarks', 60 | ] 61 | std = [ 62 | 'codec/std', 63 | "scale-info/std", 64 | 'frame-support/std', 65 | 'frame-system/std', 66 | 'frame-benchmarking/std', 67 | 'filecoindot-io/std', 68 | ] 69 | -------------------------------------------------------------------------------- /filecoindot-nft/README.md: -------------------------------------------------------------------------------- 1 | License: Unlicense 2 | -------------------------------------------------------------------------------- /filecoindot-nft/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | //! # Filecoin NFT Pallet 5 | //! 6 | //! This pallet will enabled users to mint NFTs 7 | //! providing the cid and proof. 8 | //! 9 | #![cfg_attr(not(feature = "std"), no_std)] 10 | 11 | pub use self::pallet::*; 12 | 13 | mod types; 14 | 15 | #[cfg(test)] 16 | mod tests; 17 | 18 | #[frame_support::pallet] 19 | pub mod pallet { 20 | pub use crate::types::{ClassData, TokenData}; 21 | use codec::alloc::vec; 22 | use frame_support::{pallet_prelude::*, sp_std::prelude::*}; 23 | use frame_system::pallet_prelude::*; 24 | 25 | pub type TokenIdOf = ::TokenId; 26 | pub type ClassIdOf = ::ClassId; 27 | 28 | const MINT_QUANTITY: u32 = 1; 29 | 30 | /// Configure the pallet by specifying the parameters and types on which it depends. 31 | #[pallet::config] 32 | pub trait Config: 33 | frame_system::Config + orml_nft::Config 34 | { 35 | /// The overarching event type. 36 | type Event: From> + IsType<::Event>; 37 | /// The default class id of the NFT 38 | type DefaultClassId: Get>; 39 | /// The weight for this pallet's extrinsics. 40 | type WeightInfo: WeightInfo; 41 | } 42 | 43 | #[pallet::pallet] 44 | #[pallet::generate_store(pub (super) trait Store)] 45 | pub struct Pallet(_); 46 | 47 | #[pallet::event] 48 | #[pallet::generate_deposit(pub(crate) fn deposit_event)] 49 | pub enum Event { 50 | /// Minted NFT token. \[from, class_id, quantity\] 51 | MintedToken(T::AccountId, ClassIdOf, u32), 52 | /// Transferred NFT token. \[from, to, class_id, token_id\] 53 | TransferredToken(T::AccountId, T::AccountId, ClassIdOf, TokenIdOf), 54 | } 55 | 56 | // Errors inform users that something went wrong. 57 | #[pallet::error] 58 | pub enum Error { 59 | /// The requested token id does not exist 60 | TokenIdNotFound, 61 | } 62 | 63 | #[pallet::hooks] 64 | impl Hooks for Pallet {} 65 | 66 | #[pallet::genesis_config] 67 | pub struct GenesisConfig { 68 | pub default_class: (T::AccountId, Vec), 69 | } 70 | 71 | #[cfg(feature = "std")] 72 | impl Default for GenesisConfig { 73 | fn default() -> Self { 74 | Self { 75 | default_class: (Default::default(), Default::default()), 76 | } 77 | } 78 | } 79 | 80 | #[pallet::genesis_build] 81 | impl GenesisBuild for GenesisConfig { 82 | fn build(&self) { 83 | let (owner, data) = self.default_class.clone(); 84 | // just panic if cannot create class 85 | orml_nft::Pallet::::create_class(&owner, vec![], ClassData::new(data)) 86 | .expect("cannot create default nft class"); 87 | } 88 | } 89 | 90 | #[pallet::call] 91 | impl Pallet { 92 | /// Mint the nft from cid and proof 93 | #[pallet::weight(T::WeightInfo::mint())] 94 | pub fn mint(origin: OriginFor, cid: Vec, proof: Vec>) -> DispatchResult { 95 | let who = ensure_signed(origin)?; 96 | orml_nft::Pallet::::mint( 97 | &who, 98 | T::DefaultClassId::get(), 99 | vec![], 100 | TokenData::new(cid, proof), 101 | )?; 102 | Self::deposit_event(Event::MintedToken( 103 | who, 104 | T::DefaultClassId::get(), 105 | MINT_QUANTITY, 106 | )); 107 | Ok(()) 108 | } 109 | 110 | /// Transfer the nft specified with `token_id` from the sender to `to` account 111 | #[pallet::weight(T::WeightInfo::transfer())] 112 | pub fn transfer( 113 | origin: OriginFor, 114 | to: T::AccountId, 115 | token_id: TokenIdOf, 116 | ) -> DispatchResult { 117 | let from = ensure_signed(origin)?; 118 | orml_nft::Pallet::::tokens(T::DefaultClassId::get(), token_id) 119 | .ok_or(Error::::TokenIdNotFound)?; 120 | orml_nft::Pallet::::transfer(&from, &to, (T::DefaultClassId::get(), token_id))?; 121 | Self::deposit_event(Event::TransferredToken( 122 | from, 123 | to, 124 | T::DefaultClassId::get(), 125 | token_id, 126 | )); 127 | Ok(()) 128 | } 129 | } 130 | 131 | impl Pallet { 132 | /// Get the balance of the account 133 | pub fn balance(who: &T::AccountId) -> u128 { 134 | orml_nft::TokensByOwner::::iter_prefix((who, T::DefaultClassId::get())).count() 135 | as u128 136 | } 137 | 138 | /// Get the list of token ids owned by the account. 139 | /// Quite expensive, invoke with care. Should use indexer for this. 140 | pub fn tokens(who: &T::AccountId) -> Vec> { 141 | orml_nft::TokensByOwner::::iter_prefix((who, T::DefaultClassId::get())) 142 | .map(|t| t.0) 143 | .collect::>() 144 | } 145 | 146 | /// Get the details of a specific token 147 | pub fn token_detail(token_id: TokenIdOf) -> Result> { 148 | let token = orml_nft::Pallet::::tokens(T::DefaultClassId::get(), token_id) 149 | .ok_or(Error::::TokenIdNotFound)?; 150 | Ok(token.data) 151 | } 152 | } 153 | 154 | pub trait WeightInfo { 155 | fn mint() -> Weight; 156 | fn transfer() -> Weight; 157 | } 158 | 159 | /// For backwards compatibility and tests 160 | impl WeightInfo for () { 161 | fn mint() -> Weight { 162 | Default::default() 163 | } 164 | fn transfer() -> Weight { 165 | Default::default() 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /filecoindot-nft/src/tests/mint.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | use crate::tests::mock::*; 5 | use frame_support::assert_ok; 6 | 7 | #[test] 8 | fn mint_works() { 9 | ExtBuilder::default().build().execute_with(|| { 10 | assert_ok!(FilecoinNFT::mint(Origin::signed(ALICE), vec![], vec![])); 11 | }); 12 | } 13 | 14 | #[test] 15 | fn tokens_works() { 16 | ExtBuilder::default().build().execute_with(|| { 17 | assert_ok!(FilecoinNFT::mint(Origin::signed(ALICE), vec![], vec![])); 18 | assert_ok!(FilecoinNFT::mint(Origin::signed(ALICE), vec![2], vec![])); 19 | FilecoinNFT::tokens(&ALICE); 20 | }); 21 | } 22 | 23 | #[test] 24 | fn tokens_details_works() { 25 | ExtBuilder::default().build().execute_with(|| { 26 | let cid = vec![1u8]; 27 | let proof = vec![vec![2u8]]; 28 | assert_ok!(FilecoinNFT::mint( 29 | Origin::signed(ALICE), 30 | cid.clone(), 31 | proof.clone() 32 | )); 33 | assert_ok!(FilecoinNFT::mint(Origin::signed(ALICE), vec![2], vec![])); 34 | let r = FilecoinNFT::token_detail(0).unwrap(); 35 | 36 | assert_eq!(r.cid, cid); 37 | assert_eq!(r.proof, proof); 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /filecoindot-nft/src/tests/mock.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | use crate as pallet; 5 | use crate::{ClassData, TokenData}; 6 | use frame_support::construct_runtime; 7 | use frame_support::pallet_prelude::GenesisBuild; 8 | use frame_support::parameter_types; 9 | use sp_core::{sr25519::Public, H256}; 10 | use sp_runtime::{testing::Header, traits::IdentityLookup}; 11 | 12 | type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; 13 | type Block = frame_system::mocking::MockBlock; 14 | 15 | pub type AccountId = Public; 16 | pub const ALICE: AccountId = Public([1u8; 32]); 17 | 18 | // Configure a mock runtime to test the pallet. 19 | construct_runtime!( 20 | pub enum Test where 21 | Block = Block, 22 | NodeBlock = Block, 23 | UncheckedExtrinsic = UncheckedExtrinsic, 24 | { 25 | System: frame_system::{Pallet, Call, Config, Storage, Event}, 26 | FilecoinNFT: pallet::{Pallet, Call, Storage, Event}, 27 | NFT: orml_nft::{Pallet, Storage}, 28 | } 29 | ); 30 | 31 | parameter_types! { 32 | pub const BlockHashCount: u64 = 250; 33 | pub const SS58Prefix: u8 = 42; 34 | pub const OffchainWorkerTimeout: u64 = 1_000_000; 35 | } 36 | 37 | impl frame_system::Config for Test { 38 | type Origin = Origin; 39 | type Call = Call; 40 | type Index = u64; 41 | type BlockNumber = u64; 42 | type Hash = H256; 43 | type Hashing = ::sp_runtime::traits::BlakeTwo256; 44 | type AccountId = sp_core::sr25519::Public; 45 | type Lookup = IdentityLookup; 46 | type Header = Header; 47 | type Event = Event; 48 | type BlockHashCount = BlockHashCount; 49 | type BlockWeights = (); 50 | type BlockLength = (); 51 | type Version = (); 52 | type PalletInfo = PalletInfo; 53 | type AccountData = (); 54 | type OnNewAccount = (); 55 | type OnKilledAccount = (); 56 | type DbWeight = (); 57 | type BaseCallFilter = frame_support::traits::Everything; 58 | type SystemWeightInfo = (); 59 | type SS58Prefix = (); 60 | type OnSetCode = (); 61 | } 62 | 63 | parameter_types! { 64 | pub MaxClassMetadata: u32 = 1024; 65 | pub MaxTokenMetadata: u32 = 1024; 66 | } 67 | 68 | impl orml_nft::Config for Test { 69 | type ClassId = u32; 70 | type TokenId = u32; 71 | type ClassData = ClassData; 72 | type TokenData = TokenData; 73 | type MaxClassMetadata = MaxClassMetadata; 74 | type MaxTokenMetadata = MaxTokenMetadata; 75 | } 76 | 77 | parameter_types! { 78 | pub DefaultClassId: u32 = 0; 79 | } 80 | 81 | impl pallet::Config for Test { 82 | type Event = Event; 83 | type DefaultClassId = DefaultClassId; 84 | type WeightInfo = (); 85 | } 86 | 87 | pub struct ExtBuilder { 88 | pub default_class: (AccountId, Vec), 89 | } 90 | 91 | impl Default for ExtBuilder { 92 | fn default() -> Self { 93 | Self { 94 | default_class: (ALICE, vec![0]), 95 | } 96 | } 97 | } 98 | 99 | impl ExtBuilder { 100 | pub fn build(self) -> sp_io::TestExternalities { 101 | let mut t = frame_system::GenesisConfig::default() 102 | .build_storage::() 103 | .unwrap(); 104 | 105 | pallet::GenesisConfig:: { 106 | default_class: (ALICE, vec![0]), 107 | } 108 | .assimilate_storage(&mut t) 109 | .unwrap(); 110 | t.into() 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /filecoindot-nft/src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | mod mint; 5 | mod mock; 6 | -------------------------------------------------------------------------------- /filecoindot-nft/src/types.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | use frame_support::pallet_prelude::*; 5 | use frame_support::sp_std; 6 | use scale_info::TypeInfo; 7 | use serde::{Deserialize, Serialize}; 8 | use sp_std::prelude::*; 9 | 10 | #[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, TypeInfo, Default)] 11 | #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] 12 | pub struct ClassData { 13 | /// The data stored in the class 14 | pub data: Vec, 15 | } 16 | 17 | impl ClassData { 18 | pub fn new(data: Vec) -> Self { 19 | ClassData { data } 20 | } 21 | } 22 | 23 | #[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, TypeInfo)] 24 | #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] 25 | pub struct TokenData { 26 | /// The cid of the data in filecoin 27 | pub cid: Vec, 28 | pub proof: Vec>, 29 | } 30 | 31 | impl TokenData { 32 | pub fn new(cid: Vec, proof: Vec>) -> Self { 33 | TokenData { cid, proof } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /filecoindot-proofs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "filecoindot-proofs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | serde = { version = "1.0", features = ["derive"] } 8 | byteorder = "1.3.2" 9 | cid = { package = "forest_cid", version = "0.3" } 10 | serde_bytes = { package = "cs_serde_bytes", version = "0.12" } 11 | thiserror = "1.0" 12 | sha2 = "0.9.1" 13 | once_cell = "1.5" 14 | parking_lot = "0.11" 15 | num-traits = "0.2.14" 16 | ipld_hamt = { git = "https://github.com/willeslau/forest", branch = "willes/filecoindot" } 17 | ipld_amt = { git = "https://github.com/willeslau/forest", branch = "willes/filecoindot" } 18 | forest_ipld = { version = "0.1.1" } 19 | forest_encoding = { version = "0.2.1" } 20 | forest_hash_utils = { version = "0.1" } 21 | forest_db = { git = "https://github.com/willeslau/forest", branch = "willes/filecoindot" } 22 | ipld_blockstore = { git = "https://github.com/willeslau/forest", branch = "willes/filecoindot" } 23 | serde_cbor = { version = "0.12", features = ["tags"], package = "cs_serde_cbor" } 24 | 25 | [features] 26 | default = [ "std" ] 27 | std = [] 28 | -------------------------------------------------------------------------------- /filecoindot-proofs/src/amt.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2022 ChainSafe Systems 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | use crate::errors::Error; 5 | use crate::traits::{AMTNode, BlockStore}; 6 | use cid::Cid; 7 | use forest_encoding::de::Deserializer; 8 | use serde::Deserialize; 9 | 10 | pub fn nodes_for_height(bit_width: usize, height: usize) -> usize { 11 | let height_log_two = bit_width * height; 12 | if height_log_two >= 64 { 13 | return std::usize::MAX; 14 | } 15 | 1 << height_log_two 16 | } 17 | 18 | const MAX_HEIGHT: usize = 8; 19 | const MAX_INDEX: usize = (u64::MAX - 1) as usize; 20 | 21 | #[allow(dead_code)] 22 | #[derive(Debug)] 23 | pub struct Amt<'db, BS: BlockStore, N: AMTNode> { 24 | node: N, 25 | block_store: Option<&'db BS>, 26 | bit_width: usize, 27 | height: usize, 28 | count: usize, 29 | } 30 | 31 | impl<'db, 'de, BS: BlockStore, N: AMTNode + Deserialize<'de>> Deserialize<'de> for Amt<'db, BS, N> { 32 | fn deserialize(deserializer: D) -> Result 33 | where 34 | D: Deserializer<'de>, 35 | { 36 | let (bit_width, height, count, node): (_, _, _, N) = 37 | Deserialize::deserialize(deserializer)?; 38 | Ok(Self { 39 | bit_width, 40 | height, 41 | count, 42 | node, 43 | block_store: None, 44 | }) 45 | } 46 | } 47 | 48 | impl<'db, BS, N> Amt<'db, BS, N> 49 | where 50 | BS: BlockStore, 51 | N: AMTNode + for<'de> Deserialize<'de>, 52 | { 53 | /// Constructs an AMT with a blockstore and a Cid of the root of the AMT 54 | pub fn load(cid: &Cid, block_store: &'db BS) -> Result { 55 | // Load root bytes from database 56 | let mut root = block_store.get::(cid)?; 57 | 58 | // Sanity check, this should never be possible. 59 | if root.height > MAX_HEIGHT { 60 | return Err(Error::MaxHeightExceeded); 61 | } 62 | 63 | root.block_store = Some(block_store); 64 | Ok(root) 65 | } 66 | 67 | /// Get value at index of AMT 68 | pub fn generate_proof(&self, i: usize) -> Result>, Error> { 69 | if i > MAX_INDEX { 70 | return Err(Error::NotFound); 71 | } 72 | 73 | if i >= nodes_for_height(self.bit_width, self.height + 1) { 74 | return Err(Error::NotFound); 75 | } 76 | 77 | let mut path = Vec::new(); 78 | if self.node.path_to_key( 79 | *self.block_store.as_ref().unwrap(), 80 | self.bit_width, 81 | self.height, 82 | i, 83 | &mut path, 84 | )? { 85 | Ok(path) 86 | } else { 87 | Err(Error::NotFound) 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /filecoindot-proofs/src/benchmarking.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | use crate::{ 5 | deserialize_to_node, Amt, ForestAdaptedBlockStorage, ForestAdaptedHashAlgo, 6 | ForestAdaptedHashedBits, ForestAdaptedNode, ForestAmtAdaptedNode, GetCid, HAMTNodeType, Hamt, 7 | }; 8 | use ipld_amt::Amt as ForestAmt; 9 | use ipld_blockstore::MemoryDB; 10 | use ipld_hamt::Hamt as ForestHamt; 11 | use serde_cbor::from_slice; 12 | 13 | #[allow(clippy::type_complexity)] 14 | pub fn hamt_proof_generation() -> (Vec>, Vec) { 15 | let bs = MemoryDB::default(); 16 | let mut fhamt: ForestHamt<_, _, usize> = ForestHamt::new(&bs); 17 | 18 | let max = 1000; 19 | for i in 1..max { 20 | fhamt.set(i, i.to_string()).unwrap(); 21 | } 22 | 23 | let cid = fhamt.flush().unwrap(); 24 | let store = ForestAdaptedBlockStorage::new(bs); 25 | let hamt: Hamt< 26 | ForestAdaptedBlockStorage, 27 | usize, 28 | String, 29 | ForestAdaptedHashedBits, 30 | ForestAdaptedNode, 31 | ForestAdaptedHashAlgo, 32 | > = Hamt::new(&cid, &store, 8).unwrap(); 33 | let mut p = hamt.generate_proof(&(max / 2)).unwrap(); 34 | p.reverse(); 35 | let raw_node = p.get(0).unwrap(); 36 | let node: HAMTNodeType = deserialize_to_node(None, raw_node).unwrap(); 37 | (p, node.cid().unwrap().to_bytes()) 38 | } 39 | 40 | pub fn amt_proof_generation(n: usize) -> (Vec>, Vec) { 41 | let bs = MemoryDB::default(); 42 | let mut famt = ForestAmt::new(&bs); 43 | 44 | let max = 1000; 45 | for i in 1..max { 46 | famt.set(i, i.to_string()).unwrap(); 47 | } 48 | 49 | let cid = famt.flush().unwrap(); 50 | let store = ForestAdaptedBlockStorage::new(bs); 51 | let amt: Amt, ForestAmtAdaptedNode> = 52 | Amt::load(&cid, &store).unwrap(); 53 | let p = amt.generate_proof(n).unwrap(); 54 | let raw_node = p.get(0).unwrap(); 55 | let node: ForestAmtAdaptedNode = from_slice(raw_node).unwrap(); 56 | (p, node.cid().unwrap().to_bytes()) 57 | } 58 | -------------------------------------------------------------------------------- /filecoindot-proofs/src/errors.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2022 ChainSafe Systems 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | /// Database error 5 | #[derive(thiserror::Error, Debug)] 6 | pub enum Error { 7 | #[error("Invalid Hashbit length")] 8 | InvalidHashBitLen, 9 | #[error("MaxDepth error")] 10 | MaxDepth, 11 | #[error("Not found")] 12 | NotFound, 13 | #[error("Proof verification failed")] 14 | VerificationFailed, 15 | #[error("Max height exceeded")] 16 | MaxHeightExceeded, 17 | #[error("Cid not found `{0}`")] 18 | CidNotFound(String), 19 | #[error("IPLD AMT error `{0}`")] 20 | IPLDAmt(#[from] ipld_amt::Error), 21 | #[error("IPLD HAMT error `{0}`")] 22 | IPLDHamt(#[from] ipld_hamt::Error), 23 | #[error("ForestDB error `{0}`")] 24 | ForestDB(#[from] forest_db::Error), 25 | #[error("CborEncoding error `{0}`")] 26 | CborEncoding(#[from] serde_cbor::Error), 27 | #[error("IPLD blockstore error `{0}`")] 28 | BlockStore(#[from] ipld_blockstore::Error), 29 | #[error("Generic error `{0}`")] 30 | Other(String), 31 | } 32 | -------------------------------------------------------------------------------- /filecoindot-proofs/src/generate.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | //! Generation of proofs for AMT and HAMT tries 5 | 6 | // pub mod hamt { 7 | // use crate::errors::Error; 8 | // use crate::hamt::Hamt; 9 | // 10 | // /// Generate an inclusion proof for the value in the given HAMT. 11 | // pub fn generate_proof<'db, V, BS>( 12 | // hamt: &Hamt<'db, BS, V>, 13 | // value: V, 14 | // ) -> Result>, Error> { 15 | // // each node can have up to 32 children 16 | // 17 | // todo!() 18 | // } 19 | // } 20 | -------------------------------------------------------------------------------- /filecoindot-proofs/src/hamt.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2022 ChainSafe Systems 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | use cid::Cid; 5 | use ipld_hamt::Hash; 6 | use std::marker::PhantomData; 7 | 8 | use crate::errors::Error; 9 | use crate::traits::{BlockStore, HAMTNode, HashAlgorithm, HashedBits}; 10 | 11 | /// This is a simplified implementation of HAMT based on: 12 | /// http://lampwww.epfl.ch/papers/idealhashtrees.pdf 13 | /// 14 | /// This implementation has only implemented the read related functions 15 | /// as we only care about generating the path to the node 16 | #[derive(Debug)] 17 | pub struct Hamt<'a, BS, K: Eq, V, H: HashedBits, N: HAMTNode, HashAlgo> { 18 | root: N, 19 | store: &'a BS, 20 | bit_width: u8, 21 | hash: PhantomData, 22 | _k: PhantomData, 23 | _v: PhantomData, 24 | _h: PhantomData, 25 | } 26 | 27 | impl<'a, BS, K, V, H, N, HashAlgo> Hamt<'a, BS, K, V, H, N, HashAlgo> 28 | where 29 | K: Eq + Hash, 30 | H: HashedBits, 31 | HashAlgo: HashAlgorithm, 32 | N: HAMTNode + for<'de> serde::Deserialize<'de>, 33 | BS: BlockStore, 34 | { 35 | /// Lazily instantiate a hamt from this root Cid with a specified bit width. 36 | pub fn new(root_cid: &Cid, store: &'a BS, bit_width: u8) -> Result { 37 | let root: N = store.get(root_cid)?; 38 | Ok(Self { 39 | root, 40 | store, 41 | bit_width, 42 | hash: Default::default(), 43 | _k: Default::default(), 44 | _v: Default::default(), 45 | _h: Default::default(), 46 | }) 47 | } 48 | 49 | /// Generates the path of all node bytes from the root to the node that contains the 50 | /// key. Returns Error::KeyNotFound if the key is not present in the tree. 51 | pub fn generate_proof(&self, k: &K) -> Result>, Error> { 52 | let mut path = Vec::new(); 53 | if self.root.path_to_key( 54 | &mut HashAlgo::hash(k), 55 | k, 56 | &mut path, 57 | self.bit_width, 58 | self.store, 59 | )? { 60 | Ok(path) 61 | } else { 62 | Err(Error::NotFound) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /filecoindot-proofs/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | #![feature(in_band_lifetimes)] 4 | #![deny(warnings)] 5 | 6 | mod errors; 7 | mod forest_hamt_adaptor; 8 | 9 | mod amt; 10 | pub mod benchmarking; 11 | mod forest_amt_adaptor; 12 | mod generate; 13 | mod hamt; 14 | mod traits; 15 | mod verify; 16 | 17 | pub use crate::amt::Amt; 18 | pub use crate::forest_amt_adaptor::*; 19 | pub use crate::forest_hamt_adaptor::*; 20 | pub use crate::hamt::Hamt; 21 | pub use crate::traits::{AMTNode, GetCid, HAMTNode, HashedBits, Verify}; 22 | pub use crate::verify::*; 23 | pub use cid; 24 | pub use errors::Error; 25 | -------------------------------------------------------------------------------- /filecoindot-proofs/src/traits.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2022 ChainSafe Systems 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | use crate::errors::Error; 5 | use cid::Cid; 6 | use ipld_hamt::Hash; 7 | use num_traits::Num; 8 | use serde::de::DeserializeOwned; 9 | 10 | pub trait HashAlgorithm { 11 | type Output: HashedBits; 12 | fn hash(key: &X) -> Self::Output; 13 | } 14 | 15 | pub trait HashedBits { 16 | type Value: Num + Copy; 17 | fn next(&mut self, n: u8) -> Result; 18 | } 19 | 20 | pub trait GetCid { 21 | fn cid(&self) -> Result; 22 | } 23 | 24 | pub trait HAMTNode: GetCid 25 | where 26 | K: Eq, 27 | H: HashedBits, 28 | { 29 | fn path_to_key( 30 | &self, 31 | hash_bits: &mut H, 32 | k: &K, 33 | path: &mut Vec>, 34 | bit_width: u8, 35 | store: &S, 36 | ) -> Result; 37 | 38 | fn get_by_cid(&self, cid: &Cid, store: &S) -> Result, Error> 39 | where 40 | Self: Sized; 41 | } 42 | 43 | pub trait AMTNode: GetCid { 44 | fn path_to_key( 45 | &self, 46 | store: &S, 47 | bit_width: usize, 48 | height: usize, 49 | i: usize, 50 | path: &mut Vec>, 51 | ) -> Result; 52 | 53 | fn get_by_cid( 54 | &self, 55 | cid: &Cid, 56 | store: &S, 57 | bit_width: usize, 58 | ) -> Result, Error> 59 | where 60 | Self: Sized; 61 | } 62 | 63 | /// Wrapper for database to handle inserting and retrieving ipld data with Cids 64 | pub trait BlockStore { 65 | /// Get typed object from block store by Cid. 66 | fn get(&self, cid: &Cid) -> Result; 67 | } 68 | 69 | /// The proof verification trait 70 | pub trait Verify { 71 | fn verify_proof(proof: Vec>, node_cid: Vec) -> Result<(), Error> 72 | where 73 | N: GetCid + for<'de> serde::Deserialize<'de>; 74 | } 75 | -------------------------------------------------------------------------------- /filecoindot-proofs/src/verify.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | use crate::errors::Error; 5 | use crate::traits::{GetCid, Verify}; 6 | use crate::HAMTNodeType; 7 | use cid::Cid; 8 | use serde_cbor::de::from_slice; 9 | use std::convert::TryFrom; 10 | 11 | pub struct ProofVerify; 12 | 13 | impl ProofVerify { 14 | fn traverse_and_match(proof: &[Vec], index: usize, target_cid: &Cid) -> Result<(), Error> 15 | where 16 | N: GetCid + for<'de> serde::Deserialize<'de>, 17 | { 18 | let current_node: N = from_slice(&*proof[index]).map_err(|_| Error::VerificationFailed)?; 19 | if current_node.cid()? == *target_cid { 20 | return Ok(()); 21 | } 22 | 23 | // We have not found the target_cid in the proof, search the next nodes. 24 | // The index is 0, we have reached the end of the proof, cannot proceed 25 | // any further, return error. 26 | if index == 0 { 27 | return Err(Error::VerificationFailed); 28 | } 29 | 30 | // now we search the previous index as we traverse deeper in to the trie 31 | Self::traverse_and_match::(proof, index - 1, target_cid) 32 | } 33 | } 34 | 35 | impl Verify for ProofVerify { 36 | /// Verify the proof and the the trie actually matches. Each cid in the proof 37 | /// is connected to its neighbours. The proof should match exactly in path from 38 | /// the root to the node. 39 | /// Note that proof[proof.len-1] == root_cid_bytes. This function does not assume 40 | /// the head of the proof to be equal to node_cid, as long as it's in the proof. 41 | fn verify_proof(proof: Vec>, node_cid: Vec) -> Result<(), Error> 42 | where 43 | N: GetCid + for<'de> serde::Deserialize<'de>, 44 | { 45 | let node_cid = Cid::try_from(node_cid).map_err(|_| Error::VerificationFailed)?; 46 | if proof.is_empty() { 47 | return Err(Error::VerificationFailed); 48 | } 49 | Self::traverse_and_match::(&proof, proof.len() - 1, &node_cid) 50 | } 51 | } 52 | 53 | /// Verify a proof against a Cid. 54 | /// 55 | /// Note: this is using HAMTNodeType 56 | pub fn generic_verify(proof: Vec>, cid: &Cid) -> Result<(), Error> { 57 | ProofVerify::verify_proof::(proof, cid.to_bytes()) 58 | } 59 | -------------------------------------------------------------------------------- /filecoindot-rpc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "filecoindot-rpc" 3 | authors = ['tianyi@chainsafe.io'] 4 | description = 'filecoindot rpcs.' 5 | version = "0.0.1" 6 | license = "GPL-3.0" 7 | homepage = 'https://github.com/ChainSafe/filecoindot' 8 | repository = 'https://github.com/ChainSafe/filecoindot' 9 | edition = "2021" 10 | 11 | [dependencies] 12 | cid = { package = "forest_cid", version = "0.3" } 13 | hex = "0.4.3" 14 | jsonrpc-core = "18" 15 | jsonrpc-core-client = "18" 16 | jsonrpc-derive = "18" 17 | derive_more = "^0.99" 18 | thiserror = "^1" 19 | parking_lot = "^0.10" 20 | url = "2.2.2" 21 | codec = { package = "parity-scale-codec", version = "2.3.1" } 22 | 23 | # substrate dependencies 24 | frame-support = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.13'} 25 | sp-core = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.13' } 26 | sp-offchain = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.13' } 27 | 28 | # filecoindot dependencies 29 | filecoindot-proofs = { path = "../filecoindot-proofs" } 30 | -------------------------------------------------------------------------------- /filecoindot-rpc/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | use cid::Cid; 4 | use filecoindot_proofs::{ForestAmtAdaptedNode, HAMTNodeType, ProofVerify, Verify}; 5 | use jsonrpc_derive::rpc; 6 | use parking_lot::RwLock; 7 | use result::{Error, Result}; 8 | use sp_core::{offchain::OffchainStorage, Decode, Encode}; 9 | use std::sync::Arc; 10 | use url::Url; 11 | 12 | /// filecoin rpc config 13 | pub const FILECOIN_RPC: &[u8] = b"FILECOIN_RPC"; 14 | 15 | mod result; 16 | 17 | /// decode a hex String into a Vec of Vec of bytes 18 | pub fn decode_proof_from_hex(hex: &str) -> Result>> { 19 | let p = hex::decode(hex)?; 20 | Ok(Decode::decode(&mut &*p)?) 21 | } 22 | 23 | /// filecointdot rpc api 24 | #[rpc] 25 | pub trait FilecoindotApi { 26 | /// set filecoin rpc endpoint for filecoindot 27 | #[rpc(name = "filecoindot_setRpcEndpoint")] 28 | fn set_rpc_endpoint(&self, urls: Vec) -> Result<()>; 29 | 30 | // verify receipt 31 | #[rpc(name = "filecoindot_verifyReceipt")] 32 | fn verify_receipt(&self, proof: String, cid: String) -> Result; 33 | 34 | // verify state 35 | #[rpc(name = "filecoindot_verifyState")] 36 | fn verify_state(&self, proof: String, cid: String) -> Result; 37 | } 38 | 39 | /// filecoindot rpc handler 40 | pub struct Filecoindot { 41 | storage: Arc>, 42 | } 43 | 44 | impl Filecoindot 45 | where 46 | T: OffchainStorage, 47 | { 48 | /// new filecoindot api 49 | pub fn new(storage: Arc>) -> Self { 50 | Self { storage } 51 | } 52 | } 53 | 54 | impl FilecoindotApi for Filecoindot 55 | where 56 | T: OffchainStorage + 'static, 57 | { 58 | fn set_rpc_endpoint(&self, urls: Vec) -> Result<()> { 59 | if urls.is_empty() 60 | || urls 61 | .iter() 62 | .any(|url| !url.starts_with("http") || Url::parse(url).is_err()) 63 | { 64 | return Err(Error::InvalidEndpoint); 65 | } 66 | 67 | self.storage.write().set( 68 | sp_offchain::STORAGE_PREFIX, 69 | FILECOIN_RPC, 70 | &urls 71 | .iter() 72 | .map(|url| url.as_bytes().to_vec()) 73 | .collect::>>() 74 | .encode(), 75 | ); 76 | Ok(()) 77 | } 78 | 79 | // verify receipt 80 | fn verify_receipt(&self, proof: String, cid: String) -> Result { 81 | let cid = Cid::try_from(&*cid)?; 82 | Ok(ProofVerify::verify_proof::>( 83 | decode_proof_from_hex(&proof)?, 84 | cid.to_bytes(), 85 | ) 86 | .is_ok()) 87 | } 88 | 89 | // verify state 90 | fn verify_state(&self, proof: String, cid: String) -> Result { 91 | let cid = Cid::try_from(&*cid)?; 92 | Ok(ProofVerify::verify_proof::( 93 | decode_proof_from_hex(&proof)?, 94 | cid.to_bytes(), 95 | ) 96 | .is_ok()) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /filecoindot-rpc/src/result.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | use jsonrpc_core::types::error::{Error as RpcError, ErrorCode}; 4 | 5 | /// filecoindot rpc result 6 | pub type Result = core::result::Result; 7 | 8 | #[derive(Debug, thiserror::Error)] 9 | pub enum Error { 10 | #[error("invalid filecoin rpc endpoint")] 11 | InvalidEndpoint, 12 | #[error("hex error: {0}")] 13 | FromHex(#[from] hex::FromHexError), 14 | #[error("codec error: {0}")] 15 | Codec(#[from] codec::Error), 16 | #[error("cid error: {0}")] 17 | CidE(#[from] cid::Error), 18 | } 19 | 20 | impl From for RpcError { 21 | fn from(e: Error) -> RpcError { 22 | RpcError { 23 | code: ErrorCode::InvalidRequest, 24 | message: e.to_string(), 25 | data: None, 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /filecoindot/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = 'filecoindot' 3 | authors = ['matthias@chainsafe.io'] 4 | description = 'Pallet that bridges Filecoin to substrate based chains.' 5 | version = '0.0.1' 6 | license = "GPL-3.0" 7 | homepage = 'https://github.com/ChainSafe/filecoindot' 8 | repository = 'https://github.com/ChainSafe/filecoindot' 9 | edition = "2021" 10 | 11 | [package.metadata.docs.rs] 12 | targets = ['x86_64-unknown-linux-gnu'] 13 | 14 | [dependencies] 15 | codec = { package = "parity-scale-codec", version = "2.3.1", default-features = false } 16 | serde = { version = "1.0.130", features = [ "derive" ], default-features = false } 17 | serde_json = { version = "1.0", features = [ "alloc" ], default-features = false } 18 | derive_more = "^0.99" 19 | 20 | # substrate dependencies 21 | frame-benchmarking = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.13', default-features = false, optional = true } 22 | frame-support = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.13', default-features = false } 23 | frame-system = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.13', default-features = false } 24 | sp-core = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.13', default-features = false } 25 | scale-info = { version = "1.0", default-features = false, features = ["derive"] } 26 | 27 | # filecoindot related 28 | filecoindot-io = { path = "../filecoindot-io", default-features = false } 29 | 30 | [dev-dependencies] 31 | serde = "1.0.130" 32 | bincode = "1.3.3" 33 | reqwest = { version = "0.11.9", features = [ "json" ] } 34 | parking_lot = "0.11" 35 | futures = "0.3" 36 | tokio = { version = "1.14", features = [ "full" ] } 37 | thiserror = "1.0.30" 38 | dirs = "4.0.0" 39 | 40 | sp-core = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.13', default-features = false } 41 | sp-runtime = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.13', default-features = false } 42 | sp-io = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.13', default-features = false } 43 | sp-keystore = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.13', default-features = false } 44 | ipld_hamt = { git = "https://github.com/willeslau/forest", branch = "willes/filecoindot" } 45 | ipld_amt = { git = "https://github.com/willeslau/forest", branch = "willes/filecoindot" } 46 | ipld_blockstore = { git = "https://github.com/willeslau/forest", branch = "willes/filecoindot" } 47 | cid = { package = "forest_cid", version = "0.3" } 48 | serde_cbor = { version = "0.12", features = ["tags"], package = "cs_serde_cbor" } 49 | filecoindot-proofs = { path = "../filecoindot-proofs", default-features = false } 50 | filecoindot-io = { path = "../filecoindot-io" } 51 | 52 | [features] 53 | default = ['std'] 54 | runtime-benchmarks = [ 55 | 'frame-benchmarking', 56 | 'frame-support/runtime-benchmarks', 57 | 'frame-system/runtime-benchmarks', 58 | ] 59 | std = [ 60 | 'codec/std', 61 | "scale-info/std", 62 | 'frame-support/std', 63 | 'frame-system/std', 64 | 'frame-benchmarking/std', 65 | 'filecoindot-io/std', 66 | ] 67 | -------------------------------------------------------------------------------- /filecoindot/README.md: -------------------------------------------------------------------------------- 1 | License: Unlicense 2 | -------------------------------------------------------------------------------- /filecoindot/src/benchmarking.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | //! Benchmarking setup for filecoindot 5 | 6 | use crate::*; 7 | use filecoindot_io::benchmarking::{amt_proof_generation, hamt_proof_generation}; 8 | use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite, vec}; 9 | use frame_support::traits::EnsureOrigin; 10 | use frame_system::RawOrigin; 11 | 12 | benchmarks! { 13 | add_relayer { 14 | let caller = T::ManagerOrigin::successful_origin(); 15 | let relayer: T::AccountId = account("relayer", 0, 0); 16 | }: { 17 | Pallet::::add_relayer(caller, relayer.clone())?; 18 | } verify { 19 | assert!(Relayers::::contains_key(&relayer)); 20 | } 21 | 22 | remove_relayer { 23 | let caller = T::ManagerOrigin::successful_origin(); 24 | let relayer: T::AccountId = account("relayer", 0, 0); 25 | 26 | Pallet::::add_relayer(caller.clone(), relayer.clone())?; 27 | }: { 28 | Pallet::::remove_relayer(caller, relayer.clone())?; 29 | } verify { 30 | assert!(!Relayers::::contains_key(&relayer)); 31 | } 32 | 33 | set_vote_threshold { 34 | }: { 35 | Pallet::::set_vote_threshold(T::ManagerOrigin::successful_origin(), 2)?; 36 | } verify { 37 | assert_eq!(VoteThreshold::::get(), 2); 38 | } 39 | 40 | submit_block_vote { 41 | let caller = T::ManagerOrigin::successful_origin(); 42 | let relayer: T::AccountId = account("relayer", 0, 0); 43 | 44 | Pallet::::add_relayer(caller.clone(), relayer.clone())?; 45 | Pallet::::set_vote_threshold(caller, 1)?; 46 | }: { 47 | Pallet::::submit_block_vote(RawOrigin::Signed(relayer).into(), vec![0], vec![0])?; 48 | } verify { 49 | assert!(!BlockSubmissionProposals::::contains_key(&vec![0])); 50 | } 51 | 52 | close_block_proposal { 53 | let caller = T::ManagerOrigin::successful_origin(); 54 | let relayer: T::AccountId = account("relayer", 0, 0); 55 | 56 | Pallet::::add_relayer(caller.clone(), relayer.clone())?; 57 | Pallet::::submit_block_vote(RawOrigin::Signed(relayer).into(), vec![0], vec![0])?; 58 | 59 | let now = frame_system::Pallet::::block_number(); 60 | frame_system::Pallet::::set_block_number(now + VotingPeriod::::get() + 1u32.into()); 61 | }: { 62 | Pallet::::close_block_proposal(caller, vec![0])?; 63 | } verify { 64 | assert_eq!( 65 | BlockSubmissionProposals::::get(vec![0]), 66 | None, 67 | ); 68 | } 69 | 70 | verify_receipt { 71 | let caller = T::ManagerOrigin::successful_origin(); 72 | let alice: T::AccountId = account("alice", 0, 0); 73 | let bob: T::AccountId = account("bob", 1, 1); 74 | let charlie: T::AccountId = account("charlie", 2, 2); 75 | 76 | Pallet::::add_relayer(caller.clone(), alice.clone())?; 77 | Pallet::::add_relayer(caller.clone(), bob.clone())?; 78 | Pallet::::add_relayer(caller, charlie.clone())?; 79 | 80 | let block_cid = vec![0, 1]; 81 | let message_cid = vec![0, 1]; 82 | 83 | Pallet::::submit_block_vote( 84 | RawOrigin::Signed(alice.clone()).into(), 85 | block_cid.clone(), 86 | message_cid.clone() 87 | ).unwrap(); 88 | Pallet::::submit_block_vote( 89 | RawOrigin::Signed(bob).into(), 90 | block_cid.clone(), 91 | message_cid.clone() 92 | ).unwrap(); 93 | Pallet::::submit_block_vote( 94 | RawOrigin::Signed(charlie).into(), 95 | block_cid.clone(), 96 | message_cid 97 | ).unwrap(); 98 | let (proof, cid) = amt_proof_generation(100); 99 | }: { 100 | Pallet::::verify_receipt(RawOrigin::Signed(alice).into(), proof, block_cid, cid)?; 101 | } 102 | 103 | verify_state { 104 | let caller = T::ManagerOrigin::successful_origin(); 105 | let alice: T::AccountId = account("alice", 0, 0); 106 | let bob: T::AccountId = account("bob", 1, 1); 107 | let charlie: T::AccountId = account("charlie", 2, 2); 108 | 109 | Pallet::::add_relayer(caller.clone(), alice.clone())?; 110 | Pallet::::add_relayer(caller.clone(), bob.clone())?; 111 | Pallet::::add_relayer(caller, charlie.clone())?; 112 | 113 | let block_cid = vec![0, 1]; 114 | let message_cid = vec![0, 1]; 115 | 116 | Pallet::::submit_block_vote( 117 | RawOrigin::Signed(alice.clone()).into(), 118 | block_cid.clone(), 119 | message_cid.clone() 120 | ).unwrap(); 121 | Pallet::::submit_block_vote( 122 | RawOrigin::Signed(bob).into(), 123 | block_cid.clone(), 124 | message_cid.clone() 125 | ).unwrap(); 126 | Pallet::::submit_block_vote( 127 | RawOrigin::Signed(charlie).into(), 128 | block_cid.clone(), 129 | message_cid 130 | ).unwrap(); 131 | 132 | let (proof, cid) = hamt_proof_generation(); 133 | }: { 134 | Pallet::::verify_state(RawOrigin::Signed(alice).into(), proof, block_cid, cid)?; 135 | } 136 | 137 | verify_message { 138 | let caller = T::ManagerOrigin::successful_origin(); 139 | let alice: T::AccountId = account("alice", 0, 0); 140 | let bob: T::AccountId = account("bob", 1, 1); 141 | let charlie: T::AccountId = account("charlie", 2, 2); 142 | 143 | Pallet::::add_relayer(caller.clone(), alice.clone())?; 144 | Pallet::::add_relayer(caller.clone(), bob.clone())?; 145 | Pallet::::add_relayer(caller, charlie.clone())?; 146 | 147 | let block_cid = vec![0, 1]; 148 | let message_cid = vec![0, 1]; 149 | 150 | Pallet::::submit_block_vote( 151 | RawOrigin::Signed(alice.clone()).into(), 152 | block_cid.clone(), 153 | message_cid.clone() 154 | ).unwrap(); 155 | Pallet::::submit_block_vote( 156 | RawOrigin::Signed(bob).into(), 157 | block_cid.clone(), 158 | message_cid.clone() 159 | ).unwrap(); 160 | Pallet::::submit_block_vote( 161 | RawOrigin::Signed(charlie).into(), 162 | block_cid.clone(), 163 | message_cid 164 | ).unwrap(); 165 | 166 | let (proof, cid) = hamt_proof_generation(); 167 | }: { 168 | Pallet::::verify_message(RawOrigin::Signed(alice).into(), proof, block_cid, cid)?; 169 | } 170 | } 171 | 172 | impl_benchmark_test_suite!( 173 | Pallet, 174 | crate::tests::mock::ExtBuilder::default().build(), 175 | crate::tests::mock::Test 176 | ); 177 | -------------------------------------------------------------------------------- /filecoindot/src/crypto.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | //! crypto identifier for filecoindot 5 | use sp_core::crypto::KeyTypeId; 6 | 7 | pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"fdot"); 8 | 9 | use frame_support::sp_runtime::{ 10 | app_crypto::{app_crypto, sr25519}, 11 | traits::Verify, 12 | MultiSignature, MultiSigner, 13 | }; 14 | use sp_core::sr25519::Signature as Sr25519Signature; 15 | 16 | app_crypto!(sr25519, KEY_TYPE); 17 | 18 | /// filecoindot crypto type 19 | pub struct FilecoindotId; 20 | 21 | // implemented for runtime 22 | impl frame_system::offchain::AppCrypto for FilecoindotId { 23 | type RuntimeAppPublic = Public; 24 | type GenericSignature = sp_core::sr25519::Signature; 25 | type GenericPublic = sp_core::sr25519::Public; 26 | } 27 | 28 | impl frame_system::offchain::AppCrypto<::Signer, Sr25519Signature> 29 | for FilecoindotId 30 | { 31 | type RuntimeAppPublic = Public; 32 | type GenericSignature = sp_core::sr25519::Signature; 33 | type GenericPublic = sp_core::sr25519::Public; 34 | } 35 | -------------------------------------------------------------------------------- /filecoindot/src/ocw/api/chain_head.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | use crate::ocw::{api::Api, types::TipSet}; 4 | use frame_support::sp_std::vec::Vec; 5 | 6 | /// Method `Filecoin.ChainHeight` 7 | pub const CHAIN_HEAD: &str = "Filecoin.ChainHead"; 8 | 9 | /// `Filecoin.ChainHeight` 10 | pub struct ChainHead; 11 | 12 | impl Api for ChainHead { 13 | const METHOD: &'static str = CHAIN_HEAD; 14 | type Params = Vec<()>; 15 | type Result = TipSet; 16 | } 17 | -------------------------------------------------------------------------------- /filecoindot/src/ocw/api/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | //! Filecoin APIs 5 | 6 | pub use self::chain_head::ChainHead; 7 | use frame_support::{ 8 | log, 9 | sp_runtime::offchain::{ 10 | http::{Error, Request}, 11 | Timestamp, 12 | }, 13 | sp_std::{vec, vec::Vec}, 14 | }; 15 | use serde::{de::DeserializeOwned, Deserialize, Serialize}; 16 | 17 | mod chain_head; 18 | 19 | /// Wrapper for jsonrpc result 20 | #[derive(Clone, Debug, Serialize, Deserialize)] 21 | pub struct Resp { 22 | /// JsonRPC result 23 | pub result: T, 24 | } 25 | 26 | /// Request JSON body 27 | #[derive(Clone, Debug, Serialize, Deserialize)] 28 | pub struct Req { 29 | /// reponse id 30 | pub id: u8, 31 | /// JsonRPC method 32 | pub method: &'static str, 33 | /// JsonRPC version 34 | pub jsonrpc: &'static str, 35 | /// JsonRPC result 36 | pub params: T, 37 | } 38 | 39 | /// Abstract filecoin api requests 40 | pub trait Api: Sized { 41 | const METHOD: &'static str; 42 | type Params: Serialize + Send + Sync + Clone; 43 | type Result: Serialize + DeserializeOwned + core::fmt::Debug; 44 | 45 | fn iter_req( 46 | &self, 47 | endpoints: &[&str], 48 | params: Self::Params, 49 | deadline: Timestamp, 50 | ) -> Result { 51 | let mut result = Err(Error::IoError); 52 | for endpoint in endpoints { 53 | result = self.req(endpoint, params.clone(), deadline); 54 | if result.is_ok() { 55 | return result; 56 | } 57 | } 58 | 59 | result 60 | } 61 | 62 | /// Request method with params 63 | fn req( 64 | &self, 65 | endpoint: &str, 66 | params: Self::Params, 67 | deadline: Timestamp, 68 | ) -> Result { 69 | let body = serde_json::to_vec(&Req { 70 | id: 0, 71 | method: Self::METHOD, 72 | jsonrpc: "2.0", 73 | params, 74 | }) 75 | .map_err(|_| Error::IoError)?; 76 | 77 | // build request 78 | let req = Request::post(endpoint, vec![body]) 79 | .add_header("Content-Type", "application/json") 80 | .deadline(deadline); 81 | 82 | // get response 83 | let resp = req 84 | .send() 85 | .map_err(|e| { 86 | log::error!("send request failed {:?}", e); 87 | Error::IoError 88 | })? 89 | .wait() 90 | .map_err(|e| { 91 | log::error!("wait request faild {:?}", e); 92 | Error::IoError 93 | })? 94 | .body() 95 | .collect::>(); 96 | 97 | // deserialize response 98 | Ok(serde_json::from_slice::>(&resp) 99 | .map_err(|e| { 100 | log::error!("result {:?}", resp); 101 | log::error!("parse result failed {:?}", e); 102 | Error::IoError 103 | })? 104 | .result) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /filecoindot/src/ocw/de.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | use frame_support::sp_std::vec::Vec; 5 | use serde::de::{Deserialize, Deserializer, Error as _}; 6 | use serde_json::Value; 7 | 8 | /// deserialize json `Value` to `Vec` 9 | pub fn bytes<'de, D>(data: D) -> Result, D::Error> 10 | where 11 | D: Deserializer<'de>, 12 | { 13 | let value = Value::deserialize(data)?; 14 | if let Value::String(s) = value { 15 | Ok(s.as_bytes().to_vec()) 16 | } else { 17 | Err(D::Error::custom("field not string")) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /filecoindot/src/ocw/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | #![cfg_attr(not(feature = "std"), no_std)] 4 | #![allow(dead_code)] 5 | 6 | use crate::{ 7 | crypto::FilecoindotId, 8 | ocw::{ 9 | api::{Api, ChainHead}, 10 | result::{Error, Result}, 11 | }, 12 | Call, Config, Relayers, 13 | }; 14 | use frame_support::{ 15 | codec::Encode, 16 | sp_runtime::{ 17 | offchain::{storage::StorageValueRef, Timestamp}, 18 | traits::Verify, 19 | RuntimeAppPublic, 20 | }, 21 | sp_std::vec::Vec, 22 | traits::Get, 23 | }; 24 | use frame_system::offchain::{SendSignedTransaction, Signer}; 25 | use sp_core::sr25519::Signature as Sr25519Signature; 26 | 27 | pub mod api; 28 | mod de; 29 | mod result; 30 | pub mod types; 31 | 32 | #[cfg(test)] 33 | mod tests; 34 | 35 | /// the storage key of filecoin rpc endpoint 36 | pub const FILECOIN_RPC: &[u8] = b"FILECOIN_RPC"; 37 | 38 | /// offchain worker entry 39 | pub fn offchain_worker(block_number: T::BlockNumber) -> Result<()> { 40 | // get encoded urls from storage 41 | let urls = StorageValueRef::persistent(FILECOIN_RPC) 42 | .get::>>() 43 | .map_err(|_| Error::GetStorageFailed)? 44 | .ok_or(Error::FilecoinRpcNotSet)?; 45 | 46 | // decode endpoints 47 | let endpoints: Vec<&str> = urls 48 | .iter() 49 | .map(|url_bytes| core::str::from_utf8(url_bytes)) 50 | .collect::, _>>() 51 | .map_err(|_| Error::FormatBytesFailed)?; 52 | 53 | // check if endpoints is empty 54 | if endpoints.is_empty() { 55 | return Err(Error::FilecoinRpcNotSet); 56 | } 57 | 58 | // bootstrap ocw 59 | bootstrap::(block_number, &endpoints)?; 60 | 61 | Ok(()) 62 | } 63 | 64 | /// bootstrap filcoindot ocw 65 | fn bootstrap(_: T::BlockNumber, urls: &[&str]) -> Result<()> { 66 | let all_public: Vec> = ::Signer, 68 | Sr25519Signature, 69 | >>::RuntimeAppPublic::all() 70 | .into_iter() 71 | .map(|key| key.encode()) 72 | .collect(); 73 | 74 | // check if keystore has key listed in Relayers 75 | let mut no_relayer = true; 76 | if Relayers::::iter().any(|relayer| all_public.contains(&relayer.encode())) { 77 | no_relayer = false; 78 | } 79 | 80 | let signer = Signer::::any_account(); 81 | if !signer.can_sign() || no_relayer { 82 | return Err(Error::NoRelayerFound); 83 | } 84 | 85 | vote_on_chain_head(signer, urls) 86 | } 87 | 88 | fn vote_on_chain_head(signer: Signer, urls: &[&str]) -> Result<()> { 89 | let pairs = ChainHead 90 | .iter_req( 91 | urls, 92 | Vec::new(), 93 | Timestamp::from_unix_millis(T::OffchainWorkerTimeout::get()), 94 | ) 95 | .map_err(|_| Error::HttpError)? 96 | .pairs()?; 97 | 98 | if pairs 99 | .into_iter() 100 | .map(|(cid, msg_root)| { 101 | // FIXME: 102 | // 103 | // still requires taking the ownership even under `into_iter()` 104 | let (_, res) = signer 105 | .send_signed_transaction(|_| Call::submit_block_vote { 106 | block_cid: cid.to_vec(), 107 | message_root_cid: msg_root.to_vec(), 108 | }) 109 | .ok_or(Error::NoTxResult)?; 110 | 111 | let _ = res.map_err(|_| Error::OffchainSignedTxError)?; 112 | Ok(()) 113 | }) 114 | .any(|x| x == Err(Error::OffchainSignedTxError)) 115 | { 116 | return Err(Error::OffchainSignedTxError); 117 | } 118 | 119 | Ok(()) 120 | } 121 | -------------------------------------------------------------------------------- /filecoindot/src/ocw/result.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | #![allow(clippy::enum_variant_names)] 4 | use derive_more::Display; 5 | 6 | #[derive(Debug, Display, PartialEq, Eq)] 7 | pub enum Error { 8 | #[display(fmt = "failed to parse bytes as str")] 9 | FormatBytesFailed, 10 | #[display(fmt = "get offchain worker storage failed")] 11 | GetStorageFailed, 12 | #[display(fmt = "haven't set filecoin rpc yet")] 13 | FilecoinRpcNotSet, 14 | #[display(fmt = "blocks and cids not matched in tipset")] 15 | InvalidTipSet, 16 | #[display(fmt = "http request failed")] 17 | HttpError, 18 | #[display(fmt = "signed tx error")] 19 | OffchainSignedTxError, 20 | #[display(fmt = "no tx result yet")] 21 | NoTxResult, 22 | #[display(fmt = "no relayer found")] 23 | NoRelayerFound, 24 | } 25 | 26 | pub type Result = core::result::Result; 27 | -------------------------------------------------------------------------------- /filecoindot/src/ocw/tests/data.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | use crate::ocw::types::{Block, Cid, TipSet}; 4 | 5 | pub fn get_tip_set_by_height_1199840() -> TipSet { 6 | TipSet { 7 | cids: vec![ 8 | Cid { 9 | inner: "bafy2bzacedbaliyx3k64d4rxy5q2og3wf5r5e2ra6bvf52ogldc6oad3jukbe" 10 | .as_bytes() 11 | .to_vec(), 12 | }, 13 | Cid { 14 | inner: "bafy2bzaceblrey44c6ekyu7iu6dni4inrjgnyp7sgjrrgg3xnd3poxjrt2v2i" 15 | .as_bytes() 16 | .to_vec(), 17 | }, 18 | Cid { 19 | inner: "bafy2bzacedd2wb4ijvvowm2gq3izffhl2oqlogigfubizozgbgo5l7rk73ick" 20 | .as_bytes() 21 | .to_vec(), 22 | }, 23 | Cid { 24 | inner: "bafy2bzacecav4sjwonnjryjb5kmrint45yenyhorzn2it5noxdqhnudsquyoo" 25 | .as_bytes() 26 | .to_vec(), 27 | }, 28 | ], 29 | blocks: vec![ 30 | Block { 31 | messages: Cid { 32 | inner: "bafy2bzacea4v4uwhlffeznk5sn2naw7zsl6tahsstc37ygongb3tppnqjosey" 33 | .as_bytes() 34 | .to_vec(), 35 | }, 36 | }, 37 | Block { 38 | messages: Cid { 39 | inner: "bafy2bzacebyoxanjivzgsj3aisd5e5wrdai3oeqodxnkw2gbo2yctt33mbeeo" 40 | .as_bytes() 41 | .to_vec(), 42 | }, 43 | }, 44 | Block { 45 | messages: Cid { 46 | inner: "bafy2bzacebizldd4vzjmjqp6gmmtixshxo7xlc4fl77drjmvhjg6zm5z6ojoc" 47 | .as_bytes() 48 | .to_vec(), 49 | }, 50 | }, 51 | Block { 52 | messages: Cid { 53 | inner: "bafy2bzaceducpch7kljxpbsybi5uc3wljabh3zfbk2jvhpk56c2a4gnjbveoc" 54 | .as_bytes() 55 | .to_vec(), 56 | }, 57 | }, 58 | ], 59 | height: 1199840, 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /filecoindot/src/ocw/tests/ext/db.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | //! Offchain db implementation 5 | use super::OffchainExt; 6 | use sp_core::offchain::{DbExternalities, StorageKind}; 7 | 8 | impl DbExternalities for OffchainExt { 9 | fn local_storage_set(&mut self, _kind: StorageKind, key: &[u8], value: &[u8]) { 10 | let mut state = self.0.write(); 11 | let _ = state.db.insert(key.to_vec(), value.to_vec()); 12 | } 13 | 14 | fn local_storage_clear(&mut self, _kind: StorageKind, key: &[u8]) { 15 | let mut state = self.0.write(); 16 | let _ = state.db.remove(key); 17 | } 18 | 19 | fn local_storage_compare_and_set( 20 | &mut self, 21 | _kind: StorageKind, 22 | key: &[u8], 23 | _old_value: Option<&[u8]>, 24 | new_value: &[u8], 25 | ) -> bool { 26 | let mut state = self.0.write(); 27 | state.db.insert(key.to_vec(), new_value.to_vec()).is_some() 28 | } 29 | 30 | fn local_storage_get(&mut self, _kind: StorageKind, key: &[u8]) -> Option> { 31 | let state = self.0.read(); 32 | state.db.get(key).map(|v| v.to_vec()) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /filecoindot/src/ocw/tests/ext/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | //! offchain ext for testing usages 5 | 6 | mod db; 7 | mod ext; 8 | mod result; 9 | mod state; 10 | 11 | pub use self::{ 12 | ext::OffchainExt, 13 | result::{Error, Result}, 14 | }; 15 | -------------------------------------------------------------------------------- /filecoindot/src/ocw/tests/ext/result.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | use thiserror::Error as ThisError; 5 | 6 | #[derive(ThisError, Debug)] 7 | pub enum Error { 8 | #[error("could not find FILECOINT_RPC in environment variables")] 9 | NoRPCEndpoint, 10 | #[error("Only supports http endpoints for now")] 11 | NotHttpEndpoint, 12 | #[error("rpc request failed")] 13 | RequestFailed(#[from] reqwest::Error), 14 | #[error("Serialize storage key failed")] 15 | BuildStorageKeyFailed(#[from] bincode::Error), 16 | #[error("serde_json error")] 17 | SerdeJson(#[from] serde_json::Error), 18 | } 19 | 20 | pub type Result = std::result::Result; 21 | -------------------------------------------------------------------------------- /filecoindot/src/ocw/tests/ext/state.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | use reqwest::{Client, RequestBuilder}; 4 | use std::collections::BTreeMap; 5 | 6 | /// Request with resp body and reading ptr 7 | pub struct Request { 8 | pub req: RequestBuilder, 9 | pub resp: Response, 10 | pub read: usize, 11 | } 12 | 13 | impl From for Request { 14 | fn from(req: RequestBuilder) -> Self { 15 | Self { 16 | req, 17 | resp: Default::default(), 18 | read: 0, 19 | } 20 | } 21 | } 22 | 23 | #[derive(Default)] 24 | pub struct Response { 25 | pub status: u16, 26 | pub headers: Vec<(Vec, Vec)>, 27 | pub body: Vec, 28 | } 29 | 30 | /// filecoindot offchain state 31 | #[derive(Default)] 32 | pub struct OffchainState { 33 | pub counter: u16, 34 | pub client: Client, 35 | pub db: BTreeMap, Vec>, 36 | pub requests: BTreeMap, 37 | } 38 | -------------------------------------------------------------------------------- /filecoindot/src/ocw/tests/get_tip_set_by_height.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | use crate::ocw::{api::Api, types::TipSet}; 4 | use frame_support::sp_std::vec::Vec; 5 | 6 | /// Method `Filecoin.ChainGetTipSetByHeight` 7 | pub const CHAIN_GET_TIP_SET_BY_HEIGHT: &str = "Filecoin.ChainGetTipSetByHeight"; 8 | 9 | /// `Filecoin.ChainGetTipSetByHeight` 10 | pub struct ChainGetTipSetByHeight; 11 | 12 | impl Api for ChainGetTipSetByHeight { 13 | const METHOD: &'static str = CHAIN_GET_TIP_SET_BY_HEIGHT; 14 | type Params = Vec>; 15 | type Result = TipSet; 16 | } 17 | -------------------------------------------------------------------------------- /filecoindot/src/ocw/tests/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | #![allow(unused_imports)] 4 | mod data; 5 | mod ext; 6 | mod get_tip_set_by_height; 7 | 8 | use crate::ocw::api::{Api, ChainHead}; 9 | use ext::OffchainExt; 10 | use frame_support::sp_runtime::offchain::Timestamp; 11 | use get_tip_set_by_height::ChainGetTipSetByHeight; 12 | use sp_core::offchain::{OffchainDbExt, OffchainWorkerExt}; 13 | 14 | // infura rpc for testing 15 | const FILECOIN_RPC: &str = "https://api.node.glif.io"; 16 | 17 | // #[test] 18 | // fn test_http_request() { 19 | // let offchain = OffchainExt::new(); 20 | // let mut t = sp_io::TestExternalities::default(); 21 | // t.register_extension(OffchainWorkerExt::new(offchain.clone())); 22 | // t.register_extension(OffchainDbExt::new(offchain)); 23 | // 24 | // t.execute_with(|| { 25 | // assert_eq!( 26 | // ChainGetTipSetByHeight 27 | // .req( 28 | // FILECOIN_RPC, 29 | // vec![Some(1199840), None], 30 | // Timestamp::from_unix_millis(1_000_000) 31 | // ) 32 | // .unwrap(), 33 | // data::get_tip_set_by_height_1199840() 34 | // ); 35 | // }) 36 | // } 37 | 38 | #[test] 39 | fn test_can_parse_chain_head() { 40 | let offchain = OffchainExt::new(); 41 | let mut t = sp_io::TestExternalities::default(); 42 | t.register_extension(OffchainWorkerExt::new(offchain.clone())); 43 | t.register_extension(OffchainDbExt::new(offchain)); 44 | 45 | t.execute_with(|| { 46 | assert!(ChainHead 47 | .req(FILECOIN_RPC, vec![], Timestamp::from_unix_millis(1_000_000)) 48 | .is_ok()); 49 | }) 50 | } 51 | -------------------------------------------------------------------------------- /filecoindot/src/ocw/types.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | //! Filecoin api types 5 | #![allow(missing_docs)] 6 | use crate::ocw::{Error, Result}; 7 | use frame_support::sp_std::vec::Vec; 8 | use serde::{Deserialize, Serialize}; 9 | 10 | /// Response of the [`ChainHead`](https://docs.filecoin.io/reference/lotus-api/#chainhead) RPC call 11 | #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] 12 | pub struct GetChainHead { 13 | pub result: TipSet, 14 | } 15 | 16 | #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] 17 | pub struct TipSet { 18 | #[serde(rename = "Cids")] 19 | pub cids: Vec, 20 | #[serde(rename = "Blocks")] 21 | pub blocks: Vec, 22 | #[serde(rename = "Height")] 23 | pub height: i64, 24 | } 25 | 26 | impl TipSet { 27 | /// get (cid, message_root) pairs 28 | pub fn pairs(self) -> Result, Vec)>> { 29 | if self.cids.len() != self.blocks.len() { 30 | return Err(Error::InvalidTipSet); 31 | } 32 | 33 | Ok(self 34 | .cids 35 | .into_iter() 36 | .zip(self.blocks.into_iter()) 37 | .map(|(cid, block)| (cid.inner, block.messages.inner)) 38 | .collect()) 39 | } 40 | } 41 | 42 | #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] 43 | pub struct Block { 44 | #[serde(rename = "Messages")] 45 | pub messages: Cid, 46 | } 47 | 48 | #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] 49 | pub struct Cid { 50 | #[serde(deserialize_with = "crate::ocw::de::bytes")] 51 | #[serde(rename = "/")] 52 | pub inner: Vec, 53 | } 54 | -------------------------------------------------------------------------------- /filecoindot/src/tests/mock.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | use crate as pallet; 5 | use frame_support::construct_runtime; 6 | use frame_support::pallet_prelude::EnsureOrigin; 7 | #[cfg(test)] 8 | use frame_support::pallet_prelude::GenesisBuild; 9 | use frame_support::{parameter_types, sp_runtime, sp_std}; 10 | use frame_system::ensure_signed; 11 | use sp_core::{ 12 | sr25519::{Public, Signature}, 13 | H256, 14 | }; 15 | use sp_runtime::{testing::Header, traits::IdentityLookup}; 16 | use sp_runtime::{ 17 | testing::TestXt, 18 | traits::{Extrinsic as ExtrinsicT, Verify}, 19 | }; 20 | type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; 21 | type Block = frame_system::mocking::MockBlock; 22 | 23 | pub type AccountId = Public; 24 | pub const ALICE: AccountId = Public([1u8; 32]); 25 | pub const RELAYER1: AccountId = Public([2u8; 32]); 26 | pub const RELAYER2: AccountId = Public([3u8; 32]); 27 | pub const RELAYER3: AccountId = Public([4u8; 32]); 28 | pub const RELAYER4: AccountId = Public([5u8; 32]); 29 | 30 | // Configure a mock runtime to test the pallet. 31 | construct_runtime!( 32 | pub enum Test where 33 | Block = Block, 34 | NodeBlock = Block, 35 | UncheckedExtrinsic = UncheckedExtrinsic, 36 | { 37 | System: frame_system::{Pallet, Call, Config, Storage, Event}, 38 | FileCoinModule: pallet::{Pallet, Call, Storage, Event}, 39 | } 40 | ); 41 | 42 | parameter_types! { 43 | pub const BlockHashCount: u64 = 250; 44 | pub const SS58Prefix: u8 = 42; 45 | pub const OffchainWorkerTimeout: u64 = 1_000_000; 46 | } 47 | 48 | /// An implementation of EnsureOrigin 49 | // This is for the extrinsics only can be called after the 50 | /// approval of the committee 51 | pub struct MockedRelayerAdmin(sp_std::marker::PhantomData); 52 | 53 | impl EnsureOrigin<::Origin> for MockedRelayerAdmin { 54 | type Success = AccountId; 55 | fn try_origin( 56 | o: ::Origin, 57 | ) -> Result::Origin> { 58 | let account = ensure_signed(o.clone()).unwrap(); 59 | if account == ALICE { 60 | Ok(account) 61 | } else { 62 | Err(o) 63 | } 64 | } 65 | 66 | #[cfg(feature = "runtime-benchmarks")] 67 | fn successful_origin() -> ::Origin { 68 | Origin::signed(ALICE) 69 | } 70 | } 71 | 72 | impl frame_system::Config for Test { 73 | type Origin = Origin; 74 | type Call = Call; 75 | type Index = u64; 76 | type BlockNumber = u64; 77 | type Hash = H256; 78 | type Hashing = sp_runtime::traits::BlakeTwo256; 79 | type AccountId = sp_core::sr25519::Public; 80 | type Lookup = IdentityLookup; 81 | type Header = Header; 82 | type Event = Event; 83 | type BlockHashCount = BlockHashCount; 84 | type BlockWeights = (); 85 | type BlockLength = (); 86 | type Version = (); 87 | type PalletInfo = PalletInfo; 88 | type AccountData = (); 89 | type OnNewAccount = (); 90 | type OnKilledAccount = (); 91 | type DbWeight = (); 92 | type BaseCallFilter = frame_support::traits::Everything; 93 | type SystemWeightInfo = (); 94 | type SS58Prefix = (); 95 | type OnSetCode = (); 96 | } 97 | 98 | pub type Extrinsic = TestXt; 99 | 100 | impl frame_system::offchain::SigningTypes for Test { 101 | type Public = ::Signer; 102 | type Signature = Signature; 103 | } 104 | 105 | impl frame_system::offchain::SendTransactionTypes for Test 106 | where 107 | Call: From, 108 | { 109 | type OverarchingCall = Call; 110 | type Extrinsic = Extrinsic; 111 | } 112 | 113 | impl frame_system::offchain::CreateSignedTransaction for Test 114 | where 115 | Call: From, 116 | { 117 | fn create_transaction>( 118 | call: Call, 119 | _public: ::Signer, 120 | _account: AccountId, 121 | nonce: u64, 122 | ) -> Option<(Call, ::SignaturePayload)> { 123 | Some((call, (nonce, ()))) 124 | } 125 | } 126 | 127 | impl pallet::Config for Test { 128 | type ManagerOrigin = MockedRelayerAdmin; 129 | type Event = Event; 130 | type WeightInfo = (); 131 | type AuthorityId = pallet::FilecoindotId; 132 | type OffchainWorkerTimeout = OffchainWorkerTimeout; 133 | } 134 | 135 | pub struct ExtBuilder { 136 | pub vote_threshold: u32, 137 | pub relayers: Vec, 138 | } 139 | 140 | const RELAYERS: [AccountId; 3] = [RELAYER1, RELAYER2, RELAYER3]; 141 | 142 | impl Default for ExtBuilder { 143 | fn default() -> Self { 144 | Self { 145 | vote_threshold: 3, 146 | relayers: Vec::from(RELAYERS.clone()), 147 | } 148 | } 149 | } 150 | 151 | #[cfg(test)] 152 | impl ExtBuilder { 153 | pub fn build(self) -> sp_io::TestExternalities { 154 | let mut t = frame_system::GenesisConfig::default() 155 | .build_storage::() 156 | .unwrap(); 157 | 158 | pallet::GenesisConfig:: { 159 | vote_threshold: self.vote_threshold, 160 | vote_period: 1, 161 | relayers: Vec::from(RELAYERS.clone()), 162 | } 163 | .assimilate_storage(&mut t) 164 | .unwrap(); 165 | t.into() 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /filecoindot/src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | pub mod mock; 5 | mod ocw; 6 | mod relayer; 7 | mod verify; 8 | mod vote; 9 | -------------------------------------------------------------------------------- /filecoindot/src/tests/ocw.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | use crate::{ 5 | ocw::{ 6 | api::{Req, Resp}, 7 | offchain_worker, 8 | types::TipSet, 9 | }, 10 | tests::mock::*, 11 | Relayers, 12 | }; 13 | use sp_core::{ 14 | offchain::{testing, OffchainDbExt, OffchainWorkerExt, TransactionPoolExt}, 15 | Decode, 16 | }; 17 | use sp_keystore::{testing::KeyStore, KeystoreExt, SyncCryptoStore}; 18 | use sp_runtime::{offchain::storage::StorageValueRef, RuntimeAppPublic}; 19 | use std::sync::Arc; 20 | 21 | const PHRASE: &str = "news slush supreme milk chapter athlete soap sausage put clutch what kitten"; 22 | const FILECOIN_API: &str = "http://filecoin.api"; 23 | const CHAIN_HEAD_RESP: &[u8] = br#" 24 | { 25 | "result": { 26 | "Cids": [ 27 | { "/": "bafy2bzaced43kofq4s4fvsv7esoh2tlst56wngbszkhawfgey4geszwsjj3ww" }, 28 | { "/": "bafy2bzacebbpmhfnuuxwofaaabwuhhr3zpmuypuy6yxtan4cs4cs4zbtiymzo" }, 29 | { "/": "bafy2bzacecuxwhxpehke5u2e677ies6fmtywzgnzoagjanlwtpkkkeofspysa" }, 30 | { "/": "bafy2bzaced2mght4hso3osblkjjsvcsqby7h3sg3vgq4n4f5fzh5b3dxvftua" }, 31 | { "/": "bafy2bzacedngsxfjlk7mbqtpp2i3nzpjoyp4iim4ha4ozrxc57dr7vfm3ten2" } 32 | ], 33 | "Blocks": [ 34 | { 35 | "Messages": { 36 | "/": "bafy2bzacedhiusftmig7alne5gkuywadhrnnketndnjygr7gdpw4w4cq2u5b2" 37 | } 38 | }, 39 | { 40 | "Messages": { 41 | "/": "bafy2bzacecrt4qybm3klbdri5ofk4lpvnafmo2jc2brpaq25n6pjcr2k3jmcq" 42 | } 43 | }, 44 | { 45 | "Messages": { 46 | "/": "bafy2bzacedjurwnsndbb4zz7kk7lxtrs7g4dxsasjlmggvouhfikug543dnta" 47 | } 48 | }, 49 | { 50 | "Messages": { 51 | "/": "bafy2bzaceba54ejfcbd2cvzqbvfaczbtdot2tfy7l4hlmnn64vxm5fn5bolog" 52 | } 53 | }, 54 | { 55 | "Messages": { 56 | "/": "bafy2bzacebqjfvo2k2x6holp6olhpj7f5wuy35p4mnfzkpqm7agqolpf7qmtg" 57 | } 58 | } 59 | ], 60 | "Height": 1273769 61 | } 62 | } 63 | "#; 64 | 65 | #[test] 66 | fn should_submit_vote_in_ocw() { 67 | let (offchain, state) = testing::TestOffchainExt::new(); 68 | let (pool, pool_state) = testing::TestTransactionPoolExt::new(); 69 | 70 | // set keystore 71 | let keystore = KeyStore::new(); 72 | 73 | // set up relayer 74 | let relayer = 75 | SyncCryptoStore::sr25519_generate_new(&keystore, crate::crypto::Public::ID, Some(PHRASE)) 76 | .unwrap(); 77 | 78 | // set expected response 79 | { 80 | let mut state = state.write(); 81 | let params: Vec<()> = Default::default(); 82 | state.expect_request(testing::PendingRequest { 83 | method: "POST".into(), 84 | uri: FILECOIN_API.into(), 85 | response: Some(CHAIN_HEAD_RESP.to_vec()), 86 | sent: true, 87 | headers: vec![("Content-Type".into(), "application/json".into())], 88 | body: serde_json::to_string(&Req { 89 | id: 0, 90 | method: "Filecoin.ChainHead", 91 | jsonrpc: "2.0", 92 | params, 93 | }) 94 | .unwrap() 95 | .as_bytes() 96 | .to_vec(), 97 | ..Default::default() 98 | }); 99 | } 100 | 101 | // register extensions 102 | let mut t = ExtBuilder::default().build(); 103 | t.register_extension(OffchainWorkerExt::new(offchain.clone())); 104 | t.register_extension(OffchainDbExt::new(offchain)); 105 | t.register_extension(TransactionPoolExt::new(pool)); 106 | t.register_extension(KeystoreExt(Arc::new(keystore))); 107 | 108 | // execute in test env 109 | t.execute_with(|| { 110 | // add inserted key as relayer 111 | Relayers::::insert(&relayer, ()); 112 | 113 | // set rpc endpoint 114 | let rpc = StorageValueRef::persistent("FILECOIN_RPC".as_bytes()); 115 | rpc.set(&vec![FILECOIN_API.as_bytes().to_vec()]); 116 | 117 | // bootstrap ocw on block 1 118 | offchain_worker::(1u32.into()).unwrap(); 119 | 120 | // get submited transactions 121 | let resp = serde_json::from_slice::>(CHAIN_HEAD_RESP) 122 | .unwrap() 123 | .result; 124 | for (i, raw_tx) in pool_state.read().transactions.iter().enumerate() { 125 | let tx = Extrinsic::decode(&mut &*raw_tx.to_owned()).unwrap(); 126 | assert_eq!(tx.signature, Some((i as u64, ()))); 127 | assert_eq!( 128 | tx.call, 129 | Call::FileCoinModule(crate::Call::submit_block_vote { 130 | block_cid: resp.cids[i].inner.to_vec(), 131 | message_root_cid: resp.blocks[i].messages.inner.to_vec() 132 | }) 133 | ); 134 | } 135 | }); 136 | } 137 | -------------------------------------------------------------------------------- /filecoindot/src/tests/relayer.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | use crate::{tests::mock::*, Error, RelayerCount, VoteThreshold}; 5 | use frame_support::{assert_err, assert_ok}; 6 | use sp_runtime::DispatchError::BadOrigin; 7 | 8 | #[test] 9 | fn set_threshold_works() { 10 | let v = ExtBuilder::default(); 11 | let relayers = v.relayers.len() as u32; 12 | assert!(relayers > 1, "invalid test config for relayers"); 13 | v.build().execute_with(|| { 14 | // Dispatch a signed extrinsic. 15 | assert_ok!(FileCoinModule::set_vote_threshold( 16 | Origin::signed(ALICE), 17 | relayers - 1 18 | )); 19 | assert_eq!(VoteThreshold::::get(), relayers - 1); 20 | }); 21 | } 22 | 23 | #[test] 24 | fn set_threshold_fails_not_admin() { 25 | let v = ExtBuilder::default(); 26 | let relayers = v.relayers.len() as u32; 27 | assert!(relayers > 1, "invalid test config for relayers"); 28 | v.build().execute_with(|| { 29 | // Dispatch a signed extrinsic. 30 | assert_err!( 31 | FileCoinModule::set_vote_threshold(Origin::signed(RELAYER1), relayers), 32 | BadOrigin 33 | ); 34 | assert_eq!(VoteThreshold::::get(), relayers); 35 | }); 36 | } 37 | 38 | #[test] 39 | fn set_threshold_fails_invalid_threshold_0() { 40 | let v = ExtBuilder::default(); 41 | let relayers = v.relayers.len() as u32; 42 | assert!(relayers > 1, "invalid test config for relayers"); 43 | v.build().execute_with(|| { 44 | // Dispatch a signed extrinsic. 45 | assert_err!( 46 | FileCoinModule::set_vote_threshold(Origin::signed(ALICE), 0), 47 | Error::::InvalidThreshold 48 | ); 49 | assert_eq!(VoteThreshold::::get(), relayers); 50 | }); 51 | } 52 | 53 | #[test] 54 | fn set_threshold_fails_invalid_threshold_too_large() { 55 | let v = ExtBuilder::default(); 56 | let relayers = v.relayers.len() as u32; 57 | assert!(relayers > 1, "invalid test config for relayers"); 58 | v.build().execute_with(|| { 59 | // Dispatch a signed extrinsic. 60 | assert_err!( 61 | FileCoinModule::set_vote_threshold(Origin::signed(ALICE), relayers + 1), 62 | Error::::InvalidThreshold 63 | ); 64 | assert_eq!(VoteThreshold::::get(), relayers); 65 | }); 66 | } 67 | 68 | #[test] 69 | fn add_relayer_works() { 70 | let v = ExtBuilder::default(); 71 | let relayers = v.relayers.len() as u32 + 1; 72 | v.build().execute_with(|| { 73 | // Dispatch a signed extrinsic. 74 | assert_ok!(FileCoinModule::add_relayer(Origin::signed(ALICE), RELAYER4)); 75 | assert_eq!(RelayerCount::::get(), relayers); 76 | }); 77 | } 78 | 79 | #[test] 80 | fn add_relayer_fails_already_relayer() { 81 | let v = ExtBuilder::default(); 82 | v.build().execute_with(|| { 83 | // Dispatch a signed extrinsic. 84 | assert_err!( 85 | FileCoinModule::add_relayer(Origin::signed(ALICE), RELAYER1), 86 | Error::::RelayerAlreadyExists 87 | ); 88 | }); 89 | } 90 | 91 | #[test] 92 | fn add_relayer_fails_not_admin() { 93 | ExtBuilder::default().build().execute_with(|| { 94 | // Dispatch a signed extrinsic. 95 | assert_err!( 96 | FileCoinModule::add_relayer(Origin::signed(RELAYER1), RELAYER1), 97 | BadOrigin 98 | ); 99 | }); 100 | } 101 | 102 | #[test] 103 | fn remove_relayer_works() { 104 | let v = ExtBuilder::default(); 105 | let relayers = v.relayers.len() as u32 + 1; 106 | v.build().execute_with(|| { 107 | assert_ok!(FileCoinModule::add_relayer(Origin::signed(ALICE), RELAYER4)); 108 | assert_eq!(RelayerCount::::get(), relayers); 109 | assert_ok!(FileCoinModule::remove_relayer( 110 | Origin::signed(ALICE), 111 | RELAYER1 112 | )); 113 | assert_eq!(RelayerCount::::get(), relayers - 1); 114 | }); 115 | } 116 | 117 | #[test] 118 | fn remove_relayer_fails_not_enough() { 119 | let v = ExtBuilder::default(); 120 | let relayers = v.relayers.len() as u32; 121 | v.build().execute_with(|| { 122 | assert_eq!(RelayerCount::::get(), relayers); 123 | assert_err!( 124 | FileCoinModule::remove_relayer(Origin::signed(ALICE), RELAYER2), 125 | Error::::NotEnoughRelayer 126 | ); 127 | assert_eq!(RelayerCount::::get(), relayers); 128 | }); 129 | } 130 | 131 | #[test] 132 | fn remove_relayer_fails_not_relayer() { 133 | let v = ExtBuilder::default(); 134 | let relayers = v.relayers.len() as u32; 135 | v.build().execute_with(|| { 136 | assert_eq!(RelayerCount::::get(), relayers); 137 | assert_err!( 138 | FileCoinModule::remove_relayer(Origin::signed(ALICE), ALICE), 139 | Error::::NotRelayer 140 | ); 141 | assert_eq!(RelayerCount::::get(), relayers); 142 | }); 143 | } 144 | -------------------------------------------------------------------------------- /filecoindot/src/tests/verify.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | use cid::Cid; 5 | use frame_support::{assert_err, assert_ok}; 6 | 7 | use crate::{tests::mock::*, Error}; 8 | 9 | pub fn hamt_proof_generation() -> (Vec>, Cid) { 10 | let (p, cid) = filecoindot_io::benchmarking::hamt_proof_generation(); 11 | (p, Cid::read_bytes(&*cid).unwrap()) 12 | } 13 | 14 | pub fn amt_proof_generation(n: usize) -> (Vec>, Cid) { 15 | let (p, cid) = filecoindot_io::benchmarking::amt_proof_generation(n as u64); 16 | (p, Cid::read_bytes(&*cid).unwrap()) 17 | } 18 | 19 | #[test] 20 | fn verify_state_works() { 21 | let (proof, cid) = hamt_proof_generation(); 22 | 23 | let block_cid = vec![0, 1]; 24 | let message_cid = vec![0, 1]; 25 | ExtBuilder::default().build().execute_with(|| { 26 | assert_ok!(FileCoinModule::submit_block_vote( 27 | Origin::signed(RELAYER1), 28 | block_cid.clone(), 29 | message_cid.clone() 30 | )); 31 | assert_ok!(FileCoinModule::submit_block_vote( 32 | Origin::signed(RELAYER2), 33 | block_cid.clone(), 34 | message_cid.clone() 35 | )); 36 | assert_ok!(FileCoinModule::submit_block_vote( 37 | Origin::signed(RELAYER3), 38 | block_cid.clone(), 39 | message_cid.clone() 40 | )); 41 | assert_ok!(FileCoinModule::verify_state_inner( 42 | proof, 43 | block_cid, 44 | cid.to_bytes() 45 | )); 46 | }); 47 | } 48 | 49 | #[test] 50 | fn verify_state_fails_invalid_block_cid() { 51 | let (proof, cid) = hamt_proof_generation(); 52 | 53 | let block_cid = vec![0, 1]; 54 | let message_cid = vec![0, 1]; 55 | ExtBuilder::default().build().execute_with(|| { 56 | assert_ok!(FileCoinModule::submit_block_vote( 57 | Origin::signed(RELAYER1), 58 | block_cid.clone(), 59 | message_cid.clone() 60 | )); 61 | assert_ok!(FileCoinModule::submit_block_vote( 62 | Origin::signed(RELAYER2), 63 | block_cid.clone(), 64 | message_cid.clone() 65 | )); 66 | assert_ok!(FileCoinModule::submit_block_vote( 67 | Origin::signed(RELAYER3), 68 | block_cid.clone(), 69 | message_cid.clone() 70 | )); 71 | assert_err!( 72 | FileCoinModule::verify_state_inner(proof, vec![0, 2], cid.to_bytes()), 73 | Error::::VerificationError 74 | ); 75 | }); 76 | } 77 | 78 | #[test] 79 | fn verify_receipt_works() { 80 | let (proof, cid) = amt_proof_generation(100); 81 | 82 | let block_cid = vec![0, 1]; 83 | let message_cid = vec![0, 1]; 84 | ExtBuilder::default().build().execute_with(|| { 85 | assert_ok!(FileCoinModule::submit_block_vote( 86 | Origin::signed(RELAYER1), 87 | block_cid.clone(), 88 | message_cid.clone() 89 | )); 90 | assert_ok!(FileCoinModule::submit_block_vote( 91 | Origin::signed(RELAYER2), 92 | block_cid.clone(), 93 | message_cid.clone() 94 | )); 95 | assert_ok!(FileCoinModule::submit_block_vote( 96 | Origin::signed(RELAYER3), 97 | block_cid.clone(), 98 | message_cid.clone() 99 | )); 100 | assert_ok!(FileCoinModule::verify_receipt_inner( 101 | proof, 102 | block_cid, 103 | cid.to_bytes() 104 | )); 105 | }); 106 | } 107 | 108 | #[test] 109 | fn verify_receipt_fails_invalid_block_cid() { 110 | let (proof, cid) = amt_proof_generation(100); 111 | 112 | let block_cid = vec![0, 1]; 113 | let message_cid = vec![0, 1]; 114 | ExtBuilder::default().build().execute_with(|| { 115 | assert_ok!(FileCoinModule::submit_block_vote( 116 | Origin::signed(RELAYER1), 117 | block_cid.clone(), 118 | message_cid.clone() 119 | )); 120 | assert_ok!(FileCoinModule::submit_block_vote( 121 | Origin::signed(RELAYER2), 122 | block_cid.clone(), 123 | message_cid.clone() 124 | )); 125 | assert_ok!(FileCoinModule::submit_block_vote( 126 | Origin::signed(RELAYER3), 127 | block_cid.clone(), 128 | message_cid.clone() 129 | )); 130 | assert_err!( 131 | FileCoinModule::verify_receipt_inner(proof, vec![0, 2], cid.to_bytes()), 132 | Error::::VerificationError 133 | ); 134 | }); 135 | } 136 | 137 | #[test] 138 | fn verify_message_works() { 139 | let (proof, cid) = hamt_proof_generation(); 140 | 141 | let block_cid = vec![0, 1]; 142 | let message_cid = vec![0, 1]; 143 | ExtBuilder::default().build().execute_with(|| { 144 | assert_ok!(FileCoinModule::submit_block_vote( 145 | Origin::signed(RELAYER1), 146 | block_cid.clone(), 147 | message_cid.clone() 148 | )); 149 | assert_ok!(FileCoinModule::submit_block_vote( 150 | Origin::signed(RELAYER2), 151 | block_cid.clone(), 152 | message_cid.clone() 153 | )); 154 | assert_ok!(FileCoinModule::submit_block_vote( 155 | Origin::signed(RELAYER3), 156 | block_cid.clone(), 157 | message_cid.clone() 158 | )); 159 | assert_ok!(FileCoinModule::verify_message_inner( 160 | proof, 161 | block_cid, 162 | cid.to_bytes() 163 | )); 164 | }); 165 | } 166 | 167 | #[test] 168 | fn verify_message_fails() { 169 | let (proof, cid) = hamt_proof_generation(); 170 | 171 | let block_cid = vec![0, 1]; 172 | let message_cid = vec![0, 1]; 173 | ExtBuilder::default().build().execute_with(|| { 174 | assert_ok!(FileCoinModule::submit_block_vote( 175 | Origin::signed(RELAYER1), 176 | block_cid.clone(), 177 | message_cid.clone() 178 | )); 179 | assert_ok!(FileCoinModule::submit_block_vote( 180 | Origin::signed(RELAYER2), 181 | block_cid.clone(), 182 | message_cid.clone() 183 | )); 184 | assert_ok!(FileCoinModule::submit_block_vote( 185 | Origin::signed(RELAYER3), 186 | block_cid.clone(), 187 | message_cid.clone() 188 | )); 189 | assert_err!( 190 | FileCoinModule::verify_message_inner(proof, vec![0, 2], cid.to_bytes()), 191 | Error::::VerificationError 192 | ); 193 | }); 194 | } 195 | -------------------------------------------------------------------------------- /filecoindot/src/types.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | use frame_support::pallet_prelude::*; 5 | use frame_support::sp_std; 6 | use frame_system::{Origin, RawOrigin}; 7 | use scale_info::TypeInfo; 8 | use sp_std::prelude::*; 9 | // use filecoindot_proofs::{ForestAmtAdaptedNode, HAMTNodeType, ProofVerify, Verify}; 10 | use crate::{Config, Relayers}; 11 | 12 | /// The filecoin block submission proposal 13 | #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] 14 | pub(crate) struct BlockSubmissionProposal { 15 | proposer: AccountId, 16 | /// The status of the proposal 17 | status: ProposalStatus, 18 | /// The block number that the proposal started 19 | start_block: BlockNumber, 20 | /// The block number that the proposal ended 21 | end_block: BlockNumber, 22 | } 23 | 24 | impl BlockSubmissionProposal { 25 | pub fn new(proposer: AccountId, start_block: BlockNumber, end_block: BlockNumber) -> Self { 26 | BlockSubmissionProposal { 27 | proposer, 28 | //voted: BTreeSet::new(), 29 | status: ProposalStatus::Active, 30 | start_block, 31 | end_block, 32 | } 33 | } 34 | 35 | /// Get the status of the proposal 36 | pub fn get_status(&self) -> &ProposalStatus { 37 | &self.status 38 | } 39 | 40 | pub fn set_status(&mut self, new_status: ProposalStatus) { 41 | self.status = new_status; 42 | } 43 | 44 | /// Whether the proposal is still active, i.e. can vote 45 | pub fn is_expired(&self, now: &BlockNumber) -> bool { 46 | now.gt(&self.end_block) 47 | } 48 | } 49 | 50 | /// The status of the proposal 51 | /// Expected status transition: 52 | /// Active -> Approved -> Executed 53 | /// Rejected Canceled 54 | /// Expired 55 | #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] 56 | pub(crate) enum ProposalStatus { 57 | /// The proposal is active and relayers can start voting 58 | Active, 59 | /// Proposal is approved 60 | Approved, 61 | /// Proposal is rejected 62 | Rejected, 63 | } 64 | 65 | /// An implementation of EnsureOrigin that ensures an account is the admin to the pallet. 66 | pub struct EnsureRelayer(sp_std::marker::PhantomData); 67 | 68 | impl, O>> + From> + Clone, T: Config> EnsureOrigin 69 | for EnsureRelayer 70 | { 71 | type Success = T::AccountId; 72 | 73 | fn try_origin(o: O) -> Result { 74 | let origin = o.clone().into()?; 75 | match origin { 76 | RawOrigin::Signed(i) => { 77 | if Relayers::::contains_key(&i) { 78 | Ok(i) 79 | } else { 80 | Err(o) 81 | } 82 | } 83 | _ => Err(o), 84 | } 85 | } 86 | 87 | #[cfg(feature = "runtime-benchmarks")] 88 | fn successful_origin() -> O { 89 | RawOrigin::Signed(Default::default()).into() 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /js/e2e/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint config */ 2 | 3 | module.exports = { 4 | rules: { 5 | "no-unused-vars": ["warn", { varsIgnorePattern: "^_" }], 6 | "@typescript-eslint/no-unused-vars": ["warn", { varsIgnorePattern: "^_" }], 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /js/e2e/.gitignore: -------------------------------------------------------------------------------- 1 | lib -------------------------------------------------------------------------------- /js/e2e/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChainSafe/filecoindot/b94b4407a3fbff87f8650f4e4306d07d65e9b917/js/e2e/README.md -------------------------------------------------------------------------------- /js/e2e/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * filecoindot e2e tests 3 | */ 4 | import Runner from "./src/runner"; 5 | import proof from "./proof.json"; 6 | 7 | // run e2e 8 | (async () => { 9 | const runner = new Runner({ 10 | filecoindotRpc: ["http://filecoin.rpc.noreply","https://api.node.glif.io"], 11 | id: "fdot", 12 | suri: "brief outside human axis reveal boat warm amateur dish sample enroll moment", 13 | ws: "ws://0.0.0.0:9944", 14 | proof, 15 | }); 16 | 17 | try { 18 | await runner.run() 19 | } catch (err) { 20 | throw err; 21 | } 22 | })(); 23 | -------------------------------------------------------------------------------- /js/e2e/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@filecoindot/e2e", 3 | "version": "0.1.20", 4 | "description": "e2e testing for filecoindot", 5 | "main": "index.js", 6 | "files": [ 7 | "lib/*" 8 | ], 9 | "repository": "https://github.com/chainSafe/filecoindot/", 10 | "author": "clearloop", 11 | "license": "MIT", 12 | "dependencies": { 13 | "@chainsafe/filecoindot-types": "0.1.20", 14 | "@polkadot/api": "^6.9.2", 15 | "@polkadot/keyring": "^8.2.2", 16 | "@polkadot/types": "^6.9.2", 17 | "@polkadot/util": "^8.2.2", 18 | "find-up": "^5.0.0" 19 | }, 20 | "scripts": { 21 | "build": "tsc --strict" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /js/e2e/setup.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * setup filecoindot pallet 3 | * 4 | * 0. insert singer account 5 | * 1. set filecoindot rpc endpoint 6 | * 2. deposit fund into signer account 7 | * 3. add signer account as relayer 8 | */ 9 | import Runner, { IRunnerConfig } from "./src/runner"; 10 | 11 | // setup 12 | (async () => { 13 | if (process.argv.length < 2) { 14 | throw "Error: config file not found"; 15 | } 16 | 17 | // construct runner with `argv[2]` 18 | const runner = new Runner(await import(process.argv[2])); 19 | await runner.setup().catch((err) => { 20 | throw err; 21 | }); 22 | })(); 23 | -------------------------------------------------------------------------------- /js/e2e/src/api.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Filecoindot api 3 | */ 4 | import { ApiPromise, WsProvider } from "@polkadot/api"; 5 | import { Keyring } from "@polkadot/keyring"; 6 | import { KeyringPair } from "@polkadot/keyring/types"; 7 | import { rpc as filecoindotRpc, types } from "@chainsafe/filecoindot-types"; 8 | import { EventRecord, Event, Phase } from "@polkadot/types/interfaces"; 9 | import { Balance } from "@polkadot/types/interfaces/runtime"; 10 | import { BN, u8aToHex } from "@polkadot/util"; 11 | 12 | /** 13 | * filecoindot api 14 | */ 15 | export default class Api { 16 | _: ApiPromise; 17 | suri: string; 18 | keyring: Keyring; 19 | signer: KeyringPair; 20 | testSigner: KeyringPair; 21 | 22 | /** 23 | * new filecoindot api 24 | */ 25 | static async New(ws: string, suri: string): Promise { 26 | const provider = new WsProvider(ws); 27 | const api = await ApiPromise.create({ 28 | provider, 29 | types, 30 | rpc: filecoindotRpc, 31 | }); 32 | 33 | const keyring = new Keyring({ type: "sr25519" }); 34 | const signer = keyring.createFromUri("//Alice"); 35 | const testSigner = keyring.addFromMnemonic(suri); 36 | 37 | return new Api(api, suri, keyring, signer, testSigner); 38 | } 39 | 40 | constructor( 41 | api: ApiPromise, 42 | suri: string, 43 | keyring: Keyring, 44 | signer: KeyringPair, 45 | testSigner: KeyringPair 46 | ) { 47 | this._ = api; 48 | this.suri = suri; 49 | this.keyring = keyring; 50 | this.signer = signer; 51 | this.testSigner = testSigner; 52 | } 53 | 54 | /** 55 | * traverse events 56 | */ 57 | public async events( 58 | handler: (api: ApiPromise, event: Event, phase: Phase) => void 59 | ) { 60 | this._.query.system.events((events: EventRecord[]) => { 61 | events.forEach((record) => { 62 | const { event, phase } = record; 63 | handler(this._, event, phase); 64 | }); 65 | }); 66 | } 67 | 68 | /** 69 | * tests for ocw 70 | * 71 | * 0.0. insert author key 72 | */ 73 | public async insertAuthor(id: string) { 74 | return await this._.rpc.author.insertKey( 75 | id, 76 | this.suri, 77 | u8aToHex(this.testSigner.addressRaw) 78 | ); 79 | } 80 | 81 | /** 82 | * 0.1. set filecoindot rpc endpoint 83 | */ 84 | public async setEndpoint(urls: string[]) { 85 | return await (this._.rpc as any).filecoindot.setRpcEndpoint(urls); 86 | } 87 | 88 | /** 89 | * 0.2. depoit some fund to the testing account 90 | */ 91 | public async depositFund(value: number) { 92 | const UNIT: Balance = this._.createType( 93 | "Balance", 94 | Math.pow(10, this._.registry.chainDecimals[0]) 95 | ); 96 | return await this._.tx.balances 97 | .transfer(this.testSigner.address, UNIT.mul(new BN(value))) 98 | .signAndSend(this.signer, { nonce: 0 }); 99 | } 100 | 101 | /** 102 | * 0.3. add relayer 103 | */ 104 | public async addRelayer() { 105 | return await this._.tx.sudo 106 | .sudo(this._.tx.filecoindot.addRelayer(this.testSigner.address)) 107 | .signAndSend(this.signer, { nonce: 1 }); 108 | } 109 | 110 | /** 111 | * verification testing 112 | * 113 | * 1.0. verify proof 114 | */ 115 | public async verifyProof(proofHex: string, cid: string) { 116 | return await (this._.rpc as any).filecoindot.verifyState(proofHex, cid); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /js/e2e/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * e2e module 3 | */ 4 | import Api from "./api"; 5 | 6 | export { Api }; 7 | -------------------------------------------------------------------------------- /js/e2e/src/launch.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * bootstrap filecoindot-template binary 3 | */ 4 | import { ChildProcess, StdioOptions, spawn, spawnSync } from "child_process"; 5 | import fs from "fs"; 6 | import findUp from "find-up"; 7 | import path from "path"; 8 | 9 | async function launch(stdio?: StdioOptions): Promise { 10 | const root = path.resolve(String(await findUp("Cargo.toml")), ".."); 11 | const bin = path.resolve(root, "target/release/filecoindot-template"); 12 | 13 | // Build binary if not exist 14 | if (!fs.existsSync(bin)) { 15 | spawnSync("cargo", ["build", "--release"], { 16 | cwd: root, 17 | stdio, 18 | }); 19 | } 20 | 21 | // spawn `fileconidot-template` 22 | return spawn(bin, ["--dev", "--tmp"], { 23 | stdio, 24 | env: { 25 | RUST_LOG: "runtime", 26 | }, 27 | }); 28 | } 29 | 30 | export default { 31 | launch, 32 | }; 33 | -------------------------------------------------------------------------------- /js/e2e/src/runner.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * e2e runner 3 | */ 4 | import Api from "./api"; 5 | import Launch from "./launch"; 6 | import { ChildProcess } from "child_process"; 7 | import { Event, Phase, DispatchError } from "@polkadot/types/interfaces"; 8 | import { ApiPromise } from "@polkadot/api"; 9 | import BN from "bn.js"; 10 | 11 | const OCW = "filecoindot"; 12 | const OCW_PREPARED = "haven't set filecoin rpc yet"; 13 | 14 | // Kill subprocesses 15 | function killAll(ps: ChildProcess, exitCode: number) { 16 | try { 17 | if (ps.send && !ps.killed) { 18 | ps.send("exit"); 19 | } 20 | ps.kill("SIGINT"); 21 | } catch (e) { 22 | if ((e as any).code !== "EPERM") { 23 | process.stdout.write(JSON.stringify(e)); 24 | process.exit(2); 25 | } 26 | } 27 | 28 | process.exit(exitCode); 29 | } 30 | 31 | /** 32 | * proof inteface 33 | */ 34 | export interface IProof { 35 | proof: string; 36 | cid: string; 37 | } 38 | 39 | 40 | /** 41 | * e2e runner config 42 | */ 43 | export interface IRunnerConfig { 44 | filecoindotRpc: string[]; 45 | id: string; 46 | suri: string; 47 | ws: string; 48 | proof: IProof; 49 | } 50 | 51 | /** 52 | * e2e runner 53 | */ 54 | export default class Runner { 55 | config: IRunnerConfig; 56 | 57 | constructor(config: IRunnerConfig) { 58 | this.config = config; 59 | } 60 | 61 | public async run() { 62 | console.log("bootstrap filecoindot template..."); 63 | const ps = await Launch.launch("pipe"); 64 | 65 | // bootstrap testing 66 | let started: boolean = false; 67 | this.listenStdout(ps); 68 | this.listenStderr(ps, started); 69 | this.listenExit(ps); 70 | } 71 | 72 | /** 73 | * check filecoindot events 74 | */ 75 | private checkEvents(api: ApiPromise, event: Event, phase: Phase) { 76 | const maybeErr = event.data[0]; 77 | if (maybeErr && (maybeErr as DispatchError).isModule) { 78 | const error = api.registry.findMetaError( 79 | (event.data[0] as DispatchError).asModule.toU8a() 80 | ); 81 | console.log(`${error.section}.${error.method}: ${error.docs}`); 82 | process.exit(1); 83 | } 84 | 85 | if (event.method == "VoteCasted") { 86 | console.log( 87 | `\t${event.section}:${event.method}:: (phase=${phase.toString()})` 88 | ); 89 | console.log(`\t\t${event.meta.docs.toString()}`); 90 | console.log("setup completed!"); 91 | process.exit(0); 92 | } 93 | } 94 | 95 | /** 96 | * init offchain worker 97 | */ 98 | public async setup() { 99 | const { ws, filecoindotRpc, id, suri } = this.config; 100 | const api = await Api.New(ws, suri); 101 | 102 | // test verifying proof 103 | if (this.config.proof) { 104 | if ((await api.verifyProof(this.config.proof.proof, this.config.proof.cid)).toHuman() === false) { 105 | throw "verify proof failed" 106 | } 107 | } else { 108 | console.log("WARN: no proof field found in config, skipping testing proof"); 109 | } 110 | 111 | await api.insertAuthor(id); 112 | await api.setEndpoint(filecoindotRpc); 113 | await api.addRelayer(); 114 | await api.depositFund(1000); 115 | api.events(this.checkEvents); 116 | } 117 | 118 | /** 119 | * listen stderr 120 | */ 121 | private listenStderr(ps: ChildProcess, started: boolean) { 122 | if (ps.stderr) { 123 | ps.stderr.on("data", async (chunk: Buffer) => { 124 | chunk.includes(OCW) && process.stderr.write(chunk.toString()); 125 | if (!started && chunk.includes(OCW_PREPARED)) { 126 | await this.setup(); 127 | started = true; 128 | } 129 | }); 130 | } 131 | } 132 | 133 | /** 134 | * listen stdout 135 | */ 136 | private listenStdout(ps: ChildProcess) { 137 | if (ps.stdout) { 138 | ps.stdout.on("data", async (chunk: Buffer) => { 139 | process.stdout.write(chunk.toString()); 140 | }); 141 | } 142 | } 143 | 144 | /** 145 | * listen the exit signal and kill all processes 146 | */ 147 | private listenExit(ps: ChildProcess) { 148 | // kill all processes when exiting. 149 | process.on("exit", () => { 150 | killAll(ps, Number(process.exitCode)); 151 | }); 152 | 153 | // handle ctrl+c to trigger `exit`. 154 | process.on("SIGINT", () => killAll(ps, 0)); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /js/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "indent": [true, "spaces", 2], 3 | "compilerOptions": { 4 | "allowSyntheticDefaultImports": true, 5 | "baseUrl": ".", 6 | "declaration": true, 7 | "esModuleInterop": true, 8 | "outDir": "lib", 9 | "module": "commonjs", 10 | "moduleResolution": "node", 11 | "noImplicitAny": true, 12 | "resolveJsonModule": true, 13 | "sourceMap": true, 14 | "skipLibCheck": true, 15 | "target": "es6" 16 | }, 17 | "include": ["index.ts", "src/**/*"], 18 | "exclude": ["node_modules"] 19 | } 20 | -------------------------------------------------------------------------------- /js/types/.gitignore: -------------------------------------------------------------------------------- 1 | lib -------------------------------------------------------------------------------- /js/types/README.md: -------------------------------------------------------------------------------- 1 | ## @chainsafe/filecoindot-types 2 | 3 | Provides the types and rpc definations for pallet [filecoindot][0] 4 | 5 | 6 | [0]: https://github.com/chainSafe/filecoindot 7 | -------------------------------------------------------------------------------- /js/types/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * filecoindot types 3 | */ 4 | const types = { 5 | BlockCid: "Vec", 6 | BlockSubmissionProposal: { 7 | proposer: "AccountId", 8 | status: "ProposalStatus", 9 | start_block: "BlockNumber", 10 | end_block: "BlockNumber", 11 | }, 12 | MessageRootCid: "Vec", 13 | }; 14 | 15 | /** 16 | * rpc methods 17 | */ 18 | const rpc = { 19 | filecoindot: { 20 | setRpcEndpoint: { 21 | description: "set filecoin rpc http endpoint", 22 | params: [ 23 | { 24 | name: "urls", 25 | type: "Vec", 26 | }, 27 | ], 28 | type: "()", 29 | }, 30 | verifyReceipt: { 31 | description: "verify filecoin receipt", 32 | params: [ 33 | { 34 | name: "proof", 35 | type: "String", 36 | }, 37 | { 38 | name: "cid", 39 | type: "String", 40 | }, 41 | ], 42 | type: "bool", 43 | }, 44 | verifyState: { 45 | description: "verify filecoin state", 46 | params: [ 47 | { 48 | name: "proof", 49 | type: "String", 50 | }, 51 | { 52 | name: "cid", 53 | type: "String", 54 | }, 55 | ], 56 | type: "bool", 57 | }, 58 | }, 59 | }; 60 | 61 | export { rpc, types }; 62 | -------------------------------------------------------------------------------- /js/types/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@chainsafe/filecoindot-types", 3 | "version": "0.1.20", 4 | "description": "Types bundle for filecoindot", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "files": [ 8 | "lib/*" 9 | ], 10 | "author": "tianyi@chainsafe.io", 11 | "license": "MIT", 12 | "publishConfig": { 13 | "registry": "https://registry.npmjs.org" 14 | }, 15 | "scripts": { 16 | "build": "tsc --strict" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /js/types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "indent": [true, "spaces", 2], 3 | "compilerOptions": { 4 | "allowSyntheticDefaultImports": true, 5 | "baseUrl": ".", 6 | "declaration": true, 7 | "esModuleInterop": true, 8 | "outDir": "lib", 9 | "module": "commonjs", 10 | "moduleResolution": "node", 11 | "noImplicitAny": true, 12 | "resolveJsonModule": true, 13 | "sourceMap": true, 14 | "skipLibCheck": true, 15 | "target": "es6" 16 | }, 17 | "include": ["index.ts", "src/**/*"], 18 | "exclude": ["node_modules"] 19 | } 20 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmClient": "yarn", 3 | "packages": [ 4 | "js/*" 5 | ], 6 | "version": "0.1.20" 7 | } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "workspaces": ["js/e2e", "js/types", "ui"], 4 | "devDependencies": { 5 | "@types/command-exists": "^1.2.0", 6 | "@types/find-up": "^4.0.0", 7 | "command-exists": "^1.2.9", 8 | "find-up": "^5.0.0", 9 | "lerna": "^4.0.0", 10 | "ts-node": "^10.4.0", 11 | "typescript": "^4.5.2" 12 | }, 13 | "scripts": { 14 | "build": "yarn && lerna run build", 15 | "publish": "lerna run publish", 16 | "e2e": "yarn && ts-node js/e2e/index.ts", 17 | "setup": "ts-node js/e2e/setup.ts", 18 | "build:ui": "yarn workspace filecoindot-demo build", 19 | "start:ui": "yarn workspace filecoindot-demo start" 20 | }, 21 | "name": "js", 22 | "nohoist": [ 23 | "**/react-scripts" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /resources/header.txt: -------------------------------------------------------------------------------- 1 | Copyright 2021 ChainSafe Systems 2 | SPDX-License-Identifier: LGPL-3.0-only -------------------------------------------------------------------------------- /resources/types.json: -------------------------------------------------------------------------------- 1 | { 2 | "BlockCid": "Vec", 3 | "BlockSubmissionProposal": { 4 | "proposer": "AccountId", 5 | "status": "ProposalStatus", 6 | "start_block": "BlockNumber", 7 | "end_block": "BlockNumber" 8 | }, 9 | "MessageRootCid": "Vec" 10 | } 11 | -------------------------------------------------------------------------------- /scripts/setup.json: -------------------------------------------------------------------------------- 1 | { 2 | "filecoindotRpc": ["https://api.node.glif.io"], 3 | "id": "fdot", 4 | "suri": "brief outside human axis reveal boat warm amateur dish sample enroll moment", 5 | "ws": "ws://0.0.0.0:9944" 6 | } 7 | -------------------------------------------------------------------------------- /scripts/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # paths 4 | readonly ROOT="$(dirname $(cd $(dirname $0) && pwd))" 5 | readonly SCRIPTS="${ROOT}/scripts" 6 | readonly CONFIG="${SCRIPTS}/setup.json" 7 | 8 | # build javascript packages 9 | run_setup_script() { 10 | if [ -x "$(command -v yarn)" ]; then 11 | yarn && yarn setup $@; 12 | elif [ -x "$(command -v npm)" ]; then 13 | npm install && npm run setup $@; 14 | else 15 | echo "Error: could not find npm or yarn for building setup scripts"; 16 | exit 1; 17 | fi 18 | } 19 | 20 | # run program 21 | main() { 22 | cd $ROOT 23 | 24 | run_setup_script $CONFIG 25 | 26 | exit 0; 27 | } 28 | 29 | main 30 | -------------------------------------------------------------------------------- /substrate-node-example/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style=space 5 | indent_size=2 6 | tab_width=2 7 | end_of_line=lf 8 | charset=utf-8 9 | trim_trailing_whitespace=true 10 | insert_final_newline = true 11 | 12 | [*.{rs,toml}] 13 | indent_style=tab 14 | indent_size=tab 15 | tab_width=4 16 | max_line_length=100 17 | -------------------------------------------------------------------------------- /substrate-node-example/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 | -------------------------------------------------------------------------------- /substrate-node-example/node/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "filecoindot-template" 3 | version = "3.0.0" 4 | authors = ["Substrate DevHub "] 5 | description = "A fresh FRAME-based Substrate node, ready for hacking." 6 | edition = "2021" 7 | license = "Unlicense" 8 | build = "build.rs" 9 | homepage = "https://substrate.dev" 10 | repository = "https://github.com/substrate-developer-hub/substrate-node-template/" 11 | publish = false 12 | 13 | [package.metadata.docs.rs] 14 | targets = ["x86_64-unknown-linux-gnu"] 15 | 16 | [[bin]] 17 | name = "filecoindot-template" 18 | 19 | [dependencies] 20 | structopt = "0.3.25" 21 | parking_lot = "^0.10" 22 | 23 | sc-cli = { features = ["wasmtime"], git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 24 | sp-core = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 25 | sc-executor = { features = ["wasmtime"], git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 26 | sc-service = { features = ["wasmtime"], git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 27 | sc-telemetry = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 28 | sc-keystore = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 29 | sp-inherents = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 30 | sc-transaction-pool = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 31 | sc-transaction-pool-api = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 32 | sc-consensus-aura = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 33 | sp-consensus-aura = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 34 | sp-consensus = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 35 | sc-consensus = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 36 | sc-finality-grandpa = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 37 | sp-finality-grandpa = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 38 | sc-client-api = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 39 | sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 40 | sp-timestamp = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 41 | 42 | # These dependencies are used for the node template's RPCs 43 | jsonrpc-core = '18.0.0' 44 | sc-rpc = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 45 | sp-api = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 46 | sc-rpc-api = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 47 | sp-blockchain = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 48 | sp-block-builder = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 49 | sc-basic-authorship = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 50 | substrate-frame-rpc-system = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 51 | pallet-transaction-payment-rpc = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 52 | 53 | # These dependencies are used for runtime benchmarking 54 | frame-benchmarking = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 55 | frame-benchmarking-cli = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 56 | 57 | node-template-runtime = { path = "../runtime" } 58 | 59 | # orml dependencies 60 | orml-nft = { default-features = false, git = "https://github.com/open-web3-stack/open-runtime-module-library.git", branch = "polkadot-v0.9.13", optional = true } 61 | 62 | # filecoindot related 63 | filecoindot-rpc = { path = "../../filecoindot-rpc" } 64 | filecoindot-io = { path = "../../filecoindot-io" } 65 | 66 | 67 | [build-dependencies] 68 | substrate-build-script-utils = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 69 | 70 | [features] 71 | default = [] 72 | runtime-benchmarks = [ 73 | "node-template-runtime/runtime-benchmarks", 74 | ] 75 | -------------------------------------------------------------------------------- /substrate-node-example/node/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | use substrate_build_script_utils::{generate_cargo_keys, rerun_if_git_head_changed}; 5 | 6 | fn main() { 7 | generate_cargo_keys(); 8 | 9 | rerun_if_git_head_changed(); 10 | } 11 | -------------------------------------------------------------------------------- /substrate-node-example/node/src/cli.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | use sc_cli::RunCmd; 5 | use structopt::StructOpt; 6 | 7 | #[derive(Debug, StructOpt)] 8 | pub struct Cli { 9 | #[structopt(subcommand)] 10 | pub subcommand: Option, 11 | 12 | #[structopt(flatten)] 13 | pub run: RunCmd, 14 | } 15 | 16 | #[derive(Debug, StructOpt)] 17 | pub enum Subcommand { 18 | /// Key management cli utilities 19 | Key(sc_cli::KeySubcommand), 20 | /// Build a chain specification. 21 | BuildSpec(sc_cli::BuildSpecCmd), 22 | 23 | /// Validate blocks. 24 | CheckBlock(sc_cli::CheckBlockCmd), 25 | 26 | /// Export blocks. 27 | ExportBlocks(sc_cli::ExportBlocksCmd), 28 | 29 | /// Export the state of a given block into a chain spec. 30 | ExportState(sc_cli::ExportStateCmd), 31 | 32 | /// Import blocks. 33 | ImportBlocks(sc_cli::ImportBlocksCmd), 34 | 35 | /// Remove the whole chain. 36 | PurgeChain(sc_cli::PurgeChainCmd), 37 | 38 | /// Revert the chain to a previous state. 39 | Revert(sc_cli::RevertCmd), 40 | 41 | /// The custom benchmark subcommmand benchmarking runtime pallets. 42 | #[structopt(name = "benchmark", about = "Benchmark runtime pallets.")] 43 | Benchmark(frame_benchmarking_cli::BenchmarkCmd), 44 | } 45 | -------------------------------------------------------------------------------- /substrate-node-example/node/src/command.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | chain_spec, 3 | cli::{Cli, Subcommand}, 4 | service, 5 | }; 6 | use node_template_runtime::Block; 7 | use sc_cli::{ChainSpec, RuntimeVersion, SubstrateCli}; 8 | use sc_service::PartialComponents; 9 | 10 | impl SubstrateCli for Cli { 11 | fn impl_name() -> String { 12 | "Substrate Node".into() 13 | } 14 | 15 | fn impl_version() -> String { 16 | env!("SUBSTRATE_CLI_IMPL_VERSION").into() 17 | } 18 | 19 | fn description() -> String { 20 | env!("CARGO_PKG_DESCRIPTION").into() 21 | } 22 | 23 | fn author() -> String { 24 | env!("CARGO_PKG_AUTHORS").into() 25 | } 26 | 27 | fn support_url() -> String { 28 | "support.anonymous.an".into() 29 | } 30 | 31 | fn copyright_start_year() -> i32 { 32 | 2017 33 | } 34 | 35 | fn load_spec(&self, id: &str) -> Result, String> { 36 | Ok(match id { 37 | "dev" => Box::new(chain_spec::development_config()?), 38 | "" | "local" => Box::new(chain_spec::local_testnet_config()?), 39 | path => Box::new(chain_spec::ChainSpec::from_json_file( 40 | std::path::PathBuf::from(path), 41 | )?), 42 | }) 43 | } 44 | 45 | fn native_runtime_version(_: &Box) -> &'static RuntimeVersion { 46 | &node_template_runtime::VERSION 47 | } 48 | } 49 | 50 | /// Parse and run command line arguments 51 | pub fn run() -> sc_cli::Result<()> { 52 | let cli = Cli::from_args(); 53 | 54 | match &cli.subcommand { 55 | Some(Subcommand::Key(cmd)) => cmd.run(&cli), 56 | Some(Subcommand::BuildSpec(cmd)) => { 57 | let runner = cli.create_runner(cmd)?; 58 | runner.sync_run(|config| cmd.run(config.chain_spec, config.network)) 59 | } 60 | Some(Subcommand::CheckBlock(cmd)) => { 61 | let runner = cli.create_runner(cmd)?; 62 | runner.async_run(|config| { 63 | let PartialComponents { 64 | client, 65 | task_manager, 66 | import_queue, 67 | .. 68 | } = service::new_partial(&config)?; 69 | Ok((cmd.run(client, import_queue), task_manager)) 70 | }) 71 | } 72 | Some(Subcommand::ExportBlocks(cmd)) => { 73 | let runner = cli.create_runner(cmd)?; 74 | runner.async_run(|config| { 75 | let PartialComponents { 76 | client, 77 | task_manager, 78 | .. 79 | } = service::new_partial(&config)?; 80 | Ok((cmd.run(client, config.database), task_manager)) 81 | }) 82 | } 83 | Some(Subcommand::ExportState(cmd)) => { 84 | let runner = cli.create_runner(cmd)?; 85 | runner.async_run(|config| { 86 | let PartialComponents { 87 | client, 88 | task_manager, 89 | .. 90 | } = service::new_partial(&config)?; 91 | Ok((cmd.run(client, config.chain_spec), task_manager)) 92 | }) 93 | } 94 | Some(Subcommand::ImportBlocks(cmd)) => { 95 | let runner = cli.create_runner(cmd)?; 96 | runner.async_run(|config| { 97 | let PartialComponents { 98 | client, 99 | task_manager, 100 | import_queue, 101 | .. 102 | } = service::new_partial(&config)?; 103 | Ok((cmd.run(client, import_queue), task_manager)) 104 | }) 105 | } 106 | Some(Subcommand::PurgeChain(cmd)) => { 107 | let runner = cli.create_runner(cmd)?; 108 | runner.sync_run(|config| cmd.run(config.database)) 109 | } 110 | Some(Subcommand::Revert(cmd)) => { 111 | let runner = cli.create_runner(cmd)?; 112 | runner.async_run(|config| { 113 | let PartialComponents { 114 | client, 115 | task_manager, 116 | backend, 117 | .. 118 | } = service::new_partial(&config)?; 119 | Ok((cmd.run(client, backend), task_manager)) 120 | }) 121 | } 122 | Some(Subcommand::Benchmark(cmd)) => { 123 | if cfg!(feature = "runtime-benchmarks") { 124 | let runner = cli.create_runner(cmd)?; 125 | 126 | runner.sync_run(|config| cmd.run::(config)) 127 | } else { 128 | Err( 129 | "Benchmarking wasn't enabled when building the node. You can enable it with \ 130 | `--features runtime-benchmarks`." 131 | .into(), 132 | ) 133 | } 134 | } 135 | None => { 136 | let runner = cli.create_runner(&cli.run)?; 137 | runner.run_node_until_exit(|config| async move { 138 | service::new_full(config).map_err(sc_cli::Error::Service) 139 | }) 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /substrate-node-example/node/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | pub mod chain_spec; 5 | pub mod rpc; 6 | pub mod service; 7 | -------------------------------------------------------------------------------- /substrate-node-example/node/src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | //! Substrate Node Template CLI library. 5 | #![warn(missing_docs)] 6 | 7 | mod chain_spec; 8 | #[macro_use] 9 | mod service; 10 | mod cli; 11 | mod command; 12 | mod rpc; 13 | 14 | fn main() -> sc_cli::Result<()> { 15 | command::run() 16 | } 17 | -------------------------------------------------------------------------------- /substrate-node-example/node/src/rpc.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | //! A collection of node-specific RPC methods. 5 | //! Substrate provides the `sc-rpc` crate, which defines the core RPC layer 6 | //! used by Substrate nodes. This file extends those RPC definitions with 7 | //! capabilities that are specific to this project's runtime configuration. 8 | 9 | #![warn(missing_docs)] 10 | 11 | use std::sync::Arc; 12 | 13 | use node_template_runtime::{opaque::Block, AccountId, Balance, Index}; 14 | use parking_lot::RwLock; 15 | pub use sc_rpc_api::DenyUnsafe; 16 | use sc_transaction_pool_api::TransactionPool; 17 | use sp_api::ProvideRuntimeApi; 18 | use sp_block_builder::BlockBuilder; 19 | use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; 20 | use sp_core::offchain::OffchainStorage; 21 | 22 | /// Full client dependencies. 23 | pub struct FullDeps { 24 | /// The offchain storage instance to use. 25 | pub storage: Option>>, 26 | /// The client instance to use. 27 | pub client: Arc, 28 | /// Transaction pool instance. 29 | pub pool: Arc

, 30 | /// Whether to deny unsafe calls 31 | pub deny_unsafe: DenyUnsafe, 32 | } 33 | 34 | /// Instantiate all full RPC extensions. 35 | // pub fn create_full(deps: FullDeps) -> jsonrpc_core::IoHandler 36 | pub fn create_full(deps: FullDeps) -> jsonrpc_core::IoHandler 37 | where 38 | C: ProvideRuntimeApi, 39 | C: HeaderBackend + HeaderMetadata + 'static, 40 | C: Send + Sync + 'static, 41 | C::Api: substrate_frame_rpc_system::AccountNonceApi, 42 | C::Api: pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi, 43 | C::Api: BlockBuilder, 44 | P: TransactionPool + 'static, 45 | S: OffchainStorage + 'static, 46 | { 47 | use filecoindot_rpc::{Filecoindot, FilecoindotApi}; 48 | use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApi}; 49 | use substrate_frame_rpc_system::{FullSystem, SystemApi}; 50 | 51 | let mut io = jsonrpc_core::IoHandler::default(); 52 | let FullDeps { 53 | client, 54 | deny_unsafe, 55 | pool, 56 | storage, 57 | } = deps; 58 | 59 | io.extend_with(SystemApi::to_delegate(FullSystem::new( 60 | client.clone(), 61 | pool, 62 | deny_unsafe, 63 | ))); 64 | 65 | io.extend_with(TransactionPaymentApi::to_delegate(TransactionPayment::new( 66 | client, 67 | ))); 68 | 69 | // Extend this RPC with a custom API by using the following syntax. 70 | // `YourRpcStruct` should have a reference to a client, which is needed 71 | // to call into the runtime. 72 | // `io.extend_with(YourRpcTrait::to_delegate(YourRpcStruct::new(ReferenceToClient, ...)));` 73 | 74 | // filecoindot rpc 75 | if let Some(storage) = storage { 76 | io.extend_with(FilecoindotApi::to_delegate(Filecoindot::new(storage))); 77 | } 78 | 79 | io 80 | } 81 | -------------------------------------------------------------------------------- /substrate-node-example/runtime/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "node-template-runtime" 3 | version = "3.0.0" 4 | authors = ["Substrate DevHub "] 5 | edition = "2021" 6 | license = "Unlicense" 7 | homepage = "https://substrate.dev" 8 | repository = "https://github.com/substrate-developer-hub/substrate-node-template/" 9 | publish = false 10 | 11 | [package.metadata.docs.rs] 12 | targets = ["x86_64-unknown-linux-gnu"] 13 | 14 | [dependencies] 15 | codec = { package = "parity-scale-codec", version = "2.3.1", default-features = false, features = ["derive"] } 16 | scale-info = { version = "1.0", default-features = false, features = ["derive"] } 17 | 18 | pallet-aura = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 19 | pallet-balances = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 20 | frame-support = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 21 | pallet-grandpa = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 22 | pallet-randomness-collective-flip = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 23 | pallet-sudo = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 24 | frame-system = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 25 | pallet-timestamp = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 26 | pallet-transaction-payment = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 27 | frame-executive = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 28 | sp-api = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 29 | sp-block-builder = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 30 | sp-consensus-aura = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 31 | sp-core = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 32 | sp-inherents = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 33 | sp-offchain = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 34 | sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 35 | sp-session = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 36 | sp-std = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 37 | sp-transaction-pool = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 38 | sp-version = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 39 | 40 | # orml dependencies 41 | orml-nft = { default-features = false, git = "https://github.com/open-web3-stack/open-runtime-module-library.git", branch = "polkadot-v0.9.13" } 42 | 43 | # Used for the node template's RPCs 44 | frame-system-rpc-runtime-api = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 45 | pallet-transaction-payment-rpc-runtime-api = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 46 | 47 | # Used for runtime benchmarking 48 | frame-benchmarking = { default-features = false, optional = true, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 49 | frame-system-benchmarking = { default-features = false, optional = true, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 50 | hex-literal = { version = "0.3.4", optional = true } 51 | 52 | # filecoindot dependencies 53 | filecoindot = { path = "../../filecoindot", default-features = false } 54 | filecoindot-nft = { path = "../../filecoindot-nft", default-features = false } 55 | 56 | [build-dependencies] 57 | substrate-wasm-builder = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" } 58 | 59 | [features] 60 | default = ["std"] 61 | std = [ 62 | "codec/std", 63 | "scale-info/std", 64 | "frame-executive/std", 65 | "frame-support/std", 66 | "frame-system-rpc-runtime-api/std", 67 | "frame-system/std", 68 | 69 | "pallet-aura/std", 70 | "pallet-balances/std", 71 | "pallet-grandpa/std", 72 | "pallet-randomness-collective-flip/std", 73 | "pallet-sudo/std", 74 | "pallet-timestamp/std", 75 | "pallet-transaction-payment-rpc-runtime-api/std", 76 | "pallet-transaction-payment/std", 77 | 78 | "sp-api/std", 79 | "sp-block-builder/std", 80 | "sp-consensus-aura/std", 81 | "sp-core/std", 82 | "sp-inherents/std", 83 | "sp-offchain/std", 84 | "sp-runtime/std", 85 | "sp-session/std", 86 | "sp-std/std", 87 | "sp-transaction-pool/std", 88 | "sp-version/std", 89 | 90 | "filecoindot/std", 91 | 92 | "orml-nft/std" 93 | ] 94 | runtime-benchmarks = [ 95 | 'frame-benchmarking', 96 | 'frame-support/runtime-benchmarks', 97 | 'frame-system-benchmarking', 98 | 'frame-system/runtime-benchmarks', 99 | 'hex-literal', 100 | 'pallet-balances/runtime-benchmarks', 101 | 'pallet-timestamp/runtime-benchmarks', 102 | 'sp-runtime/runtime-benchmarks', 103 | 104 | "filecoindot/runtime-benchmarks", 105 | ] 106 | -------------------------------------------------------------------------------- /substrate-node-example/runtime/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | use substrate_wasm_builder::WasmBuilder; 5 | 6 | fn main() { 7 | WasmBuilder::new() 8 | .with_current_project() 9 | .export_heap_base() 10 | .import_memory() 11 | .build() 12 | } 13 | -------------------------------------------------------------------------------- /substrate-node-example/runtime/src/weights/filecoindot.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | //! Autogenerated weights for `filecoindot` 5 | //! 6 | //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev 7 | //! DATE: 2022-02-08, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` 8 | //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 128 9 | 10 | // Executed Command: 11 | // ./target/release/filecoindot-template 12 | // benchmark 13 | // -p 14 | // filecoindot 15 | // -e 16 | // * 17 | // --execution 18 | // wasm 19 | // --wasm-execution 20 | // compiled 21 | // --raw 22 | // --output 23 | // ./substrate-node-example/runtime/src/weights/filecoindot.rs 24 | // --steps 25 | // 50 26 | // --repeat 27 | // 20 28 | // --heap-pages 29 | // 4096 30 | 31 | #![cfg_attr(rustfmt, rustfmt_skip)] 32 | #![allow(unused_parens)] 33 | #![allow(unused_imports)] 34 | 35 | use frame_support::{traits::Get, weights::Weight}; 36 | use sp_std::marker::PhantomData; 37 | 38 | /// Weight functions for `filecoindot`. 39 | pub struct WeightInfo(PhantomData); 40 | impl filecoindot::WeightInfo for WeightInfo { 41 | // Storage: Filecoindot Relayers (r:1 w:1) 42 | // Storage: Filecoindot RelayerCount (r:1 w:1) 43 | fn add_relayer() -> Weight { 44 | (15_411_000 as Weight) 45 | .saturating_add(T::DbWeight::get().reads(2 as Weight)) 46 | .saturating_add(T::DbWeight::get().writes(2 as Weight)) 47 | } 48 | // Storage: Filecoindot Relayers (r:1 w:1) 49 | // Storage: Filecoindot VoteThreshold (r:1 w:0) 50 | // Storage: Filecoindot RelayerCount (r:1 w:1) 51 | fn remove_relayer() -> Weight { 52 | (17_435_000 as Weight) 53 | .saturating_add(T::DbWeight::get().reads(3 as Weight)) 54 | .saturating_add(T::DbWeight::get().writes(2 as Weight)) 55 | } 56 | // Storage: Filecoindot RelayerCount (r:1 w:0) 57 | // Storage: Filecoindot VoteThreshold (r:0 w:1) 58 | fn set_vote_threshold() -> Weight { 59 | (11_321_000 as Weight) 60 | .saturating_add(T::DbWeight::get().reads(1 as Weight)) 61 | .saturating_add(T::DbWeight::get().writes(1 as Weight)) 62 | } 63 | // Storage: Filecoindot Relayers (r:1 w:0) 64 | // Storage: Filecoindot VerifiedBlocks (r:1 w:1) 65 | // Storage: Filecoindot BlockSubmissionProposals (r:1 w:1) 66 | // Storage: Filecoindot VotingPeriod (r:1 w:0) 67 | // Storage: Filecoindot BlockProposalVotes (r:1 w:1) 68 | // Storage: Filecoindot VoteThreshold (r:1 w:0) 69 | // Storage: Filecoindot MessageRootCidCounter (r:1 w:1) 70 | fn submit_block_vote() -> Weight { 71 | (50_491_000 as Weight) 72 | .saturating_add(T::DbWeight::get().reads(7 as Weight)) 73 | .saturating_add(T::DbWeight::get().writes(4 as Weight)) 74 | } 75 | // Storage: Filecoindot VerifiedBlocks (r:1 w:1) 76 | // Storage: Filecoindot BlockSubmissionProposals (r:1 w:1) 77 | // Storage: Filecoindot VoteThreshold (r:1 w:0) 78 | // Storage: Filecoindot MessageRootCidCounter (r:0 w:1) 79 | fn close_block_proposal() -> Weight { 80 | (25_034_000 as Weight) 81 | .saturating_add(T::DbWeight::get().reads(3 as Weight)) 82 | .saturating_add(T::DbWeight::get().writes(3 as Weight)) 83 | } 84 | // Storage: Filecoindot VerifiedBlocks (r:1 w:0) 85 | fn verify_receipt() -> Weight { 86 | (7_567_000 as Weight) 87 | .saturating_add(T::DbWeight::get().reads(1 as Weight)) 88 | } 89 | // Storage: Filecoindot VerifiedBlocks (r:1 w:0) 90 | fn verify_state() -> Weight { 91 | (125_020_000 as Weight) 92 | .saturating_add(T::DbWeight::get().reads(1 as Weight)) 93 | } 94 | // Storage: Filecoindot VerifiedBlocks (r:1 w:0) 95 | fn verify_message() -> Weight { 96 | (125_149_000 as Weight) 97 | .saturating_add(T::DbWeight::get().reads(1 as Weight)) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /substrate-node-example/runtime/src/weights/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ChainSafe Systems 2 | // SPDX-License-Identifier: LGPL-3.0-only 3 | #![allow(clippy::unnecessary_cast)] 4 | pub mod filecoindot; 5 | -------------------------------------------------------------------------------- /ui/.env.example: -------------------------------------------------------------------------------- 1 | REACT_APP_WS_PROVIDER="ws://localhost:9944" -------------------------------------------------------------------------------- /ui/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "ecmaVersion": 2020, // Allows for the parsing of modern ECMAScript features 5 | "sourceType": "module", // Allows for the use of imports 6 | "ecmaFeatures": { 7 | "jsx": true // Allows for the parsing of JSX 8 | } 9 | }, 10 | "plugins": ["@typescript-eslint", "ternary"], 11 | "extends": [ 12 | "eslint:recommended", 13 | "plugin:@typescript-eslint/eslint-recommended", 14 | "plugin:@typescript-eslint/recommended", 15 | "plugin:react/recommended", 16 | "plugin:react-hooks/recommended", 17 | "plugin:ternary/recommended" 18 | ], 19 | "settings": { 20 | "react": { 21 | "version": "detect" 22 | } 23 | }, 24 | "rules": { 25 | "comma-spacing": ["error", { "before": false, "after": true }], 26 | "@typescript-eslint/indent": ["error", 2], 27 | "linebreak-style": ["error", "unix"], 28 | "quotes": ["error", "double"], 29 | "semi": ["error", "never"], 30 | "max-len": [ 31 | "error", 32 | { 33 | "code": 140 34 | } 35 | ], 36 | "react-hooks/rules-of-hooks": "error", 37 | "react-hooks/exhaustive-deps": "warn", 38 | "react/prop-types": 0, 39 | "react/jsx-max-props-per-line": ["error", { "maximum": 1, "when": "always" }], 40 | "react/jsx-first-prop-new-line": ["error", "multiline-multiprop"], 41 | "react/jsx-fragments": "error", 42 | "arrow-spacing": "error", 43 | "space-infix-ops": "error", 44 | "no-trailing-spaces": ["error", { "ignoreComments": true }], 45 | "comma-dangle": ["error", "never"], 46 | "object-curly-spacing": ["error", "always"], 47 | "space-in-parens": ["error", "never"], 48 | "ternary/no-unreachable": "off", 49 | "ternary/nesting": "off", 50 | "@typescript-eslint/no-unused-vars": "error", 51 | "@typescript-eslint/no-use-before-define": "off", 52 | "@typescript-eslint/explicit-function-return-type": "off", 53 | "@typescript-eslint/no-explicit-any": "off", 54 | "@typescript-eslint/camelcase": "off", 55 | "@typescript-eslint/interface-name-prefix": "off", 56 | "@typescript-eslint/explicit-module-boundary-types": "off", 57 | "@typescript-eslint/member-delimiter-style" : ["warn", { 58 | "multiline": { 59 | "delimiter": "none", 60 | "requireLast": true 61 | }, 62 | "singleline": { 63 | "delimiter": "semi", 64 | "requireLast": false 65 | } 66 | }] 67 | }, 68 | "ignorePatterns": [ 69 | ".github/**", 70 | ".vscode/**", 71 | ".yarn/**", 72 | "**/dist/*", 73 | "**/node_modules/*" 74 | ] 75 | } -------------------------------------------------------------------------------- /ui/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | .env 4 | 5 | # dependencies 6 | /node_modules 7 | /.pnp 8 | .pnp.js 9 | 10 | # testing 11 | /coverage 12 | 13 | # production 14 | /build 15 | 16 | # misc 17 | .DS_Store 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | -------------------------------------------------------------------------------- /ui/.nvmrc: -------------------------------------------------------------------------------- 1 | v16.0.0 -------------------------------------------------------------------------------- /ui/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Filecoindot UI 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | You need to setup an environment variable `REACT_APP_WS_PROVIDER` or rename `.env.exemple` into `.env` to use the default localhost value: "ws://localhost:9944" 6 | 7 | ## Available Scripts 8 | 9 | In the project directory, you can run: 10 | 11 | ### `yarn start` 12 | 13 | Runs the app in the development mode.\ 14 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 15 | 16 | The page will reload if you make edits.\ 17 | You will also see any lint errors in the console. 18 | 19 | ### `yarn test` 20 | 21 | Launches the test runner in the interactive watch mode.\ 22 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 23 | 24 | ### `yarn build` 25 | 26 | Builds the app for production to the `build` folder.\ 27 | It correctly bundles React in production mode and optimizes the build for the best performance. 28 | 29 | The build is minified and the filenames include the hashes.\ 30 | Your app is ready to be deployed! 31 | 32 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 33 | 34 | ### `yarn eject` 35 | 36 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 37 | 38 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 39 | 40 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 41 | 42 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 43 | 44 | ## Learn More 45 | 46 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 47 | 48 | To learn React, check out the [React documentation](https://reactjs.org/). 49 | -------------------------------------------------------------------------------- /ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "filecoindot-demo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@chainsafe/filecoindot-types": "^0.1.20", 7 | "@emotion/react": "^11.7.1", 8 | "@emotion/styled": "^11.6.0", 9 | "@mui/icons-material": "^5.2.5", 10 | "@mui/lab": "^5.0.0-alpha.65", 11 | "@mui/material": "^5.2.8", 12 | "@mui/styled-engine-sc": "^5.1.0", 13 | "@mui/styles": "^5.3.0", 14 | "@polkadot/api": "^7.3.1", 15 | "@polkadot/extension-dapp": "^0.42.5", 16 | "@polkadot/react-identicon": "0.87.6", 17 | "@testing-library/jest-dom": "^5.16.1", 18 | "@testing-library/react": "^12.1.2", 19 | "@testing-library/user-event": "^13.5.0", 20 | "@types/jest": "^27.4.0", 21 | "@types/node": "^17.0.8", 22 | "@types/react": "^17.0.38", 23 | "@types/react-dom": "^17.0.11", 24 | "axios": "^0.25.0", 25 | "clsx": "^1.1.1", 26 | "mui-file-dropzone": "^4.0.1", 27 | "react": "18.0.0-rc.0", 28 | "react-dom": "18.0.0-rc.0", 29 | "react-router-dom": "^6.0.2", 30 | "react-scripts": "5.0.0", 31 | "styled-components": "^5.3.3", 32 | "typescript": "^4.5.4", 33 | "web-vitals": "^2.1.3" 34 | }, 35 | "devDependencies": { 36 | "@polkadot/typegen": "^7.5.1", 37 | "@typescript-eslint/eslint-plugin": "^5.9.1", 38 | "@typescript-eslint/parser": "^5.9.1", 39 | "eslint-plugin-react": "^7.28.0", 40 | "eslint-plugin-react-hooks": "^4.3.0", 41 | "eslint-plugin-ternary": "^2.0.0" 42 | }, 43 | "scripts": { 44 | "start": "react-scripts start", 45 | "build": "react-scripts build", 46 | "test": "react-scripts test", 47 | "eject": "react-scripts eject", 48 | "lint": "eslint 'src/**/*.{js,ts,tsx}'", 49 | "lint:types": "tsc --pretty", 50 | "lint:fix": "yarn run lint --fix", 51 | "generate:types-from-chain": "yarn polkadot-types-from-chain --endpoint ws://localhost:9944 --output ./src/interfaces", 52 | "generate:type-from-defs": "yarn polkadot-types-from-defs --endpoint ./filecoindot-metadata.json --input ./src/interfaces" 53 | }, 54 | "browserslist": { 55 | "production": [ 56 | ">0.2%", 57 | "not ie <= 99", 58 | "not android <= 4.4.4", 59 | "not dead", 60 | "not op_mini all" 61 | ], 62 | "development": [ 63 | "last 1 chrome version", 64 | "last 1 firefox version", 65 | "last 1 safari version" 66 | ] 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /ui/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChainSafe/filecoindot/b94b4407a3fbff87f8650f4e4306d07d65e9b917/ui/public/favicon.ico -------------------------------------------------------------------------------- /ui/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 22 | 31 | Filecoin Substrate pallet demo 32 | 33 | 34 | 35 |

36 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /ui/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Filecoindot demo", 3 | "name": "Filecoin Substrate pallet demo", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /ui/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /ui/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ui/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { render, screen } from "@testing-library/react" 3 | import App from "./App" 4 | 5 | test("renders learn react link", () => { 6 | render() 7 | const linkElement = screen.getByText(/learn react/i) 8 | expect(linkElement).toBeInTheDocument() 9 | }) 10 | -------------------------------------------------------------------------------- /ui/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import "./App.css" 3 | import { BrowserRouter, Route, Routes } from "react-router-dom" 4 | import { MintNFT } from "./pages/MintNFT" 5 | import { Header } from "./containers/Header" 6 | import Container from "@mui/material/Container" 7 | import { VerifyBlock } from "./pages/VerifyBlock" 8 | import { UserSpace } from "./containers/UserSpace" 9 | import { AccountContextProvider } from "./contexts/AccountsContext" 10 | import { ApiContextProvider } from "./contexts/ApiContext" 11 | import { NftContextProvider } from "./contexts/NftContext" 12 | 13 | function App() { 14 | return ( 15 | 16 | 17 | 18 | 19 |
20 | 23 | 24 | 25 | } 28 | /> 29 | } 32 | /> 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | ) 41 | } 42 | 43 | export default App 44 | -------------------------------------------------------------------------------- /ui/src/components/layout/Center.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Grid } from "@mui/material" 3 | 4 | export const Center: React.FC = ({ children }) => ( 5 | 12 | 15 | {children} 16 | 17 | 18 | ) 19 | -------------------------------------------------------------------------------- /ui/src/constants/substrate.ts: -------------------------------------------------------------------------------- 1 | export const DAPP_NAME = "filecoindot" 2 | -------------------------------------------------------------------------------- /ui/src/containers/AccountSelect/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { CardHeader, IconButton, Menu, MenuItem } from "@mui/material" 3 | import { Identicon } from "@polkadot/react-identicon" 4 | import { MenuProps } from "@mui/material/Menu/Menu" 5 | import { useAccountList } from "../../contexts/AccountsContext" 6 | 7 | interface Props extends Omit { 8 | anchorEl: null | HTMLElement 9 | onClose: () => void 10 | } 11 | 12 | export const AccountSelect: React.FC = ({ anchorEl, onClose, ...props }) => { 13 | 14 | const { accountList, selectAccount } = useAccountList() 15 | 16 | if (!accountList) { 17 | return null 18 | } 19 | 20 | const handleSelect = (address: string) => () => { 21 | selectAccount(address) 22 | onClose() 23 | } 24 | 25 | return ( 26 | 32 | {accountList.map((account) => ( 33 | 36 | 39 | 44 | 45 | } 46 | title={account.meta.name} 47 | subheader={account.address} 48 | /> 49 | 50 | ))} 51 | 52 | ) 53 | } 54 | -------------------------------------------------------------------------------- /ui/src/containers/Header/index.tsx: -------------------------------------------------------------------------------- 1 | import AppBar from "@mui/material/AppBar" 2 | import React, { useEffect } from "react" 3 | import { Box, Button, Container, Toolbar, Tooltip, Typography } from "@mui/material" 4 | import { AccountSelect } from "../AccountSelect" 5 | import { Identicon } from "@polkadot/react-identicon" 6 | import { Link } from "react-router-dom" 7 | import { useAccountList } from "../../contexts/AccountsContext" 8 | import { useApi } from "../../contexts/ApiContext" 9 | 10 | export const Header: React.FC = () => { 11 | const { selectedAddress, getAccountByAddress } = useAccountList() 12 | const [anchorElUser, setAnchorElUser] = React.useState(null) 13 | const { api, isApiReady } = useApi() 14 | const handleOpenUserMenu = (event: React.MouseEvent) => { 15 | setAnchorElUser(event.currentTarget) 16 | } 17 | 18 | useEffect(() => { 19 | if (!isApiReady) return 20 | 21 | let unsubscribe: () => void 22 | 23 | // use the api 24 | api.derive.chain.bestNumber((number) => { 25 | console.log(number.toNumber()) 26 | }) 27 | .then(unsub => {unsubscribe = unsub}) 28 | .catch(console.error) 29 | 30 | return () => unsubscribe && unsubscribe() 31 | }) 32 | 33 | const handleCloseUserMenu = () => { 34 | setAnchorElUser(null) 35 | } 36 | 37 | return ( 38 | 39 | 40 | 41 | 47 | Filecoindot pallet demo 48 | 49 | {selectedAddress && ( 50 | <> 51 | 52 | 59 | 65 | 66 | 67 | 68 | 86 | 87 | 120 | 121 | 122 | )} 123 | 124 | 125 | 126 | ) 127 | } 128 | -------------------------------------------------------------------------------- /ui/src/containers/Settings/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Box, Fab, Tooltip } from "@mui/material" 3 | import SettingsIcon from "@mui/icons-material/Settings" 4 | 5 | export const Settings: React.FC = () => { 6 | return ( 7 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /ui/src/containers/UserSpace/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Center } from "../../components/layout/Center" 3 | import { Box, CircularProgress } from "@mui/material" 4 | // import { AccountSelect } from "../AccountSelect" 5 | import { useAccountList } from "../../contexts/AccountsContext" 6 | import { useApi } from "../../contexts/ApiContext" 7 | 8 | export const UserSpace: React.FC = ({ children }) => { 9 | const { isApiReady } = useApi() 10 | const { extensionNotFound, isAccountListEmpty, isAccountLoading, selectedAddress } = useAccountList() 11 | // const [anchorElUser, setAnchorElUser] = React.useState(null) 12 | 13 | // const handleOpenUserMenu = (event: React.MouseEvent) => { 14 | // setAnchorElUser(event.currentTarget) 15 | // } 16 | 17 | // const handleCloseUserMenu = () => { 18 | // setAnchorElUser(null) 19 | // } 20 | 21 | if(!isApiReady || isAccountLoading){ 22 | return ( 23 | 32 | 33 | Connecting to the node at {process.env.REACT_APP_WS_PROVIDER} 34 | 35 | ) 36 | } 37 | 38 | console.log("selected", selectedAddress) 39 | if (selectedAddress) return <>{children} 40 | 41 | if(extensionNotFound) 42 | return ( 43 |
44 |

Please install the 49 | Polkadot.js extension 50 | 51 |

52 |
53 | ) 54 | 55 | if(isAccountListEmpty) 56 | return ( 57 |
58 |

Please create at least an account in the extension

59 |
60 | ) 61 | 62 | // if (state === PluginState.INITIALIZATION) return null; 63 | 64 | // if (state === PluginState.UNAUTHORIZED) 65 | // return ( 66 | //
67 | //

Please Authorise page

68 | //
69 | // ); 70 | 71 | // if (state === PluginState.NONE) 72 | // return ( 73 | //
74 | //

There is no plugin :sad:

75 | //
76 | // ); 77 | 78 | // if (state === PluginState.INJECTED) 79 | // return ( 80 | //
81 | //

Please Allow Access

82 | //
83 | // ); 84 | 85 | // if (!accounts.length) 86 | // return ( 87 | //
88 | //

Please Add Account

89 | //
90 | // ); 91 | 92 | return ( 93 | 102 | 103 | Loading accounts... 104 | 105 | //
106 | // 112 | // 115 | //
116 | ) 117 | } 118 | -------------------------------------------------------------------------------- /ui/src/contexts/AccountsContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, createContext, useContext, useCallback } from "react" 2 | import { web3Accounts, web3Enable } from "@polkadot/extension-dapp" 3 | import { InjectedAccountWithMeta } from "@polkadot/extension-inject/types" 4 | import { DAPP_NAME } from "../constants/substrate" 5 | 6 | const LOCALSTORAGE_KEY = "fsb.selectedAccount" 7 | 8 | type AccountContextProps = { 9 | children: React.ReactNode | React.ReactNode[] 10 | } 11 | 12 | export interface IAccountContext { 13 | selectedAddress?: string 14 | accountList?: InjectedAccountWithMeta[] 15 | selectAccount: (address: string) => void 16 | getAccountByAddress: (address: string) => InjectedAccountWithMeta | undefined 17 | isAccountLoading: boolean 18 | extensionNotFound: boolean 19 | isAccountListEmpty: boolean 20 | } 21 | 22 | const AccountContext = createContext(undefined) 23 | 24 | const AccountContextProvider = ({ children }: AccountContextProps) => { 25 | const [selectedAddress, setSelected] = useState("") 26 | const [accountList, setAccountList] = useState([]) 27 | const [isAccountLoading, setIsAccountLoading] = useState(false) 28 | const [extensionNotFound, setExtensionNotFound] = useState(false) 29 | const [isAccountListEmpty, setIsAccountListEmpty] = useState(false) 30 | 31 | const selectAccount = useCallback((address: string) => { 32 | localStorage.setItem(LOCALSTORAGE_KEY, address) 33 | setSelected(address) 34 | }, []) 35 | 36 | const getaccountList = useCallback(async (): Promise => { 37 | const extensions = await web3Enable(DAPP_NAME) 38 | 39 | if (extensions.length === 0) { 40 | setExtensionNotFound(true) 41 | setIsAccountLoading(false) 42 | return 43 | } else { 44 | setExtensionNotFound(false) 45 | } 46 | 47 | const accountList = await web3Accounts() 48 | 49 | if (accountList.length === 0) { 50 | setIsAccountListEmpty(true) 51 | setIsAccountLoading(false) 52 | return 53 | } 54 | 55 | setAccountList(accountList) 56 | 57 | if (accountList.length > 0) { 58 | const previousAccountAddress = localStorage.getItem(LOCALSTORAGE_KEY) 59 | 60 | if(!previousAccountAddress){ 61 | selectAccount(accountList[0].address) 62 | } else { selectAccount(previousAccountAddress) 63 | } 64 | 65 | } 66 | 67 | setIsAccountLoading(false) 68 | return 69 | }, [selectAccount]) 70 | 71 | const getAccountByAddress = useCallback((address: string) => { 72 | return accountList.find(account => account.address === address) 73 | }, [accountList]) 74 | 75 | useEffect(() => { 76 | if (!accountList.length) { 77 | getaccountList() 78 | } 79 | }, [accountList, getaccountList]) 80 | 81 | return ( 82 | 93 | {children} 94 | 95 | ) 96 | } 97 | 98 | const useAccountList = () => { 99 | const context = useContext(AccountContext) 100 | if (context === undefined) { 101 | throw new Error("useAccountList must be used within a AccountContextProvider") 102 | } 103 | return context 104 | } 105 | 106 | export { AccountContextProvider, useAccountList } 107 | -------------------------------------------------------------------------------- /ui/src/contexts/ApiContext.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { ApiPromise, WsProvider } from "@polkadot/api" 3 | import { ApiOptions } from "@polkadot/api/types" 4 | import { TypeRegistry } from "@polkadot/types" 5 | import { useState, useEffect, createContext, useContext } from "react" 6 | import { useDidUpdateEffect } from "../hooks/useDidUpdateEffect" 7 | import { rpc } from "@chainsafe/filecoindot-types" 8 | 9 | type ApiContextProps = { 10 | children: React.ReactNode | React.ReactNode[] 11 | types?: ApiOptions["types"] 12 | } 13 | 14 | const registry = new TypeRegistry() 15 | 16 | export interface IApiContext { 17 | api: ApiPromise // From @polkadot/api\ 18 | isApiReady: boolean 19 | } 20 | 21 | const ApiContext = createContext(undefined) 22 | 23 | 24 | const ApiContextProvider = ({ children, types }: ApiContextProps) => { 25 | const WS_PROVIDER = process.env.REACT_APP_WS_PROVIDER 26 | const provider = new WsProvider(WS_PROVIDER) 27 | const [apiPromise, setApiPromise] = useState( 28 | new ApiPromise({ provider, types, rpc }) 29 | ) 30 | const [isReady, setIsReady] = useState(false) 31 | 32 | useDidUpdateEffect(() => { 33 | // We want to fetch all the information again each time we reconnect. We 34 | // might be connecting to a different node, or the node might have changed 35 | // settings. 36 | setApiPromise(new ApiPromise({ provider, types, rpc })) 37 | 38 | setIsReady(false) 39 | }) 40 | 41 | useEffect(() => { 42 | // We want to fetch all the information again each time we reconnect. We 43 | // might be connecting to a different node, or the node might have changed 44 | // settings. 45 | apiPromise.isReady 46 | .then(() => { 47 | if (types) { 48 | registry.register(types) 49 | } 50 | 51 | setIsReady(true) 52 | }) 53 | .catch(e => console.error(e)) 54 | }, [apiPromise.isReady, types]) 55 | 56 | 57 | if (!WS_PROVIDER) { 58 | console.error("REACT_APP_WS_PROVIDER not set") 59 | return null 60 | } 61 | 62 | return ( 63 | 69 | {children} 70 | 71 | ) 72 | } 73 | 74 | const useApi = () => { 75 | const context = useContext(ApiContext) 76 | if (context === undefined) { 77 | throw new Error("useApi must be used within a ApiContextProvider") 78 | } 79 | return context 80 | } 81 | 82 | export { ApiContextProvider, useApi } 83 | -------------------------------------------------------------------------------- /ui/src/contexts/NftContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, createContext, useContext, useCallback } from "react" 2 | 3 | type AccountContextProps = { 4 | children: React.ReactNode | React.ReactNode[] 5 | } 6 | 7 | interface Nft { 8 | filePreview: string 9 | name: string 10 | cid: string 11 | } 12 | export interface INftContext { 13 | nftList: Nft[] 14 | addNft: (nft: Nft) => void 15 | } 16 | 17 | const NftContext = createContext(undefined) 18 | 19 | const NftContextProvider = ({ children }: AccountContextProps) => { 20 | const [nftList, setNftList] = useState([]) 21 | 22 | const addNft = useCallback((nft: Nft) => { 23 | setNftList((prevList) => [...prevList, nft]) 24 | }, []) 25 | 26 | return ( 27 | 33 | {children} 34 | 35 | ) 36 | } 37 | 38 | const useNftList = () => { 39 | const context = useContext(NftContext) 40 | if (context === undefined) { 41 | throw new Error("useAccountList must be used within a NftContextProvider") 42 | } 43 | return context 44 | } 45 | 46 | export { NftContextProvider, useNftList } 47 | -------------------------------------------------------------------------------- /ui/src/hooks/usNft.tsx: -------------------------------------------------------------------------------- 1 | import { Keyring } from "@polkadot/api" 2 | import { web3FromSource } from "@polkadot/extension-dapp" 3 | import { useCallback, useState } from "react" 4 | import { useAccountList } from "../contexts/AccountsContext" 5 | import { useApi } from "../contexts/ApiContext" 6 | import useBuckets from "./useBuckets" 7 | 8 | const COLLECTION_ID = "d43593c7a56da27d-FILDOT" 9 | const COLLECTION = { 10 | "max": 0, 11 | "issuer": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", 12 | "symbol": "FILDOT", 13 | "id": COLLECTION_ID, 14 | "metadata": "ipfs://ipfs/QmQCVGmrSTixoxBe8qg4McW3KxipUZq9jugucHyfmyPGrk" 15 | } 16 | 17 | const useNft = () => { 18 | const [isCollectionCreated, setIsCollectionCreated] = useState(false) 19 | const { isApiReady, api } = useApi() 20 | const { selectedAddress, getAccountByAddress } = useAccountList() 21 | const { nftMetaBucketId, uploadFile } = useBuckets() 22 | 23 | const createCollection = async () => { 24 | if (isCollectionCreated || !isApiReady) return 25 | 26 | console.log("Creating collection with Alice") 27 | 28 | const payload = `RMRK::MINT::2.0.0::${encodeURIComponent(JSON.stringify(COLLECTION))}` 29 | const keyring = new Keyring({ type: "sr25519" }) 30 | const signer = keyring.createFromUri("//Alice") 31 | return new Promise((resolve, reject) => { 32 | api.tx.system.remark(payload) 33 | .signAndSend(signer, ({ status }) => { 34 | if(status.isInBlock) { 35 | console.log("collection created at block", status.asInBlock.toString()) 36 | setIsCollectionCreated(true) 37 | resolve() 38 | } 39 | }) 40 | .catch((e: Error) => { 41 | console.error(e) 42 | reject(e) 43 | }) 44 | }) 45 | } 46 | 47 | const uploadNftMetadata = useCallback((imageCid: string, name: string) => { 48 | const nftMetadata = { 49 | "external_url": "https://github.com/ChainSafe/filecoindot", 50 | "image": `ipfs://ipfs/${imageCid}`, 51 | "description": "This Nft was created using the Filecoindot demo", 52 | "name": name, 53 | "properties": {} 54 | } 55 | 56 | const jsonNftMetadata = JSON.stringify(nftMetadata) 57 | const jsonNftMetadataFile = new File([jsonNftMetadata], `${name}_Metadata.json`, { type: "application/json" }) 58 | 59 | return uploadFile(jsonNftMetadataFile, nftMetaBucketId) 60 | }, [nftMetaBucketId, uploadFile]) 61 | 62 | 63 | const mintNft = useCallback(async (imageCid: string, name: string) => { 64 | 65 | if (!selectedAddress) return 66 | 67 | const nftMetadataCid = await uploadNftMetadata(imageCid, name) 68 | const signerAccount = getAccountByAddress(selectedAddress) 69 | 70 | if (!signerAccount) { 71 | throw new Error("No account selected") 72 | } 73 | 74 | const injector = await web3FromSource(signerAccount.meta.source) 75 | 76 | const nft = { 77 | "collection": COLLECTION_ID, 78 | "transferable": 0, 79 | "sn": "00000001", 80 | "metadata": `ipfs://ipfs/${nftMetadataCid}` 81 | } 82 | 83 | const payload = `RMRK::MINT::2.0.0::${encodeURIComponent(JSON.stringify(nft))}` 84 | return new Promise((resolve, reject) => { 85 | api.tx.system.remark(payload) 86 | .signAndSend(signerAccount.address, { signer: injector.signer }, ({ status }) => { 87 | if(status.isInBlock) { 88 | console.log("Nft minted at block", status.asInBlock.toString()) 89 | resolve(status.asInBlock.toString()) 90 | } 91 | }) 92 | .catch((e: Error) => { 93 | console.error(e) 94 | reject(e) 95 | }) 96 | }) 97 | }, [api, getAccountByAddress, uploadNftMetadata, selectedAddress]) 98 | 99 | return { createCollection, isCollectionCreated, mintNft } 100 | } 101 | 102 | export default useNft 103 | 104 | -------------------------------------------------------------------------------- /ui/src/hooks/useBuckets.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import axios from "axios" 3 | import { useCallback, useEffect, useState } from "react" 4 | 5 | // const COLLECTION_METADATA_BUCKET_NAME = "filecoindot-collection-metadata" 6 | const NFT_METADATA_BUCKET_NAME = "filecoindot-nft-metadata" 7 | const NFT_IMAGES_BUCKET_NAME = "filecoindot-nft-images" 8 | 9 | const STORAGE_API_TOKEN = process.env.REACT_APP_STORAGE_TOKEN || "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NDI1OTg5NTgsImNuZiI6eyJqa3UiOiIvY2VydHMiLCJraWQiOiI5aHE4bnlVUWdMb29ER2l6VnI5SEJtOFIxVEwxS0JKSFlNRUtTRXh4eGtLcCJ9LCJ0eXBlIjoiYXBpX3NlY3JldCIsImlkIjo2MywidXVpZCI6IjU2Zjk3ZTAyLTUzMjctNDI5Ni1iMDllLTVkMmY0NDc3NDdjZCIsInBlcm0iOnsiYmlsbGluZyI6IioiLCJzZWFyY2giOiIqIiwic3RvcmFnZSI6IioiLCJ1c2VyIjoiKiJ9LCJhcGlfa2V5IjoiWFBQSk9RRUFZR1BXUkpBT05NWFYiLCJzZXJ2aWNlIjoic3RvcmFnZSJ9.VjOxvoPRHWFKkD60n5QH2v8O1w0Y2x1c4cKfg_1WhJufVwCFPNa4U_yBgG9-butWU1igZqDkmuGFDKwN0JVbNg" 10 | const API = "https://stage.imploy.site/api/v1" 11 | 12 | const useBuckets = () => { 13 | // const [collectionMetaBucketId, setCollectionMetaBucketId] = useState("") 14 | const [nftMetaBucketId, setNftMetaBucketId] = useState("") 15 | const [nftImagesBucketId, setNftImagesBucketId] = useState("") 16 | 17 | const getBuckets = useCallback(() => { 18 | return axios.get[]>( 19 | `${API}/buckets`, 20 | { headers: { 21 | "Authorization" : `Bearer ${STORAGE_API_TOKEN}` 22 | } } 23 | ) 24 | }, []) 25 | 26 | const createBucket = useCallback((bucketName: string, setter: React.Dispatch>) => { 27 | const bucketCreationBody = { 28 | type: "fps", 29 | name: bucketName 30 | } 31 | 32 | axios.post( 33 | `${API}/buckets`, 34 | bucketCreationBody, 35 | { headers: { 36 | "Authorization" : `Bearer ${STORAGE_API_TOKEN}` 37 | } } 38 | ).then(({ data }) => { 39 | setter(data.id) 40 | }) 41 | .catch(console.error) 42 | }, []) 43 | 44 | const uploadFile = useCallback((file: File, bucketId: string) => { 45 | if(!bucketId) return Promise.reject("no bucket id") 46 | 47 | // Create an object of formData 48 | const formData = new FormData() 49 | 50 | // Update the formData object 51 | formData.append( 52 | "file", 53 | file, 54 | file.name 55 | ) 56 | 57 | formData.append("path", "/") 58 | 59 | return axios.post( 60 | `${API}/bucket/${bucketId}/upload`, 61 | formData, 62 | { headers: { 63 | "Authorization" : `Bearer ${STORAGE_API_TOKEN}` 64 | } } 65 | ).then(async ({ data }) => { 66 | console.log("data", data) 67 | const fileInfo = await axios.post<{content: {cid: string}}>( 68 | `${API}/bucket/${bucketId}/file`, 69 | { 70 | path: `${file.name}` 71 | }, 72 | { headers: { 73 | "Authorization" : `Bearer ${STORAGE_API_TOKEN}` 74 | } } 75 | ) 76 | 77 | return fileInfo.data.content.cid 78 | }) 79 | .catch((e) => { 80 | console.error(e) 81 | return Promise.reject(e) 82 | }) 83 | }, []) 84 | 85 | const setOrCreateBucket = useCallback((bucketName: string, setter: React.Dispatch>) => { 86 | getBuckets() 87 | .then(({ data }) => { 88 | const bucketToFind = data.find((bucket) => bucket.name === bucketName) 89 | 90 | bucketToFind 91 | ? setter(bucketToFind.id) 92 | : createBucket(bucketName, setter) 93 | }) 94 | .catch(console.error) 95 | 96 | }, [createBucket, getBuckets]) 97 | 98 | useEffect(() => { 99 | // if(!collectionMetaBucketId) setOrCreateBucket(COLLECTION_METADATA_BUCKET_NAME, setCollectionMetaBucketId) 100 | 101 | if(!nftMetaBucketId) setOrCreateBucket(NFT_METADATA_BUCKET_NAME, setNftMetaBucketId) 102 | 103 | if(!nftImagesBucketId) setOrCreateBucket(NFT_IMAGES_BUCKET_NAME, setNftImagesBucketId) 104 | }, [nftImagesBucketId, nftMetaBucketId, setOrCreateBucket]) 105 | 106 | 107 | return { nftImagesBucketId, nftMetaBucketId, uploadFile } 108 | } 109 | 110 | export default useBuckets -------------------------------------------------------------------------------- /ui/src/hooks/useDidUpdateEffect.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import { EffectCallback, useEffect, useRef } from "react" 3 | 4 | /** 5 | * Exactly like React's `useEffect`, but skips initial render. Tries to 6 | * reproduce `componentDidUpdate` behavior. 7 | * 8 | * @see https://stackoverflow.com/questions/53179075/with-useeffect-how-can-i-skip-applying-an-effect-upon-the-initial-render/53180013#53180013 9 | */ 10 | export function useDidUpdateEffect( 11 | fn: EffectCallback 12 | // inputs?: DependencyList 13 | ): void { 14 | const didMountRef = useRef(false) 15 | 16 | return useEffect(() => { 17 | if (didMountRef.current) fn() 18 | else didMountRef.current = true 19 | // eslint-disable-next-line react-hooks/exhaustive-deps 20 | }, []) 21 | } -------------------------------------------------------------------------------- /ui/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 4 | 'Droid Sans', 'Helvetica Neue', sans-serif; 5 | -webkit-font-smoothing: antialiased; 6 | -moz-osx-font-smoothing: grayscale; 7 | } 8 | 9 | code { 10 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; 11 | } 12 | 13 | #root { 14 | min-height: 100vh; 15 | } 16 | -------------------------------------------------------------------------------- /ui/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import ReactDOM from "react-dom" 3 | import "./index.css" 4 | import App from "./App" 5 | import reportWebVitals from "./reportWebVitals" 6 | import { CssBaseline } from "@mui/material" 7 | 8 | ReactDOM.render( 9 | 10 | 11 | 12 | , 13 | document.getElementById("root") 14 | ) 15 | 16 | // If you want to start measuring performance in your app, pass a function 17 | // to log results (for example: reportWebVitals(console.log)) 18 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 19 | reportWebVitals() 20 | -------------------------------------------------------------------------------- /ui/src/interfaces/augment-api-consts.ts: -------------------------------------------------------------------------------- 1 | // Auto-generated via `yarn polkadot-types-from-chain`, do not edit 2 | /* eslint-disable */ 3 | 4 | import type { ApiTypes } from '@polkadot/api-base/types'; 5 | import type { Vec, u128, u16, u32, u64, u8 } from '@polkadot/types-codec'; 6 | import type { Codec } from '@polkadot/types-codec/types'; 7 | import type { FrameSupportWeightsRuntimeDbWeight, FrameSupportWeightsWeightToFeeCoefficient, FrameSystemLimitsBlockLength, FrameSystemLimitsBlockWeights, SpVersionRuntimeVersion } from '@polkadot/types/lookup'; 8 | 9 | declare module '@polkadot/api-base/types/consts' { 10 | export interface AugmentedConsts { 11 | balances: { 12 | /** 13 | * The minimum amount required to keep an account open. 14 | **/ 15 | existentialDeposit: u128 & AugmentedConst; 16 | /** 17 | * The maximum number of locks that should exist on an account. 18 | * Not strictly enforced, but used for weight estimation. 19 | **/ 20 | maxLocks: u32 & AugmentedConst; 21 | /** 22 | * The maximum number of named reserves that can exist on an account. 23 | **/ 24 | maxReserves: u32 & AugmentedConst; 25 | /** 26 | * Generic const 27 | **/ 28 | [key: string]: Codec; 29 | }; 30 | grandpa: { 31 | /** 32 | * Max Authorities in use 33 | **/ 34 | maxAuthorities: u32 & AugmentedConst; 35 | /** 36 | * Generic const 37 | **/ 38 | [key: string]: Codec; 39 | }; 40 | system: { 41 | /** 42 | * Maximum number of block number to block hash mappings to keep (oldest pruned first). 43 | **/ 44 | blockHashCount: u32 & AugmentedConst; 45 | /** 46 | * The maximum length of a block (in bytes). 47 | **/ 48 | blockLength: FrameSystemLimitsBlockLength & AugmentedConst; 49 | /** 50 | * Block & extrinsics weights: base values and limits. 51 | **/ 52 | blockWeights: FrameSystemLimitsBlockWeights & AugmentedConst; 53 | /** 54 | * The weight of runtime database operations the runtime can invoke. 55 | **/ 56 | dbWeight: FrameSupportWeightsRuntimeDbWeight & AugmentedConst; 57 | /** 58 | * The designated SS85 prefix of this chain. 59 | * 60 | * This replaces the "ss58Format" property declared in the chain spec. Reason is 61 | * that the runtime should know about the prefix in order to make use of it as 62 | * an identifier of the chain. 63 | **/ 64 | ss58Prefix: u16 & AugmentedConst; 65 | /** 66 | * Get the chain's current version. 67 | **/ 68 | version: SpVersionRuntimeVersion & AugmentedConst; 69 | /** 70 | * Generic const 71 | **/ 72 | [key: string]: Codec; 73 | }; 74 | timestamp: { 75 | /** 76 | * The minimum period between blocks. Beware that this is different to the *expected* 77 | * period that the block production apparatus provides. Your chosen consensus system will 78 | * generally work with this to determine a sensible block time. e.g. For Aura, it will be 79 | * double this period on default settings. 80 | **/ 81 | minimumPeriod: u64 & AugmentedConst; 82 | /** 83 | * Generic const 84 | **/ 85 | [key: string]: Codec; 86 | }; 87 | transactionPayment: { 88 | /** 89 | * A fee mulitplier for `Operational` extrinsics to compute "virtual tip" to boost their 90 | * `priority` 91 | * 92 | * This value is multipled by the `final_fee` to obtain a "virtual tip" that is later 93 | * added to a tip component in regular `priority` calculations. 94 | * It means that a `Normal` transaction can front-run a similarly-sized `Operational` 95 | * extrinsic (with no tip), by including a tip value greater than the virtual tip. 96 | * 97 | * ```rust,ignore 98 | * // For `Normal` 99 | * let priority = priority_calc(tip); 100 | * 101 | * // For `Operational` 102 | * let virtual_tip = (inclusion_fee + tip) * OperationalFeeMultiplier; 103 | * let priority = priority_calc(tip + virtual_tip); 104 | * ``` 105 | * 106 | * Note that since we use `final_fee` the multiplier applies also to the regular `tip` 107 | * sent with the transaction. So, not only does the transaction get a priority bump based 108 | * on the `inclusion_fee`, but we also amplify the impact of tips applied to `Operational` 109 | * transactions. 110 | **/ 111 | operationalFeeMultiplier: u8 & AugmentedConst; 112 | /** 113 | * The fee to be paid for making a transaction; the per-byte portion. 114 | **/ 115 | transactionByteFee: u128 & AugmentedConst; 116 | /** 117 | * The polynomial that is applied in order to derive fee from weight. 118 | **/ 119 | weightToFee: Vec & AugmentedConst; 120 | /** 121 | * Generic const 122 | **/ 123 | [key: string]: Codec; 124 | }; 125 | } // AugmentedConsts 126 | } // declare module 127 | -------------------------------------------------------------------------------- /ui/src/interfaces/augment-api-events.ts: -------------------------------------------------------------------------------- 1 | // Auto-generated via `yarn polkadot-types-from-chain`, do not edit 2 | /* eslint-disable */ 3 | 4 | import type { ApiTypes } from '@polkadot/api-base/types'; 5 | import type { Bytes, Null, Result, Vec, u128, u32, u64 } from '@polkadot/types-codec'; 6 | import type { ITuple } from '@polkadot/types-codec/types'; 7 | import type { AccountId32, H256 } from '@polkadot/types/interfaces/runtime'; 8 | import type { FrameSupportTokensMiscBalanceStatus, FrameSupportWeightsDispatchInfo, SpFinalityGrandpaAppPublic, SpRuntimeDispatchError } from '@polkadot/types/lookup'; 9 | 10 | declare module '@polkadot/api-base/types/events' { 11 | export interface AugmentedEvents { 12 | balances: { 13 | /** 14 | * A balance was set by root. 15 | **/ 16 | BalanceSet: AugmentedEvent; 17 | /** 18 | * Some amount was deposited (e.g. for transaction fees). 19 | **/ 20 | Deposit: AugmentedEvent; 21 | /** 22 | * An account was removed whose balance was non-zero but below ExistentialDeposit, 23 | * resulting in an outright loss. 24 | **/ 25 | DustLost: AugmentedEvent; 26 | /** 27 | * An account was created with some free balance. 28 | **/ 29 | Endowed: AugmentedEvent; 30 | /** 31 | * Some balance was reserved (moved from free to reserved). 32 | **/ 33 | Reserved: AugmentedEvent; 34 | /** 35 | * Some balance was moved from the reserve of the first account to the second account. 36 | * Final argument indicates the destination balance type. 37 | **/ 38 | ReserveRepatriated: AugmentedEvent; 39 | /** 40 | * Some amount was removed from the account (e.g. for misbehavior). 41 | **/ 42 | Slashed: AugmentedEvent; 43 | /** 44 | * Transfer succeeded. 45 | **/ 46 | Transfer: AugmentedEvent; 47 | /** 48 | * Some balance was unreserved (moved from reserved to free). 49 | **/ 50 | Unreserved: AugmentedEvent; 51 | /** 52 | * Some amount was withdrawn from the account (e.g. for transaction fees). 53 | **/ 54 | Withdraw: AugmentedEvent; 55 | /** 56 | * Generic event 57 | **/ 58 | [key: string]: AugmentedEvent; 59 | }; 60 | filecoindot: { 61 | /** 62 | * The proposal is approved 63 | * \[BlockCid\] 64 | **/ 65 | ProposalApproved: AugmentedEvent; 66 | /** 67 | * Proposal created 68 | * \[BlockCid\] 69 | **/ 70 | ProposalCreated: AugmentedEvent; 71 | /** 72 | * The proposal is rejected 73 | * \[BlockCid\] 74 | **/ 75 | ProposalRejected: AugmentedEvent; 76 | /** 77 | * Relayer added to set 78 | * \[AccountId\] 79 | **/ 80 | RelayerAdded: AugmentedEvent; 81 | /** 82 | * Relayer removed from set 83 | * \[AccountId\] 84 | **/ 85 | RelayerRemoved: AugmentedEvent; 86 | /** 87 | * Vote against the proposal casted 88 | * \[BlockCid, AccountId\] 89 | **/ 90 | VoteAgainstCasted: AugmentedEvent; 91 | /** 92 | * Vote for the proposal casted 93 | * \[BlockCid, AccountId\] 94 | **/ 95 | VoteCasted: AugmentedEvent; 96 | /** 97 | * Relayer threshold updated to value 98 | * \[RelayerThreshold\] 99 | **/ 100 | VoteThresholdChanged: AugmentedEvent; 101 | /** 102 | * Generic event 103 | **/ 104 | [key: string]: AugmentedEvent; 105 | }; 106 | filecoindotNFT: { 107 | /** 108 | * Minted NFT token. \[from, class_id, quantity\] 109 | **/ 110 | MintedToken: AugmentedEvent; 111 | /** 112 | * Transferred NFT token. \[from, to, class_id, token_id\] 113 | **/ 114 | TransferredToken: AugmentedEvent; 115 | /** 116 | * Generic event 117 | **/ 118 | [key: string]: AugmentedEvent; 119 | }; 120 | grandpa: { 121 | /** 122 | * New authority set has been applied. 123 | **/ 124 | NewAuthorities: AugmentedEvent>]>; 125 | /** 126 | * Current authority set has been paused. 127 | **/ 128 | Paused: AugmentedEvent; 129 | /** 130 | * Current authority set has been resumed. 131 | **/ 132 | Resumed: AugmentedEvent; 133 | /** 134 | * Generic event 135 | **/ 136 | [key: string]: AugmentedEvent; 137 | }; 138 | sudo: { 139 | /** 140 | * The \[sudoer\] just switched identity; the old key is supplied. 141 | **/ 142 | KeyChanged: AugmentedEvent; 143 | /** 144 | * A sudo just took place. \[result\] 145 | **/ 146 | Sudid: AugmentedEvent]>; 147 | /** 148 | * A sudo just took place. \[result\] 149 | **/ 150 | SudoAsDone: AugmentedEvent]>; 151 | /** 152 | * Generic event 153 | **/ 154 | [key: string]: AugmentedEvent; 155 | }; 156 | system: { 157 | /** 158 | * `:code` was updated. 159 | **/ 160 | CodeUpdated: AugmentedEvent; 161 | /** 162 | * An extrinsic failed. \[error, info\] 163 | **/ 164 | ExtrinsicFailed: AugmentedEvent; 165 | /** 166 | * An extrinsic completed successfully. \[info\] 167 | **/ 168 | ExtrinsicSuccess: AugmentedEvent; 169 | /** 170 | * An \[account\] was reaped. 171 | **/ 172 | KilledAccount: AugmentedEvent; 173 | /** 174 | * A new \[account\] was created. 175 | **/ 176 | NewAccount: AugmentedEvent; 177 | /** 178 | * On on-chain remark happened. \[origin, remark_hash\] 179 | **/ 180 | Remarked: AugmentedEvent; 181 | /** 182 | * Generic event 183 | **/ 184 | [key: string]: AugmentedEvent; 185 | }; 186 | } // AugmentedEvents 187 | } // declare module 188 | -------------------------------------------------------------------------------- /ui/src/interfaces/augment-api.ts: -------------------------------------------------------------------------------- 1 | // Auto-generated via `yarn polkadot-types-from-chain`, do not edit 2 | /* eslint-disable */ 3 | 4 | import './augment-api-consts'; 5 | import './augment-api-errors'; 6 | import './augment-api-events'; 7 | import './augment-api-query'; 8 | import './augment-api-tx'; 9 | import './augment-api-rpc'; 10 | -------------------------------------------------------------------------------- /ui/src/interfaces/definitions.ts: -------------------------------------------------------------------------------- 1 | export {} -------------------------------------------------------------------------------- /ui/src/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | // Auto-generated via `yarn polkadot-types-from-defs`, do not edit 2 | /* eslint-disable */ 3 | 4 | export * from './types'; 5 | -------------------------------------------------------------------------------- /ui/src/interfaces/types.ts: -------------------------------------------------------------------------------- 1 | // Auto-generated via `yarn polkadot-types-from-defs`, do not edit 2 | /* eslint-disable */ 3 | 4 | export const types = { 5 | BlockCid: "Vec", 6 | BlockSubmissionProposal: { 7 | proposer: "AccountId", 8 | status: "ProposalStatus", 9 | start_block: "BlockNumber", 10 | end_block: "BlockNumber", 11 | }, 12 | MessageRootCid: "Vec", 13 | }; 14 | 15 | /** 16 | * rpc methods 17 | */ 18 | export const rpc = { 19 | filecoindot: { 20 | setRpcEndpoint: { 21 | description: "set filecoin rpc http endpoint", 22 | params: [ 23 | { 24 | name: "urls", 25 | type: "Vec", 26 | }, 27 | ], 28 | type: "()", 29 | }, 30 | verifyReceipt: { 31 | description: "verify filecoin receipt", 32 | params: [ 33 | { 34 | name: "proof", 35 | type: "String", 36 | }, 37 | { 38 | name: "cid", 39 | type: "String", 40 | }, 41 | ], 42 | type: "bool", 43 | }, 44 | verifyState: { 45 | description: "verify filecoin state", 46 | params: [ 47 | { 48 | name: "proof", 49 | type: "String", 50 | }, 51 | { 52 | name: "cid", 53 | type: "String", 54 | }, 55 | ], 56 | type: "bool", 57 | }, 58 | }, 59 | }; 60 | exports.rpc = rpc; -------------------------------------------------------------------------------- /ui/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ui/src/logos/Filecoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChainSafe/filecoindot/b94b4407a3fbff87f8650f4e4306d07d65e9b917/ui/src/logos/Filecoin.png -------------------------------------------------------------------------------- /ui/src/logos/chainsafe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChainSafe/filecoindot/b94b4407a3fbff87f8650f4e4306d07d65e9b917/ui/src/logos/chainsafe.png -------------------------------------------------------------------------------- /ui/src/logos/rmrk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChainSafe/filecoindot/b94b4407a3fbff87f8650f4e4306d07d65e9b917/ui/src/logos/rmrk.png -------------------------------------------------------------------------------- /ui/src/logos/substrate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChainSafe/filecoindot/b94b4407a3fbff87f8650f4e4306d07d65e9b917/ui/src/logos/substrate.png -------------------------------------------------------------------------------- /ui/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /ui/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from "web-vitals" 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry) 7 | getFID(onPerfEntry) 8 | getFCP(onPerfEntry) 9 | getLCP(onPerfEntry) 10 | getTTFB(onPerfEntry) 11 | }) 12 | } 13 | } 14 | 15 | export default reportWebVitals 16 | -------------------------------------------------------------------------------- /ui/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import "@testing-library/jest-dom" 6 | -------------------------------------------------------------------------------- /ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx", 18 | "paths": { 19 | "@polkadot/types/lookup": ["./src/interfaces/types-lookup.ts"], 20 | "@polkadot/api/augment": ["./src/interfaces/augment-api.ts"], 21 | "@polkadot/types/augment": ["./src/interfaces/augment-types.ts"] 22 | }, 23 | }, 24 | "include": ["src"] 25 | } 26 | --------------------------------------------------------------------------------