Skip to content

Commit

Permalink
feat(base_layer): validate acceptance window expiration on dan layer (#…
Browse files Browse the repository at this point in the history
…4251)

Description
---
New validation step on contract acceptances (and contract update proposal acceptances) that:
* Retrieve the mined block height of the corresponding constitution/proposal
* Retrieve the specified acceptance window (as relative blocks) from the constitution/proposal
* Checks that the current tip height has not yet passed the sum of the two

Motivation and Context
---
The base layer need to verify that an acceptance (both constitution or proposal one) is inside the allowed acceptance window, specified as relative blocks in the constitution (or proposal).

How Has This Been Tested?
---
New unit tests to replicate the expiration error
  • Loading branch information
mrnaveira committed Jun 30, 2022
1 parent 32c4032 commit 25e316b
Show file tree
Hide file tree
Showing 6 changed files with 331 additions and 78 deletions.
117 changes: 99 additions & 18 deletions base_layer/core/src/validation/dan_validators/acceptance_validator.rs
Expand Up @@ -26,6 +26,7 @@ use tari_utilities::hex::Hex;
use super::helpers::{
fetch_contract_constitution,
fetch_contract_features,
fetch_contract_utxos,
get_sidechain_features,
validate_output_type,
};
Expand Down Expand Up @@ -57,10 +58,10 @@ pub fn validate_acceptance<B: BlockchainBackend>(
let constitution = fetch_contract_constitution(db, contract_id)?;

validate_uniqueness(db, contract_id, validator_node_public_key)?;
validate_public_key(constitution, validator_node_public_key)?;
validate_public_key(&constitution, validator_node_public_key)?;
validate_acceptance_window(db, contract_id, &constitution)?;

// TODO: check that the signature of the transaction is valid
// TODO: check that the acceptance is inside the acceptance window of the constiution
// TODO: check that the stake of the transaction is at least the minimum specified in the constitution

Ok(())
Expand Down Expand Up @@ -102,7 +103,7 @@ fn validate_uniqueness<B: BlockchainBackend>(

/// Checks that the validator public key is present as part of the proposed committee in the constitution
fn validate_public_key(
constitution: ContractConstitution,
constitution: &ContractConstitution,
validator_node_public_key: &PublicKey,
) -> Result<(), ValidationError> {
let is_validator_in_committee = constitution
Expand All @@ -120,21 +121,67 @@ fn validate_public_key(
Ok(())
}

fn validate_acceptance_window<B: BlockchainBackend>(
db: &BlockchainDatabase<B>,
contract_id: FixedHash,
constitution: &ContractConstitution,
) -> Result<(), ValidationError> {
let constitution_height = fetch_constitution_height(db, contract_id)?;
let max_allowed_absolute_height =
constitution_height + constitution.acceptance_requirements.acceptance_period_expiry;
let current_height = db.get_height()?;

let window_has_expired = current_height > max_allowed_absolute_height;
if window_has_expired {
let msg = format!(
"Acceptance window has expired for contract_id ({})",
contract_id.to_hex()
);
return Err(ValidationError::DanLayerError(msg));
}

Ok(())
}

pub fn fetch_constitution_height<B: BlockchainBackend>(
db: &BlockchainDatabase<B>,
contract_id: FixedHash,
) -> Result<u64, ValidationError> {
let utxos = fetch_contract_utxos(db, contract_id, OutputType::ContractConstitution)?;
// Only one constitution should be stored for a particular contract_id
match utxos.first() {
Some(utxo) => Ok(utxo.mined_height),
None => {
let msg = format!(
"Could not find constitution UTXO for contract_id ({})",
contract_id.to_hex(),
);
Err(ValidationError::DanLayerError(msg))
},
}
}

#[cfg(test)]
mod test {
use std::convert::TryInto;

use tari_common_types::types::PublicKey;
use tari_utilities::hex::Hex;

use crate::validation::dan_validators::test_helpers::{
assert_dan_validator_fail,
assert_dan_validator_success,
create_block,
create_contract_acceptance_schema,
create_contract_constitution_schema,
init_test_blockchain,
publish_constitution,
publish_definition,
schema_to_transaction,
use crate::{
txn_schema,
validation::dan_validators::test_helpers::{
assert_dan_validator_fail,
assert_dan_validator_success,
create_block,
create_contract_acceptance_schema,
create_contract_constitution,
create_contract_constitution_schema,
init_test_blockchain,
publish_constitution,
publish_definition,
schema_to_transaction,
},
};

#[test]
Expand All @@ -147,8 +194,9 @@ mod test {

// publish the contract constitution into a block
let validator_node_public_key = PublicKey::default();
let committee = vec![validator_node_public_key.clone()];
publish_constitution(&mut blockchain, change[1].clone(), contract_id, committee);
let mut constitution = create_contract_constitution();
constitution.validator_committee = vec![validator_node_public_key.clone()].try_into().unwrap();
publish_constitution(&mut blockchain, change[1].clone(), contract_id, constitution);

// create a valid contract acceptance transaction
let schema = create_contract_acceptance_schema(contract_id, change[2].clone(), validator_node_public_key);
Expand Down Expand Up @@ -185,8 +233,9 @@ mod test {

// publish the contract constitution into a block
let validator_node_public_key = PublicKey::default();
let committee = vec![validator_node_public_key.clone()];
publish_constitution(&mut blockchain, change[1].clone(), contract_id, committee);
let mut constitution = create_contract_constitution();
constitution.validator_committee = vec![validator_node_public_key.clone()].try_into().unwrap();
publish_constitution(&mut blockchain, change[1].clone(), contract_id, constitution);

// publish a contract acceptance into a block
let schema =
Expand All @@ -212,7 +261,9 @@ mod test {
// publish the contract constitution into a block
// we deliberately use a committee with only a defult public key to be able to trigger the committee error later
let committee = vec![PublicKey::default()];
let schema = create_contract_constitution_schema(contract_id, change[1].clone(), committee);
let mut constitution = create_contract_constitution();
constitution.validator_committee = committee.try_into().unwrap();
let schema = create_contract_constitution_schema(contract_id, change[1].clone(), constitution);
create_block(&mut blockchain, "constitution", schema);

// create a contract acceptance transaction
Expand All @@ -225,4 +276,34 @@ mod test {
// try to validate the acceptance transaction and check that we get the committee error
assert_dan_validator_fail(&blockchain, &tx, "Validator node public key is not in committee");
}

#[test]
fn it_rejects_expired_acceptances() {
// initialise a blockchain with enough funds to spend at contract transactions
let (mut blockchain, change) = init_test_blockchain();

// publish the contract definition into a block
let contract_id = publish_definition(&mut blockchain, change[0].clone());

// publish the contract constitution into a block, with a very short (1 block) expiration time
let validator_node_public_key = PublicKey::default();
let committee = vec![validator_node_public_key.clone()];
let mut constitution = create_contract_constitution();
constitution.validator_committee = committee.try_into().unwrap();
constitution.acceptance_requirements.acceptance_period_expiry = 1;
publish_constitution(&mut blockchain, change[1].clone(), contract_id, constitution);

// publish some filler blocks in, just to make the expiration height pass
let schema = txn_schema!(from: vec![change[2].clone()], to: vec![0.into()]);
create_block(&mut blockchain, "filler1", schema);
let schema = txn_schema!(from: vec![change[3].clone()], to: vec![0.into()]);
create_block(&mut blockchain, "filler2", schema);

// create a contract acceptance after the expiration block height
let schema = create_contract_acceptance_schema(contract_id, change[4].clone(), validator_node_public_key);
let (tx, _) = schema_to_transaction(&schema);

// try to validate the acceptance transaction and check that we get the expiration error
assert_dan_validator_fail(&blockchain, &tx, "Acceptance window has expired");
}
}
Expand Up @@ -107,13 +107,16 @@ fn validate_updated_constiution(

#[cfg(test)]
mod test {
use std::convert::TryInto;

use tari_common_types::types::PublicKey;

use crate::validation::dan_validators::test_helpers::{
assert_dan_validator_fail,
assert_dan_validator_success,
create_block,
create_contract_amendment_schema,
create_contract_constitution,
init_test_blockchain,
publish_constitution,
publish_definition,
Expand All @@ -132,7 +135,9 @@ mod test {
// publish the contract constitution into a block
let validator_node_public_key = PublicKey::default();
let committee = vec![validator_node_public_key];
publish_constitution(&mut blockchain, change[1].clone(), contract_id, committee.clone());
let mut constitution = create_contract_constitution();
constitution.validator_committee = committee.try_into().unwrap();
publish_constitution(&mut blockchain, change[1].clone(), contract_id, constitution.clone());

// publish a contract update proposal into a block
let proposal_id: u64 = 1;
Expand All @@ -141,12 +146,12 @@ mod test {
change[2].clone(),
contract_id,
proposal_id,
committee.clone(),
constitution.clone(),
);

// create a valid amendment transaction
let proposal_id = 1;
let schema = create_contract_amendment_schema(contract_id, change[3].clone(), proposal_id, committee);
let schema = create_contract_amendment_schema(contract_id, change[3].clone(), proposal_id, constitution);
let (tx, _) = schema_to_transaction(&schema);
assert_dan_validator_success(&blockchain, &tx);
}
Expand All @@ -162,13 +167,15 @@ mod test {
// publish the contract constitution into a block
let validator_node_public_key = PublicKey::default();
let committee = vec![validator_node_public_key];
publish_constitution(&mut blockchain, change[1].clone(), contract_id, committee.clone());
let mut constitution = create_contract_constitution();
constitution.validator_committee = committee.try_into().unwrap();
publish_constitution(&mut blockchain, change[1].clone(), contract_id, constitution.clone());

// skip the publication of the contract update proposal

// create an amendment transaction
let proposal_id = 1;
let schema = create_contract_amendment_schema(contract_id, change[1].clone(), proposal_id, committee);
let schema = create_contract_amendment_schema(contract_id, change[1].clone(), proposal_id, constitution);
let (tx, _) = schema_to_transaction(&schema);

// try to validate the acceptance transaction and check that we get the error
Expand All @@ -186,7 +193,9 @@ mod test {
// publish the contract constitution into a block
let validator_node_public_key = PublicKey::default();
let committee = vec![validator_node_public_key];
publish_constitution(&mut blockchain, change[1].clone(), contract_id, committee.clone());
let mut constitution = create_contract_constitution();
constitution.validator_committee = committee.try_into().unwrap();
publish_constitution(&mut blockchain, change[1].clone(), contract_id, constitution.clone());

// publish a contract update proposal into a block
let proposal_id: u64 = 1;
Expand All @@ -195,15 +204,16 @@ mod test {
change[2].clone(),
contract_id,
proposal_id,
committee.clone(),
constitution.clone(),
);

// publish the contract amendment into a block
let schema = create_contract_amendment_schema(contract_id, change[3].clone(), proposal_id, committee.clone());
let schema =
create_contract_amendment_schema(contract_id, change[3].clone(), proposal_id, constitution.clone());
create_block(&mut blockchain, "amendment", schema);

// create a (duplicated) contract amendment transaction
let schema = create_contract_amendment_schema(contract_id, change[4].clone(), proposal_id, committee);
let schema = create_contract_amendment_schema(contract_id, change[4].clone(), proposal_id, constitution);
let (tx, _) = schema_to_transaction(&schema);

// try to validate the duplicated amendment transaction and check that we get the error
Expand All @@ -221,15 +231,25 @@ mod test {
// publish the contract constitution into a block
let validator_node_public_key = PublicKey::default();
let committee = vec![validator_node_public_key];
publish_constitution(&mut blockchain, change[1].clone(), contract_id, committee.clone());
let mut constitution = create_contract_constitution();
constitution.validator_committee = committee.try_into().unwrap();
publish_constitution(&mut blockchain, change[1].clone(), contract_id, constitution.clone());

// publish a contract update proposal into a block
let proposal_id: u64 = 1;
publish_update_proposal(&mut blockchain, change[2].clone(), contract_id, proposal_id, committee);
publish_update_proposal(
&mut blockchain,
change[2].clone(),
contract_id,
proposal_id,
constitution,
);

// create an amendment with an altered committee (compared to the proposal)
let altered_committee = vec![];
let schema = create_contract_amendment_schema(contract_id, change[4].clone(), proposal_id, altered_committee);
let mut altered_constitution = create_contract_constitution();
altered_constitution.validator_committee = vec![].try_into().unwrap();
let schema =
create_contract_amendment_schema(contract_id, change[4].clone(), proposal_id, altered_constitution);
let (tx, _) = schema_to_transaction(&schema);

// try to validate the amendment transaction and check that we get the error
Expand Down
Expand Up @@ -85,6 +85,7 @@ mod test {
use crate::validation::dan_validators::test_helpers::{
assert_dan_validator_fail,
assert_dan_validator_success,
create_contract_constitution,
create_contract_constitution_schema,
init_test_blockchain,
publish_constitution,
Expand All @@ -101,7 +102,8 @@ mod test {
let contract_id = publish_definition(&mut blockchain, change[0].clone());

// construct a valid constitution transaction
let schema = create_contract_constitution_schema(contract_id, change[2].clone(), Vec::new());
let constitution = create_contract_constitution();
let schema = create_contract_constitution_schema(contract_id, change[2].clone(), constitution);
let (tx, _) = schema_to_transaction(&schema);

assert_dan_validator_success(&blockchain, &tx);
Expand All @@ -114,7 +116,8 @@ mod test {

// construct a transaction for a constitution, without a prior definition
let contract_id = FixedHash::default();
let schema = create_contract_constitution_schema(contract_id, change[2].clone(), Vec::new());
let constitution = create_contract_constitution();
let schema = create_contract_constitution_schema(contract_id, change[2].clone(), constitution);
let (tx, _) = schema_to_transaction(&schema);

// try to validate the constitution transaction and check that we get the error
Expand All @@ -126,12 +129,15 @@ mod test {
// initialise a blockchain with enough funds to spend at contract transactions
let (mut blockchain, change) = init_test_blockchain();

// publish the contract definition and constitution into a block
// publish the contract definition into a block
let contract_id = publish_definition(&mut blockchain, change[0].clone());
publish_constitution(&mut blockchain, change[1].clone(), contract_id, vec![]);

// publish the contract constitution into a block
let constitution = create_contract_constitution();
publish_constitution(&mut blockchain, change[1].clone(), contract_id, constitution.clone());

// construct a transaction for the duplicated contract constitution
let schema = create_contract_constitution_schema(contract_id, change[2].clone(), Vec::new());
let schema = create_contract_constitution_schema(contract_id, change[2].clone(), constitution);
let (tx, _) = schema_to_transaction(&schema);

// try to validate the duplicated constitution transaction and check that we get the error
Expand Down

0 comments on commit 25e316b

Please sign in to comment.