// Copyright 2020 The Exonum Team // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use exonum::{ blockchain::CallInBlock, crypto::{self, KeyPair}, helpers::{Height, ValidatorId}, merkledb::ObjectHash, runtime::{CommonError, ErrorMatch, InstanceId, SnapshotExt, SUPERVISOR_INSTANCE_ID}, }; use exonum_testkit::{Spec, TestKitBuilder}; use crate::{utils::*, IncService as ConfigChangeService}; use exonum_supervisor::{ CommonError as SupervisorCommonError, ConfigVote, ConfigurationError, Supervisor, SupervisorInterface, }; #[test] fn test_multiple_consensus_change_proposes() { let mut testkit = testkit_with_supervisor(1); let config_proposal = ConfigProposeBuilder::new(CFG_CHANGE_HEIGHT) .extend_consensus_config_propose(consensus_config_propose_first_variant(&testkit)) .extend_consensus_config_propose(consensus_config_propose_second_variant(&testkit)) .build(); let signed_proposal = sign_config_propose_transaction(&testkit, config_proposal, ValidatorId(0)); let block = testkit.create_block_with_transaction(signed_proposal); let err = block.transactions[0].status().unwrap_err(); assert_eq!( *err, ErrorMatch::from_fail(&ConfigurationError::MalformedConfigPropose) .for_service(SUPERVISOR_INSTANCE_ID) .with_any_description() ); assert_eq!(config_propose_entry(&testkit), None); } #[test] fn test_deadline_config_exceeded() { let mut testkit = testkit_with_supervisor(4); let initiator_id = testkit.network().us().validator_id().unwrap(); let consensus_config = testkit.consensus_config(); let new_consensus_config = consensus_config_propose_first_variant(&testkit); let config_proposal = ConfigProposeBuilder::new(CFG_CHANGE_HEIGHT) .extend_consensus_config_propose(new_consensus_config) .build(); testkit .create_block_with_transaction(sign_config_propose_transaction( &testkit, config_proposal, initiator_id, )) .transactions[0] .status() .expect("Transaction with change propose discarded."); testkit.create_blocks_until(CFG_CHANGE_HEIGHT.next()); assert_eq!(config_propose_entry(&testkit), None); assert_eq!(testkit.consensus_config(), consensus_config); } #[test] fn test_sent_new_config_after_expired_one() { let mut testkit = testkit_with_supervisor(4); let initiator_id = testkit.network().us().validator_id().unwrap(); let first_consensus_config = consensus_config_propose_first_variant(&testkit); let config_proposal = ConfigProposeBuilder::new(CFG_CHANGE_HEIGHT) .configuration_number(0) .extend_consensus_config_propose(first_consensus_config) .build(); testkit .create_block_with_transaction(sign_config_propose_transaction( &testkit, config_proposal, initiator_id, )) .transactions[0] .status() .expect("Transaction with change propose discarded."); testkit.create_blocks_until(CFG_CHANGE_HEIGHT.next()); assert_eq!(config_propose_entry(&testkit), None); // Send config one more time and vote for it let cfg_change_height = Height(7); let second_consensus_config = consensus_config_propose_second_variant(&testkit); let config_proposal = ConfigProposeBuilder::new(cfg_change_height) .configuration_number(1) .extend_consensus_config_propose(second_consensus_config.clone()) .build(); let proposal_hash = config_proposal.object_hash(); testkit .create_block_with_transaction(sign_config_propose_transaction( &testkit, config_proposal, initiator_id, )) .transactions[0] .status() .expect("Transaction with change propose discarded."); let signed_txs = build_confirmation_transactions(&testkit, proposal_hash, initiator_id); testkit .create_block_with_transactions(signed_txs) .transactions[0] .status() .expect("Transaction with confirmations discarded."); testkit.create_blocks_until(cfg_change_height); assert_eq!(config_propose_entry(&testkit), None); assert_eq!(testkit.consensus_config(), second_consensus_config); } #[test] fn test_discard_config_with_not_enough_confirms() { let mut testkit = testkit_with_supervisor(4); let initiator_id = testkit.network().us().validator_id().unwrap(); testkit.create_block(); let base_consensus_config = testkit.consensus_config(); let cfg_change_height = Height(3); let consensus_config = consensus_config_propose_first_variant(&testkit); let config_proposal = ConfigProposeBuilder::new(cfg_change_height) .extend_consensus_config_propose(consensus_config) .build(); let proposal_hash = config_proposal.object_hash(); testkit .create_block_with_transaction(sign_config_propose_transaction( &testkit, config_proposal, initiator_id, )) .transactions[0] .status() .expect("Transaction with change propose discarded."); // Sign confirmation transaction by second validator let keypair = testkit.network().validators()[1].service_keypair(); let signed_confirm = keypair.confirm_config_change(SUPERVISOR_INSTANCE_ID, ConfigVote::new(proposal_hash)); testkit .create_block_with_transaction(signed_confirm) .transactions[0] .status() .expect("Transaction with confirmations discarded."); testkit.create_blocks_until(cfg_change_height.next()); assert_eq!(config_propose_entry(&testkit), None); assert_eq!(testkit.consensus_config(), base_consensus_config); } #[test] fn test_apply_config_by_min_required_majority() { let mut testkit = testkit_with_supervisor(4); let initiator_id = testkit.network().us().validator_id().unwrap(); let cfg_change_height = Height(3); let consensus_config = consensus_config_propose_first_variant(&testkit); let config_proposal = ConfigProposeBuilder::new(cfg_change_height) .extend_consensus_config_propose(consensus_config.clone()) .build(); let proposal_hash = config_proposal.object_hash(); testkit .create_block_with_transaction(sign_config_propose_transaction( &testkit, config_proposal, initiator_id, )) .transactions[0] .status() .expect("Transaction with change propose discarded."); let confirm = ConfigVote::new(proposal_hash); // Sign and send confirmation transaction by second validator let keys = testkit.network().validators()[1].service_keypair(); let tx = keys.confirm_config_change(SUPERVISOR_INSTANCE_ID, confirm.clone()); testkit.create_block_with_transaction(tx).transactions[0] .status() .expect("Transaction with confirmations discarded."); // Sign confirmation transaction by third validator let keys = testkit.network().validators()[2].service_keypair(); let tx = keys.confirm_config_change(SUPERVISOR_INSTANCE_ID, confirm); testkit.create_block_with_transaction(tx).transactions[0] .status() .expect("Transaction with confirmation discarded."); assert_eq!(config_propose_entry(&testkit), None); assert_eq!(testkit.consensus_config(), consensus_config); } #[test] fn test_send_confirmation_by_initiator() { let mut testkit = testkit_with_supervisor(4); let initiator_id = testkit.network().us().validator_id().unwrap(); let consensus_config = consensus_config_propose_first_variant(&testkit); let config_proposal = ConfigProposeBuilder::new(CFG_CHANGE_HEIGHT) .extend_consensus_config_propose(consensus_config) .build(); let proposal_hash = config_proposal.object_hash(); testkit .create_block_with_transaction(sign_config_propose_transaction( &testkit, config_proposal, initiator_id, )) .transactions[0] .status() .expect("Transaction with change propose discarded."); // Try to send confirmation transaction by the initiator let keys = testkit.network().us().service_keypair(); let signed_confirm = keys.confirm_config_change(SUPERVISOR_INSTANCE_ID, ConfigVote::new(proposal_hash)); let block = testkit.create_block_with_transaction(signed_confirm); let err = block.transactions[0].status().unwrap_err(); assert_eq!( *err, ErrorMatch::from_fail(&ConfigurationError::AttemptToVoteTwice) .for_service(SUPERVISOR_INSTANCE_ID) ); } #[test] fn test_propose_config_change_by_incorrect_validator() { let mut testkit = testkit_with_supervisor(1); let consensus_config = consensus_config_propose_first_variant(&testkit); let change = ConfigProposeBuilder::new(CFG_CHANGE_HEIGHT) .extend_consensus_config_propose(consensus_config) .build(); let keys = KeyPair::random(); let signed_confirm = keys.propose_config_change(SUPERVISOR_INSTANCE_ID, change); let block = testkit.create_block_with_transaction(signed_confirm); let err = block.transactions[0].status().unwrap_err(); assert_eq!( *err, ErrorMatch::from_fail(&CommonError::UnauthorizedCaller).for_service(SUPERVISOR_INSTANCE_ID) ); } #[test] fn test_confirm_config_by_incorrect_validator() { let mut testkit = testkit_with_supervisor(1); let initiator_id = testkit.network().us().validator_id().unwrap(); let consensus_config = consensus_config_propose_first_variant(&testkit); let config_proposal = ConfigProposeBuilder::new(CFG_CHANGE_HEIGHT) .extend_consensus_config_propose(consensus_config) .build(); let proposal_hash = config_proposal.object_hash(); testkit .create_block_with_transaction(sign_config_propose_transaction( &testkit, config_proposal, initiator_id, )) .transactions[0] .status() .expect("Transaction with change propose discarded."); let keys = KeyPair::random(); let signed_confirm = keys.confirm_config_change(SUPERVISOR_INSTANCE_ID, ConfigVote::new(proposal_hash)); let block = testkit.create_block_with_transaction(signed_confirm); let err = block.transactions[0].status().unwrap_err(); assert_eq!( *err, ErrorMatch::from_fail(&CommonError::UnauthorizedCaller).for_service(SUPERVISOR_INSTANCE_ID) ); } #[test] fn test_try_confirm_non_existent_proposal() { let mut testkit = testkit_with_supervisor(4); let initiator_id = testkit.network().us().validator_id().unwrap(); let consensus_config = consensus_config_propose_first_variant(&testkit); let config_proposal = ConfigProposeBuilder::new(CFG_CHANGE_HEIGHT) .extend_consensus_config_propose(consensus_config) .build(); testkit .create_block_with_transaction(sign_config_propose_transaction( &testkit, config_proposal, initiator_id, )) .transactions[0] .status() .expect("Transaction with change propose discarded."); let wrong_hash = crypto::hash(&[0]); let signed_confirm = build_confirmation_transactions(&testkit, wrong_hash, initiator_id); let block = testkit.create_block_with_transactions(signed_confirm); let err = block.transactions[0].status().unwrap_err(); assert_eq!( *err, ErrorMatch::from_fail(&ConfigurationError::ConfigProposeNotRegistered) .for_service(SUPERVISOR_INSTANCE_ID) .with_description_containing("Mismatch between the hash of the saved proposal") ); } #[test] fn test_service_config_change() { let mut testkit = testkit_with_supervisor_and_service(4); let initiator_id = testkit.network().us().validator_id().unwrap(); let params = "I am a new parameter".to_owned(); let propose = ConfigProposeBuilder::new(CFG_CHANGE_HEIGHT) .extend_service_config_propose(params.clone()) .build(); let proposal_hash = propose.object_hash(); testkit .create_block_with_transaction(sign_config_propose_transaction( &testkit, propose, initiator_id, )) .transactions[0] .status() .expect("Transaction with change propose discarded."); let signed_txs = build_confirmation_transactions(&testkit, proposal_hash, initiator_id); testkit .create_block_with_transactions(signed_txs) .transactions[0] .status() .expect("Transaction with confirmations discarded."); testkit.create_blocks_until(CFG_CHANGE_HEIGHT); assert_eq!(config_propose_entry(&testkit), None); check_service_actual_param(&testkit, Some(params)); } #[test] fn test_discard_errored_service_config_change() { let mut testkit = testkit_with_supervisor_and_service(4); let new_consensus_config = consensus_config_propose_first_variant(&testkit); let propose = ConfigProposeBuilder::new(CFG_CHANGE_HEIGHT) .extend_service_config_propose("error".to_string()) .extend_consensus_config_propose(new_consensus_config) .build(); let signed_proposal = sign_config_propose_transaction(&testkit, propose, ValidatorId(0)); let block = testkit.create_block_with_transaction(signed_proposal); let err = block.transactions[0].status().unwrap_err(); assert!(err .description() .contains("IncService: Configure error request")); assert_eq!(config_propose_entry(&testkit), None); } #[test] fn test_discard_panicked_service_config_change() { let mut testkit = testkit_with_supervisor_and_service(4); let params = "I am a discarded parameter".to_owned(); let new_consensus_config = consensus_config_propose_first_variant(&testkit); let propose = ConfigProposeBuilder::new(CFG_CHANGE_HEIGHT) .extend_service_config_propose(params) .extend_service_config_propose("panic".to_string()) .extend_consensus_config_propose(new_consensus_config) .build(); let signed_proposal = sign_config_propose_transaction(&testkit, propose, ValidatorId(0)); let block = testkit.create_block_with_transaction(signed_proposal); let err = block.transactions[0].status().unwrap_err(); assert_eq!( *err, ErrorMatch::from_fail(&ConfigurationError::MalformedConfigPropose) .for_service(SUPERVISOR_INSTANCE_ID) .with_any_description() ); assert_eq!(config_propose_entry(&testkit), None); } #[test] fn test_incorrect_actual_from_field() { let mut testkit = testkit_with_supervisor_and_service(1); let params = "I am a new parameter".to_owned(); let propose = ConfigProposeBuilder::new(CFG_CHANGE_HEIGHT) .extend_service_config_propose(params) .build(); testkit.create_blocks_until(CFG_CHANGE_HEIGHT); let signed_proposal = sign_config_propose_transaction(&testkit, propose, ValidatorId(0)); let block = testkit.create_block_with_transaction(signed_proposal); let err = block.transactions[0].status().unwrap_err(); let expected_msg = "Actual height for config proposal (3) is in the past (current height: 3)"; assert_eq!( *err, ErrorMatch::from_fail(&SupervisorCommonError::ActualFromIsPast) .with_description_containing(expected_msg) ); } #[test] fn test_another_configuration_change_proposal() { let mut testkit = testkit_with_supervisor_and_service(4); let initiator_id = testkit.network().us().validator_id().unwrap(); let params = "I am a new parameter".to_owned(); let cfg_change_height = Height(4); let propose = ConfigProposeBuilder::new(cfg_change_height) .configuration_number(0) .extend_service_config_propose(params.clone()) .build(); let proposal_hash = propose.object_hash(); testkit .create_block_with_transaction(sign_config_propose_transaction( &testkit, propose, initiator_id, )) .transactions[0] .status() .expect("Transaction with change propose discarded."); // Try to commit second config change propose. let second_propose = ConfigProposeBuilder::new(cfg_change_height) .configuration_number(1) .extend_service_config_propose("I am an overridden parameter".to_string()) .build(); let signed_proposal = sign_config_propose_transaction(&testkit, second_propose, initiator_id); let block = testkit.create_block_with_transaction(signed_proposal); let err = block.transactions[0].status().unwrap_err(); assert_eq!( *err, ErrorMatch::from_fail(&ConfigurationError::ConfigProposeExists) .for_service(SUPERVISOR_INSTANCE_ID) ); let signed_txs = build_confirmation_transactions(&testkit, proposal_hash, initiator_id); testkit .create_block_with_transactions(signed_txs) .transactions[0] .status() .expect("Transaction with confirmations discarded."); testkit.create_blocks_until(cfg_change_height); assert_eq!(config_propose_entry(&testkit), None); check_service_actual_param(&testkit, Some(params)); } #[test] fn test_service_config_discard_fake_supervisor() { const FAKE_SUPERVISOR_ID: InstanceId = 5; let keypair = KeyPair::random(); let mut testkit = TestKitBuilder::validator() .with_validators(1) .with(Spec::new(Supervisor).with_instance( FAKE_SUPERVISOR_ID, "fake-supervisor", Supervisor::decentralized_config(), )) .with(Spec::new(ConfigChangeService).with_default_instance()) .build(); let params = "I am a new parameter".to_owned(); let propose = ConfigProposeBuilder::new(CFG_CHANGE_HEIGHT) .extend_service_config_propose(params) .build(); let tx = keypair.propose_config_change(FAKE_SUPERVISOR_ID, propose); let block = testkit.create_block_with_transaction(tx); let err = block.transactions[0].status().unwrap_err(); assert_eq!( *err, ErrorMatch::from_fail(&CommonError::UnauthorizedCaller).for_service(FAKE_SUPERVISOR_ID) ); } #[test] fn test_test_configuration_and_rollbacks() { let mut testkit = testkit_with_supervisor(4); testkit.create_blocks_until(CFG_CHANGE_HEIGHT); let cfg_change_height = Height(5); let old_config = testkit.consensus_config(); testkit.checkpoint(); let new_config = consensus_config_propose_first_variant(&testkit); let propose = ConfigProposeBuilder::new(cfg_change_height) .extend_consensus_config_propose(new_config.clone()) .build(); let proposal_hash = propose.object_hash(); testkit .create_block_with_transaction(sign_config_propose_transaction( &testkit, propose, ValidatorId(0), )) .transactions[0] .status() .expect("Transaction with change propose discarded."); let signed_txs = build_confirmation_transactions(&testkit, proposal_hash, ValidatorId(0)); testkit .create_block_with_transactions(signed_txs) .transactions[0] .status() .expect("Transaction with confirmations discarded."); testkit.create_blocks_until(cfg_change_height); assert_eq!(config_propose_entry(&testkit), None); assert_eq!(testkit.consensus_config(), new_config); testkit.checkpoint(); testkit.create_block(); testkit.rollback(); assert_eq!(testkit.consensus_config(), new_config); assert_eq!(config_propose_entry(&testkit), None); testkit.rollback(); // As rollback is behind the time a proposal entered the blockchain, // the proposal is effectively forgotten. testkit.create_blocks_until(cfg_change_height); assert_eq!(testkit.consensus_config(), old_config); assert_eq!(config_propose_entry(&testkit), None); } #[test] fn test_service_config_discard_single_apply_error() { let mut testkit = testkit_with_supervisor_and_service(1); let params = "apply_error".to_owned(); let propose = ConfigProposeBuilder::new(CFG_CHANGE_HEIGHT) .extend_service_config_propose(params) .build(); testkit .create_block_with_transaction(sign_config_propose_transaction( &testkit, propose, ValidatorId(0), )) .transactions[0] .status() .expect("Transaction with change propose discarded."); testkit.create_blocks_until(CFG_CHANGE_HEIGHT); let snapshot = testkit.snapshot(); let err = snapshot .for_core() .call_records(testkit.height()) .unwrap() .get(CallInBlock::after_transactions(SUPERVISOR_INSTANCE_ID)) .unwrap_err(); assert!(err.description().contains("IncService: Configure error")); // Create one more block for supervisor to remove failed config. testkit.create_block(); assert_eq!(config_propose_entry(&testkit), None); check_service_actual_param(&testkit, None); } #[test] fn test_service_config_discard_single_apply_panic() { let mut testkit = testkit_with_supervisor_and_service(1); let initiator_id = testkit.network().us().validator_id().unwrap(); let params = "apply_panic".to_owned(); let propose = ConfigProposeBuilder::new(CFG_CHANGE_HEIGHT) .extend_service_config_propose(params) .build(); testkit .create_block_with_transaction(sign_config_propose_transaction( &testkit, propose, initiator_id, )) .transactions[0] .status() .expect("Transaction with change propose discarded."); testkit.create_blocks_until(CFG_CHANGE_HEIGHT); let snapshot = testkit.snapshot(); let err = snapshot .for_core() .call_records(testkit.height()) .unwrap() .get(CallInBlock::after_transactions(SUPERVISOR_INSTANCE_ID)) .unwrap_err(); assert!(err.description().contains("Configure panic")); // Create one more block for supervisor to remove failed config. testkit.create_block(); assert_eq!(config_propose_entry(&testkit), None); check_service_actual_param(&testkit, None); } // This test checks that we can send a new config proposal right after // the failure of the previous config applying. #[test] fn test_send_config_right_after_error() { let mut testkit = testkit_with_supervisor_and_service(1); let initiator_id = testkit.network().us().validator_id().unwrap(); let propose = ConfigProposeBuilder::new(CFG_CHANGE_HEIGHT) .extend_service_config_propose("apply_panic".into()) .build(); testkit .create_block_with_transaction(sign_config_propose_transaction( &testkit, propose, initiator_id, )) .transactions[0] .status() .expect("Transaction with change propose discarded."); testkit.create_blocks_until(CFG_CHANGE_HEIGHT); // Send a new config right after the failure. let new_height = Height(100); // We don't really care about height, we're checking the tx approval only. let propose = ConfigProposeBuilder::new(new_height) .configuration_number(1) .extend_service_config_propose("good_config".into()) .build(); testkit .create_block_with_transaction(sign_config_propose_transaction( &testkit, propose, initiator_id, )) .transactions[0] .status() .expect("Transaction with change propose discarded."); } #[test] fn test_services_config_apply_multiple_configs() { let mut testkit = testkit_with_supervisor_and_2_services(4); let initiator_id = testkit.network().us().validator_id().unwrap(); let params = "I am a new parameter".to_owned(); let propose = ConfigProposeBuilder::new(CFG_CHANGE_HEIGHT) .extend_service_config_propose(params.clone()) .extend_second_service_config_propose(params.clone()) .build(); let proposal_hash = propose.object_hash(); testkit .create_block_with_transaction(sign_config_propose_transaction( &testkit, propose, initiator_id, )) .transactions[0] .status() .expect("Transaction with change propose discarded."); let signed_txs = build_confirmation_transactions(&testkit, proposal_hash, initiator_id); testkit .create_block_with_transactions(signed_txs) .transactions[0] .status() .expect("Transaction with confirmations discarded."); testkit.create_blocks_until(CFG_CHANGE_HEIGHT); check_service_actual_param(&testkit, Some(params.clone())); check_second_service_actual_param(&testkit, Some(params)); } #[test] fn test_services_config_discard_multiple_configs() { let mut testkit = testkit_with_supervisor_and_2_services(1); let initiator_id = testkit.network().us().validator_id().unwrap(); let params = "I am a new parameter".to_owned(); let propose = ConfigProposeBuilder::new(CFG_CHANGE_HEIGHT) .extend_service_config_propose(params.clone()) .extend_second_service_config_propose(params) .extend_second_service_config_propose("I am a extra proposal".to_owned()) .build(); let signed_proposal = sign_config_propose_transaction(&testkit, propose, initiator_id); let block = testkit.create_block_with_transaction(signed_proposal); let err = block.transactions[0].status().unwrap_err(); assert_eq!( *err, ErrorMatch::from_fail(&ConfigurationError::MalformedConfigPropose) .for_service(SUPERVISOR_INSTANCE_ID) .with_any_description() ); assert_eq!(config_propose_entry(&testkit), None); } #[test] fn test_several_service_config_changes() { let mut testkit = testkit_with_supervisor_and_service(4); let initiator_id = testkit.network().us().validator_id().unwrap(); for i in 1..5 { let cfg_change_height = Height(2 * i); let params = format!("Change {}", i); let propose = ConfigProposeBuilder::new(cfg_change_height) .configuration_number(i - 1) .extend_service_config_propose(params.clone()) .build(); let proposal_hash = propose.object_hash(); testkit .create_block_with_transaction(sign_config_propose_transaction( &testkit, propose, initiator_id, )) .transactions[0] .status() .expect("Transaction with change propose discarded."); let signed_txs = build_confirmation_transactions(&testkit, proposal_hash, initiator_id); testkit.create_block_with_transactions(signed_txs)[0] .status() .unwrap(); testkit.create_blocks_until(cfg_change_height); assert_eq!(config_propose_entry(&testkit), None); } check_service_actual_param(&testkit, Some("Change 4".to_string())); } /// Checks that config with incorrect configuration number is discarded. #[test] fn test_discard_incorrect_configuration_number() { let mut testkit = testkit_with_supervisor(4); // Attempt to send config with incorrect configuration number (expected 0, actual 100). let incorrect_configuration_number = 100; let first_config_height = Height(2); let config_proposal = ConfigProposeBuilder::new(first_config_height) .configuration_number(incorrect_configuration_number) .extend_consensus_config_propose(consensus_config_propose_first_variant(&testkit)) .build(); let signed_proposal = sign_config_propose_transaction(&testkit, config_proposal, ValidatorId(0)); let block = testkit.create_block_with_transaction(signed_proposal); let err = block.transactions[0].status().unwrap_err(); let expected_msg = "Number for config proposal (100) differs from the expected one (0)"; assert_eq!( *err, ErrorMatch::from_fail(&ConfigurationError::IncorrectConfigurationNumber) .for_service(SUPERVISOR_INSTANCE_ID) .with_description_containing(expected_msg) ); assert_eq!(config_propose_entry(&testkit), None); // Apply some correct config (expected 0, actual 0). let second_config_height = Height(4); let initiator_id = testkit.network().us().validator_id().unwrap(); let first_consensus_config = consensus_config_propose_first_variant(&testkit); let config_proposal = ConfigProposeBuilder::new(second_config_height) .configuration_number(0) .extend_consensus_config_propose(first_consensus_config.clone()) .build(); let proposal_hash = config_proposal.object_hash(); testkit .create_block_with_transaction(sign_config_propose_transaction( &testkit, config_proposal, initiator_id, )) .transactions[0] .status() .expect("Transaction with change propose discarded."); let signed_txs = build_confirmation_transactions(&testkit, proposal_hash, initiator_id); testkit .create_block_with_transactions(signed_txs) .transactions[0] .status() .expect("Transaction with confirmations discarded."); testkit.create_blocks_until(second_config_height); assert_eq!(config_propose_entry(&testkit), None); assert_eq!(testkit.consensus_config(), first_consensus_config); // Attempt to send config with outdated configuration number (expected 1, actual 0). let incorrect_configuration_number = 0; let third_config_height = Height(6); let config_proposal = ConfigProposeBuilder::new(third_config_height) .configuration_number(incorrect_configuration_number) .extend_consensus_config_propose(consensus_config_propose_first_variant(&testkit)) .build(); let signed_proposal = sign_config_propose_transaction(&testkit, config_proposal, ValidatorId(0)); let block = testkit.create_block_with_transaction(signed_proposal); let err = block.transactions[0].status().unwrap_err(); assert_eq!( *err, ErrorMatch::from_fail(&ConfigurationError::IncorrectConfigurationNumber) .with_any_description() ); assert_eq!(config_propose_entry(&testkit), None); } /// Checks that if config applying error, none of changes from the proposal are applied. #[test] fn test_all_changes_are_discarded_on_panic() { let mut testkit = testkit_with_supervisor_and_service(4); let initiator_id = testkit.network().us().validator_id().unwrap(); let erroneous_config_params = ["apply_error", "apply_panic"]; for (i, params) in erroneous_config_params.iter().enumerate() { let cfg_change_height = Height(3 * (i + 1) as u64); let mut propose = ConfigProposeBuilder::new(cfg_change_height).configuration_number(i as u64); // Add a valid config entry. let old_consensus_config = testkit.consensus_config(); let consensus_config = consensus_config_propose_first_variant(&testkit); propose = propose.extend_consensus_config_propose(consensus_config.clone()); // Add an erroneous config entry. propose = propose.extend_service_config_propose((*params).to_owned()); // Send config proposal. let propose = propose.build(); let proposal_hash = propose.object_hash(); testkit .create_block_with_transaction(sign_config_propose_transaction( &testkit, propose, initiator_id, )) .transactions[0] .status() .expect("Transaction with change propose discarded."); let signed_txs = build_confirmation_transactions(&testkit, proposal_hash, initiator_id); testkit.create_block_with_transactions(signed_txs)[0] .status() .unwrap(); testkit.create_blocks_until(cfg_change_height); testkit.create_block(); // Check that config didn't change. assert_eq!(config_propose_entry(&testkit), None); assert_eq!(testkit.consensus_config(), old_consensus_config); } }