Skip to content

Commit a1a9f28

Browse files
authored
Add commands to initialize light client for given chain and start verifying headers (informalsystems#26)
* Add function to initialize lite client without trusted state * Make LightClient generic over chain type via a generic store * Add stub to initialize the light client with trust options * Use custom sum type for store heights * Rename LiteClient to LightClient * Add stub command `light init` * Implement LightClient::init_with_node_trusted_state * Implement LightClient::init_with_trusted_state * Refactor light client * Verify trusted state on light client initialization * Remove unused file * Add stub for Client::check_trusted_header * Fail when needed in Client::update_trusted_state * Partially implement Client::update * Implement LightClient::verify_header * Update comment * Fix clippy warnings * Use serde-humantime to parse trusting_period * Move config defaults into their own module * Create light client and display last trusted state * Use checked arithmetic when incrementing height * Update trusted store in Client::update * Fix clippy warnings * Rename StoreHeight:GivenHeight to Given * Simplify verify_header signature * Spawn empty relayer, and one client per configured chain * Update tendermint-rs repository * Remove dep on tendermint-rs/light_node by copying RpcRequester code over * Improve reporting a bit * Fix RpcRequester unit test * Add persistent trusted store implementation * Use persistent trusted store in commands * Ignore database folders in Git * Fix clippy warnings * Remove superfluous Tendermint type struct * Add some doc comments * Document the relayer::client::Client struct * More doc comments * Ignore .db and .sh files in cli folder * Fix misleading doc comment * Update README and LICENSE file * Remove verbose flag in README * Add status info to `light init` command
1 parent 61f5bcf commit a1a9f28

24 files changed

+1283
-53
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ Cargo.lock
99
# These are backup files generated by rustfmt
1010
**/*.rs.bk
1111

12+
# Ignore database folders
13+
**/*.db/

LICENSE

+2-2
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@
187187
same "printed page" as the copyright notice for easier
188188
identification within third-party archives.
189189

190-
Copyright [yyyy] [name of copyright owner]
190+
Copyright 2020 Informal Systems
191191

192192
Licensed under the Apache License, Version 2.0 (the "License");
193193
you may not use this file except in compliance with the License.
@@ -199,4 +199,4 @@
199199
distributed under the License is distributed on an "AS IS" BASIS,
200200
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201201
See the License for the specific language governing permissions and
202-
limitations under the License.
202+
limitations under the License.

README.md

+48-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,49 @@
11
# ibc-rs
2-
Rust implementation of IBC modules and relayer
2+
3+
> Rust implementation of IBC modules and relayer
4+
5+
## Disclaimer
6+
7+
THIS PROJECT IS UNDER HEAVY DEVELOPMENT AND IS NOT IN A WORKING STAGE NOW, USE AT YOUR OWN RISK.
8+
9+
## Requirements
10+
11+
- Rust 1.42+ (might work on earlier versions but this has not been tested yet)
12+
13+
## Usage
14+
15+
Provided that one has a Tendermint node running on port 26657, one can
16+
configure and spawn two light clients for this chain with the following commands:
17+
18+
1. Fetch a trusted header from the chain:
19+
20+
```bash
21+
$ curl -s http://localhost:26657/status | jq '.result.sync_info|.latest_block_hash,.latest_block_height'
22+
```
23+
24+
2. Initialize a light client for chain A with the trusted height and hash fetched in step 1:
25+
26+
```bash
27+
ibc-rs > cargo run --bin relayer -- -c ./relayer/relay/tests/config/fixtures/relayer_conf_example.toml light init -x HASH -h HEIGHT chain_A
28+
```
29+
> Replace `HASH` and `HEIGHT` with the appropriate values.
30+
31+
3. Repeat step 1 and 2 above for `chain_B`.
32+
33+
> For this, update the height and hash, and change the chain identifier in the command above from `chain_A` to `chain_B`.
34+
35+
4. Start the light clients and a dummy relayer thread:
36+
37+
```bash
38+
ibc-rs > cargo run --bin relayer -- -c ./relayer/relay/tests/config/fixtures/relayer_conf_example.toml run
39+
```
40+
41+
## License
42+
43+
Copyright © 2020 Informal Systems
44+
45+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
46+
47+
https://www.apache.org/licenses/LICENSE-2.0
48+
49+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

relayer/cli/.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
/target
22
**/*.rs.bk
3+
4+
*.sh
5+
*.db

relayer/cli/Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@ authors = [
99

1010
[dependencies]
1111
relayer = { path = "../relay" }
12+
tendermint = { git = "https://github.com/informalsystems/tendermint-rs.git" }
1213

1314
anomaly = "0.2.0"
1415
gumdrop = "0.7"
1516
serde = { version = "1", features = ["serde_derive"] }
1617
thiserror = "1"
18+
abscissa_tokio = "0.5.1"
19+
tokio = "0.2.13"
1720

1821
[dependencies.abscissa_core]
1922
version = "0.5.2"

relayer/cli/src/application.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,11 @@ impl Application for CliApp {
8282
/// beyond the default ones provided by the framework, this is the place
8383
/// to do so.
8484
fn register_components(&mut self, command: &Self::Cmd) -> Result<(), FrameworkError> {
85-
let components = self.framework_components(command)?;
85+
use abscissa_tokio::TokioComponent;
86+
87+
let mut components = self.framework_components(command)?;
88+
components.push(Box::new(TokioComponent::new()?));
89+
8690
self.state.components.register(components)
8791
}
8892

relayer/cli/src/commands.rs

+9-4
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66
//! application's configuration file.
77
88
mod config;
9+
mod light;
910
mod start;
1011
mod version;
1112

12-
use self::{config::ConfigCmd, start::StartCmd, version::VersionCmd};
13+
use self::{config::ConfigCmd, light::LightCmd, start::StartCmd, version::VersionCmd};
1314

1415
use crate::config::Config;
1516
use abscissa_core::{Command, Configurable, FrameworkError, Help, Options, Runnable};
@@ -25,6 +26,10 @@ pub enum CliCmd {
2526
#[options(help = "get usage information")]
2627
Help(Help<Self>),
2728

29+
/// The `version` subcommand
30+
#[options(help = "display version information")]
31+
Version(VersionCmd),
32+
2833
/// The `start` subcommand
2934
#[options(help = "start the relayer")]
3035
Start(StartCmd),
@@ -33,9 +38,9 @@ pub enum CliCmd {
3338
#[options(help = "manipulate the relayer configuration")]
3439
Config(ConfigCmd),
3540

36-
/// The `version` subcommand
37-
#[options(help = "display version information")]
38-
Version(VersionCmd),
41+
/// The `light` subcommand
42+
#[options(help = "basic functionality for managing the lite clients")]
43+
Light(LightCmd),
3944
}
4045

4146
/// This trait allows you to define how application configuration is loaded.

relayer/cli/src/commands/light.rs

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//! `light` subcommand
2+
3+
use abscissa_core::{Command, Options, Runnable};
4+
5+
mod init;
6+
7+
/// `light` subcommand
8+
#[derive(Command, Debug, Options, Runnable)]
9+
pub enum LightCmd {
10+
/// The `light init` subcommand
11+
#[options(help = "initiate a light client for a given chain")]
12+
Init(init::InitCmd),
13+
}
+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
use std::future::Future;
2+
3+
// use crate::application::APPLICATION;
4+
use crate::prelude::*;
5+
6+
use abscissa_core::{Command, Options, Runnable};
7+
8+
use tendermint::chain::Id as ChainId;
9+
use tendermint::hash::Hash;
10+
use tendermint::lite::Height;
11+
12+
use relayer::chain::tendermint::TendermintChain;
13+
use relayer::client::trust_options::TrustOptions;
14+
use relayer::config::{ChainConfig, Config};
15+
use relayer::store::{sled::SledStore, Store};
16+
17+
#[derive(Command, Debug, Options)]
18+
pub struct InitCmd {
19+
#[options(free, help = "identifier of the chain to initialize light client for")]
20+
chain_id: Option<ChainId>,
21+
22+
#[options(help = "trusted header hash", short = "x")]
23+
hash: Option<Hash>,
24+
25+
#[options(help = "trusted header height", short = "h")]
26+
height: Option<Height>,
27+
}
28+
29+
#[derive(Clone, Debug)]
30+
struct InitOptions {
31+
/// identifier of chain to initialize light client for
32+
chain_id: ChainId,
33+
34+
/// trusted header hash
35+
trusted_hash: Hash,
36+
37+
/// trusted header height
38+
trusted_height: Height,
39+
}
40+
41+
impl InitCmd {
42+
fn get_chain_config_and_options(
43+
&self,
44+
config: &Config,
45+
) -> Result<(ChainConfig, InitOptions), String> {
46+
match (&self.chain_id, &self.hash, self.height) {
47+
(Some(chain_id), Some(trusted_hash), Some(trusted_height)) => {
48+
let chain_config = config.chains.iter().find(|c| c.id == *chain_id);
49+
50+
match chain_config {
51+
Some(chain_config) => {
52+
let opts = InitOptions {
53+
chain_id: *chain_id,
54+
trusted_hash: *trusted_hash,
55+
trusted_height,
56+
};
57+
58+
Ok((chain_config.clone(), opts))
59+
}
60+
None => Err(format!("cannot find chain {} in config", chain_id)),
61+
}
62+
}
63+
64+
(None, _, _) => Err("missing chain identifier".to_string()),
65+
(_, None, _) => Err("missing trusted hash".to_string()),
66+
(_, _, None) => Err("missing trusted height".to_string()),
67+
}
68+
}
69+
}
70+
71+
impl Runnable for InitCmd {
72+
/// Initialize the light client for the given chain
73+
fn run(&self) {
74+
// FIXME: This just hangs and never runs the given future
75+
// abscissa_tokio::run(&APPLICATION, ...).unwrap();
76+
77+
let config = app_config();
78+
79+
let (chain_config, opts) = match self.get_chain_config_and_options(&config) {
80+
Err(err) => {
81+
status_err!("invalid options: {}", err);
82+
return;
83+
}
84+
Ok(result) => result,
85+
};
86+
87+
block_on(async {
88+
let trust_options = TrustOptions::new(
89+
opts.trusted_hash,
90+
opts.trusted_height,
91+
chain_config.trusting_period,
92+
Default::default(),
93+
)
94+
.unwrap();
95+
96+
let mut store: SledStore<TendermintChain> =
97+
relayer::store::persistent(format!("store_{}.db", chain_config.id));
98+
99+
store.set_trust_options(trust_options).unwrap(); // FIXME: unwrap
100+
101+
status_ok!(
102+
chain_config.id,
103+
"Set trusted options: hash={} height={}",
104+
opts.trusted_hash,
105+
opts.trusted_height
106+
);
107+
});
108+
}
109+
}
110+
111+
fn block_on<F: Future>(future: F) -> F::Output {
112+
tokio::runtime::Builder::new()
113+
.basic_scheduler()
114+
.enable_all()
115+
.build()
116+
.unwrap()
117+
.block_on(future)
118+
}

relayer/cli/src/commands/start.rs

+93-12
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,105 @@
1-
//! `start` subcommand
1+
use std::future::Future;
2+
use std::time::{Duration, SystemTime};
23

3-
/// App-local prelude includes `app_reader()`/`app_writer()`/`app_config()`
4-
/// accessors along with logging macros. Customize as you see fit.
4+
// use crate::application::APPLICATION;
55
use crate::prelude::*;
66

77
use abscissa_core::{Command, Options, Runnable};
88

9-
/// `start` subcommand
10-
///
11-
/// The `Options` proc macro generates an option parser based on the struct
12-
/// definition, and is defined in the `gumdrop` crate. See their documentation
13-
/// for a more comprehensive example:
14-
///
15-
/// <https://docs.rs/gumdrop/>
9+
use tendermint::lite::types::Header;
10+
11+
use relayer::chain::tendermint::TendermintChain;
12+
use relayer::chain::Chain;
13+
use relayer::client::Client;
14+
use relayer::config::ChainConfig;
15+
use relayer::store::Store;
16+
1617
#[derive(Command, Debug, Options)]
1718
pub struct StartCmd {}
1819

1920
impl Runnable for StartCmd {
20-
/// Start the application.
2121
fn run(&self) {
22-
status_ok!("{}", "Quitting...");
22+
let config = app_config().clone();
23+
24+
// FIXME: This just hangs and never runs the given future
25+
// abscissa_tokio::run(&APPLICATION, ...).unwrap();
26+
27+
block_on(async {
28+
for chain_config in config.chains {
29+
status_info!(
30+
"Relayer",
31+
"Spawning light client for chain {}",
32+
chain_config.id
33+
);
34+
35+
let _handle = tokio::spawn(async move {
36+
let client = create_client(chain_config).await;
37+
let trusted_state = client.last_trusted_state().unwrap();
38+
39+
status_ok!(
40+
client.chain().id(),
41+
"Spawned new client now at trusted state: {} at height {}",
42+
trusted_state.last_header().header().hash(),
43+
trusted_state.last_header().header().height(),
44+
);
45+
46+
update_headers(client).await;
47+
});
48+
}
49+
50+
start_relayer().await
51+
})
52+
}
53+
}
54+
55+
async fn start_relayer() {
56+
let mut interval = tokio::time::interval(Duration::from_secs(3));
57+
58+
loop {
59+
status_info!("Relayer", "Relayer is running");
60+
61+
interval.tick().await;
2362
}
2463
}
64+
65+
async fn update_headers<C: Chain, S: Store<C>>(mut client: Client<C, S>) {
66+
let mut interval = tokio::time::interval(Duration::from_secs(3));
67+
68+
loop {
69+
let result = client.update(SystemTime::now()).await;
70+
71+
match result {
72+
Ok(Some(trusted_state)) => status_ok!(
73+
client.chain().id(),
74+
"Updated to trusted state: {} at height {}",
75+
trusted_state.header().hash(),
76+
trusted_state.header().height()
77+
),
78+
79+
Ok(None) => status_info!(client.chain().id(), "Ignoring update to a previous state"),
80+
Err(err) => status_info!(client.chain().id(), "Error when updating headers: {}", err),
81+
}
82+
83+
interval.tick().await;
84+
}
85+
}
86+
87+
async fn create_client(
88+
chain_config: ChainConfig,
89+
) -> Client<TendermintChain, impl Store<TendermintChain>> {
90+
let chain = TendermintChain::from_config(chain_config).unwrap();
91+
92+
let store = relayer::store::persistent(format!("store_{}.db", chain.id()));
93+
let trust_options = store.get_trust_options().unwrap(); // FIXME: unwrap
94+
95+
Client::new(chain, store, trust_options).await.unwrap()
96+
}
97+
98+
fn block_on<F: Future>(future: F) -> F::Output {
99+
tokio::runtime::Builder::new()
100+
.basic_scheduler()
101+
.enable_all()
102+
.build()
103+
.unwrap()
104+
.block_on(future)
105+
}

0 commit comments

Comments
 (0)