Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
35 changes: 4 additions & 31 deletions core/src/consensus/light_client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self, DecoderError> {
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<Bytes>,
}

#[derive(PartialEq, Debug)]
Expand Down
38 changes: 33 additions & 5 deletions core/src/consensus/light_client/verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use super::{ClientState, Seal, UpdateHeader};
use crate::consensus::tendermint::types::TendermintSealView;
use ckey::verify_schnorr;
pub use ctypes::BlockNumber;
use ctypes::CompactValidatorSet;
Expand All @@ -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
}
Expand All @@ -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
}
Expand Down Expand Up @@ -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};
Expand All @@ -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;
Expand All @@ -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<usize> = seal.iter().map(|(index, _)| *index).collect();
let seal_signs: Vec<SchnorrSignature> = 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,
Expand All @@ -122,7 +148,9 @@ mod tests {
let proposal = UpdateHeader {
number: 11,
hash,
seal,
seal: Seal {
raw: seal_enc.seal_fields().unwrap(),
},
validator_set: vset,
};

Expand Down
1 change: 1 addition & 0 deletions core/src/ibc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
96 changes: 96 additions & 0 deletions core/src/ibc/querier/mod.rs
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.

/// 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<T>(ctx: &dyn ibc::Context, path: &CommitmentPath) -> Option<T>
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),
}
}
94 changes: 92 additions & 2 deletions rpc/src/v1/impls/ibc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,15 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

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)]
Expand All @@ -36,4 +43,87 @@ where
}
}

impl<C> IBC for IBCClient<C> where C: StateInfo + 'static + Send + Sync + BlockChainClient {}
impl<C> IBC for IBCClient<C>
where
C: StateInfo + 'static + Send + Sync + BlockChainClient,
{
fn query_client_state(
&self,
client_id: String,
block_number: Option<u64>,
) -> Result<Option<IBCQuery<ClientState>>> {
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<ibc::client_02::types::ClientState> = 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<u64>,
) -> Result<Option<IBCQuery<ConsensusState>>> {
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<ibc::client_02::types::ConsensusState> = 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<Option<Bytes>> {
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)))
}
}
26 changes: 25 additions & 1 deletion rpc/src/v1/traits/ibc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,29 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

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<u64>)
-> Result<Option<IBCQuery<ClientState>>>;

/// 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<u64>,
) -> Result<Option<IBCQuery<ConsensusState>>>;

/// 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<Option<Bytes>>;
}
Loading