Skip to content

Commit

Permalink
Export all light client specific code (#17)
Browse files Browse the repository at this point in the history
* add more descriptive errors for invalid mmr update

* add more details to errors

* minor fix

* remove unused errors

* refactor and export logic for constructing beefy client state

* api clarifications
  • Loading branch information
Wizdave97 authored Jul 29, 2022
1 parent 156e6b6 commit daaf6d2
Show file tree
Hide file tree
Showing 13 changed files with 1,166 additions and 34,071 deletions.
293 changes: 250 additions & 43 deletions Cargo.lock

Large diffs are not rendered by default.

29 changes: 20 additions & 9 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,22 @@ authors = ["Composable developers"]
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

[build-dependencies]
subxt-codegen = "0.22.0"
jsonrpsee = { version = "0.15.0", features = ["async-client", "client-ws-transport", "http-client"] }
frame-metadata = { version = "15.0.0", features = ["v14", "std"] }
syn = "1.0.98"
codec = { package = "parity-scale-codec", version = "3.0.0" }
color-eyre = "0.6.2"
hex = "0.4.3"
tokio = { version = "1.19.2", features = ["macros", "rt-multi-thread"] }

[dependencies]
sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" }
sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" }
sp-core = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" }
sp-runtime = { default-features = false, version = "6.0.0" }
sp-core = { default-features = false, version = "6.0.0" }
sp-core-hashing = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" }

frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24", default-features = false }
beefy-mmr = { package = "pallet-beefy-mmr", git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24", default-features = false }
pallet-mmr = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" }
pallet-mmr-primitives = { package = "sp-mmr-primitives", default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" }
Expand All @@ -21,19 +31,19 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features =
rs_merkle = { version = "1.2.0", default-features = false }
mmr-lib= { package = "ckb-merkle-mountain-range", version = "0.3.2", default-features = false }
derive_more = { version = "0.99.17", default-features = false, features = ["from"] }
# Mock deps
subxt = { version = "0.21.0", optional = true }
# Optional deps
subxt = { version = "0.22.0", optional = true }
tokio = { version = "1.17.0", features = ["full"], optional = true }
hex-literal = { version = "0.3.4", optional = true }
serde_json = { version = "1.0.74", optional = true }
pallet-mmr-rpc = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24", optional = true }
frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24", optional = true }
sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24", optional = true }
sp-trie = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24", optional = true }
hex = { version = "0.4.3", optional = true }

[dev-dependencies]
subxt = "0.21.0"
tokio = { version = "1.17.0", features = ["full"] }
subxt = "0.22.0"
hex-literal = "0.3.4"
serde_json = "1.0.74"
pallet-mmr-rpc = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" }
Expand All @@ -55,6 +65,7 @@ std = [
"pallet-mmr/std",
"pallet-mmr-primitives/std",
"rs_merkle/std",
"mmr-lib/std"
"mmr-lib/std",
"frame-support/std"
]
mocks = ["std", "subxt", "hex-literal", "serde_json", "pallet-mmr-rpc", "frame-support", "sp-io", "sp-trie"]
prover = ["std", "subxt", "hex-literal", "serde_json", "pallet-mmr-rpc", "sp-io", "sp-trie", "hex"]
64 changes: 64 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use codec::{Decode, Input};
use frame_metadata::RuntimeMetadataPrefixed;
use jsonrpsee::{
async_client::ClientBuilder,
client_transport::ws::{Uri, WsTransportClientBuilder},
core::{client::ClientT, Error},
rpc_params,
};
use std::{env, fs, path::Path};
use subxt_codegen::DerivesRegistry;

// We need this build script to rebuild the runtime metadata from a live node
// Since we have exported functions that depends on the having the latest relay chain metadata
// Build is disabled by default and only enabled when the env variable PROVER_ENABLED is set to 1

async fn fetch_metadata_ws() -> color_eyre::Result<Vec<u8>> {
let node_var = env::var("NODE_ENDPOINT").unwrap_or("ws://127.0.0.1:9944".to_string());
let (sender, receiver) = WsTransportClientBuilder::default()
.build(node_var.parse::<Uri>().unwrap())
.await
.map_err(|e| Error::Transport(e.into()))?;

let client = ClientBuilder::default()
.max_notifs_per_subscription(4096)
.build_with_tokio(sender, receiver);

let metadata: String = client.request("state_getMetadata", rpc_params![]).await?;
Ok(hex::decode(metadata.trim_start_matches("0x"))?)
}

fn codegen<I: Input>(encoded: &mut I) -> color_eyre::Result<String> {
let metadata = <RuntimeMetadataPrefixed as Decode>::decode(encoded)?;
let generator = subxt_codegen::RuntimeGenerator::new(metadata);
let item_mod = syn::parse_quote!(
pub mod api {}
);

// add any derives you want here:
let p = Vec::<String>::new()
.iter()
.map(|raw| syn::parse_str(raw))
.collect::<Result<Vec<_>, _>>()?;
let mut derives = DerivesRegistry::default();
derives.extend_for_all(p.into_iter());

let runtime_api = generator.generate_runtime(item_mod, derives);
Ok(format!("{}", runtime_api))
}

#[tokio::main]
async fn main() -> color_eyre::Result<()> {
let build_enabled = env::var("PROVER_ENABLED")
.map(|v| v == "1")
.unwrap_or(false);
if build_enabled {
let metadata = fetch_metadata_ws().await?;
let code = codegen(&mut &metadata[..])?;
let out_dir = env::var_os("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("subxt_codegen.rs");
fs::write(&dest_path, &code)?;
}

Ok(())
}
54 changes: 43 additions & 11 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,41 @@
//! Light client error definition

use codec::alloc::string::String;
use sp_std::prelude::*;
#[derive(sp_std::fmt::Debug, PartialEq, Eq, derive_more::From)]
#[derive(sp_std::fmt::Debug, derive_more::From)]
/// Error definition for the light client
pub enum BeefyClientError {
/// Failed to read a value from storage
StorageReadError,
/// Failed to write a value to storage
StorageWriteError,
/// Error decoding some value
DecodingError,
/// Invalid Mmr Update
InvalidMmrUpdate,
/// Outdated commitment
#[from(ignore)]
OutdatedCommitment {
/// Latest beefy height stored
latest_beefy_height: u32,
/// Commitment block number received
commitment_block_number: u32,
},
/// Mmr root hash not found in commitment
MmrRootHashNotFound,
/// Invalid Authority set id received
#[from(ignore)]
AuthoritySetMismatch {
/// Current authority set id
current_set_id: u64,
/// Next authority set id
next_set_id: u64,
/// Authority set id in commitment
commitment_set_id: u64,
},
/// Incomplete Signature threshold
IncompleteSignatureThreshold,
/// Error recovering public key from signature
InvalidSignature,
/// Some invalid merkle root hash
InvalidRootHash,
#[from(ignore)]
InvalidRootHash {
/// Root hash
root_hash: Vec<u8>,
/// Root hash length
len: u64,
},
/// Some invalid mmr proof
InvalidMmrProof,
/// Invalid authority proof
Expand All @@ -26,4 +44,18 @@ pub enum BeefyClientError {
InvalidMerkleProof,
/// Mmr Error
MmrVerificationError(mmr_lib::Error),
#[cfg(any(test, feature = "prover"))]
/// subxt error
Subxt(subxt::BasicError),
#[cfg(any(test, feature = "prover"))]
/// subxt rpc error
SubxtRRpc(subxt::rpc::RpcError),
#[cfg(any(test, feature = "prover"))]
/// Trie error
TrieProof(Box<sp_trie::TrieError<sp_trie::LayoutV0<sp_runtime::traits::BlakeTwo256>>>),
/// Codec error
Codec(codec::Error),
/// Custom error
#[from(ignore)]
Custom(String),
}
43 changes: 29 additions & 14 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,14 @@

//! Beefy Light Client Implementation
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(not(feature = "mocks"), deny(missing_docs))]
#![cfg_attr(not(feature = "prover"), deny(missing_docs))]

use core::marker::PhantomData;

pub mod error;
pub mod primitives;
#[cfg(any(test, feature = "mocks"))]
pub mod runtime;
#[cfg(any(test, feature = "mocks"))]
pub mod test_utils;
#[cfg(any(test, feature = "prover"))]
pub mod queries;
#[cfg(test)]
mod tests;
pub mod traits;
Expand All @@ -38,9 +36,9 @@ use crate::traits::{ClientState, HostFunctions};
use beefy_primitives::known_payload_ids::MMR_ROOT_ID;
use beefy_primitives::mmr::MmrLeaf;
use codec::Encode;
use sp_core::crypto::ByteArray;
use frame_support::sp_runtime::app_crypto::ByteArray;
use frame_support::sp_runtime::traits::Convert;
use sp_core::H256;
use sp_runtime::traits::Convert;

use sp_std::prelude::*;
use sp_std::vec;
Expand Down Expand Up @@ -78,7 +76,11 @@ impl<Crypto: HostFunctions + Clone> BeefyLightClient<Crypto> {

if current_authority_set.id != validator_set_id && next_authority_set.id != validator_set_id
{
return Err(BeefyClientError::InvalidMmrUpdate);
return Err(BeefyClientError::AuthoritySetMismatch {
current_set_id: current_authority_set.id,
next_set_id: next_authority_set.id,
commitment_set_id: validator_set_id,
});
}

// Extract root hash from signed commitment and validate it
Expand All @@ -92,10 +94,13 @@ impl<Crypto: HostFunctions + Clone> BeefyLightClient<Crypto> {
if root.len() == HASH_LENGTH {
root
} else {
return Err(BeefyClientError::InvalidRootHash);
return Err(BeefyClientError::InvalidRootHash {
root_hash: root.clone(),
len: root.len() as u64,
});
}
} else {
return Err(BeefyClientError::InvalidMmrUpdate);
return Err(BeefyClientError::MmrRootHashNotFound);
}
};

Expand Down Expand Up @@ -152,13 +157,23 @@ impl<Crypto: HostFunctions + Clone> BeefyLightClient<Crypto> {
}
authorities_changed = true;
}
_ => return Err(BeefyClientError::InvalidMmrUpdate),
_ => {
return Err(BeefyClientError::AuthoritySetMismatch {
current_set_id: current_authority_set.id,
next_set_id: next_authority_set.id,
commitment_set_id: validator_set_id,
})
}
}

let latest_beefy_height = trusted_client_state.latest_beefy_height;

if mmr_update.signed_commitment.commitment.block_number <= latest_beefy_height {
return Err(BeefyClientError::InvalidMmrUpdate);
let commitment_block_number = mmr_update.signed_commitment.commitment.block_number;
if commitment_block_number <= latest_beefy_height {
return Err(BeefyClientError::OutdatedCommitment {
latest_beefy_height,
commitment_block_number,
});
}

// Move on to verify mmr_proof
Expand Down Expand Up @@ -250,7 +265,7 @@ impl<Crypto: HostFunctions + Clone> BeefyLightClient<Crypto> {
}

/// Calculate the leaf index for this block number
fn get_leaf_index_for_block_number(activation_block: u32, block_number: u32) -> u32 {
pub fn get_leaf_index_for_block_number(activation_block: u32, block_number: u32) -> u32 {
// calculate the leaf index for this leaf.
if activation_block == 0 {
// in this case the leaf index is the same as the block number - 1 (leaf index starts at 0)
Expand Down
4 changes: 4 additions & 0 deletions src/queries.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub mod helpers;
pub mod relay_chain_queries;
pub mod runtime;
pub mod utils;
Loading

0 comments on commit daaf6d2

Please sign in to comment.