diff --git a/toolkit/cli/smart-contracts-commands/src/lib.rs b/toolkit/cli/smart-contracts-commands/src/lib.rs index d7e627690..9222ad286 100644 --- a/toolkit/cli/smart-contracts-commands/src/lib.rs +++ b/toolkit/cli/smart-contracts-commands/src/lib.rs @@ -2,10 +2,11 @@ use log4rs::{ append::{console::ConsoleAppender, file::FileAppender}, config::Appender, }; -use sidechain_domain::MainchainPrivateKey; +use sidechain_domain::{AuraPublicKey, GrandpaPublicKey, MainchainPrivateKey, SidechainPublicKey}; pub mod get_scripts; pub mod init_governance; +pub mod register; #[derive(Clone, Debug, clap::Subcommand)] #[allow(clippy::large_enum_variant)] @@ -14,6 +15,8 @@ pub enum SmartContractsCmd { GetScripts(get_scripts::GetScripts), /// Initialize Partner Chain governance InitGovernance(init_governance::InitGovernanceCmd), + /// Register candidate + Register(register::RegisterCmd), } #[derive(Clone, Debug, clap::Parser)] @@ -30,6 +33,7 @@ impl SmartContractsCmd { match self { Self::InitGovernance(cmd) => cmd.execute().await, Self::GetScripts(cmd) => cmd.execute().await, + Self::Register(cmd) => cmd.execute().await, } } @@ -75,3 +79,20 @@ pub fn setup_logging() -> Result<(), Box> { Ok(()) } + +// Parses public keys in formatted as SIDECHAIN_KEY:AURA_KEY:GRANDPA_KEY +pub(crate) fn parse_sidechain_public_keys( + sidechain_public_keys: &str, +) -> CmdResult<(SidechainPublicKey, AuraPublicKey, GrandpaPublicKey)> { + if let [sidechain_pub_key, aura_pub_key, grandpa_pub_key] = + sidechain_public_keys.split(":").collect::>()[..] + { + Ok(( + SidechainPublicKey(hex::decode(sidechain_pub_key)?), + AuraPublicKey(hex::decode(aura_pub_key)?), + GrandpaPublicKey(hex::decode(grandpa_pub_key)?), + )) + } else { + Err("Failed to parse sidechain public keys.".into()) + } +} diff --git a/toolkit/cli/smart-contracts-commands/src/register.rs b/toolkit/cli/smart-contracts-commands/src/register.rs new file mode 100644 index 000000000..75efc5ee7 --- /dev/null +++ b/toolkit/cli/smart-contracts-commands/src/register.rs @@ -0,0 +1,59 @@ +use jsonrpsee::http_client::HttpClient; +use partner_chains_cardano_offchain::register::run_register; +use sidechain_domain::{ + AdaBasedStaking, AuraPublicKey, BlockProducerRegistration, GrandpaPublicKey, + MainchainPublicKey, MainchainSignature, SidechainPublicKey, SidechainSignature, UtxoId, +}; + +use crate::{parse_sidechain_public_keys, read_private_key_from_file}; + +#[derive(Clone, Debug, clap::Parser)] +pub struct RegisterCmd { + #[clap(flatten)] + common_arguments: crate::CommonArguments, + #[arg(long)] + genesis_utxo: UtxoId, + #[arg(long)] + registration_utxo: UtxoId, + #[arg(long)] + payment_key_file: String, + #[arg(long)] + stake_signing_key_file: String, + #[arg(long, value_name = "SIDECHAIN_KEY:AURA_KEY:GRANDPA_KEY", value_parser = parse_sidechain_public_keys)] + sidechain_public_keys: (SidechainPublicKey, AuraPublicKey, GrandpaPublicKey), + #[arg(long)] + sidechain_signature: SidechainSignature, + #[arg(long)] + spo_public_key: MainchainPublicKey, + #[arg(long)] + spo_signature: MainchainSignature, +} + +impl RegisterCmd { + pub async fn execute(self) -> crate::CmdResult<()> { + let payment_key = read_private_key_from_file(&self.payment_key_file)?; + let client = HttpClient::builder().build(self.common_arguments.ogmios_host)?; + let block_producer_registration = BlockProducerRegistration { + stake_ownership: AdaBasedStaking { + pub_key: self.spo_public_key, + signature: self.spo_signature, + }, + sidechain_pub_key: self.sidechain_public_keys.0, + sidechain_signature: self.sidechain_signature, + registration_utxo: self.registration_utxo, + aura_pub_key: self.sidechain_public_keys.1, + grandpa_pub_key: self.sidechain_public_keys.2, + }; + + run_register( + self.genesis_utxo, + &block_producer_registration, + self.registration_utxo, + payment_key, + &client, + ) + .await?; + + Ok(()) + } +} diff --git a/toolkit/offchain/src/lib.rs b/toolkit/offchain/src/lib.rs index acf32efa0..02b2b3d3a 100644 --- a/toolkit/offchain/src/lib.rs +++ b/toolkit/offchain/src/lib.rs @@ -15,7 +15,7 @@ pub mod permissioned_candidates; /// Utilities for handling Plutus script data mod plutus_script; /// Supports candidate registration -mod register; +pub mod register; /// Provides synthetized scripts data pub mod scripts_data; #[cfg(test)] diff --git a/toolkit/offchain/src/register.rs b/toolkit/offchain/src/register.rs index 97d091cad..471c725f7 100644 --- a/toolkit/offchain/src/register.rs +++ b/toolkit/offchain/src/register.rs @@ -5,9 +5,11 @@ use crate::csl::{ TransactionContext, }; use crate::plutus_script::PlutusScript; +use crate::OffchainError; use anyhow::anyhow; use cardano_serialization_lib::{ - BigNum, DataCost, Ed25519KeyHash, ExUnits, JsError, MinOutputAdaCalculator, PlutusData, Transaction, TransactionBuilder, TransactionOutputBuilder, TxInputsBuilder + BigNum, DataCost, Ed25519KeyHash, ExUnits, JsError, MinOutputAdaCalculator, PlutusData, + Transaction, TransactionBuilder, TransactionOutputBuilder, TxInputsBuilder, }; use ogmios_client::{ query_ledger_state::QueryLedgerState, query_network::QueryNetwork, transactions::Transactions, @@ -17,27 +19,63 @@ use partner_chains_plutus_data::registered_candidates::{ block_producer_registration_to_plutus_data, RegisterValidatorDatum, }; use sidechain_domain::{ - BlockProducerRegistration, MainchainAddressHash, MainchainPublicKey, McTxHash, UtxoId, + BlockProducerRegistration, MainchainAddressHash, MainchainPrivateKey, MainchainPublicKey, + McTxHash, UtxoId, }; -pub async fn register( +pub trait Register { + #[allow(async_fn_in_trait)] + async fn register( + &self, + genesis_utxo: UtxoId, + block_producer_registration: &BlockProducerRegistration, + registration_utxo: UtxoId, + payment_signing_key: MainchainPrivateKey, + ) -> Result; +} + +impl Register for T +where + T: QueryLedgerState + Transactions + QueryNetwork, +{ + async fn register( + &self, + genesis_utxo: UtxoId, + block_producer_registration: &BlockProducerRegistration, + registration_utxo: UtxoId, + payment_signing_key: MainchainPrivateKey, + ) -> Result { + run_register( + genesis_utxo, + block_producer_registration, + registration_utxo, + payment_signing_key, + self, + ) + .await + .map_err(|e| OffchainError::InternalError(e.to_string())) + } +} + +pub async fn run_register( genesis_utxo: UtxoId, block_producer_registration: &BlockProducerRegistration, - input_utxo: UtxoId, - payment_signing_key: [u8; 32], + registration_utxo: UtxoId, + payment_signing_key: MainchainPrivateKey, ogmios_client: &C, ) -> anyhow::Result { - let ctx = TransactionContext::for_payment_key(payment_signing_key, ogmios_client).await?; + let ctx = TransactionContext::for_payment_key(payment_signing_key.0, ogmios_client).await?; + let own_pkh = ed25519_key_hash_to_mainchain_address_hash(ctx.payment_key_hash()); let validator = crate::scripts_data::registered_candidates_scripts(genesis_utxo)?; let validator_address = validator.address_bech32(ctx.network)?; - let input_utxo = ctx + let registration_utxo = ctx .payment_key_utxos .iter() - .find(|u| u.to_domain() == input_utxo) - .ok_or(anyhow!("input utxo not found at payment address"))?; + .find(|u| u.to_domain() == registration_utxo) + .ok_or(anyhow!("registration utxo not found at payment address"))?; let all_registration_utxos = ogmios_client.query_utxos(&[validator_address]).await?; let own_registrations = get_own_registrations( - ed25519_key_hash_to_mainchain_address_hash(ctx.payment_key_hash()), + own_pkh, block_producer_registration.stake_ownership.pub_key.clone(), &all_registration_utxos, )?; @@ -52,8 +90,9 @@ pub async fn register( let zero_ex_units = ExUnits::new(&0u64.into(), &0u64.into()); let tx = register_tx( &validator, + own_pkh, block_producer_registration, - input_utxo, + registration_utxo, &own_registration_utxos, &ctx, zero_ex_units, @@ -70,8 +109,9 @@ pub async fn register( let validator_redeemer_ex_units = get_first_validator_budget(evaluate_response)?; let tx = register_tx( &validator, + own_pkh, block_producer_registration, - input_utxo, + registration_utxo, &own_registration_utxos, &ctx, validator_redeemer_ex_units, @@ -89,18 +129,17 @@ pub async fn register( Ok(McTxHash(res.transaction.id)) } - - fn ed25519_key_hash_to_mainchain_address_hash(value: Ed25519KeyHash) -> MainchainAddressHash { - // Ed25519KeyHash is represented as [u8;28], same as MainchainAddressHash, - // but it is private and can only be accessed as Vec so we need to do this - MainchainAddressHash( - value - .to_bytes() - .as_slice() - .try_into() - .expect("impossible: Ed25519KeyHash failed to convert to MainchainAddressHash"), - ) - } +fn ed25519_key_hash_to_mainchain_address_hash(value: Ed25519KeyHash) -> MainchainAddressHash { + // Ed25519KeyHash is represented as [u8;28], same as MainchainAddressHash, + // but it is private and can only be accessed as Vec so we need to do this + MainchainAddressHash( + value + .to_bytes() + .as_slice() + .try_into() + .expect("impossible: Ed25519KeyHash failed to convert to MainchainAddressHash"), + ) +} fn get_own_registrations( own_pkh: MainchainAddressHash, @@ -115,14 +154,16 @@ fn get_own_registrations( let datum_plutus_data = PlutusData::from_bytes(datum.bytes).map_err(|e| { anyhow!("Internal error: could not decode datum of validator script: {}", e) })?; - let block_producer_registration: BlockProducerRegistration = - RegisterValidatorDatum::try_from(datum_plutus_data) - .map_err(|e| { - anyhow!("Internal error: could not decode datum of validator script: {}", e) - })? - .into(); + let (own_pkh_from_datum, block_producer_registration): ( + MainchainAddressHash, + BlockProducerRegistration, + ) = RegisterValidatorDatum::try_from(datum_plutus_data) + .map_err(|e| { + anyhow!("Internal error: could not decode datum of validator script: {}", e) + })? + .into(); if block_producer_registration.stake_ownership.pub_key == spo_pub_key - && block_producer_registration.own_pkh == own_pkh + && own_pkh_from_datum == own_pkh { own_registrations.push((validator_utxo.clone(), block_producer_registration)) } @@ -132,6 +173,7 @@ fn get_own_registrations( fn register_tx( validator: &PlutusScript, + own_pkh: MainchainAddressHash, block_producer_registration: &BlockProducerRegistration, registration_utxo: &OgmiosUtxo, own_registration_utxos: &[OgmiosUtxo], @@ -155,7 +197,8 @@ fn register_tx( } { - let datum = block_producer_registration_to_plutus_data(block_producer_registration); + let datum = + block_producer_registration_to_plutus_data(own_pkh, block_producer_registration); let amount_builder = TransactionOutputBuilder::new() .with_address(&validator.address(ctx.network)) .with_plutus_data(&datum) @@ -205,6 +248,10 @@ mod tests { const MIN_UTXO_LOVELACE: u64 = 1000000; const FIVE_ADA: u64 = 5000000; + fn own_pkh() -> MainchainAddressHash { + MainchainAddressHash([0; 28]) + } + fn block_producer_registration(registration_utxo: UtxoId) -> BlockProducerRegistration { BlockProducerRegistration { stake_ownership: AdaBasedStaking { @@ -214,7 +261,6 @@ mod tests { sidechain_pub_key: SidechainPublicKey(Vec::new()), sidechain_signature: SidechainSignature(Vec::new()), registration_utxo, - own_pkh: MainchainAddressHash([0; 28]), aura_pub_key: AuraPublicKey(Vec::new()), grandpa_pub_key: GrandpaPublicKey(Vec::new()), } @@ -254,6 +300,7 @@ mod tests { block_producer_registration(registration_utxo.to_domain()); let tx = register_tx( &test_values::test_validator(), + own_pkh(), &block_producer_registration, registration_utxo, &own_registration_utxos, @@ -283,7 +330,7 @@ mod tests { ); assert_eq!( script_output.plutus_data().unwrap(), - block_producer_registration_to_plutus_data(&block_producer_registration) + block_producer_registration_to_plutus_data(own_pkh(), &block_producer_registration) ); } @@ -305,6 +352,7 @@ mod tests { }; let tx = register_tx( &test_values::test_validator(), + own_pkh(), &block_producer_registration, registration_utxo, &own_registration_utxos, @@ -319,6 +367,7 @@ mod tests { fee_is_less_than_one_and_half_ada(&tx); output_at_validator_has_register_candidate_datum( &tx, + own_pkh(), &block_producer_registration, validator_address, ); @@ -362,6 +411,7 @@ mod tests { fn output_at_validator_has_register_candidate_datum( tx: &Transaction, + own_pkh: MainchainAddressHash, block_producer_registration: &BlockProducerRegistration, validator_address: &Address, ) { @@ -370,7 +420,7 @@ mod tests { outputs.into_iter().find(|o| o.address() == *validator_address).unwrap(); assert_eq!( validator_output.plutus_data().unwrap(), - block_producer_registration_to_plutus_data(block_producer_registration) + block_producer_registration_to_plutus_data(own_pkh, block_producer_registration) ); } diff --git a/toolkit/primitives/domain/src/lib.rs b/toolkit/primitives/domain/src/lib.rs index 645f6bb18..8085314db 100644 --- a/toolkit/primitives/domain/src/lib.rs +++ b/toolkit/primitives/domain/src/lib.rs @@ -267,6 +267,16 @@ impl MainchainAddressHash { #[byte_string(debug, hex_serialize)] pub struct MainchainSignature(pub Vec); +impl FromStr for MainchainSignature { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + let bytes_vec = + from_hex(s).map_err(|_| "Mainchain Signature must be a valid hex string")?; + Ok(MainchainSignature(bytes_vec)) + } +} + #[derive( Clone, Copy, @@ -314,6 +324,16 @@ pub struct SidechainPublicKey(pub Vec); #[byte_string(debug, hex_serialize, hex_deserialize)] pub struct SidechainSignature(pub Vec); +impl FromStr for SidechainSignature { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + let bytes_vec = + from_hex(s).map_err(|_| "Mainchain Signature must be a valid hex string")?; + Ok(SidechainSignature(bytes_vec)) + } +} + #[derive(Clone, Encode, Decode, PartialEq, Eq, TypeInfo)] #[byte_string(debug, hex_serialize)] pub struct CrossChainPublicKey(pub Vec); @@ -636,7 +656,7 @@ pub struct BlockProducerRegistration { pub sidechain_pub_key: SidechainPublicKey, pub sidechain_signature: SidechainSignature, pub registration_utxo: UtxoId, - pub own_pkh: MainchainAddressHash, + // pub own_pkh: MainchainAddressHash, pub aura_pub_key: AuraPublicKey, pub grandpa_pub_key: GrandpaPublicKey, } diff --git a/toolkit/primitives/plutus-data/src/registered_candidates.rs b/toolkit/primitives/plutus-data/src/registered_candidates.rs index caecf8a92..0e8603677 100644 --- a/toolkit/primitives/plutus-data/src/registered_candidates.rs +++ b/toolkit/primitives/plutus-data/src/registered_candidates.rs @@ -76,6 +76,7 @@ impl VersionedDatum for RegisterValidatorDatum { } pub fn block_producer_registration_to_plutus_data( + own_pkh: MainchainAddressHash, block_producer_registration: &sidechain_domain::BlockProducerRegistration, ) -> PlutusData { RegisterValidatorDatum::V0 { @@ -83,14 +84,14 @@ pub fn block_producer_registration_to_plutus_data( sidechain_pub_key: block_producer_registration.sidechain_pub_key.clone(), sidechain_signature: block_producer_registration.sidechain_signature.clone(), registration_utxo: block_producer_registration.registration_utxo, - own_pkh: block_producer_registration.own_pkh, + own_pkh, aura_pub_key: block_producer_registration.aura_pub_key.clone(), grandpa_pub_key: block_producer_registration.grandpa_pub_key.clone(), } .into() } -impl From for BlockProducerRegistration { +impl From for (MainchainAddressHash, BlockProducerRegistration) { fn from(value: RegisterValidatorDatum) -> Self { match value { RegisterValidatorDatum::V0 { @@ -101,15 +102,17 @@ impl From for BlockProducerRegistration { own_pkh, aura_pub_key, grandpa_pub_key, - } => BlockProducerRegistration { - stake_ownership, - sidechain_pub_key, - sidechain_signature, - registration_utxo, + } => ( own_pkh, - aura_pub_key, - grandpa_pub_key, - }, + BlockProducerRegistration { + stake_ownership, + sidechain_pub_key, + sidechain_signature, + registration_utxo, + aura_pub_key, + grandpa_pub_key, + }, + ), } } }