use exonum::{
crypto::Hash,
helpers::{Height, ValidatorId},
merkledb::access::AccessExt,
messages::{AnyTx, Verified},
runtime::{
CommonError, ErrorMatch, ExecutionContext, ExecutionError, InstanceId, SnapshotExt,
SUPERVISOR_INSTANCE_ID,
},
};
use exonum_derive::*;
use exonum_rust_runtime::{
spec::{JustFactory, Spec},
DefaultInstance, Service, ServiceFactory as _,
};
use exonum_testkit::{ApiKind, TestKit, TestKitBuilder};
use exonum_supervisor::{
ConfigPropose, Configure, DeployRequest, Schema, Supervisor, SupervisorInterface,
};
pub fn sign_config_propose_transaction(
testkit: &TestKit,
config: ConfigPropose,
initiator_id: ValidatorId,
) -> Verified<AnyTx> {
let keypair = testkit.validator(initiator_id).service_keypair();
keypair.propose_config_change(SUPERVISOR_INSTANCE_ID, config)
}
pub fn sign_config_propose_transaction_by_us(
testkit: &TestKit,
config: ConfigPropose,
) -> Verified<AnyTx> {
let initiator_id = testkit.us().validator_id().unwrap();
sign_config_propose_transaction(&testkit, config, initiator_id)
}
#[derive(Debug, ServiceDispatcher, ServiceFactory)]
#[service_factory(artifact_name = "deployable-test-service", artifact_version = "0.1.0")]
pub struct DeployableService;
impl Service for DeployableService {}
#[derive(Debug, ServiceDispatcher, ServiceFactory)]
#[service_dispatcher(implements(raw = "Configure<Params = String>"))]
#[service_factory(artifact_name = "config-change-test-service")]
pub struct ConfigChangeService;
impl DefaultInstance for ConfigChangeService {
const INSTANCE_ID: InstanceId = 119;
const INSTANCE_NAME: &'static str = "config-change";
}
impl Service for ConfigChangeService {}
impl Configure for ConfigChangeService {
type Params = String;
fn verify_config(
&self,
context: ExecutionContext<'_>,
params: Self::Params,
) -> Result<(), ExecutionError> {
context
.caller()
.as_supervisor()
.ok_or(CommonError::UnauthorizedCaller)?;
match params.as_str() {
"error" => Err(CommonError::malformed_arguments("Error!")),
"panic" => panic!("Aaaa!"),
_ => Ok(()),
}
}
fn apply_config(
&self,
context: ExecutionContext<'_>,
params: Self::Params,
) -> Result<(), ExecutionError> {
context
.caller()
.as_supervisor()
.ok_or(CommonError::UnauthorizedCaller)?;
context
.service_data()
.get_entry("params")
.set(params.clone());
match params.as_str() {
"apply_error" => Err(CommonError::malformed_arguments("Error!")),
"apply_panic" => panic!("Aaaa!"),
_ => Ok(()),
}
}
}
fn assert_config_change_is_applied(testkit: &TestKit) {
let snapshot = testkit.snapshot();
let schema: Schema<_> = snapshot.service_schema(Supervisor::NAME).unwrap();
assert!(!schema.pending_proposal.exists());
}
#[test]
fn change_consensus_config_with_one_confirmation() {
let initial_validator_count = 4;
let expected_new_validator_number = initial_validator_count;
let mut testkit = TestKitBuilder::auditor()
.with_validators(initial_validator_count)
.with(Supervisor::simple())
.build();
let cfg_change_height = Height(5);
let new_consensus_config = {
let mut cfg = testkit.consensus_config();
cfg.validator_keys.push(testkit.us().public_keys());
cfg.validator_keys
.push(testkit.network_mut().add_node().public_keys());
cfg
};
let initiator_id = testkit.network().validators()[0].validator_id().unwrap();
let config_propose =
ConfigPropose::new(0, cfg_change_height).consensus_config(new_consensus_config.clone());
testkit.create_block_with_transaction(sign_config_propose_transaction(
&testkit,
config_propose,
initiator_id,
));
testkit.create_blocks_until(cfg_change_height.previous());
assert_eq!(testkit.network().us().validator_id(), None);
testkit.create_block();
assert_config_change_is_applied(&testkit);
assert_eq!(
testkit.network().us().validator_id(),
Some(ValidatorId(expected_new_validator_number))
);
assert_eq!(
&testkit.network().validators()[expected_new_validator_number as usize],
testkit.network().us()
);
assert_eq!(testkit.consensus_config(), new_consensus_config);
}
#[test]
fn service_config_change() {
let mut testkit = TestKitBuilder::validator()
.with_validators(2)
.with(Supervisor::simple())
.with(Spec::new(ConfigChangeService).with_default_instance())
.build();
let cfg_change_height = Height(5);
let params = "I am a new parameter".to_owned();
let config_propose = ConfigPropose::new(0, cfg_change_height)
.service_config(ConfigChangeService::INSTANCE_ID, params.clone());
testkit.create_block_with_transaction(sign_config_propose_transaction_by_us(
&testkit,
config_propose,
));
testkit.create_blocks_until(cfg_change_height);
let actual_params: String = testkit
.snapshot()
.for_service(ConfigChangeService::INSTANCE_NAME)
.unwrap()
.get_entry("params")
.get()
.unwrap();
assert_eq!(actual_params, params);
}
#[test]
fn incorrect_actual_from_field() {
let mut testkit = TestKitBuilder::validator()
.with_validators(2)
.with(Supervisor::simple())
.with(Spec::new(ConfigChangeService).with_default_instance())
.build();
let cfg_change_height = Height(5);
let params = "I am a new parameter".to_owned();
testkit.create_blocks_until(cfg_change_height);
let config_propose = ConfigPropose::new(0, cfg_change_height)
.service_config(ConfigChangeService::INSTANCE_ID, params);
testkit
.create_block_with_transaction(sign_config_propose_transaction_by_us(
&testkit,
config_propose,
))
.transactions[0]
.status()
.unwrap_err();
}
#[test]
fn discard_config_propose_from_auditor() {
let mut testkit = TestKitBuilder::auditor()
.with_validators(2)
.with(Supervisor::simple())
.build();
let cfg_change_height = Height(5);
let old_consensus_config = testkit.consensus_config();
let new_consensus_config = {
let mut cfg = testkit.consensus_config();
cfg.validator_keys.push(testkit.us().public_keys());
cfg
};
let old_validators = testkit.network().validators();
let propose = ConfigPropose::new(0, cfg_change_height).consensus_config(new_consensus_config);
let keys = testkit.us().service_keypair();
let propose = keys.propose_config_change(SUPERVISOR_INSTANCE_ID, propose);
let block = testkit.create_block_with_transaction(propose);
let expected_err =
ErrorMatch::from_fail(&CommonError::UnauthorizedCaller).for_service(SUPERVISOR_INSTANCE_ID);
assert_eq!(*block[0].status().unwrap_err(), expected_err);
testkit.create_blocks_until(cfg_change_height);
let new_validators = testkit.network().validators();
assert_eq!(testkit.consensus_config(), old_consensus_config);
assert_eq!(testkit.network().us().validator_id(), None);
assert_eq!(old_validators, new_validators);
}
#[tokio::test]
async fn test_send_proposal_with_api() {
let mut testkit = TestKitBuilder::validator()
.with_validators(2)
.with(Supervisor::simple())
.build();
let old_validators = testkit.network().validators();
let cfg_change_height = Height(5);
let new_consensus_config = {
let mut cfg = testkit.consensus_config();
cfg.validator_keys.remove(0);
cfg
};
let config_propose =
ConfigPropose::new(0, cfg_change_height).consensus_config(new_consensus_config.clone());
let hash: Hash = testkit
.api()
.private(ApiKind::Service("supervisor"))
.query(&config_propose)
.post("propose-config")
.await
.unwrap();
let block = testkit.create_block();
block[hash].status().unwrap();
let snapshot = testkit.snapshot();
let schema: Schema<_> = snapshot.service_schema(Supervisor::NAME).unwrap();
assert_eq!(
schema.pending_proposal.get().unwrap().config_propose,
config_propose
);
testkit.create_blocks_until(cfg_change_height);
assert_config_change_is_applied(&testkit);
assert_eq!(testkit.consensus_config(), new_consensus_config);
assert_eq!(testkit.network().us().validator_id(), None);
assert_ne!(old_validators, testkit.network().validators());
}
#[tokio::test]
async fn deploy_service() {
let mut testkit = TestKitBuilder::validator()
.with(Supervisor::simple())
.with(JustFactory::new(DeployableService))
.build();
let deadline_height = Height(5);
let artifact = DeployableService.artifact_id();
let deploy_request = DeployRequest::new(artifact.clone(), deadline_height);
let hash: Hash = testkit
.api()
.private(ApiKind::Service("supervisor"))
.query(&deploy_request)
.post("deploy-artifact")
.await
.unwrap();
let block = testkit.create_block();
block[hash].status().unwrap();
testkit.create_blocks_until(deadline_height);
let snapshot = testkit.snapshot();
assert!(snapshot
.for_dispatcher()
.service_artifacts()
.contains(&artifact));
}
#[tokio::test]
async fn actual_from_is_zero() {
let initial_validator_count = 4;
let expected_new_validator_number = initial_validator_count;
let mut testkit = TestKitBuilder::auditor()
.with_validators(initial_validator_count)
.with(Supervisor::simple())
.build();
let cfg_change_height = Height(0);
let new_consensus_config = {
let mut cfg = testkit.consensus_config();
cfg.validator_keys.push(testkit.us().public_keys());
cfg.validator_keys
.push(testkit.network_mut().add_node().public_keys());
cfg
};
let initiator_id = testkit.network().validators()[0].validator_id().unwrap();
let config_propose =
ConfigPropose::new(0, cfg_change_height).consensus_config(new_consensus_config.clone());
testkit.create_block_with_transaction(sign_config_propose_transaction(
&testkit,
config_propose,
initiator_id,
));
testkit.create_block();
assert_config_change_is_applied(&testkit);
assert_eq!(
testkit.network().us().validator_id(),
Some(ValidatorId(expected_new_validator_number))
);
assert_eq!(
&testkit.network().validators()[expected_new_validator_number as usize],
testkit.network().us()
);
assert_eq!(testkit.consensus_config(), new_consensus_config);
}