Skip to content

Commit

Permalink
feat(wallet): new cli commands to initialise proposals and amendments (
Browse files Browse the repository at this point in the history
…#4205)

Description
---
* Created a new `init-update-proposal` subcommand to create a proposal JSON file. 
    * The fields are very similar to a constitution, only the `proposal_id` is new.
    * For now, the `signature` is set to the default value. In the future we will need to implement a way for the user to be able to sign the proposal via a private key.
* Create a new `init-amendment` subcommand to create an amendment JSON file. 
    * The user is required to specify an update proposal JSON file path with the proposal to amend.
    * Asks for the `activation_window` to the user, which specifies the amount of blocks until the changes are enforced by the base layer.
    * For now, the list of signatures for each accepting committee member is set as empty. In the future we will need to retrieve those from the acceptance transactions themselves.
* Renamed the contract command handling function for name consistency.

Motivation and Context
---
We want an easy way to initialise both contract update proposals and amendments JSON files, to publish them later with other CLI commands.

How Has This Been Tested?
---
Manually


* create init command for update proposal

* improve internal naming

* create init command for amendments
  • Loading branch information
mrnaveira authored Jun 20, 2022
1 parent 01b600a commit 40cbd50
Show file tree
Hide file tree
Showing 4 changed files with 190 additions and 7 deletions.
140 changes: 134 additions & 6 deletions applications/tari_console_wallet/src/automation/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ use tari_wallet::{
ContractDefinitionFileFormat,
ContractSpecificationFileFormat,
ContractUpdateProposalFileFormat,
SignatureFileFormat,
},
error::WalletError,
output_manager_service::handle::OutputManagerHandle,
Expand All @@ -88,8 +89,10 @@ use crate::{
CliCommands,
ContractCommand,
ContractSubcommand,
InitAmendmentArgs,
InitConstitutionArgs,
InitDefinitionArgs,
InitUpdateProposalArgs,
PublishFileArgs,
},
utils::db::{CUSTOM_BASE_NODE_ADDRESS_KEY, CUSTOM_BASE_NODE_PUBLIC_KEY_KEY},
Expand Down Expand Up @@ -745,8 +748,8 @@ pub async fn command_runner(
.await
.map_err(CommandError::TransactionServiceError)?;
},
Contract(subcommand) => {
handle_contract_definition_command(&wallet, subcommand).await?;
Contract(command) => {
handle_contract_command(&wallet, command).await?;
},
}
}
Expand Down Expand Up @@ -789,13 +792,12 @@ pub async fn command_runner(
Ok(())
}

async fn handle_contract_definition_command(
wallet: &WalletSqlite,
command: ContractCommand,
) -> Result<(), CommandError> {
async fn handle_contract_command(wallet: &WalletSqlite, command: ContractCommand) -> Result<(), CommandError> {
match command.subcommand {
ContractSubcommand::InitDefinition(args) => init_contract_definition_spec(args),
ContractSubcommand::InitConstitution(args) => init_contract_constitution_spec(args),
ContractSubcommand::InitUpdateProposal(args) => init_contract_update_proposal_spec(args),
ContractSubcommand::InitAmendment(args) => init_contract_amendment_spec(args),
ContractSubcommand::PublishDefinition(args) => publish_contract_definition(wallet, args).await,
ContractSubcommand::PublishConstitution(args) => publish_contract_constitution(wallet, args).await,
ContractSubcommand::PublishUpdateProposal(args) => publish_contract_update_proposal(wallet, args).await,
Expand Down Expand Up @@ -901,6 +903,132 @@ fn init_contract_constitution_spec(args: InitConstitutionArgs) -> Result<(), Com
Ok(())
}

fn init_contract_update_proposal_spec(args: InitUpdateProposalArgs) -> Result<(), CommandError> {
if args.dest_path.exists() {
if args.force {
println!("{} exists and will be overwritten.", args.dest_path.to_string_lossy());
} else {
println!(
"{} exists. Use `--force` to overwrite.",
args.dest_path.to_string_lossy()
);
return Ok(());
}
}
let dest = args.dest_path;

let contract_id = Prompt::new("Contract id (hex):")
.skip_if_some(args.contract_id)
.get_result()?;
let proposal_id = Prompt::new("Proposal id (integer, unique inside the contract scope):")
.skip_if_some(args.proposal_id)
.with_default("0".to_string())
.get_result()?
.parse::<u64>()
.map_err(|e| CommandError::InvalidArgument(e.to_string()))?;
let committee: Vec<String> = Prompt::new("Validator committee ids (hex):").ask_repeatedly()?;
let acceptance_period_expiry = Prompt::new("Acceptance period expiry (in blocks, integer):")
.skip_if_some(args.acceptance_period_expiry)
.with_default("50".to_string())
.get_result()?;
let minimum_quorum_required = Prompt::new("Minimum quorum:")
.skip_if_some(args.minimum_quorum_required)
.with_default(committee.len().to_string())
.get_result()?;

let updated_constitution = ConstitutionDefinitionFileFormat {
contract_id,
validator_committee: committee.iter().map(|c| PublicKey::from_hex(c).unwrap()).collect(),
consensus: SideChainConsensus::MerkleRoot,
initial_reward: 0,
acceptance_parameters: ContractAcceptanceRequirements {
acceptance_period_expiry: acceptance_period_expiry
.parse::<u64>()
.map_err(|e| CommandError::InvalidArgument(e.to_string()))?,
minimum_quorum_required: minimum_quorum_required
.parse::<u32>()
.map_err(|e| CommandError::InvalidArgument(e.to_string()))?,
},
checkpoint_parameters: CheckpointParameters {
minimum_quorum_required: 0,
abandoned_interval: 0,
},
constitution_change_rules: ConstitutionChangeRulesFileFormat {
change_flags: 0,
requirements_for_constitution_change: None,
},
};

let update_proposal = ContractUpdateProposalFileFormat {
proposal_id,
// TODO: use a private key to sign the proposal
signature: SignatureFileFormat::default(),
updated_constitution,
};

let file = File::create(&dest).map_err(|e| CommandError::JsonFile(e.to_string()))?;
let writer = BufWriter::new(file);
serde_json::to_writer_pretty(writer, &update_proposal).map_err(|e| CommandError::JsonFile(e.to_string()))?;
println!("Wrote {}", dest.to_string_lossy());
Ok(())
}

fn init_contract_amendment_spec(args: InitAmendmentArgs) -> Result<(), CommandError> {
if args.dest_path.exists() {
if args.force {
println!("{} exists and will be overwritten.", args.dest_path.to_string_lossy());
} else {
println!(
"{} exists. Use `--force` to overwrite.",
args.dest_path.to_string_lossy()
);
return Ok(());
}
}
let dest = args.dest_path;

// check that the proposal file exists
if !args.proposal_file_path.exists() {
println!(
"Proposal file path {} not found",
args.proposal_file_path.to_string_lossy()
);
return Ok(());
}
let proposal_file = File::open(&args.proposal_file_path).map_err(|e| CommandError::JsonFile(e.to_string()))?;
let proposal_file_reader = BufReader::new(proposal_file);

// parse the JSON file with the proposal
let update_proposal: ContractUpdateProposalFileFormat =
serde_json::from_reader(proposal_file_reader).map_err(|e| CommandError::JsonFile(e.to_string()))?;

// read the activation_window value from the user
let activation_window = Prompt::new("Activation window (in blocks, integer):")
.skip_if_some(args.activation_window)
.with_default("50".to_string())
.get_result()?
.parse::<u64>()
.map_err(|e| CommandError::InvalidArgument(e.to_string()))?;

// create the amendment from the proposal
let amendment = ContractAmendmentFileFormat {
proposal_id: update_proposal.proposal_id,
validator_committee: update_proposal.updated_constitution.validator_committee.clone(),
// TODO: import the real signatures for all the proposal acceptances
validator_signatures: Vec::new(),
updated_constitution: update_proposal.updated_constitution,
activation_window,
};

// write the amendment to the destination file
let file = File::create(&dest).map_err(|e| CommandError::JsonFile(e.to_string()))?;
let writer = BufWriter::new(file);
serde_json::to_writer_pretty(writer, &amendment).map_err(|e| CommandError::JsonFile(e.to_string()))?;
println!("Wrote {}", dest.to_string_lossy());

Ok(())
}

async fn publish_contract_definition(wallet: &WalletSqlite, args: PublishFileArgs) -> Result<(), CommandError> {
// open the JSON file with the contract definition values
let file = File::open(&args.file_path).map_err(|e| CommandError::JsonFile(e.to_string()))?;
Expand Down
42 changes: 42 additions & 0 deletions applications/tari_console_wallet/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,12 @@ pub enum ContractSubcommand {
/// A generator for constitution files that can be edited and passed to other contract commands
InitConstitution(InitConstitutionArgs),

/// A generator for update proposal files that can be edited and passed to other contract commands
InitUpdateProposal(InitUpdateProposalArgs),

/// A generator for amendment files that can be edited and passed to other contract commands
InitAmendment(InitAmendmentArgs),

/// Creates and publishes a contract definition UTXO from the JSON spec file.
PublishDefinition(PublishFileArgs),

Expand Down Expand Up @@ -258,6 +264,42 @@ pub struct InitConstitutionArgs {
pub minimum_quorum_required: Option<String>,
}

#[derive(Debug, Args, Clone)]
pub struct InitUpdateProposalArgs {
/// The destination path of the contract definition to create
pub dest_path: PathBuf,
/// Force overwrite the destination file if it already exists
#[clap(short = 'f', long)]
pub force: bool,
#[clap(long, alias = "id")]
pub contract_id: Option<String>,
#[clap(long, alias = "proposal_id")]
pub proposal_id: Option<String>,
#[clap(long, alias = "committee")]
pub validator_committee: Option<Vec<String>>,
#[clap(long, alias = "acceptance_period")]
pub acceptance_period_expiry: Option<String>,
#[clap(long, alias = "quorum_required")]
pub minimum_quorum_required: Option<String>,
}

#[derive(Debug, Args, Clone)]
pub struct InitAmendmentArgs {
/// The destination path of the contract amendment to create
pub dest_path: PathBuf,

/// Force overwrite the destination file if it already exists
#[clap(short = 'f', long)]
pub force: bool,

/// The source file path of the update proposal to amend
#[clap(short = 'p', long)]
pub proposal_file_path: PathBuf,

#[clap(long, alias = "activation_window")]
pub activation_window: Option<String>,
}

#[derive(Debug, Args, Clone)]
pub struct PublishFileArgs {
pub file_path: PathBuf,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,16 @@ impl TryFrom<SignatureFileFormat> for Signature {
Ok(Signature::new(public_key, signature))
}
}

impl Default for SignatureFileFormat {
fn default() -> Self {
let default_sig = Signature::default();
let public_nonce = default_sig.get_public_nonce().to_hex();
let signature = default_sig.get_signature().to_hex();

Self {
public_nonce,
signature,
}
}
}
2 changes: 1 addition & 1 deletion base_layer/wallet/src/assets/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,4 @@ mod contract_update_proposal_file_format;
pub use constitution_definition_file_format::{ConstitutionChangeRulesFileFormat, ConstitutionDefinitionFileFormat};
pub use contract_amendment_file_format::ContractAmendmentFileFormat;
pub use contract_definition_file_format::{ContractDefinitionFileFormat, ContractSpecificationFileFormat};
pub use contract_update_proposal_file_format::ContractUpdateProposalFileFormat;
pub use contract_update_proposal_file_format::{ContractUpdateProposalFileFormat, SignatureFileFormat};

0 comments on commit 40cbd50

Please sign in to comment.