Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compile the tendermint and light-client crates to WASM #553

Merged
merged 18 commits into from
Sep 9, 2020
Merged
Show file tree
Hide file tree
Changes from 14 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
14 changes: 14 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,17 @@ jobs:
file: ${{ steps.coverage.outputs.report }}
yml: ./codecov.yml
fail_ci_if_error: true

light-client-wasm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
target: wasm32-unknown-unknown
- name: Build Tendermint for WASM
run: cd tendermint && cargo build --target wasm32-unknown-unknown --release --no-default-features
- name: Build Light Client for WASM
run: cd light-client && cargo build --target wasm32-unknown-unknown --release --no-default-features
Comment on lines +161 to +173
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wohoooo 🎉

3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## Unreleased

- [wasm] The `tendermint`, `tendermint-rpc`, and `tendermint-light-client` crates
now compile to WASM on the `wasm32-unknown-unknown` and `wasm32-wasi` targets ([#553])

## v0.16.0

*Aug 31, 2020*
Expand Down
13 changes: 9 additions & 4 deletions light-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,17 @@ description = """
Implementation of the Tendermint Light Client Verification Protocol.
"""

[lib]
crate-type = ["cdylib", "rlib"]

[features]
default = ["rpc-client"]
rpc-client = ["tendermint-rpc/client"]
secp256k1 = ["tendermint/secp256k1", "tendermint-rpc/secp256k1"]

[dependencies]
tendermint = { version = "0.16.0", path = "../tendermint" }
tendermint-rpc = { version = "0.16.0", path = "../rpc", features = ["client"] }
tendermint-rpc = { version = "0.16.0", path = "../rpc", default-features = false }

anomaly = { version = "0.2.0", features = ["serializer"] }
contracts = "0.4.0"
Expand All @@ -40,6 +48,3 @@ tokio = "0.2.20"
serde_json = "1.0.51"
gumdrop = "0.8.0"
tendermint-testgen = { path = "../testgen"}

[features]
secp256k1 = ["tendermint/secp256k1", "tendermint-rpc/secp256k1"]
218 changes: 113 additions & 105 deletions light-client/src/components/io.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,12 @@
//! Provides an interface and a default implementation of the `Io` component

use std::collections::HashMap;
use std::time::Duration;

use contracts::{contract_trait, post, pre};
use contracts::{contract_trait, post};
use serde::{Deserialize, Serialize};
use thiserror::Error;

use tendermint::{
block::signed_header::SignedHeader as TMSignedHeader, validator::Set as TMValidatorSet,
};

use tendermint_rpc as rpc;

use crate::{
bail,
types::{Height, LightBlock, PeerId},
};
use crate::types::{Height, LightBlock, PeerId};

/// Type for selecting either a specific height or the latest one
pub enum AtHeight {
Expand Down Expand Up @@ -81,113 +71,131 @@ where
}
}

/// Production implementation of the Io component, which fetches
/// light blocks from full nodes via RPC.
#[derive(Clone, Debug)]
pub struct ProdIo {
peer_map: HashMap<PeerId, tendermint::net::Address>,
timeout: Option<Duration>,
}
#[cfg(feature = "rpc-client")]
pub use self::prod::ProdIo;

#[contract_trait]
impl Io for ProdIo {
fn fetch_light_block(&self, peer: PeerId, height: AtHeight) -> Result<LightBlock, IoError> {
let signed_header = self.fetch_signed_header(peer, height)?;
let height = signed_header.header.height;
#[cfg(feature = "rpc-client")]
mod prod {
use super::*;

let validator_set = self.fetch_validator_set(peer, height.into())?;
let next_validator_set = self.fetch_validator_set(peer, height.increment().into())?;
use std::collections::HashMap;
use std::time::Duration;

let light_block = LightBlock::new(signed_header, validator_set, next_validator_set, peer);
use crate::bail;
use contracts::{contract_trait, pre};
use tendermint::{
block::signed_header::SignedHeader as TMSignedHeader, validator::Set as TMValidatorSet,
};

Ok(light_block)
}
}

impl ProdIo {
/// Constructs a new ProdIo component.
///
/// A peer map which maps peer IDS to their network address must be supplied.
pub fn new(
/// Production implementation of the Io component, which fetches
/// light blocks from full nodes via RPC.
#[derive(Clone, Debug)]
pub struct ProdIo {
peer_map: HashMap<PeerId, tendermint::net::Address>,
timeout: Option<Duration>,
) -> Self {
Self { peer_map, timeout }
}

#[pre(self.peer_map.contains_key(&peer))]
fn fetch_signed_header(
&self,
peer: PeerId,
height: AtHeight,
) -> Result<TMSignedHeader, IoError> {
let rpc_client = self.rpc_client_for(peer);

let res = block_on(
async {
match height {
AtHeight::Highest => rpc_client.latest_commit().await,
AtHeight::At(height) => rpc_client.commit(height).await,
}
},
peer,
self.timeout,
)?;

match res {
Ok(response) => Ok(response.signed_header),
Err(err) => Err(IoError::IoError(err)),
#[contract_trait]
impl Io for ProdIo {
fn fetch_light_block(&self, peer: PeerId, height: AtHeight) -> Result<LightBlock, IoError> {
let signed_header = self.fetch_signed_header(peer, height)?;
let height = signed_header.header.height;

let validator_set = self.fetch_validator_set(peer, height.into())?;
let next_validator_set = self.fetch_validator_set(peer, height.increment().into())?;

let light_block =
LightBlock::new(signed_header, validator_set, next_validator_set, peer);

Ok(light_block)
}
}

#[pre(self.peer_map.contains_key(&peer))]
fn fetch_validator_set(
&self,
peer: PeerId,
height: AtHeight,
) -> Result<TMValidatorSet, IoError> {
let height = match height {
AtHeight::Highest => bail!(IoError::InvalidHeight(
"given height must be greater than 0".to_string()
)),
AtHeight::At(height) => height,
};

let res = block_on(
self.rpc_client_for(peer).validators(height),
peer,
self.timeout,
)?;

match res {
Ok(response) => Ok(TMValidatorSet::new(response.validators)),
Err(err) => Err(IoError::IoError(err)),
impl ProdIo {
/// Constructs a new ProdIo component.
///
/// A peer map which maps peer IDS to their network address must be supplied.
pub fn new(
peer_map: HashMap<PeerId, tendermint::net::Address>,
timeout: Option<Duration>,
) -> Self {
Self { peer_map, timeout }
}

#[pre(self.peer_map.contains_key(&peer))]
fn fetch_signed_header(
&self,
peer: PeerId,
height: AtHeight,
) -> Result<TMSignedHeader, IoError> {
let rpc_client = self.rpc_client_for(peer);

let res = block_on(
async {
match height {
AtHeight::Highest => rpc_client.latest_commit().await,
AtHeight::At(height) => rpc_client.commit(height).await,
}
},
peer,
self.timeout,
)?;

match res {
Ok(response) => Ok(response.signed_header),
Err(err) => Err(IoError::IoError(err)),
}
}

#[pre(self.peer_map.contains_key(&peer))]
fn fetch_validator_set(
&self,
peer: PeerId,
height: AtHeight,
) -> Result<TMValidatorSet, IoError> {
let height = match height {
AtHeight::Highest => bail!(IoError::InvalidHeight(
"given height must be greater than 0".to_string()
)),
AtHeight::At(height) => height,
};

let res = block_on(
self.rpc_client_for(peer).validators(height),
peer,
self.timeout,
)?;

match res {
Ok(response) => Ok(TMValidatorSet::new(response.validators)),
Err(err) => Err(IoError::IoError(err)),
}
}
}

// FIXME: Cannot enable precondition because of "autoref lifetime" issue
// #[pre(self.peer_map.contains_key(&peer))]
fn rpc_client_for(&self, peer: PeerId) -> rpc::Client {
let peer_addr = self.peer_map.get(&peer).unwrap().to_owned();
rpc::Client::new(peer_addr)
// FIXME: Cannot enable precondition because of "autoref lifetime" issue
// #[pre(self.peer_map.contains_key(&peer))]
fn rpc_client_for(&self, peer: PeerId) -> rpc::Client {
let peer_addr = self.peer_map.get(&peer).unwrap().to_owned();
rpc::Client::new(peer_addr)
}
}
}

fn block_on<F: std::future::Future>(
f: F,
peer: PeerId,
timeout: Option<Duration>,
) -> Result<F::Output, IoError> {
let mut rt = tokio::runtime::Builder::new()
.basic_scheduler()
.enable_all()
.build()
.unwrap();

if let Some(timeout) = timeout {
rt.block_on(async { tokio::time::timeout(timeout, f).await })
.map_err(|_| IoError::Timeout(peer))
} else {
Ok(rt.block_on(f))
fn block_on<F: std::future::Future>(
f: F,
peer: PeerId,
timeout: Option<Duration>,
) -> Result<F::Output, IoError> {
let mut rt = tokio::runtime::Builder::new()
.basic_scheduler()
.enable_all()
.build()
.unwrap();

if let Some(timeout) = timeout {
rt.block_on(async { tokio::time::timeout(timeout, f).await })
.map_err(|_| IoError::Timeout(peer))
} else {
Ok(rt.block_on(f))
}
}
}
Loading