diff --git a/.changelog/unreleased/improvements/538-test-for-client-expiry.md b/.changelog/unreleased/improvements/538-test-for-client-expiry.md new file mode 100644 index 000000000..701905ada --- /dev/null +++ b/.changelog/unreleased/improvements/538-test-for-client-expiry.md @@ -0,0 +1,2 @@ +- Add test for expired client status. + ([\#538](https://github.com/cosmos/ibc-rs/issues/538)) diff --git a/.github/workflows/cw-check.yaml b/.github/workflows/cw-check.yaml index e06e19251..ae84a64d9 100644 --- a/.github/workflows/cw-check.yaml +++ b/.github/workflows/cw-check.yaml @@ -4,8 +4,6 @@ on: paths: - .github/workflows/cw-check.yml - ci/cw-check/** - -on: push: tags: - v[0-9]+.* diff --git a/crates/ibc/Cargo.toml b/crates/ibc/Cargo.toml index 0a7a66025..afbba5561 100644 --- a/crates/ibc/Cargo.toml +++ b/crates/ibc/Cargo.toml @@ -46,7 +46,7 @@ schema = ["dep:schemars", "serde", "std"] # This feature grants access to development-time mocking libraries, such as `MockContext` or `MockHeader`. # Depends on the `testgen` suite for generating Tendermint light blocks. -mocks = ["tendermint-testgen", "tendermint/clock", "parking_lot"] +mocks = ["tendermint-testgen", "tendermint/clock", "parking_lot", "typed-builder"] [dependencies] # Proto definitions for all IBC-related interfaces, e.g., connections or channels. @@ -73,6 +73,7 @@ scale-info = { version = "2.1.2", default-features = false, features = ["derive" ## for borsh encode or decode borsh = {version = "0.10", default-features = false, optional = true } parking_lot = { version = "0.12.1", default-features = false, optional = true } +typed-builder = { version = "0.17.0", optional = true } ibc-derive = { version ="0.3.0", path = "../ibc-derive" } @@ -102,5 +103,4 @@ rstest = "0.18.1" tracing-subscriber = { version = "0.3.14", features = ["fmt", "env-filter", "json"]} test-log = { version = "0.2.10", features = ["trace"] } tendermint-rpc = { version = "0.34", features = ["http-client", "websocket-client"] } -tendermint-testgen = { version = "0.34" } # Needed for generating (synthetic) light blocks. -parking_lot = { version = "0.12.1" } +ibc = { path = ".", features = ["mocks"] } diff --git a/crates/ibc/src/clients/ics07_tendermint/client_state.rs b/crates/ibc/src/clients/ics07_tendermint/client_state.rs index cc7c1f66a..7fc11d421 100644 --- a/crates/ibc/src/clients/ics07_tendermint/client_state.rs +++ b/crates/ibc/src/clients/ics07_tendermint/client_state.rs @@ -1129,7 +1129,8 @@ pub mod test_util { use tendermint::block::Header; use crate::clients::ics07_tendermint::client_state::{AllowUpdate, ClientState}; - use crate::clients::ics07_tendermint::error::Error; + use crate::clients::ics07_tendermint::error::{Error as ClientError, Error}; + use crate::clients::ics07_tendermint::trust_threshold::TrustThreshold; use crate::core::ics02_client::height::Height; use crate::core::ics23_commitment::specs::ProofSpecs; use crate::core::ics24_host::identifier::ChainId; @@ -1180,4 +1181,42 @@ pub mod test_util { allow_update_after_misbehaviour: false, } } + + #[derive(typed_builder::TypedBuilder, Debug)] + pub struct ClientStateConfig { + pub chain_id: ChainId, + #[builder(default)] + pub trust_level: TrustThreshold, + #[builder(default = Duration::from_secs(64000))] + pub trusting_period: Duration, + #[builder(default = Duration::from_secs(128000))] + pub unbonding_period: Duration, + #[builder(default = Duration::from_millis(3000))] + max_clock_drift: Duration, + pub latest_height: Height, + #[builder(default)] + pub proof_specs: ProofSpecs, + #[builder(default)] + pub upgrade_path: Vec, + #[builder(default = AllowUpdate { after_expiry: false, after_misbehaviour: false })] + allow_update: AllowUpdate, + } + + impl TryFrom for ClientState { + type Error = ClientError; + + fn try_from(config: ClientStateConfig) -> Result { + ClientState::new( + config.chain_id, + config.trust_level, + config.trusting_period, + config.unbonding_period, + config.max_clock_drift, + config.latest_height, + config.proof_specs, + config.upgrade_path, + config.allow_update, + ) + } + } } diff --git a/crates/ibc/src/clients/ics07_tendermint/header.rs b/crates/ibc/src/clients/ics07_tendermint/header.rs index da0167e3e..22056d4a6 100644 --- a/crates/ibc/src/clients/ics07_tendermint/header.rs +++ b/crates/ibc/src/clients/ics07_tendermint/header.rs @@ -318,13 +318,14 @@ pub mod test_util { fn from(light_block: SyntheticTmBlock) -> Self { let SyntheticTmBlock { trusted_height, + trusted_next_validators, light_block, } = light_block; Self { signed_header: light_block.signed_header, validator_set: light_block.validators, trusted_height, - trusted_next_validator_set: light_block.next_validators, + trusted_next_validator_set: trusted_next_validators, } } } diff --git a/crates/ibc/src/core/ics02_client/handler/update_client.rs b/crates/ibc/src/core/ics02_client/handler/update_client.rs index 10335d7b7..ddd7bef73 100644 --- a/crates/ibc/src/core/ics02_client/handler/update_client.rs +++ b/crates/ibc/src/core/ics02_client/handler/update_client.rs @@ -143,7 +143,9 @@ mod tests { use crate::core::ics24_host::identifier::{ChainId, ClientId}; use crate::core::timestamp::Timestamp; use crate::mock::client_state::{client_type as mock_client_type, MockClientState}; - use crate::mock::context::{AnyConsensusState, MockContext}; + use crate::mock::context::{ + AnyConsensusState, MockClientConfig, MockContext, MockContextConfig, + }; use crate::mock::header::MockHeader; use crate::mock::host::{HostBlock, HostType}; use crate::mock::misbehaviour::Misbehaviour as MockMisbehaviour; @@ -249,47 +251,70 @@ mod tests { fn test_update_synthetic_tendermint_client_validator_change_ok() { let client_id = ClientId::new(tm_client_type(), 0).unwrap(); let client_height = Height::new(1, 20).unwrap(); - let update_height = Height::new(1, 21).unwrap(); let chain_id_b = ChainId::new("mockgaiaB", 1).unwrap(); - let mut ctx = MockContext::new( - ChainId::new("mockgaiaA", 1).unwrap(), - HostType::Mock, - 5, - Height::new(1, 1).unwrap(), - ) - .with_client_parametrized_with_chain_id( - chain_id_b.clone(), - &client_id, - client_height, - Some(tm_client_type()), // The target host chain (B) is synthetic TM. - Some(client_height), - ); + let mut ctx_a = MockContextConfig::builder() + .host_id(ChainId::new("mockgaiaA", 1).unwrap()) + .latest_height(Height::new(1, 1).unwrap()) + .build() + .with_client_config( + // client state initialized with client_height, and + // [{id: 1, power: 50}, {id: 2, power: 50}] for validator set and next validator set. + MockClientConfig::builder() + .client_chain_id(chain_id_b.clone()) + .client_id(client_id.clone()) + .client_state_height(client_height) + .client_type(tm_client_type()) + .build(), + ); - let ctx_b = MockContext::new_with_validator_history( - chain_id_b, - HostType::SyntheticTendermint, - &[ - // TODO(rano): the validator set params during setups. - // Here I picked the default validator set which is - // used at host side client creation. - vec![ - TestgenValidator::new("1").voting_power(50), - TestgenValidator::new("2").voting_power(50), - ], - vec![ - TestgenValidator::new("1").voting_power(60), - TestgenValidator::new("2").voting_power(40), - ], + let ctx_b_val_history = vec![ + // First two validator sets are default at client creation + // + // validator set of height-20 + vec![ + TestgenValidator::new("1").voting_power(50), + TestgenValidator::new("2").voting_power(50), ], - update_height, - ); + // validator set of height-21 + vec![ + TestgenValidator::new("1").voting_power(50), + TestgenValidator::new("2").voting_power(50), + ], + // validator set of height-22 + vec![ + TestgenValidator::new("1").voting_power(30), + TestgenValidator::new("2").voting_power(70), + ], + // validator set of height-23 + vec![ + TestgenValidator::new("1").voting_power(20), + TestgenValidator::new("2").voting_power(80), + ], + ]; + + let update_height = client_height.add(ctx_b_val_history.len() as u64 - 2); + + let ctx_b = MockContextConfig::builder() + .host_id(chain_id_b.clone()) + .host_type(HostType::SyntheticTendermint) + .latest_height(update_height) + .max_history_size(ctx_b_val_history.len() as u64 - 1) + .validator_set_history(ctx_b_val_history) + .build(); let signer = get_dummy_account_id(); let mut block = ctx_b.host_block(&update_height).unwrap().clone(); block.set_trusted_height(client_height); + let trusted_next_validator_set = match ctx_b.host_block(&client_height).expect("no error") { + HostBlock::SyntheticTendermint(header) => header.light_block.next_validators.clone(), + _ => panic!("unexpected host block type"), + }; + + block.set_trusted_next_validators_set(trusted_next_validator_set); + let latest_header_height = block.height(); let msg = MsgUpdateClient { client_id, @@ -297,20 +322,102 @@ mod tests { signer, }; - let res = validate(&ctx, MsgUpdateOrMisbehaviour::UpdateClient(msg.clone())); + let res = validate(&ctx_a, MsgUpdateOrMisbehaviour::UpdateClient(msg.clone())); assert!(res.is_ok()); - let res = execute(&mut ctx, MsgUpdateOrMisbehaviour::UpdateClient(msg.clone())); + let res = execute( + &mut ctx_a, + MsgUpdateOrMisbehaviour::UpdateClient(msg.clone()), + ); assert!(res.is_ok(), "result: {res:?}"); - let client_state = ctx.client_state(&msg.client_id).unwrap(); + let client_state = ctx_a.client_state(&msg.client_id).unwrap(); assert!(client_state - .status(&ctx, &msg.client_id) + .status(&ctx_a, &msg.client_id) .unwrap() .is_active()); assert_eq!(client_state.latest_height(), latest_header_height); } + #[test] + fn test_update_synthetic_tendermint_client_validator_change_fail() { + let client_id = ClientId::new(tm_client_type(), 0).unwrap(); + let client_height = Height::new(1, 20).unwrap(); + let chain_id_b = ChainId::new("mockgaiaB", 1).unwrap(); + + let ctx_a = MockContextConfig::builder() + .host_id(ChainId::new("mockgaiaA", 1).unwrap()) + .latest_height(Height::new(1, 1).unwrap()) + .build() + .with_client_config( + // client state initialized with client_height, and + // [{id: 1, power: 50}, {id: 2, power: 50}] for validator set and next validator set. + MockClientConfig::builder() + .client_chain_id(chain_id_b.clone()) + .client_id(client_id.clone()) + .client_state_height(client_height) + .client_type(tm_client_type()) + .build(), + ); + + let ctx_b_val_history = vec![ + // First two validator sets are default at client creation + // + // validator set of height-20 + vec![ + TestgenValidator::new("1").voting_power(50), + TestgenValidator::new("2").voting_power(50), + ], + // incorrect next validator set for height-20 + // validator set of height-21 + vec![ + TestgenValidator::new("1").voting_power(45), + TestgenValidator::new("2").voting_power(55), + ], + // validator set of height-22 + vec![ + TestgenValidator::new("1").voting_power(30), + TestgenValidator::new("2").voting_power(70), + ], + // validator set of height-23 + vec![ + TestgenValidator::new("1").voting_power(20), + TestgenValidator::new("2").voting_power(80), + ], + ]; + + let update_height = client_height.add(ctx_b_val_history.len() as u64 - 2); + + let ctx_b = MockContextConfig::builder() + .host_id(chain_id_b.clone()) + .host_type(HostType::SyntheticTendermint) + .latest_height(update_height) + .max_history_size(ctx_b_val_history.len() as u64 - 1) + .validator_set_history(ctx_b_val_history) + .build(); + + let signer = get_dummy_account_id(); + + let mut block = ctx_b.host_block(&update_height).unwrap().clone(); + block.set_trusted_height(client_height); + + let trusted_next_validator_set = match ctx_b.host_block(&client_height).expect("no error") { + HostBlock::SyntheticTendermint(header) => header.light_block.next_validators.clone(), + _ => panic!("unexpected host block type"), + }; + + block.set_trusted_next_validators_set(trusted_next_validator_set); + + let msg = MsgUpdateClient { + client_id, + client_message: block.into(), + signer, + }; + + let res = validate(&ctx_a, MsgUpdateOrMisbehaviour::UpdateClient(msg.clone())); + assert!(res.is_err()); + } + #[test] fn test_update_synthetic_tendermint_client_non_adjacent_ok() { let client_id = ClientId::new(tm_client_type(), 0).unwrap(); @@ -753,4 +860,113 @@ mod tests { assert!(res.is_ok()); ensure_misbehaviour(&ctx_a, &client_id, &tm_client_type()); } + + #[test] + fn test_expired_client() { + let chain_id_b = ChainId::new("mockgaiaB", 1).unwrap(); + + let update_height = Height::new(1, 21).unwrap(); + let client_height = update_height.sub(3).unwrap(); + + let client_id = ClientId::new(tm_client_type(), 0).unwrap(); + + let timestamp = Timestamp::now(); + + let trusting_period = Duration::from_secs(64); + + let mut ctx = MockContextConfig::builder() + .host_id(ChainId::new("mockgaiaA", 1).unwrap()) + .latest_height(Height::new(1, 1).unwrap()) + .latest_timestamp(timestamp) + .build() + .with_client_config( + MockClientConfig::builder() + .client_chain_id(chain_id_b.clone()) + .client_id(client_id.clone()) + .client_state_height(client_height) + .client_type(tm_client_type()) + .latest_timestamp(timestamp) + .trusting_period(trusting_period) + .build(), + ); + + while ctx.host_timestamp().expect("no error") + < (timestamp + trusting_period).expect("no error") + { + ctx.advance_host_chain_height(); + } + + let client_state = ctx.client_state(&client_id).unwrap(); + + assert!(client_state.status(&ctx, &client_id).unwrap().is_expired()); + } + + #[test] + fn test_client_update_max_clock_drift() { + let chain_id_b = ChainId::new("mockgaiaB", 1).unwrap(); + + let client_height = Height::new(1, 20).unwrap(); + + let client_id = ClientId::new(tm_client_type(), 0).unwrap(); + + let timestamp = Timestamp::now(); + + let max_clock_drift = Duration::from_secs(64); + + let ctx_a = MockContextConfig::builder() + .host_id(ChainId::new("mockgaiaA", 1).unwrap()) + .latest_height(Height::new(1, 1).unwrap()) + .latest_timestamp(timestamp) + .build() + .with_client_config( + MockClientConfig::builder() + .client_chain_id(chain_id_b.clone()) + .client_id(client_id.clone()) + .client_state_height(client_height) + .client_type(tm_client_type()) + .latest_timestamp(timestamp) + .max_clock_drift(max_clock_drift) + .build(), + ); + + let mut ctx_b = MockContextConfig::builder() + .host_id(chain_id_b.clone()) + .host_type(HostType::SyntheticTendermint) + .latest_height(client_height) + .latest_timestamp(timestamp) + .max_history_size(u64::MAX) + .build(); + + while ctx_b.host_timestamp().expect("no error") + < (ctx_a.host_timestamp().expect("no error") + max_clock_drift).expect("no error") + { + ctx_b.advance_host_chain_height(); + } + + // include current block + ctx_b.advance_host_chain_height(); + + let update_height = ctx_b.latest_height(); + + let signer = get_dummy_account_id(); + + let mut block = ctx_b.host_block(&update_height).unwrap().clone(); + block.set_trusted_height(client_height); + + let trusted_next_validator_set = match ctx_b.host_block(&client_height).expect("no error") { + HostBlock::SyntheticTendermint(header) => header.light_block.next_validators.clone(), + _ => panic!("unexpected host block type"), + }; + + block.set_trusted_next_validators_set(trusted_next_validator_set); + + let msg = MsgUpdateClient { + client_id, + client_message: block.clone().into(), + signer, + }; + + let res = validate(&ctx_a, MsgUpdateOrMisbehaviour::UpdateClient(msg.clone())); + assert!(res.is_err()); + } } diff --git a/crates/ibc/src/mock/context.rs b/crates/ibc/src/mock/context.rs index f31e23a6d..53b64956b 100644 --- a/crates/ibc/src/mock/context.rs +++ b/crates/ibc/src/mock/context.rs @@ -15,9 +15,11 @@ use ibc_proto::protobuf::Protobuf; use parking_lot::Mutex; use tendermint_testgen::Validator as TestgenValidator; use tracing::debug; +use typed_builder::TypedBuilder; use super::client_state::{MOCK_CLIENT_STATE_TYPE_URL, MOCK_CLIENT_TYPE}; use super::consensus_state::MOCK_CONSENSUS_STATE_TYPE_URL; +use crate::clients::ics07_tendermint::client_state::test_util::ClientStateConfig as TmClientStateConfig; use crate::clients::ics07_tendermint::client_state::{ ClientState as TmClientState, TENDERMINT_CLIENT_STATE_TYPE_URL, }; @@ -217,6 +219,135 @@ pub struct MockContext { pub logs: Vec, } +#[derive(Debug, TypedBuilder)] +#[builder(build_method(into = MockContext))] +pub struct MockContextConfig { + #[builder(default = HostType::Mock)] + host_type: HostType, + + host_id: ChainId, + + #[builder(default = Duration::from_secs(DEFAULT_BLOCK_TIME_SECS))] + block_time: Duration, + + // may panic if validator_set_history size is less than max_history_size + 1 + #[builder(default = 5)] + max_history_size: u64, + + #[builder(default, setter(strip_option))] + validator_set_history: Option>>, + + latest_height: Height, + + #[builder(default = Timestamp::now())] + latest_timestamp: Timestamp, +} + +impl From for MockContext { + fn from(params: MockContextConfig) -> Self { + assert_ne!( + params.max_history_size, 0, + "The chain must have a non-zero max_history_size" + ); + + assert_ne!( + params.latest_height.revision_height(), + 0, + "The chain must have a non-zero revision_height" + ); + + // Compute the number of blocks to store. + let n = min( + params.max_history_size, + params.latest_height.revision_height(), + ); + + assert_eq!( + params.host_id.revision_number(), + params.latest_height.revision_number(), + "The version in the chain identifier must match the version in the latest height" + ); + + let next_block_timestamp = params + .latest_timestamp + .add(params.block_time) + .expect("Never fails"); + + let history = if let Some(validator_set_history) = params.validator_set_history { + (0..n) + .rev() + .map(|i| { + // generate blocks with timestamps -> N, N - BT, N - 2BT, ... + // where N = now(), BT = block_time + HostBlock::generate_block_with_validators( + params.host_id.clone(), + params.host_type, + params + .latest_height + .sub(i) + .expect("Never fails") + .revision_height(), + next_block_timestamp + .sub(params.block_time * ((i + 1) as u32)) + .expect("Never fails"), + &validator_set_history[(n - i) as usize - 1], + &validator_set_history[(n - i) as usize], + ) + }) + .collect() + } else { + (0..n) + .rev() + .map(|i| { + // generate blocks with timestamps -> N, N - BT, N - 2BT, ... + // where N = now(), BT = block_time + HostBlock::generate_block( + params.host_id.clone(), + params.host_type, + params + .latest_height + .sub(i) + .expect("Never fails") + .revision_height(), + next_block_timestamp + .sub(params.block_time * ((i + 1) as u32)) + .expect("Never fails"), + ) + }) + .collect() + }; + + MockContext { + host_chain_type: params.host_type, + host_chain_id: params.host_id.clone(), + max_history_size: params.max_history_size, + history, + block_time: params.block_time, + ibc_store: Arc::new(Mutex::new(MockIbcStore::default())), + events: Vec::new(), + logs: Vec::new(), + } + } +} + +#[derive(Debug, TypedBuilder)] +pub struct MockClientConfig { + client_chain_id: ChainId, + client_id: ClientId, + #[builder(default = mock_client_type())] + client_type: ClientType, + client_state_height: Height, + #[builder(default)] + consensus_state_heights: Vec, + #[builder(default = Timestamp::now())] + latest_timestamp: Timestamp, + + #[builder(default = Duration::from_secs(64000))] + pub trusting_period: Duration, + #[builder(default = Duration::from_millis(3000))] + max_clock_drift: Duration, +} + /// Returns a MockContext with bare minimum initialization: no clients, no connections and no channels are /// present, and the chain has Height(5). This should be used sparingly, mostly for testing the /// creation of new domain objects. @@ -324,7 +455,7 @@ impl MockContext { validator_history: &[Vec], latest_height: Height, ) -> Self { - let max_history_size = validator_history.len() - 1; + let max_history_size = validator_history.len() as u64 - 1; assert_ne!( max_history_size, 0, @@ -338,7 +469,7 @@ impl MockContext { ); assert!( - max_history_size as u64 <= latest_height.revision_height(), + max_history_size <= latest_height.revision_height(), "The number of blocks must be greater than the number of validator set histories" ); @@ -359,17 +490,12 @@ impl MockContext { HostBlock::generate_block_with_validators( host_id.clone(), host_type, - latest_height - .sub(i as u64) - .expect("Never fails") - .revision_height(), + latest_height.sub(i).expect("Never fails").revision_height(), next_block_timestamp - .sub(Duration::from_secs( - DEFAULT_BLOCK_TIME_SECS * (i as u64 + 1), - )) + .sub(Duration::from_secs(DEFAULT_BLOCK_TIME_SECS * (i + 1))) .expect("Never fails"), - &validator_history[i], - &validator_history[i + 1], + &validator_history[(max_history_size - i) as usize - 1], + &validator_history[(max_history_size - i) as usize], ) }) .collect(); @@ -377,7 +503,7 @@ impl MockContext { MockContext { host_chain_type: host_type, host_chain_id: host_id.clone(), - max_history_size: max_history_size as u64, + max_history_size, history, block_time, ibc_store: Arc::new(Mutex::new(MockIbcStore::default())), @@ -554,6 +680,82 @@ impl MockContext { self } + pub fn with_client_config(self, client: MockClientConfig) -> Self { + let cs_heights = if client.consensus_state_heights.is_empty() { + vec![client.client_state_height] + } else { + client.consensus_state_heights + }; + + let (client_state, consensus_states) = match client.client_type.as_str() { + MOCK_CLIENT_TYPE => { + let blocks: Vec<_> = cs_heights + .into_iter() + .map(|cs_height| (cs_height, MockHeader::new(cs_height))) + .collect(); + + let client_state = MockClientState::new(blocks.last().expect("never fails").1); + + let cs_states = blocks + .into_iter() + .map(|(height, block)| (height, MockConsensusState::new(block).into())) + .collect(); + + (client_state.into(), cs_states) + } + TENDERMINT_CLIENT_TYPE => { + let n_blocks = cs_heights.len(); + let blocks: Vec<_> = cs_heights + .into_iter() + .enumerate() + .map(|(i, cs_height)| { + ( + cs_height, + HostBlock::generate_tm_block( + client.client_chain_id.clone(), + cs_height.revision_height(), + client + .latest_timestamp + .sub(self.block_time * ((n_blocks - 1 - i) as u32)) + .expect("never fails"), + ), + ) + }) + .collect(); + + let client_state: TmClientState = TmClientStateConfig::builder() + .chain_id(client.client_chain_id) + .latest_height(client.client_state_height) + .trusting_period(client.trusting_period) + .max_clock_drift(client.max_clock_drift) + .build() + .try_into() + .expect("never fails"); + + client_state.validate().expect("never fails"); + + let cs_states = blocks + .into_iter() + .map(|(height, block)| (height, block.into())) + .collect(); + + (client_state.into(), cs_states) + } + _ => panic!("unknown client type"), + }; + + let client_record = MockClientRecord { + client_state: Some(client_state), + consensus_states, + }; + + self.ibc_store + .lock() + .clients + .insert(client.client_id.clone(), client_record); + self + } + /// Associates a connection to this context. pub fn with_connection( self, diff --git a/crates/ibc/src/mock/host.rs b/crates/ibc/src/mock/host.rs index 18950335d..30444b478 100644 --- a/crates/ibc/src/mock/host.rs +++ b/crates/ibc/src/mock/host.rs @@ -6,6 +6,7 @@ use ibc_proto::google::protobuf::Any; use ibc_proto::ibc::lightclients::tendermint::v1::Header as RawHeader; use ibc_proto::protobuf::Protobuf as ErasedProtobuf; use tendermint::block::Header as TmHeader; +use tendermint::validator::Set as ValidatorSet; use tendermint_testgen::light_block::TmLightBlock; use tendermint_testgen::{ Generator, Header as TestgenHeader, LightBlock as TestgenLightBlock, @@ -38,6 +39,7 @@ pub enum HostType { #[derive(Clone, Debug, PartialEq, Eq)] pub struct SyntheticTmBlock { pub trusted_height: Height, + pub trusted_next_validators: ValidatorSet, pub light_block: TmLightBlock, } @@ -78,6 +80,15 @@ impl HostBlock { } } + pub fn set_trusted_next_validators_set(&mut self, trusted_next_validators: ValidatorSet) { + match self { + HostBlock::Mock(_) => {} + HostBlock::SyntheticTendermint(light_block) => { + light_block.trusted_next_validators = trusted_next_validators + } + } + } + /// Returns the timestamp of a block. pub fn timestamp(&self) -> Timestamp { match self { @@ -119,18 +130,23 @@ impl HostBlock { timestamp, })), HostType::SyntheticTendermint => { + let light_block = TestgenLightBlock::new_default_with_header( + TestgenHeader::new(validators) + .height(height) + .chain_id(&chain_id.to_string()) + .next_validators(next_validators) + .time(timestamp.into_tm_time().expect("Never fails")), + ) + .validators(validators) + .next_validators(next_validators) + .generate() + .expect("Never fails"); + HostBlock::SyntheticTendermint(Box::new(SyntheticTmBlock { trusted_height: Height::new(chain_id.revision_number(), 1) .expect("Never fails"), - light_block: TestgenLightBlock::new_default_with_header( - TestgenHeader::new(validators) - .height(height) - .chain_id(&chain_id.to_string()) - .next_validators(next_validators) - .time(timestamp.into_tm_time().expect("Never fails")), - ) - .generate() - .expect("Never fails"), + trusted_next_validators: light_block.next_validators.clone(), + light_block, })) } } @@ -141,15 +157,24 @@ impl HostBlock { height: u64, timestamp: Timestamp, ) -> SyntheticTmBlock { - let light_block = TestgenLightBlock::new_default_with_time_and_chain_id( - chain_id.to_string(), - timestamp.into_tm_time().expect("Never fails"), - height, - ) - .generate() - .expect("Never fails"); + let validators = [ + TestgenValidator::new("1").voting_power(50), + TestgenValidator::new("2").voting_power(50), + ]; + + let header = TestgenHeader::new(&validators) + .height(height) + .chain_id(chain_id.as_str()) + .next_validators(&validators) + .time(timestamp.into_tm_time().expect("Never fails")); + + let light_block = TestgenLightBlock::new_default_with_header(header) + .generate() + .expect("Never fails"); + SyntheticTmBlock { trusted_height: Height::new(chain_id.revision_number(), 1).expect("Never fails"), + trusted_next_validators: light_block.next_validators.clone(), light_block, } } @@ -197,6 +222,7 @@ impl From for Any { let SyntheticTmBlock { trusted_height, + trusted_next_validators, light_block, } = light_block; @@ -204,7 +230,7 @@ impl From for Any { signed_header: Some(light_block.signed_header.into()), validator_set: Some(light_block.validators.into()), trusted_height: Some(trusted_height.into()), - trusted_validators: Some(light_block.next_validators.into()), + trusted_validators: Some(trusted_next_validators.into()), } .encode_to_vec() }