Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

APAC submission (Part two) Example of creating a pallet which is using IPFS-core to talk to an IPFS ocw. #10

Open
wants to merge 1 commit into
base: ipfs-ocw
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ members = [
"frame/bags-list/fuzzer",
"pallets/template",
"pallets/ipfs-exmaple",
"pallets/ipfs-template",
"pallets/ipfs-core",
"pallets/pocket-mints",
"primitives/api",
Expand Down
7 changes: 7 additions & 0 deletions bin/node/cli/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,13 @@ pub fn new_full_base(
Some("//Alice"),
).expect("Creating key with account Alice should succeed");

//ipfs-template
sp_keystore::SyncCryptoStore::sr25519_generate_new(
&*keystore,
sp_core::crypto::key_types::IPFS_EXAMPLE,
Some("//Alice"),
).expect("Creating key with account Alice should succeed");

//pocket-mints
sp_keystore::SyncCryptoStore::sr25519_generate_new(
&*keystore,
Expand Down
42 changes: 42 additions & 0 deletions pallets/ipfs-template/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
[package]
name = 'pallet-ipfs-template'
version = "4.0.0-dev"
description = "A template pallet for interacting with ipfs-core."
authors = ['Dan Henton <https://github.com/DanHenton>']
homepage = "https://substrate.io/"
edition = '2018'
license = "Unlicense"
publish = false
repository = "https://github.com/substrate-developer-hub/substrate-node-template/"

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

[dependencies]
codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] }
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
frame-support = { default-features = false, version = "4.0.0-dev", path = "../../frame/support" }
frame-system = { default-features = false, version = "4.0.0-dev", path = "../../frame/system" }
frame-benchmarking = { default-features = false, version = "4.0.0-dev", path = "../../frame/benchmarking", optional = true }
log = { version = "0.4" }
sp-std = { default-features = false, version = "4.0.0-dev", path = "../../primitives/std" }
sp-core = { default-features = false, version = "4.0.0-dev", path = "../../primitives/core" }
sp-io = { default-features = false, version = "4.0.0-dev", path = "../../primitives/io" }
sp-runtime = { default-features = false, version = "4.0.0-dev", path = "../../primitives/runtime" }

pallet-ipfs-core = { default-features = false, version = "4.0.0-dev", path = "../ipfs-core" }

[features]
default = ['std']
std = [
'codec/std',
'scale-info/std',
'frame-support/std',
'frame-system/std',
'frame-benchmarking/std',
'sp-std/std',
'pallet-ipfs-core/std',
]

runtime-benchmarks = ["frame-benchmarking"]
try-runtime = ["frame-support/try-runtime"]
1 change: 1 addition & 0 deletions pallets/ipfs-template/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
License: Unlicense
11 changes: 11 additions & 0 deletions pallets/ipfs-template/src/benchmarking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//! Benchmarking setup for pallet-template

use super::*;

#[allow(unused)]
use crate::Pallet as Template;
use frame_benchmarking::{benchmarks, whitelisted_caller};
use frame_system::RawOrigin;

benchmarks! {
}
253 changes: 253 additions & 0 deletions pallets/ipfs-template/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
#![cfg_attr(not(feature = "std"), no_std)]

/// Edit this file to define custom logic or remove it if it is not needed.
/// Learn more about FRAME and the core library of Substrate FRAME pallets:
/// <https://docs.substrate.io/v3/runtime/frame>

use codec::{ Decode, Encode };
use frame_system::{
offchain::{ AppCrypto, CreateSignedTransaction, SendSignedTransaction, Signer},
};
use sp_runtime::{
offchain::{
ipfs,
storage::{MutateStorageError, StorageRetrievalError, StorageValueRef},
},
RuntimeDebug
};

use frame_support::{
traits::Randomness,
dispatch::DispatchResult,
};

#[cfg(feature = "std")]
use frame_support::serde::{ Deserialize, Serialize };

use sp_core::{
offchain::{ Duration, IpfsRequest, IpfsResponse, OpaqueMultiaddr, Timestamp},
};
use log::{ error, info };
use sp_std::str;
use sp_std::vec::Vec;
use core::{convert::TryInto};

#[cfg(test)]
mod mock;

#[cfg(test)]
mod tests;

#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;

pub mod crypto {
use sp_core::{
sr25519::Signature as Sr25519Signature,
};
use sp_runtime::{
app_crypto::{app_crypto, sr25519},
traits::Verify,
MultiSignature, MultiSigner
};

app_crypto!(sr25519, sp_core::crypto::key_types::IPFS_EXAMPLE);

pub struct TestAuthId;

impl frame_system::offchain::AppCrypto<MultiSigner, MultiSignature> for TestAuthId {
type RuntimeAppPublic = Public;
type GenericPublic = sp_core::sr25519::Public;
type GenericSignature = sp_core::sr25519::Signature;
}

// Implemented for mock runtime in tests
impl frame_system::offchain::AppCrypto<<Sr25519Signature as Verify>::Signer, Sr25519Signature> for TestAuthId {
type RuntimeAppPublic = Public;
type GenericPublic = sp_core::sr25519::Public;
type GenericSignature = sp_core::sr25519::Signature;
}
}

pub use pallet::*;

#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use sp_core::crypto::KeyTypeId;
use pallet_ipfs_core as IpfsCore;
use pallet_ipfs_core::{
generate_id, ipfs_request, ocw_process_command,
ocw_parse_ipfs_response, addresses_to_utf8_safe_bytes,
IpfsCommand, CommandRequest,
Event as IpfsEvent,
Error as IpfsError,
};

pub const KEY_TYPE: KeyTypeId = sp_core::crypto::key_types::IPFS_EXAMPLE;
const PROCESSED_COMMANDS: &[u8; 24] = b"ipfs::ipfs-example-comds";

/// Configure the pallet by specifying the parameters and types on which it depends.
#[pallet::config]
pub trait Config: frame_system::Config + pallet_ipfs_core::Config + CreateSignedTransaction<Call<Self>> {
/// The identifier type for an offchain worker.
type AuthorityId: AppCrypto<Self::Public, Self::Signature>;

/// Because this pallet emits events, it depends on the runtime's definition of an event.
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;

/// overarching dispatch call type.
type Call: From<Call<Self>>;

type IpfsRandomness: Randomness<Self::Hash, Self::BlockNumber>;
}

#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);

// The pallet's runtime storage items.
// https://docs.substrate.io/v3/runtime/storage

/** Store a list of Commands for the ocw to process */
// EXAMPLE: store the CommandRequests in a StorageValue for fast iteration.
#[pallet::storage]
#[pallet::getter(fn commands)]
pub type Commands<T: Config> = StorageValue<_, Vec<CommandRequest<T>>>;

/** Pallets use events to inform users when important changes are made.
*/
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
OcwCallback(T::AccountId),
}

/** Errors inform users that something went wrong.
- RequestFailed,
*/
#[pallet::error]
pub enum Error<T> {
StorageError,
FailedToFindCid,
}

#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn offchain_worker(block_number: T::BlockNumber) {
if let Err(_err) = Self::ocw_process_command_requests(block_number) { error!("IPFS: command request"); }
}
}


// Dispatchable functions allows users to interact with the pallet and invoke state changes.
// These functions materialize as "extrinsics", which are often compared to transactions.
// Dispatchable functions must be annotated with a weight and must return a DispatchResult.
#[pallet::call]
impl<T: Config> Pallet<T> {
// EXAMPLE: Add extrinsics here.

/** Process a signed callback from the ocw */
#[pallet::weight(0)]
pub fn ocw_callback(origin: OriginFor<T>, identifier: [u8; 32], data: Vec<u8>) -> DispatchResult {
let signer = ensure_signed(origin)?;

// Side effect:, swap_remove will change the ordering of the Vec!
// TODO: the lock still exists in the ocw storage
let mut callback_command :Option<CommandRequest<T>> = None;
Commands::<T>::mutate(|command_requests| {
let mut commands = command_requests.clone().unwrap();

if let Some(index) = commands.iter().position(|cmd| { cmd.identifier == identifier }) {
info!("Removing at index {}", index.clone());
callback_command = Some(commands.swap_remove(index).clone());
};

*command_requests = Some(commands);
});

Self::deposit_event(Event::OcwCallback(signer));

Self::command_callback(&callback_command.unwrap(), data);

Ok(())
}
}

impl<T: Config> Pallet<T> {
/**
Iterate over all of the Active CommandRequests calling them.

Im sure some more logic will go in here.
*/
fn ocw_process_command_requests(block_number: T::BlockNumber) -> Result<(), Error<T>> {
let commands: Vec<CommandRequest<T>> = Commands::<T>::get().unwrap_or(Vec::<CommandRequest<T>>::new());

for command_request in commands {

match ocw_process_command::<T>(block_number, command_request.clone(), PROCESSED_COMMANDS) {
Ok(responses) => {
let callback_response = ocw_parse_ipfs_response::<T>(responses);

// EXAMPLE: Some more logic can be put here.

Self::signed_callback(&command_request, callback_response);
}
Err(e) => {
match e {
IpfsError::<T>::RequestFailed => { error!("IPFS: failed to perform a request") },
_ => {}
}
}
}
}

Ok(())
}

/** callback to the on-chain validators to continue processing the CID **/
fn signed_callback(command_request: &CommandRequest<T>, data: Vec<u8>) -> Result<(), IpfsError<T>> {
// TODO: Dynamic signers so that its not only the validator who can sign the request.
let signer = Signer::<T, T::AuthorityId>::all_accounts();
if !signer.can_sign() {
error!("*** IPFS *** ---- No local accounts available. Consider adding one via `author_insertKey` RPC.");
return Err(IpfsError::<T>::RequestFailed)?
}

let results = signer.send_signed_transaction(|_account| {
Call::ocw_callback{
identifier: command_request.identifier,
data: data.clone()
}
});
for (account, result) in &results {
match result {
Ok(()) => { info!("callback sent") },
Err(e) => { error!("Failed to submit transaction {:?}", e)}
}
}
// TODO: return a better Result
Ok(())
}

// - Ideally the callback function can be override by another pallet that is coupled to this one allowing for custom functionality.
// - data can be used for callbacks IE add a cid to the signer / uploader.
// - Validate a connected peer has the CID, and which peer has it etc.
// TODO: Result
fn command_callback(command_request: &CommandRequest<T>, data: Vec<u8>) -> Result<(), Error<T>>{
let owner = &command_request.clone().requester;

for command in command_request.clone().ipfs_commands {
match command {
// EXAMPLE: Callback logic goes in here. Matching pallet_ipfs_core::pallet::IpfsCommand's
_ => {}
}
}

// TODO: return a better Result
Ok(())
}
}
}
Loading