Skip to content

Commit

Permalink
Compile the tendermint and light-client crates to WASM (#553)
Browse files Browse the repository at this point in the history
* Enable compiling the tendermint crate to WASM

* Enable compiling the light-client crate to WASM

* Add CI job to build tendermint and light-client crates for WASM

* Formatting

* Install WASM target in CI job

* Fix wrong feature name in rpc crate

* Enable client as default option of rpc crate

* Remove unused import

* Formatting

* Disable default features of rpc crate in light-client

* Disable acceptance test for the light node to work around doc test error

It is currently unclear why this error has appeared, though I suspect it
is a bug in Cargo related to the combination of doc tests and features
flags.

       Doc-tests tendermint-light-client
    error[E0460]: found possibly newer version of crate `tendermint` which `tendermint_rpc` depends on
     --> /Users/coromac/Informal/Code/Current/tendermint-rs/light-client/src/components/io.rs:7:5
      |
    7 | use tendermint_rpc as rpc;
      |     ^^^^^^^^^^^^^^
      |
      = note: perhaps that crate needs to be recompiled?
      = note: the following crate versions were found:
              crate `tendermint`: /Users/coromac/Informal/Code/Current/tendermint-rs/target/debug/deps/libtendermint.rlib
              crate `tendermint`: /Users/coromac/Informal/Code/Current/tendermint-rs/target/debug/deps/libtendermint-735716e8d731096d.rmeta
              crate `tendermint_rpc`: /Users/coromac/Informal/Code/Current/tendermint-rs/target/debug/deps/libtendermint_rpc-ad41c355acf2752f.rlib

    error: aborting due to previous error

* Update changelog

* Add PR number to the changelog entry

* Update changelog

* Add link to related issue to ignored test

* Formatting
  • Loading branch information
romac authored Sep 9, 2020
1 parent 2cb3afb commit 76d5e82
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 152 deletions.
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
8 changes: 5 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
## Unreleased

- Add spec for the light client attack evidence handling ([#544])
- return rfc6962 hash for empty merkle tree ([#498])
- Add spec for the light client attack evidence handling ([#526])
- Return RFC6962 hash for empty merkle tree ([#498])
- The `tendermint`, `tendermint-rpc`, and `tendermint-light-client` crates now compile to WASM on the `wasm32-unknown-unknown` and `wasm32-wasi` targets ([#463])

[#544]: https://github.com/informalsystems/tendermint-rs/pull/544
[#526]: https://github.com/informalsystems/tendermint-rs/issues/526
[#498]: https://github.com/informalsystems/tendermint-rs/issues/498
[#463]: https://github.com/informalsystems/tendermint-rs/issues/463

## v0.16.0

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

0 comments on commit 76d5e82

Please sign in to comment.