Skip to content

Commit

Permalink
feat(core): new output features for changes in contracts (#4169)
Browse files Browse the repository at this point in the history
Description
---
* New output features for all the new types representing contract changes, as subfields of the existing `SidechainFeatures` struct.
* Implement consensus encoding/decoding for the new types.
* Updated all the required gRPC specifications and the related conversions.
* New type `CommitteeSignatures` to wrap a list of signatures, in a similar fashion to the existing `CommitteeMembers`.

Motivation and Context
---
We need a way to represent, in UTXO features, all the information needed to specify changes in a contract:
* `ContractUpdateProposal`: specifies the new proposed constitution.
* `ContractUpdateProposalAcceptance`: acceptance, by a validator node, of a contract update proposal.
* `ContractAmendment`: ratification of a update proposal, specifying when it's going to be enforced in the base layer.

How Has This Been Tested?
---
* New unit tests to check consensus encoding/decoding of the new features.
* The existing tests for side-chain features pass with the new types.
  • Loading branch information
mrnaveira authored Jun 9, 2022
1 parent 87f9969 commit 41570f6
Show file tree
Hide file tree
Showing 12 changed files with 977 additions and 58 deletions.
27 changes: 27 additions & 0 deletions applications/tari_app_grpc/proto/types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,9 @@ message SideChainFeatures {
ContractDefinition definition = 2;
ContractConstitution constitution = 3;
ContractAcceptance acceptance = 4;
ContractUpdateProposal update_proposal = 5;
ContractUpdateProposalAcceptance update_proposal_acceptance = 6;
ContractAmendment amendment = 7;
}

message ContractConstitution {
Expand Down Expand Up @@ -278,6 +281,30 @@ enum SideChainConsensus {
MERKLE_ROOT = 3;
}

message ContractUpdateProposal {
uint64 proposal_id = 1;
Signature signature = 2;
ContractConstitution updated_constitution = 3;
}

message ContractUpdateProposalAcceptance {
uint64 proposal_id = 1;
bytes validator_node_public_key = 2;
Signature signature = 3;
}

message ContractAmendment {
uint64 proposal_id = 1;
CommitteeMembers validator_committee = 2;
CommitteeSignatures validator_signatures = 3;
ContractConstitution updated_constitution = 4;
uint64 activation_window = 5;
}

message CommitteeSignatures {
repeated Signature signatures = 1;
}

// TODO: DEPRECATED

message AssetOutputFeatures {
Expand Down
173 changes: 171 additions & 2 deletions applications/tari_app_grpc/src/conversions/sidechain_features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,22 @@

use std::convert::{TryFrom, TryInto};

use tari_common_types::types::{FixedHash, PublicKey};
use tari_common_types::types::{FixedHash, PublicKey, Signature};
use tari_core::transactions::transaction_components::{
vec_into_fixed_string,
CheckpointParameters,
CommitteeMembers,
CommitteeSignatures,
ConstitutionChangeFlags,
ConstitutionChangeRules,
ContractAcceptance,
ContractAcceptanceRequirements,
ContractAmendment,
ContractConstitution,
ContractDefinition,
ContractSpecification,
ContractUpdateProposal,
ContractUpdateProposalAcceptance,
FunctionRef,
PublicFunction,
RequirementsForConstitutionChange,
Expand All @@ -51,6 +55,9 @@ impl From<SideChainFeatures> for grpc::SideChainFeatures {
definition: value.definition.map(Into::into),
constitution: value.constitution.map(Into::into),
acceptance: value.acceptance.map(Into::into),
update_proposal: value.update_proposal.map(Into::into),
update_proposal_acceptance: value.update_proposal_acceptance.map(Into::into),
amendment: value.amendment.map(Into::into),
}
}
}
Expand All @@ -62,12 +69,24 @@ impl TryFrom<grpc::SideChainFeatures> for SideChainFeatures {
let definition = features.definition.map(ContractDefinition::try_from).transpose()?;
let constitution = features.constitution.map(ContractConstitution::try_from).transpose()?;
let acceptance = features.acceptance.map(ContractAcceptance::try_from).transpose()?;
let update_proposal = features
.update_proposal
.map(ContractUpdateProposal::try_from)
.transpose()?;
let update_proposal_acceptance = features
.update_proposal_acceptance
.map(ContractUpdateProposalAcceptance::try_from)
.transpose()?;
let amendment = features.amendment.map(ContractAmendment::try_from).transpose()?;

Ok(Self {
contract_id: features.contract_id.try_into().map_err(|_| "Invalid contract_id")?,
definition,
constitution,
acceptance,
update_proposal,
update_proposal_acceptance,
amendment,
})
}
}
Expand Down Expand Up @@ -108,6 +127,9 @@ impl TryFrom<grpc::CreateConstitutionDefinitionRequest> for SideChainFeatures {
initial_reward: 100.into(),
}),
acceptance: None,
update_proposal: None,
update_proposal_acceptance: None,
amendment: None,
})
}
}
Expand Down Expand Up @@ -435,6 +457,42 @@ impl TryFrom<grpc::CommitteeMembers> for CommitteeMembers {
}
}

//---------------------------------- CommitteeSignatures --------------------------------------------//
impl From<CommitteeSignatures> for grpc::CommitteeSignatures {
fn from(value: CommitteeSignatures) -> Self {
Self {
signatures: value.signatures().into_iter().map(Into::into).collect(),
}
}
}

impl TryFrom<grpc::CommitteeSignatures> for CommitteeSignatures {
type Error = String;

fn try_from(value: grpc::CommitteeSignatures) -> Result<Self, Self::Error> {
if value.signatures.len() > CommitteeSignatures::MAX_SIGNATURES {
return Err(format!(
"Too many committee signatures: expected {} but got {}",
CommitteeSignatures::MAX_SIGNATURES,
value.signatures.len()
));
}

let signatures = value
.signatures
.into_iter()
.enumerate()
.map(|(i, s)| {
Signature::try_from(s)
.map_err(|err| format!("committee signature #{} was not a valid signature: {}", i + 1, err))
})
.collect::<Result<Vec<_>, _>>()?;

let signatures = CommitteeSignatures::try_from(signatures).map_err(|e| e.to_string())?;
Ok(signatures)
}
}

//---------------------------------- ContractAcceptance --------------------------------------------//

impl From<ContractAcceptance> for grpc::ContractAcceptance {
Expand All @@ -457,11 +515,122 @@ impl TryFrom<grpc::ContractAcceptance> for ContractAcceptance {
.signature
.ok_or_else(|| "signature not provided".to_string())?
.try_into()
.map_err(|_| "signaturecould not be converted".to_string())?;
.map_err(|_| "signature could not be converted".to_string())?;

Ok(Self {
validator_node_public_key,
signature,
})
}
}

//---------------------------------- ContractUpdateProposal --------------------------------------------//

impl From<ContractUpdateProposal> for grpc::ContractUpdateProposal {
fn from(value: ContractUpdateProposal) -> Self {
Self {
proposal_id: value.proposal_id,
signature: Some(value.signature.into()),
updated_constitution: Some(value.updated_constitution.into()),
}
}
}

impl TryFrom<grpc::ContractUpdateProposal> for ContractUpdateProposal {
type Error = String;

fn try_from(value: grpc::ContractUpdateProposal) -> Result<Self, Self::Error> {
let signature = value
.signature
.ok_or_else(|| "signature not provided".to_string())?
.try_into()
.map_err(|_| "signature could not be converted".to_string())?;

let updated_constitution = value
.updated_constitution
.ok_or_else(|| "updated_constiution not provided".to_string())?
.try_into()?;

Ok(Self {
proposal_id: value.proposal_id,
signature,
updated_constitution,
})
}
}

//---------------------------------- ContractUpdateProposalAcceptance --------------------------------------------//

impl From<ContractUpdateProposalAcceptance> for grpc::ContractUpdateProposalAcceptance {
fn from(value: ContractUpdateProposalAcceptance) -> Self {
Self {
proposal_id: value.proposal_id,
validator_node_public_key: value.validator_node_public_key.as_bytes().to_vec(),
signature: Some(value.signature.into()),
}
}
}

impl TryFrom<grpc::ContractUpdateProposalAcceptance> for ContractUpdateProposalAcceptance {
type Error = String;

fn try_from(value: grpc::ContractUpdateProposalAcceptance) -> Result<Self, Self::Error> {
let validator_node_public_key =
PublicKey::from_bytes(value.validator_node_public_key.as_bytes()).map_err(|err| format!("{:?}", err))?;

let signature = value
.signature
.ok_or_else(|| "signature not provided".to_string())?
.try_into()
.map_err(|_| "signature could not be converted".to_string())?;

Ok(Self {
proposal_id: value.proposal_id,
validator_node_public_key,
signature,
})
}
}

//---------------------------------- ContractAmendment --------------------------------------------//

impl From<ContractAmendment> for grpc::ContractAmendment {
fn from(value: ContractAmendment) -> Self {
Self {
proposal_id: value.proposal_id,
validator_committee: Some(value.validator_committee.into()),
validator_signatures: Some(value.validator_signatures.into()),
updated_constitution: Some(value.updated_constitution.into()),
activation_window: value.activation_window,
}
}
}

impl TryFrom<grpc::ContractAmendment> for ContractAmendment {
type Error = String;

fn try_from(value: grpc::ContractAmendment) -> Result<Self, Self::Error> {
let validator_committee = value
.validator_committee
.map(TryInto::try_into)
.ok_or("validator_committee not provided")??;

let validator_signatures = value
.validator_signatures
.map(TryInto::try_into)
.ok_or("validator_signatures not provided")??;

let updated_constitution = value
.updated_constitution
.ok_or_else(|| "updated_constiution not provided".to_string())?
.try_into()?;

Ok(Self {
proposal_id: value.proposal_id,
validator_committee,
validator_signatures,
updated_constitution,
activation_window: value.activation_window,
})
}
}
27 changes: 27 additions & 0 deletions base_layer/core/src/proto/transaction.proto
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ message SideChainFeatures {
ContractDefinition definition = 2;
ContractConstitution constitution = 3;
ContractAcceptance acceptance = 4;
ContractUpdateProposal update_proposal = 5;
ContractUpdateProposalAcceptance update_proposal_acceptance = 6;
ContractAmendment amendment = 7;
}

message ContractConstitution {
Expand Down Expand Up @@ -205,6 +208,30 @@ message ContractAcceptance {
Signature signature = 2;
}

message ContractUpdateProposal {
uint64 proposal_id = 1;
Signature signature = 2;
ContractConstitution updated_constitution = 3;
}

message ContractUpdateProposalAcceptance {
uint64 proposal_id = 1;
bytes validator_node_public_key = 2;
Signature signature = 3;
}

message ContractAmendment {
uint64 proposal_id = 1;
CommitteeMembers validator_committee = 2;
CommitteeSignatures validator_signatures = 3;
ContractConstitution updated_constitution = 4;
uint64 activation_window = 5;
}

message CommitteeSignatures {
repeated Signature signatures = 1;
}

// The components of the block or transaction. The same struct can be used for either, since in Mimblewimble,
// cut-through means that blocks and transactions have the same structure. The inputs, outputs and kernels should
// be sorted by their Blake2b-256bit digest hash
Expand Down
Loading

0 comments on commit 41570f6

Please sign in to comment.