From d84f1138fa95924518257f901c289e9107aa996c Mon Sep 17 00:00:00 2001 From: mrnaveira <47919901+mrnaveira@users.noreply.github.com> Date: Wed, 15 Jun 2022 17:09:55 +0100 Subject: [PATCH 1/3] create init command for update proposal --- .../src/automation/commands.rs | 73 +++++++++++++++++++ applications/tari_console_wallet/src/cli.rs | 22 ++++++ .../contract_update_proposal_file_format.rs | 13 ++++ base_layer/wallet/src/assets/mod.rs | 2 +- 4 files changed, 109 insertions(+), 1 deletion(-) diff --git a/applications/tari_console_wallet/src/automation/commands.rs b/applications/tari_console_wallet/src/automation/commands.rs index 3b6dfb3357..383947e88e 100644 --- a/applications/tari_console_wallet/src/automation/commands.rs +++ b/applications/tari_console_wallet/src/automation/commands.rs @@ -67,6 +67,7 @@ use tari_wallet::{ ContractDefinitionFileFormat, ContractSpecificationFileFormat, ContractUpdateProposalFileFormat, + SignatureFileFormat, }, error::WalletError, output_manager_service::handle::OutputManagerHandle, @@ -89,6 +90,7 @@ use crate::{ ContractSubcommand, InitConstitutionArgs, InitDefinitionArgs, + InitUpdateProposalArgs, PublishFileArgs, }, utils::db::{CUSTOM_BASE_NODE_ADDRESS_KEY, CUSTOM_BASE_NODE_PUBLIC_KEY_KEY}, @@ -795,6 +797,7 @@ async fn handle_contract_definition_command( 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::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, @@ -899,6 +902,76 @@ 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::() + .map_err(|e| CommandError::InvalidArgument(e.to_string()))?; + let committee: Vec = 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::() + .map_err(|e| CommandError::InvalidArgument(e.to_string()))?, + minimum_quorum_required: minimum_quorum_required + .parse::() + .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(()) +} + 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()))?; diff --git a/applications/tari_console_wallet/src/cli.rs b/applications/tari_console_wallet/src/cli.rs index fa58c189f7..eb5de66489 100644 --- a/applications/tari_console_wallet/src/cli.rs +++ b/applications/tari_console_wallet/src/cli.rs @@ -213,6 +213,9 @@ 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), + /// Creates and publishes a contract definition UTXO from the JSON spec file. PublishDefinition(PublishFileArgs), @@ -255,6 +258,25 @@ pub struct InitConstitutionArgs { pub minimum_quorum_required: Option, } +#[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, + #[clap(long, alias = "proposal_id")] + pub proposal_id: Option, + #[clap(long, alias = "committee")] + pub validator_committee: Option>, + #[clap(long, alias = "acceptance_period")] + pub acceptance_period_expiry: Option, + #[clap(long, alias = "quorum_required")] + pub minimum_quorum_required: Option, +} + #[derive(Debug, Args, Clone)] pub struct PublishFileArgs { pub file_path: PathBuf, diff --git a/base_layer/wallet/src/assets/contract_update_proposal_file_format.rs b/base_layer/wallet/src/assets/contract_update_proposal_file_format.rs index ba451088d5..461138451f 100644 --- a/base_layer/wallet/src/assets/contract_update_proposal_file_format.rs +++ b/base_layer/wallet/src/assets/contract_update_proposal_file_format.rs @@ -64,3 +64,16 @@ impl TryFrom 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, + } + } +} diff --git a/base_layer/wallet/src/assets/mod.rs b/base_layer/wallet/src/assets/mod.rs index 5e30e27612..df5d26fcd2 100644 --- a/base_layer/wallet/src/assets/mod.rs +++ b/base_layer/wallet/src/assets/mod.rs @@ -37,4 +37,4 @@ mod contract_update_proposal_file_format; pub use constitution_definition_file_format::{ConstitutionChangeRulesFileFormat, ConstitutionDefinitionFileFormat}; 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}; From 2bf1f38a07e6168d85f043cb38a5da2a9248d525 Mon Sep 17 00:00:00 2001 From: mrnaveira <47919901+mrnaveira@users.noreply.github.com> Date: Wed, 15 Jun 2022 17:38:58 +0100 Subject: [PATCH 2/3] improve internal naming --- .../tari_console_wallet/src/automation/commands.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/applications/tari_console_wallet/src/automation/commands.rs b/applications/tari_console_wallet/src/automation/commands.rs index 383947e88e..b9a43a8067 100644 --- a/applications/tari_console_wallet/src/automation/commands.rs +++ b/applications/tari_console_wallet/src/automation/commands.rs @@ -746,8 +746,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?; }, } } @@ -790,10 +790,7 @@ 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), From 8fc8eb93f983695eb613d8ce4d3c6caa9a19d658 Mon Sep 17 00:00:00 2001 From: mrnaveira <47919901+mrnaveira@users.noreply.github.com> Date: Wed, 15 Jun 2022 18:41:02 +0100 Subject: [PATCH 3/3] create init command for amendments --- .../src/automation/commands.rs | 58 +++++++++++++++++++ applications/tari_console_wallet/src/cli.rs | 20 +++++++ 2 files changed, 78 insertions(+) diff --git a/applications/tari_console_wallet/src/automation/commands.rs b/applications/tari_console_wallet/src/automation/commands.rs index b52210577a..f2de54dc5d 100644 --- a/applications/tari_console_wallet/src/automation/commands.rs +++ b/applications/tari_console_wallet/src/automation/commands.rs @@ -90,6 +90,7 @@ use crate::{ CliCommands, ContractCommand, ContractSubcommand, + InitAmendmentArgs, InitConstitutionArgs, InitDefinitionArgs, InitUpdateProposalArgs, @@ -797,6 +798,7 @@ async fn handle_contract_command(wallet: &WalletSqlite, command: ContractCommand 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, @@ -972,6 +974,62 @@ fn init_contract_update_proposal_spec(args: InitUpdateProposalArgs) -> Result<() 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::() + .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()))?; diff --git a/applications/tari_console_wallet/src/cli.rs b/applications/tari_console_wallet/src/cli.rs index 52d63e8790..6fbb948294 100644 --- a/applications/tari_console_wallet/src/cli.rs +++ b/applications/tari_console_wallet/src/cli.rs @@ -216,6 +216,9 @@ pub enum ContractSubcommand { /// 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), @@ -280,6 +283,23 @@ pub struct InitUpdateProposalArgs { pub minimum_quorum_required: Option, } +#[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, +} + #[derive(Debug, Args, Clone)] pub struct PublishFileArgs { pub file_path: PathBuf,