diff --git a/core/src/consensus/light_client/mod.rs b/core/src/consensus/light_client/mod.rs index 1e1b3de8b0..021711107c 100644 --- a/core/src/consensus/light_client/mod.rs +++ b/core/src/consensus/light_client/mod.rs @@ -16,41 +16,14 @@ pub mod verification; pub use self::verification::verify_header; -use ckey::SchnorrSignature; pub use ctypes::BlockNumber; use ctypes::{CompactValidatorSet, Header}; -pub use primitives::{H256, H512}; -use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream}; +pub use primitives::{Bytes, H256, H512}; -#[derive(PartialEq, Debug)] +#[derive(RlpEncodable, RlpDecodable, PartialEq, Debug)] // (index_in_vset, sign); schnorr scheme -pub struct Seal(Vec<(usize, SchnorrSignature)>); - -impl Encodable for Seal { - fn rlp_append(&self, s: &mut RlpStream) { - s.begin_list(self.0.len() * 2); - for (x, y) in self.0.iter() { - s.append(x); - s.append(y); - } - } -} - -impl Decodable for Seal { - fn decode(rlp: &Rlp<'_>) -> Result { - let item_count = rlp.item_count()?; - if item_count % 2 == 1 { - return Err(DecoderError::RlpInvalidLength { - expected: item_count + 1, - got: item_count, - }) - } - let mut vec = Vec::with_capacity(item_count / 2); - for i in 0..(item_count / 2) { - vec.push((rlp.val_at(i * 2)?, rlp.val_at(i * 2 + 1)?)); - } - Ok(Self(vec)) - } +pub struct Seal { + pub raw: Vec, } #[derive(PartialEq, Debug)] diff --git a/core/src/consensus/light_client/verification.rs b/core/src/consensus/light_client/verification.rs index ed57aa2820..fa2840db4a 100644 --- a/core/src/consensus/light_client/verification.rs +++ b/core/src/consensus/light_client/verification.rs @@ -15,6 +15,7 @@ // along with this program. If not, see . use super::{ClientState, Seal, UpdateHeader}; +use crate::consensus::tendermint::types::TendermintSealView; use ckey::verify_schnorr; pub use ctypes::BlockNumber; use ctypes::CompactValidatorSet; @@ -23,7 +24,13 @@ use primitives::H256; pub fn verify_signature(block_hash: H256, vset: &CompactValidatorSet, seal: &Seal) -> bool { let n = vset.len(); - for (index, sign) in &seal.0 { + let seal_view = TendermintSealView::new(&seal.raw).signatures(); + if seal_view.is_err() { + return false + } + let seal_dec = seal_view.unwrap(); + + for (index, sign) in &seal_dec { if *index >= n { return false } @@ -37,9 +44,15 @@ pub fn verify_signature(block_hash: H256, vset: &CompactValidatorSet, seal: &Sea } pub fn verify_quorum(vset: &CompactValidatorSet, seal: &Seal) -> bool { + let seal_view = TendermintSealView::new(&seal.raw).signatures(); + if seal_view.is_err() { + return false + } + let seal_dec = seal_view.unwrap(); + // Note that invalid index would already be rejcted in verify_signature() let total_delegation: u64 = vset.iter().map(|validator| validator.delegation).sum(); - let voted_delegation: u64 = seal.0.iter().map(|(index, _)| vset[*index].delegation).sum(); + let voted_delegation: u64 = seal_dec.iter().map(|(index, _)| vset[*index].delegation).sum(); if total_delegation * 2 >= voted_delegation * 3 { return false } @@ -70,8 +83,11 @@ mod tests { in unit tests in rust-merkle-trie */ use super::*; + use crate::consensus::BitSet; + use crate::consensus::Seal as TendermintSeal; use ccrypto::blake256; use ckey::sign_schnorr; + use ckey::SchnorrSignature; use ckey::{Generator, Private, Public, Random}; use ctypes::CompactValidatorEntry; use rand::{rngs::StdRng, Rng}; @@ -88,7 +104,7 @@ mod tests { let mut users: Vec<(Public, Private)> = Vec::new(); let mut vset = CompactValidatorSet::new(Vec::new()); - let mut seal = Seal(Vec::new()); + let mut seal: Vec<(usize, SchnorrSignature)> = Vec::new(); let mut del_total = 0 as u64; let mut del_signed = 0 as u64; @@ -109,10 +125,20 @@ mod tests { if rng.gen_range(0, 3) == 0 { continue } else { - seal.0.push((i as usize, sign_schnorr(&user.1, &hash).unwrap())); + seal.push((i as usize, sign_schnorr(&user.1, &hash).unwrap())); del_signed += vset[i].delegation; } } + let seal_indices: Vec = seal.iter().map(|(index, _)| *index).collect(); + let seal_signs: Vec = seal.iter().map(|(_, sign)| *sign).collect(); + + let precommit_bitset = BitSet::new_with_indices(&seal_indices); + let seal_enc = TendermintSeal::Tendermint { + prev_view: 0, + cur_view: 0, + precommits: seal_signs, + precommit_bitset, + }; let client_state = ClientState { number: 10, @@ -122,7 +148,9 @@ mod tests { let proposal = UpdateHeader { number: 11, hash, - seal, + seal: Seal { + raw: seal_enc.seal_fields().unwrap(), + }, validator_set: vset, }; diff --git a/core/src/ibc/mod.rs b/core/src/ibc/mod.rs index ccb0fe7be9..8afc0244a0 100644 --- a/core/src/ibc/mod.rs +++ b/core/src/ibc/mod.rs @@ -23,6 +23,7 @@ pub mod commitment_23; pub mod connection_03; pub mod context; mod kv_store; +pub mod querier; mod transaction_handler; pub use self::client_02 as client; diff --git a/core/src/ibc/querier/mod.rs b/core/src/ibc/querier/mod.rs new file mode 100644 index 0000000000..4e6eeb7dfe --- /dev/null +++ b/core/src/ibc/querier/mod.rs @@ -0,0 +1,96 @@ +// Copyright 2020 Kodebox, Inc. +// This file is part of CodeChain. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +/// This module representes all accessible interface +use crate::ibc; +use ibc::commitment_23::types::{create_membership_proof, create_non_membership_proof}; +use ibc::commitment_23::{CommitmentPath, CommitmentState}; +use primitives::Bytes; +use rlp::Decodable; + +use crate::ibc::IdentifierSlice; + +pub trait DebugName { + fn debug_name() -> &'static str; +} + +impl DebugName for ibc::connection_03::types::ConnectionEnd { + fn debug_name() -> &'static str { + "ConnectionEnd" + } +} + +impl DebugName for ibc::client_02::types::ClientState { + fn debug_name() -> &'static str { + "ClientState" + } +} + +impl DebugName for ibc::client_02::types::ConsensusState { + fn debug_name() -> &'static str { + "ConsensusState" + } +} + +/// Queries the path and returns the result in decoded struct +pub fn query(ctx: &dyn ibc::Context, path: &CommitmentPath) -> Option +where + T: Decodable + DebugName, { + if ctx.get_kv_store().has(&path.raw) { + let data = ctx.get_kv_store().get(&path.raw); + // error means that state DB has stored an invalid data. (must never happen) + Some(rlp::decode(&data).unwrap_or_else(|_| panic!(format!("Illformed {} stored in DB", T::debug_name())))) + } else { + None + } +} + +/// Caller of this function should not care about the type of proof. Thus we return as Bytes +/// It may create both proof of presence and absence. Caller should be aware of which one would be. +pub fn make_proof(ctx: &dyn ibc::Context, path: &CommitmentPath) -> Bytes { + if ctx.get_kv_store().has(&path.raw) { + let commitment_state = CommitmentState { + kv_store: ctx.get_kv_store(), + }; + let value = ctx.get_kv_store().get(&path.raw); + let proof = create_membership_proof(&commitment_state, &path, &value); + rlp::encode(&proof) + } else { + let commitment_state = CommitmentState { + kv_store: ctx.get_kv_store(), + }; + let proof = create_non_membership_proof(&commitment_state, &path); + rlp::encode(&proof) + } +} + +pub fn path_client_state(id: IdentifierSlice) -> CommitmentPath { + CommitmentPath { + raw: ibc::client_02::path_client_state(id), + } +} + +pub fn path_consensus_state(id: IdentifierSlice, num: u64) -> CommitmentPath { + CommitmentPath { + raw: ibc::client_02::path_consensus_state(id, num), + } +} + +pub fn path_connection_end(id: IdentifierSlice) -> CommitmentPath { + CommitmentPath { + raw: ibc::connection_03::path(id), + } +} diff --git a/rpc/src/v1/impls/ibc.rs b/rpc/src/v1/impls/ibc.rs index fdfd9a4e41..2639b22738 100644 --- a/rpc/src/v1/impls/ibc.rs +++ b/rpc/src/v1/impls/ibc.rs @@ -14,8 +14,15 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +use super::super::errors; use super::super::traits::IBC; -use ccore::{BlockChainClient, StateInfo}; +use super::super::types::{ClientState, ConsensusState, IBCQuery}; +use ccore::ibc; +use ccore::ibc::querier; +use ccore::{BlockChainClient, BlockId, StateInfo}; +use ibc::client_02::types::Header; +use jsonrpc_core::Result; +use primitives::Bytes; use std::sync::Arc; #[allow(dead_code)] @@ -36,4 +43,87 @@ where } } -impl IBC for IBCClient where C: StateInfo + 'static + Send + Sync + BlockChainClient {} +impl IBC for IBCClient +where + C: StateInfo + 'static + Send + Sync + BlockChainClient, +{ + fn query_client_state( + &self, + client_id: String, + block_number: Option, + ) -> Result>> { + let block_id = block_number.map(BlockId::Number).unwrap_or(BlockId::Latest); + if self.client.state_at(block_id).is_none() { + return Ok(None) + } + let mut state = self.client.state_at(block_id).unwrap(); + let block_number = match self.client.block_number(&block_id) { + None => return Ok(None), + Some(block_number) => block_number, + }; + + let context = ibc::context::TopLevelContext::new(&mut state, block_number); + let path = querier::path_client_state(&client_id); + let client_state: Option = querier::query(&context, &path); + + Ok(Some(IBCQuery { + number: block_number, + data: client_state.map(|x| ClientState::from_core(&x)), + proof: querier::make_proof(&context, &path), + })) + } + + fn query_consensus_state( + &self, + client_id: String, + counterparty_block_number: u64, + block_number: Option, + ) -> Result>> { + let block_id = block_number.map(BlockId::Number).unwrap_or(BlockId::Latest); + if self.client.state_at(block_id).is_none() { + return Ok(None) + } + let mut state = self.client.state_at(block_id).unwrap(); + let block_number = match self.client.block_number(&block_id) { + None => return Ok(None), + Some(block_number) => block_number, + }; + + let context = ibc::context::TopLevelContext::new(&mut state, block_number); + let path = querier::path_consensus_state(&client_id, counterparty_block_number); + let consensus_state: Option = querier::query(&context, &path); + + Ok(Some(IBCQuery { + number: block_number, + data: consensus_state.map(|x| ConsensusState::from_core(&x)), + proof: querier::make_proof(&context, &path), + })) + } + + fn compose_header(&self, block_number: u64) -> Result> { + let block_id = BlockId::Number(block_number); + if self.client.state_at(block_id).is_none() { + return Ok(None) + } + let state = self.client.state_at(block_id).unwrap(); + + let header_core = self.client.block_header(&block_id).unwrap(); + let vset_raw = ccore::stake::NextValidators::load_from_state(&state).map_err(errors::core)?; + + let vset = vset_raw.create_compact_validator_set(); + let header = Header { + header_proposal: ccore::consensus::light_client::UpdateHeader { + number: block_number, + hash: *header_core.hash(), + seal: ccore::consensus::light_client::Seal { + raw: header_core.seal(), + }, + validator_set: vset, + }, + state_root: ccore::ibc::commitment_23::CommitmentRoot { + raw: header_core.state_root(), + }, + }; + Ok(Some(rlp::encode(&header))) + } +} diff --git a/rpc/src/v1/traits/ibc.rs b/rpc/src/v1/traits/ibc.rs index 31cac1c4b5..71199123fa 100644 --- a/rpc/src/v1/traits/ibc.rs +++ b/rpc/src/v1/traits/ibc.rs @@ -14,5 +14,29 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +use super::super::types::IBCQuery; +use super::super::types::{ClientState, ConsensusState}; +use jsonrpc_core::Result; +use primitives::Bytes; + #[rpc(server)] -pub trait IBC {} +pub trait IBC { + /// Gets client state + #[rpc(name = "ibc_query_client_state")] + fn query_client_state(&self, client_id: String, block_number: Option) + -> Result>>; + + /// Gets consensus state on arbitrary number + #[rpc(name = "ibc_query_consensus_state")] + fn query_consensus_state( + &self, + client_id: String, + counterparty_block_number: u64, + block_number: Option, + ) -> Result>>; + + /// Compose an ICS header that updates Foundry light client (not me, but being in some counterparty chain) + /// from block_number-1 to block_number. It will stay opaque until it gets finally delieverd to Foundry light client. + #[rpc(name = "ibc_compose_header")] + fn compose_header(&self, block_number: u64) -> Result>; +} diff --git a/rpc/src/v1/types/ibc.rs b/rpc/src/v1/types/ibc.rs index d13b202ca5..b0eca59693 100644 --- a/rpc/src/v1/types/ibc.rs +++ b/rpc/src/v1/types/ibc.rs @@ -13,3 +13,58 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . + +use codechain_core::ibc::client_02::types::{ClientState as CoreClientState, ConsensusState as CoreConsensusState}; +use primitives::{Bytes, H256}; +use serde::Serialize; + +/// Many of RPC responses will be expressed with this +/// Because of the nature of IBC, they commonly +/// 1. Requires a block number for which proof stands +/// 2. The data should be transparent: +/// relayer must be able to open it and extract required infomation +/// 3. Includes a cryptographical proof of that +/// Note : proof may represents both that of presence and absence. It depends on option of data. +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct IBCQuery { + pub number: u64, + pub data: Option, + pub proof: Bytes, +} + +/// Client 02 related types + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ClientState { + /// Unpacked light_client::ClientState + pub number: u64, + pub next_validator_set_hash: H256, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ConsensusState { + pub validator_set_hash: H256, + /// Unpacked CommitmentRoot + pub state_root: H256, +} + +impl ClientState { + pub fn from_core(state: &CoreClientState) -> Self { + ClientState { + number: state.raw.number, + next_validator_set_hash: state.raw.next_validator_set_hash, + } + } +} + +impl ConsensusState { + pub fn from_core(state: &CoreConsensusState) -> Self { + ConsensusState { + validator_set_hash: state.validator_set_hash, + state_root: state.state_root.raw, + } + } +} diff --git a/rpc/src/v1/types/mod.rs b/rpc/src/v1/types/mod.rs index b1f9b8a0af..d6080306cf 100644 --- a/rpc/src/v1/types/mod.rs +++ b/rpc/src/v1/types/mod.rs @@ -25,6 +25,7 @@ mod work; pub use self::action::{Action, ActionWithTracker}; pub use self::block::Block; pub use self::block::BlockNumberAndHash; +pub use self::ibc::{ClientState, ConsensusState, IBCQuery}; pub use self::mem_pool::MemPoolMinFees; pub use self::transaction::{PendingTransactions, Transaction}; pub use self::unsigned_transaction::UnsignedTransaction;