Skip to content

Commit

Permalink
light-client: add missing RPC endpoint and extend handler/supervisor (#…
Browse files Browse the repository at this point in the history
…449)

* WIP: add `LatestStatus` type and a method to the handler

* add `LatestStatus` type and a method to the handler/supervisor and an RPC endpoint

* Actually add the connected nodes

* Add info output and fix main output
 - from `synced to block {} 1234` to `synced to block: 1234`

* Fix some typos while debugging

* Fix #450 🎉 thanks @OStevan 🙏

* add mock test for added rpc end-point

* Refer to additional RPC endpoint in README.md

* Update Changelog

* Fix example in README.md

* Update CHANGES.md

* Fix README.md: Add closing `</details>` for 1st example

* Make all fields but connected_nodes optional

* Always return a status instead

* Fix paste err (thx @xla)
  • Loading branch information
liamsi committed Jul 16, 2020
1 parent b906047 commit 02fa831
Show file tree
Hide file tree
Showing 10 changed files with 170 additions and 11 deletions.
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@
- Correctly handle blocks marked `Trusted` in accordance with the specification ([#407])
- Treat `Trusted` status as a special case of `Verified` as per the spec ([#419])
- Add integration test ([#431])
- Rework light-node CLI to use `Supervisor` / `Handle` ([#430])
- Add `latest_status` to the supervisor `Handle` ([#449])
- Add JSONRPC endpoints to query the light-node ([#363], [#449])

[#302]: https://github.com/informalsystems/tendermint-rs/pull/302
[#336]: https://github.com/informalsystems/tendermint-rs/pull/336
[#360]: https://github.com/informalsystems/tendermint-rs/pull/360
[#363]: https://github.com/informalsystems/tendermint-rs/pull/363
[#364]: https://github.com/informalsystems/tendermint-rs/pull/364
[#365]: https://github.com/informalsystems/tendermint-rs/pull/365
[#371]: https://github.com/informalsystems/tendermint-rs/pull/371
Expand All @@ -32,7 +36,9 @@
[#403]: https://github.com/informalsystems/tendermint-rs/pull/403
[#407]: https://github.com/informalsystems/tendermint-rs/pull/407
[#419]: https://github.com/informalsystems/tendermint-rs/pull/419
[#430]: https://github.com/informalsystems/tendermint-rs/pull/430
[#431]: https://github.com/informalsystems/tendermint-rs/pull/431
[#449]: https://github.com/informalsystems/tendermint-rs/pull/449

[ADR-007]: https://github.com/informalsystems/tendermint-rs/blob/master/docs/architecture/adr-007-light-client-supervisor-ergonomics.md

Expand Down
2 changes: 1 addition & 1 deletion light-client/src/light_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ impl LightClient {

/// Attempt to update the light client to the highest block of the primary node.
///
/// Note: This functin delegates the actual work to `verify_to_target`.
/// Note: This function delegates the actual work to `verify_to_target`.
pub fn verify_to_highest(&mut self, state: &mut State) -> Result<LightBlock, Error> {
let target_block = match self.io.fetch_light_block(self.peer, AtHeight::Highest) {
Ok(last_block) => last_block,
Expand Down
2 changes: 1 addition & 1 deletion light-client/src/peer_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ pub struct PeerListBuilder<T> {
}

// This instance must be derived manually because the automatically
// derived instancce constrains T to be Default.
// derived instance constrains T to be Default.
// See https://github.com/rust-lang/rust/issues/26925
impl<T> Default for PeerListBuilder<T> {
fn default() -> Self {
Expand Down
4 changes: 2 additions & 2 deletions light-client/src/store/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ impl LightStore for MemoryStore {
fn latest(&self, status: Status) -> Option<LightBlock> {
self.store
.iter()
.rev()
.find(|(_, e)| e.status == status)
.filter(|(_, e)| e.status == status)
.max_by_key(|(&height, _)| height)
.map(|(_, e)| e.light_block.clone())
}

Expand Down
4 changes: 3 additions & 1 deletion light-client/src/store/sled.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ impl LightStore for SledStore {
}

fn latest(&self, status: Status) -> Option<LightBlock> {
self.db(status).iter(&self.db).next_back()
self.db(status)
.iter(&self.db)
.max_by(|first, second| first.height().cmp(&second.height()))
}

fn all(&self, status: Status) -> Box<dyn Iterator<Item = LightBlock>> {
Expand Down
40 changes: 39 additions & 1 deletion light-client/src/supervisor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,19 @@ use crate::fork_detector::{Fork, ForkDetection, ForkDetector};
use crate::light_client::LightClient;
use crate::peer_list::PeerList;
use crate::state::State;
use crate::types::{Height, LightBlock, PeerId, Status};
use crate::types::{Height, LatestStatus, LightBlock, PeerId, Status};
use tendermint::lite::{Header, ValidatorSet};

pub trait Handle {
/// Get latest trusted block from the [`Supervisor`].
fn latest_trusted(&self) -> Result<Option<LightBlock>, Error> {
todo!()
}

fn latest_status(&self) -> Result<LatestStatus, Error> {
todo!()
}

/// Verify to the highest block.
fn verify_to_highest(&self) -> Result<LightBlock, Error> {
todo!()
Expand Down Expand Up @@ -45,6 +50,8 @@ enum HandleInput {
VerifyToTarget(Height, channel::Sender<Result<LightBlock, Error>>),
/// Get the latest trusted block.
LatestTrusted(channel::Sender<Option<LightBlock>>),
/// Get the current status of the LightClient
GetStatus(channel::Sender<LatestStatus>),
}

/// A light client `Instance` packages a `LightClient` together with its `State`.
Expand Down Expand Up @@ -166,6 +173,25 @@ impl Supervisor {
self.verify(None)
}

/// Return latest trusted status summary.
fn latest_status(&mut self) -> LatestStatus {
let latest_trusted = self.peers.primary().latest_trusted();
let mut connected_nodes: Vec<PeerId> = Vec::new();
connected_nodes.push(self.peers.primary_id());
connected_nodes.append(&mut self.peers.witnesses_ids().iter().copied().collect());

match latest_trusted {
Some(trusted) => LatestStatus::new(
Some(trusted.signed_header.header.height()),
Some(trusted.signed_header.header.hash()),
Some(trusted.next_validators.hash()),
connected_nodes,
),
// only return connected nodes to see what is going on:
None => LatestStatus::new(None, None, None, connected_nodes),
}
}

/// Verify to the block at the given height.
pub fn verify_to_target(&mut self, height: Height) -> Result<LightBlock, Error> {
self.verify(Some(height))
Expand Down Expand Up @@ -322,6 +348,10 @@ impl Supervisor {
let outcome = self.verify_to_highest();
sender.send(outcome).map_err(ErrorKind::from)?;
}
HandleInput::GetStatus(sender) => {
let outcome = self.latest_status();
sender.send(outcome).map_err(ErrorKind::from)?;
}
}
}
}
Expand Down Expand Up @@ -363,6 +393,14 @@ impl Handle for SupervisorHandle {
Ok(receiver.recv().map_err(ErrorKind::from)?)
}

fn latest_status(&self) -> Result<LatestStatus, Error> {
let (sender, receiver) = channel::bounded::<LatestStatus>(1);
self.sender
.send(HandleInput::GetStatus(sender))
.map_err(ErrorKind::from)?;
Ok(receiver.recv().map_err(ErrorKind::from)?)
}

fn verify_to_highest(&self) -> Result<LightBlock, Error> {
self.verify(HandleInput::VerifyToHighest)
}
Expand Down
32 changes: 32 additions & 0 deletions light-client/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,38 @@ impl LightBlock {
}
}

/// Contains the local status information, like the latest height, latest block and valset hashes,
/// list of of connected full nodes (primary and witnesses).
#[derive(Clone, Debug, Display, PartialEq, Serialize, Deserialize)]
#[display(fmt = "{:?}", self)]
pub struct LatestStatus {
/// The latest height we are trusting.
pub height: Option<Height>,
/// The latest block hash we are trusting.
pub block_hash: Option<Hash>,
/// The latest validator set we are trusting.
/// Note that this potentially did not yet sign a header yet.
pub valset_hash: Option<Hash>,
/// The list of fullnodes we are connected to, primary and witnesses.
pub connected_nodes: Vec<PeerId>,
}

impl LatestStatus {
pub fn new(
height: Option<u64>,
block_hash: Option<Hash>,
valset_hash: Option<Hash>,
connected_nodes: Vec<PeerId>,
) -> Self {
LatestStatus {
height,
block_hash,
valset_hash,
connected_nodes,
}
}
}

#[cfg(test)]
mod tests {

Expand Down
36 changes: 35 additions & 1 deletion light-node/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,42 @@ $ curl localhost:8888 -X POST -H 'Content-Type: application/json' \
-d '{"jsonrpc": "2.0", "method": "state", "id": 1}' | jq
```
Or you can query a shorter summary via the `/status` endpoint:
```
$ curl localhost:8888 -X POST -H 'Content-Type: application/json'\ 15:58:52
-d '{"jsonrpc": "2.0", "method": "status", "id": 1}' | jq

```
<details>
<summary><b>Click here</b> to see an example for expected output for the status endpoint:</summary>
```
$ curl localhost:8888 -X POST -H 'Content-Type: application/json' \
-d '{"jsonrpc": "2.0", "method": "status", "id": 1}' | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 364 100 317 100 47 1843 273 --:--:-- --:--:-- --:--:-- 2104
```
```json
{
"jsonrpc": "2.0",
"result": {
"block_hash": "ED745723430944215F65ED78AD7DF9ED0AA8A2A3B465BF421E0BAF66AA55AA08",
"connected_nodes": [
"BADFADAD0BEFEEDC0C0ADEADBEEFC0FFEEFACADE",
"CEFEEDBADFADAD0C0CEEFACADE0ADEADBEEFC0FF"
],
"height": 3850,
"valset_hash": "74F2AC2B6622504D08DD2509E28CE731985CFE4D133C9DB0CB85763EDCA95AA3"
},
"id": 1
}
```
</details>

<details>
<summary><b>Click here</b> to see an example for expected output:</summary>
<summary><b>Click here</b> to see an example for expected output for the state endpoint:</summary>

Command:
```
Expand Down
6 changes: 3 additions & 3 deletions light-node/src/commands/start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ use tendermint_light_client::store::sled::SledStore;
use tendermint_light_client::store::LightStore;
use tendermint_light_client::supervisor::Handle;
use tendermint_light_client::supervisor::{Instance, Supervisor};
use tendermint_light_client::types::Status;

/// `start` subcommand
///
Expand Down Expand Up @@ -70,7 +69,7 @@ impl Runnable for StartCmd {
loop {
match handle.verify_to_highest() {
Ok(light_block) => {
status_info!("synced to block {}", light_block.height().to_string());
status_info!("synced to block:", light_block.height().to_string());
}
Err(err) => {
status_err!("sync failed: {}", err);
Expand Down Expand Up @@ -157,6 +156,7 @@ impl StartCmd {
let laddr = app_config().rpc_config.listen_addr;
// TODO(liamsi): figure out how to handle the potential error on run
std::thread::spawn(move || rpc::run(server, &laddr.to_string()));
status_info!("started RPC server:", laddr.to_string());
}
}

Expand Down Expand Up @@ -189,7 +189,7 @@ impl StartCmd {
Supervisor::new(
peer_list,
ProdForkDetector::default(),
ProdEvidenceReporter::new(peer_map.clone()),
ProdEvidenceReporter::new(peer_map),
)
}
}
49 changes: 48 additions & 1 deletion light-node/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub use sealed::{Client, Rpc, Server};

/// Run the given [`Server`] on the given address and blocks until closed.
///
/// n.b. The underlying server has semantics to close on drop. Also does it not offer any way to
/// n.b. The underlying server has semantics to close on drop. Also does it does not offer any way to
/// get the underlying Future to await, so we are left with this rather rudimentary way to control
/// the lifecycle. Should we be interested in a more controlled way to close the server we can
/// expose a handle in the future.
Expand Down Expand Up @@ -38,13 +38,18 @@ mod sealed {
use jsonrpc_derive::rpc;

use tendermint_light_client::supervisor::Handle;
use tendermint_light_client::types::LatestStatus;
use tendermint_light_client::types::LightBlock;

#[rpc]
pub trait Rpc {
/// Returns the latest trusted block.
#[rpc(name = "state")]
fn state(&self) -> FutureResult<Option<LightBlock>, Error>;

/// Returns the latest status.
#[rpc(name = "status")]
fn status(&self) -> FutureResult<LatestStatus, Error>;
}

pub use self::rpc_impl_Rpc::gen_client::Client;
Expand Down Expand Up @@ -79,6 +84,17 @@ mod sealed {

future::result(res)
}

fn status(&self) -> FutureResult<LatestStatus, Error> {
let res = self.handle.latest_status().map_err(|e| {
let mut err = Error::internal_error();
err.message = e.to_string();
err.data = serde_json::to_value(e.kind()).ok();
err
});

future::result(res)
}
}
}

Expand All @@ -92,6 +108,7 @@ mod test {

use tendermint_light_client::errors::Error;
use tendermint_light_client::supervisor::Handle;
use tendermint_light_client::types::LatestStatus;
use tendermint_light_client::types::LightBlock;

use super::{Client, Rpc as _, Server};
Expand All @@ -111,6 +128,21 @@ mod test {
assert_eq!(have, want);
}

#[tokio::test]
async fn status() {
let server = Server::new(MockHandle {});
let fut = {
let mut io = IoHandler::new();
io.extend_with(server.to_delegate());
let (client, server) = local::connect::<Client, _, _>(io);
client.status().join(server)
};
let (have, _) = fut.compat().await.unwrap();
let want = serde_json::from_str(STATUS_JSON).unwrap();

assert_eq!(have, want);
}

struct MockHandle;

impl Handle for MockHandle {
Expand All @@ -119,6 +151,11 @@ mod test {

Ok(Some(block))
}
fn latest_status(&self) -> Result<LatestStatus, Error> {
let status: LatestStatus = serde_json::from_str(STATUS_JSON).unwrap();

Ok(status)
}
}

const LIGHTBLOCK_JSON: &str = r#"
Expand Down Expand Up @@ -239,4 +276,14 @@ mod test {
"provider": "9D61B19DEFFD5A60BA844AF492EC2CC44449C569"
}
"#;
const STATUS_JSON: &str = r#"
{
"block_hash": "5A55D7AF2DF9AE4BF4B46FDABBBAD1B66D37B5E044A4843AB0FB0EBEC3E0422C",
"connected_nodes": [
"BADFADAD0BEFEEDC0C0ADEADBEEFC0FFEEFACADE",
"CEFEEDBADFADAD0C0CEEFACADE0ADEADBEEFC0FF"
],
"height": 1565,
"valset_hash": "74F2AC2B6622504D08DD2509E28CE731985CFE4D133C9DB0CB85763EDCA95AA3"
}"#;
}

0 comments on commit 02fa831

Please sign in to comment.