Skip to content

Commit

Permalink
feat: add quarantine and backup keys (#4289)
Browse files Browse the repository at this point in the history
Description
---
This PR adds the quarantine interval and backup_keys to the constitution as per the RFC. 

This also moves the side_chain features to boxed struct.

Motivation and Context
---
Adding the quarantine interval and backup_keys makes the output_features big enough that it causes a stack overflow error in some of the wallet tests. Since we will most likely add more features to the side_chain features, this is moved to a boxed struct so that it wont fail. 

How Has This Been Tested?
---
Unit tests
  • Loading branch information
SWvheerden authored Jul 14, 2022
1 parent 2bf7efe commit ed9f8cb
Show file tree
Hide file tree
Showing 34 changed files with 192 additions and 86 deletions.
4 changes: 4 additions & 0 deletions applications/tari_app_grpc/proto/types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ message ContractCheckpoint {
message CheckpointParameters {
uint64 abandoned_interval = 1;
uint32 minimum_quorum_required = 2;
uint64 quarantine_interval = 3;
}

message ConstitutionChangeRules {
Expand All @@ -283,6 +284,9 @@ message RequirementsForConstitutionChange {
// An allowlist of keys that are able to accept and ratify the initial constitution and its amendments. If this is
// None, the constitution cannot be amended.
CommitteeMembers constitution_committee = 2;
// An allowlist of keys that can be used in case of checkpoint abandonment. These keys can quarantine the the constitution
// and if the quarantine period is exceeded, the backup keys can take over.
CommitteeMembers backup_keys = 3;
}

enum SideChainConsensus {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ impl From<OutputFeatures> for grpc::OutputFeatures {
metadata: features.metadata,
unique_id: features.unique_id.unwrap_or_default(),
recovery_byte: u32::from(features.recovery_byte),
sidechain_features: features.sidechain_features.map(Into::into),
sidechain_features: features.sidechain_features.map(|v| *v).map(Into::into),

// TODO: Deprecated
asset: None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,14 @@ impl TryFrom<grpc::CreateConstitutionDefinitionRequest> for SideChainFeatures {
checkpoint_params: CheckpointParameters {
minimum_quorum_required: 5,
abandoned_interval: 100,
quarantine_interval: 100,
},
constitution_change_rules: ConstitutionChangeRules {
change_flags: ConstitutionChangeFlags::all(),
requirements_for_constitution_change: Some(RequirementsForConstitutionChange {
minimum_constitution_committee_signatures: 5,
constitution_committee: Some(validator_committee),
constitution_committee: Some(validator_committee.clone()),
backup_keys: Some(validator_committee),
}),
},
initial_reward: 100.into(),
Expand Down Expand Up @@ -387,6 +389,7 @@ impl From<CheckpointParameters> for grpc::CheckpointParameters {
Self {
minimum_quorum_required: value.minimum_quorum_required,
abandoned_interval: value.abandoned_interval,
quarantine_interval: value.quarantine_interval,
}
}
}
Expand All @@ -398,6 +401,7 @@ impl TryFrom<grpc::CheckpointParameters> for CheckpointParameters {
Ok(Self {
minimum_quorum_required: value.minimum_quorum_required,
abandoned_interval: value.abandoned_interval,
quarantine_interval: value.quarantine_interval,
})
}
}
Expand Down Expand Up @@ -435,6 +439,7 @@ impl From<RequirementsForConstitutionChange> for grpc::RequirementsForConstituti
Self {
minimum_constitution_committee_signatures: value.minimum_constitution_committee_signatures,
constitution_committee: value.constitution_committee.map(Into::into),
backup_keys: value.backup_keys.map(Into::into),
}
}
}
Expand All @@ -449,6 +454,7 @@ impl TryFrom<grpc::RequirementsForConstitutionChange> for RequirementsForConstit
.constitution_committee
.map(CommitteeMembers::try_from)
.transpose()?,
backup_keys: value.backup_keys.map(CommitteeMembers::try_from).transpose()?,
})
}
}
Expand Down
2 changes: 2 additions & 0 deletions applications/tari_console_wallet/src/automation/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -965,6 +965,7 @@ async fn init_contract_constitution_spec(
checkpoint_parameters: CheckpointParameters {
minimum_quorum_required: 0,
abandoned_interval: 0,
quarantine_interval: 0,
},
constitution_change_rules: ConstitutionChangeRulesFileFormat {
change_flags: 0,
Expand Down Expand Up @@ -1034,6 +1035,7 @@ async fn init_contract_update_proposal_spec(
checkpoint_parameters: CheckpointParameters {
minimum_quorum_required: 0,
abandoned_interval: 0,
quarantine_interval: 0,
},
constitution_change_rules: ConstitutionChangeRulesFileFormat {
change_flags: 0,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<dl>
<dt>Abandoned interval</dt><dd>{{abandoned_interval}}</dd>
<dt>Quarantine interval</dt><dd>{{quarantine_interval}}</dd>
<dt>Minimum quorum required</dt><dd>{{minimum_quorum_required}}</dd>
</dl>
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@
<dd>{{minimum_constitution_committee_signatures}}</dd>
<dt>Constitution committee</dt>
<dd>{{>CommitteeMembers constitution_committee}}</dd>
<dt>Backup keys</dt>
<dd>{{>CommitteeMembers backup_keys}}</dd>
</dl>
9 changes: 5 additions & 4 deletions base_layer/core/src/chain_storage/tests/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ pub fn create_blockchain_without_validation() -> TestBlockchain {
pub fn create_contract_definition_features(contract_id: FixedHash) -> OutputFeatures {
OutputFeatures {
output_type: OutputType::ContractDefinition,
sidechain_features: Some(
sidechain_features: Some(Box::new(
SideChainFeatures::builder(contract_id)
.with_contract_definition(ContractDefinition {
contract_name: [1u8; 32],
Expand All @@ -73,7 +73,7 @@ pub fn create_contract_definition_features(contract_id: FixedHash) -> OutputFeat
},
})
.finish(),
),
)),
..Default::default()
}
}
Expand All @@ -95,7 +95,7 @@ pub fn create_contract_constitution_transaction(
) -> (Transaction, Vec<UnblindedOutput>) {
let features = OutputFeatures {
output_type: OutputType::ContractConstitution,
sidechain_features: Some(
sidechain_features: Some(Box::new(
SideChainFeatures::builder(contract_id)
.with_contract_constitution(ContractConstitution {
validator_committee: CommitteeMembers::default(),
Expand All @@ -107,6 +107,7 @@ pub fn create_contract_constitution_transaction(
checkpoint_params: CheckpointParameters {
minimum_quorum_required: 1,
abandoned_interval: 20,
quarantine_interval: 20,
},
constitution_change_rules: ConstitutionChangeRules {
change_flags: ConstitutionChangeFlags::empty(),
Expand All @@ -115,7 +116,7 @@ pub fn create_contract_constitution_transaction(
initial_reward: 10u64.into(),
})
.finish(),
),
)),
..Default::default()
};
let (transactions, outputs) =
Expand Down
18 changes: 18 additions & 0 deletions base_layer/core/src/consensus/consensus_encoding/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,24 @@ impl<T: ConsensusDecoding> ConsensusDecoding for Option<T> {
}
}

//---------------------------------- Box<T> --------------------------------------------//

impl<T: ConsensusEncoding> ConsensusEncoding for Box<T> {
fn consensus_encode<W: Write>(&self, writer: &mut W) -> Result<(), io::Error> {
self.as_ref().consensus_encode(writer)?;
Ok(())
}
}

impl<T: ConsensusEncodingSized> ConsensusEncodingSized for Box<T> {}

impl<T: ConsensusDecoding> ConsensusDecoding for Box<T> {
fn consensus_decode<R: Read>(reader: &mut R) -> Result<Self, io::Error> {
let t = T::consensus_decode(reader)?;
Ok(Box::new(t))
}
}

//---------------------------------- Vec<T> --------------------------------------------//
impl<T: ConsensusEncoding> ConsensusEncoding for Vec<T> {
fn consensus_encode<W: Write>(&self, writer: &mut W) -> Result<(), io::Error> {
Expand Down
49 changes: 36 additions & 13 deletions base_layer/core/src/covenants/fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ use crate::{
encoder::CovenentWriteExt,
error::CovenantError,
},
transactions::transaction_components::{SideChainFeatures, TransactionInput, TransactionOutput},
transactions::transaction_components::{TransactionInput, TransactionOutput},
};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand Down Expand Up @@ -86,7 +86,7 @@ impl OutputField {
self as u8
}

pub(super) fn get_field_value_ref<T: 'static>(self, output: &TransactionOutput) -> Option<&T> {
pub(super) fn get_field_value_ref<T: 'static + std::fmt::Debug>(self, output: &TransactionOutput) -> Option<&T> {
#[allow(clippy::enum_glob_use)]
use OutputField::*;
let val = match self {
Expand All @@ -105,7 +105,7 @@ impl OutputField {
// sidechain_features because there is no reference to Option<FixedHash> for
// sidechain_features.contract_id to return.
match &output.features.sidechain_features {
Some(SideChainFeatures { ref contract_id, .. }) => contract_id as &dyn Any,
Some(v) => &v.contract_id as &dyn Any,
none => none as &dyn Any,
}
},
Expand Down Expand Up @@ -185,19 +185,39 @@ impl OutputField {
}
}

pub fn is_eq<T: PartialEq + 'static>(self, output: &TransactionOutput, val: &T) -> Result<bool, CovenantError> {
pub fn is_eq<T: PartialEq + std::fmt::Debug + 'static>(
self,
output: &TransactionOutput,
val: &T,
) -> Result<bool, CovenantError> {
#[allow(clippy::enum_glob_use)]
use OutputField::*;
match self {
// Handle edge cases
FeaturesParentPublicKey | FeaturesUniqueId | FeaturesSideChainFeatures => {
match self.get_field_value_ref::<Option<T>>(output) {
Some(Some(field_val)) => Ok(field_val == val),
FeaturesParentPublicKey | FeaturesUniqueId => match self.get_field_value_ref::<Option<T>>(output) {
Some(Some(field_val)) => Ok(field_val == val),
Some(None) => Ok(false),
None => Err(CovenantError::InvalidArgument {
filter: "is_eq",
details: format!("Invalid type for field {}", self),
}),
},
FeaturesSideChainFeatures => {
match self.get_field_value_ref::<Option<Box<T>>>(output) {
Some(Some(field_val)) => Ok(**field_val == *val),
Some(None) => Ok(false),
None => Err(CovenantError::InvalidArgument {
filter: "is_eq",
details: format!("Invalid type for field {}", self),
}),
None => {
// We need to check this, if T is of type output, then we need to check for a boxed<T>
// otherwise we need to check for a T, so we check both cases.
match self.get_field_value_ref::<Option<T>>(output) {
Some(Some(field_val)) => Ok(field_val == val),
Some(None) => Ok(false),
None => Err(CovenantError::InvalidArgument {
filter: "is_eq",
details: format!("Invalid type for field {}", self),
}),
}
},
}
},
FeaturesSideChainFeaturesContractId => match self.get_field_value_ref::<T>(output) {
Expand Down Expand Up @@ -403,7 +423,7 @@ mod test {
let output = create_outputs(1, UtxoTestParams {
features: OutputFeatures {
parent_public_key: Some(Default::default()),
sidechain_features: Some(SideChainFeatures::new(FixedHash::zero())),
sidechain_features: Some(Box::new(SideChainFeatures::new(FixedHash::zero()))),
..Default::default()
},
script: script![Drop Nop],
Expand All @@ -421,6 +441,9 @@ mod test {
assert!(OutputField::FeaturesFlags
.is_eq(&output, &output.features.output_type)
.unwrap());
assert!(OutputField::FeaturesSideChainFeatures
.is_eq(&output, &SideChainFeatures::new(FixedHash::zero()))
.unwrap());
assert!(OutputField::FeaturesSideChainFeatures
.is_eq(&output, output.features.sidechain_features.as_ref().unwrap())
.unwrap());
Expand All @@ -441,7 +464,7 @@ mod test {
let output = create_outputs(1, UtxoTestParams {
features: OutputFeatures {
parent_public_key: Some(parent_pk),
sidechain_features: Some(SideChainFeatures::new(FixedHash::hash_bytes(b"A"))),
sidechain_features: Some(Box::new(SideChainFeatures::new(FixedHash::hash_bytes(b"A")))),
..Default::default()
},
script: script![Drop Nop],
Expand Down
6 changes: 3 additions & 3 deletions base_layer/core/src/covenants/filters/and.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@ mod test {
let input = create_input();
let (mut context, outputs) = setup_filter_test(&covenant, &input, 0, |outputs| {
outputs[5].features.maturity = 42;
outputs[5].features.sidechain_features = Some(SideChainFeatures::new(hash));
outputs[5].features.sidechain_features = Some(Box::new(SideChainFeatures::new(hash)));
outputs[7].features.maturity = 42;
outputs[7].features.sidechain_features = Some(SideChainFeatures::new(hash));
outputs[7].features.sidechain_features = Some(Box::new(SideChainFeatures::new(hash)));
// Does not have maturity = 42
outputs[8].features.maturity = 123;
outputs[8].features.sidechain_features = Some(SideChainFeatures::new(hash));
outputs[8].features.sidechain_features = Some(Box::new(SideChainFeatures::new(hash)));
});

let mut output_set = OutputSet::new(&outputs);
Expand Down
2 changes: 1 addition & 1 deletion base_layer/core/src/covenants/filters/fields_hashed_eq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ mod test {
fn it_filters_outputs_with_fields_that_hash_to_given_hash() {
let features = OutputFeatures {
maturity: 42,
sidechain_features: Some(SideChainFeatures::new(FixedHash::hash_bytes("A"))),
sidechain_features: Some(Box::new(SideChainFeatures::new(FixedHash::hash_bytes("A")))),
..Default::default()
};
let hashed = Challenge::new().chain(features.to_consensus_bytes()).finalize();
Expand Down
8 changes: 4 additions & 4 deletions base_layer/core/src/covenants/filters/fields_preserved.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,17 @@ mod test {
let covenant = covenant!(fields_preserved(@fields(@field::features_maturity, @field::features_contract_id, @field::features_flags)));
let mut input = create_input();
input.set_maturity(42).unwrap();
input.features_mut().unwrap().sidechain_features = Some(SideChainFeatures::new(hash));
input.features_mut().unwrap().sidechain_features = Some(Box::new(SideChainFeatures::new(hash)));
input.features_mut().unwrap().output_type = OutputType::ContractDefinition;
let (mut context, outputs) = setup_filter_test(&covenant, &input, 0, |outputs| {
outputs[5].features.maturity = 42;
outputs[5].features.sidechain_features = Some(SideChainFeatures::new(hash));
outputs[5].features.sidechain_features = Some(Box::new(SideChainFeatures::new(hash)));
outputs[5].features.output_type = OutputType::ContractDefinition;
outputs[7].features.maturity = 42;
outputs[7].features.output_type = OutputType::ContractDefinition;
outputs[7].features.sidechain_features = Some(SideChainFeatures::new(FixedHash::hash_bytes("B")));
outputs[7].features.sidechain_features = Some(Box::new(SideChainFeatures::new(FixedHash::hash_bytes("B"))));
outputs[8].features.maturity = 42;
outputs[8].features.sidechain_features = Some(SideChainFeatures::new(hash));
outputs[8].features.sidechain_features = Some(Box::new(SideChainFeatures::new(hash)));
outputs[8].features.output_type = OutputType::Coinbase;
});
let mut output_set = OutputSet::new(&outputs);
Expand Down
4 changes: 2 additions & 2 deletions base_layer/core/src/covenants/filters/not.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ mod test {
let input = create_input();
let (mut context, outputs) = setup_filter_test(&covenant, &input, 0, |outputs| {
outputs[5].features.maturity = 42;
outputs[5].features.sidechain_features = Some(SideChainFeatures::new(hash));
outputs[5].features.sidechain_features = Some(Box::new(SideChainFeatures::new(hash)));
outputs[7].features.maturity = 42;
outputs[8].features.sidechain_features = Some(SideChainFeatures::new(hash));
outputs[8].features.sidechain_features = Some(Box::new(SideChainFeatures::new(hash)));
});
let mut output_set = OutputSet::new(&outputs);
NotFilter.filter(&mut context, &mut output_set).unwrap();
Expand Down
4 changes: 2 additions & 2 deletions base_layer/core/src/covenants/filters/or.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ mod test {
let input = create_input();
let (mut context, outputs) = setup_filter_test(&covenant, &input, 0, |outputs| {
outputs[5].features.maturity = 42;
outputs[5].features.sidechain_features = Some(SideChainFeatures::new(hash));
outputs[5].features.sidechain_features = Some(Box::new(SideChainFeatures::new(hash)));
outputs[7].features.maturity = 42;
outputs[8].features.sidechain_features = Some(SideChainFeatures::new(hash));
outputs[8].features.sidechain_features = Some(Box::new(SideChainFeatures::new(hash)));
});
let mut output_set = OutputSet::new(&outputs);
OrFilter.filter(&mut context, &mut output_set).unwrap();
Expand Down
4 changes: 2 additions & 2 deletions base_layer/core/src/covenants/filters/xor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ mod test {
let input = create_input();
let (mut context, outputs) = setup_filter_test(&covenant, &input, 0, |outputs| {
outputs[5].features.maturity = 42;
outputs[5].features.sidechain_features = Some(SideChainFeatures::new(hash));
outputs[5].features.sidechain_features = Some(Box::new(SideChainFeatures::new(hash)));
outputs[7].features.maturity = 42;
outputs[8].features.sidechain_features = Some(SideChainFeatures::new(hash));
outputs[8].features.sidechain_features = Some(Box::new(SideChainFeatures::new(hash)));
});
let mut output_set = OutputSet::new(&outputs);
XorFilter.filter(&mut context, &mut output_set).unwrap();
Expand Down
4 changes: 4 additions & 0 deletions base_layer/core/src/proto/transaction.proto
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ message CommitteeMembers {
message CheckpointParameters {
uint64 abandoned_interval = 1;
uint32 minimum_quorum_required = 2;
uint64 quarantine_interval = 3;
}

message ConstitutionChangeRules {
Expand All @@ -156,6 +157,9 @@ message RequirementsForConstitutionChange {
// An allowlist of keys that are able to accept and ratify the initial constitution and its amendments. If this is
// None, the constitution cannot be amended.
CommitteeMembers constitution_committee = 2;
// An allowlist of keys that can be used in case of checkpoint abandonment. These keys can quarantine the the constitution
// and if the quarantine period is exceeded, the backup keys can take over.
CommitteeMembers backup_keys = 3;
}

enum SideChainConsensus {
Expand Down
Loading

0 comments on commit ed9f8cb

Please sign in to comment.