Skip to content

Commit

Permalink
ETCM-8753: implement deregister command (#318)
Browse files Browse the repository at this point in the history
  • Loading branch information
kpinter-iohk authored Dec 13, 2024
1 parent 5b529ad commit 81a1f5f
Show file tree
Hide file tree
Showing 10 changed files with 177 additions and 39 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 3 additions & 12 deletions toolkit/cli/smart-contracts-commands/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ pub enum SmartContractsCmd {
UpsertDParameter(d_parameter::UpsertDParameterCmd),
/// Register candidate
Register(register::RegisterCmd),
/// Deregister candidate
Deregister(register::DeregisterCmd),
}

#[derive(Clone, Debug, clap::Parser)]
Expand All @@ -34,6 +36,7 @@ impl SmartContractsCmd {
Self::GetScripts(cmd) => cmd.execute().await,
Self::UpsertDParameter(cmd) => cmd.execute().await,
Self::Register(cmd) => cmd.execute().await,
Self::Deregister(cmd) => cmd.execute().await,
}
}

Expand Down Expand Up @@ -76,18 +79,6 @@ pub(crate) fn parse_partnerchain_public_keys(
}
}

fn payment_signing_key_to_mainchain_address_hash(
payment_signing_key: MainchainPrivateKey,
) -> CmdResult<MainchainAddressHash> {
Ok(cardano_serialization_lib::PrivateKey::from_normal_bytes(&payment_signing_key.0)?
.to_public()
.hash()
.to_bytes()
.as_slice()
.try_into()
.map(MainchainAddressHash)?)
}

#[cfg(test)]
mod test {
use crate::parse_partnerchain_public_keys;
Expand Down
39 changes: 36 additions & 3 deletions toolkit/cli/smart-contracts-commands/src/register.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use jsonrpsee::http_client::HttpClient;
use partner_chains_cardano_offchain::{await_tx::FixedDelayRetries, register::run_register};
use partner_chains_cardano_offchain::{
await_tx::FixedDelayRetries,
register::{run_deregister, run_register},
};
use sidechain_domain::{
AdaBasedStaking, CandidateRegistration, MainchainPublicKey, MainchainSignature,
PermissionedCandidateData, SidechainSignature, UtxoId,
Expand All @@ -21,7 +24,7 @@ pub struct RegisterCmd {
long,
value_name = "PARTNERCHAIN_KEY:AURA_KEY:GRANDPA_KEY",
alias = "sidechain-public-keys",
value_parser=parse_partnerchain_public_keys
value_parser = parse_partnerchain_public_keys
)]
partnerchain_public_keys: PermissionedCandidateData,
#[arg(long, alias = "sidechain-signature")]
Expand All @@ -43,7 +46,7 @@ impl RegisterCmd {
},
partnerchain_pub_key: self.partnerchain_public_keys.sidechain_public_key,
partnerchain_signature: self.partnerchain_signature,
own_pkh: crate::payment_signing_key_to_mainchain_address_hash(payment_key.clone())?,
own_pkh: payment_key.to_pub_key_hash(),
registration_utxo: self.registration_utxo,
aura_pub_key: self.partnerchain_public_keys.aura_public_key,
grandpa_pub_key: self.partnerchain_public_keys.grandpa_public_key,
Expand All @@ -61,3 +64,33 @@ impl RegisterCmd {
Ok(())
}
}

#[derive(Clone, Debug, clap::Parser)]
pub struct DeregisterCmd {
#[clap(flatten)]
common_arguments: crate::CommonArguments,
#[arg(long)]
genesis_utxo: UtxoId,
#[arg(long)]
payment_key_file: String,
#[arg(long)]
spo_public_key: MainchainPublicKey,
}

impl DeregisterCmd {
pub async fn execute(self) -> crate::CmdResult<()> {
let payment_signing_key = read_private_key_from_file(&self.payment_key_file)?;
let client = HttpClient::builder().build(self.common_arguments.ogmios_url)?;

run_deregister(
self.genesis_utxo,
payment_signing_key,
self.spo_public_key,
&client,
FixedDelayRetries::two_minutes(),
)
.await?;

Ok(())
}
}
6 changes: 2 additions & 4 deletions toolkit/offchain/src/init_governance/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use ogmios_client::{
types::{OgmiosTx, OgmiosUtxo},
};
use partner_chains_plutus_data::version_oracle::VersionOracleDatum;
use sidechain_domain::{MainchainAddressHash, MainchainPrivateKey, McTxHash, UtxoId, UtxoIndex};
use sidechain_domain::{MainchainAddressHash, MainchainPrivateKey, UtxoId};

#[cfg(test)]
mod tests;
Expand Down Expand Up @@ -128,9 +128,7 @@ pub async fn run_init_governance<
let result = client.submit_transaction(&signed_transaction.to_bytes()).await?;
let tx_id = result.transaction.id;
log::info!("✅ Transaction submitted. ID: {}", hex::encode(result.transaction.id));
await_tx
.await_tx_output(client, UtxoId { tx_hash: McTxHash(tx_id), index: UtxoIndex(0) })
.await?;
await_tx.await_tx_output(client, UtxoId::new(tx_id, 0)).await?;

Ok((genesis_utxo.to_domain(), result.transaction))
}
Expand Down
120 changes: 115 additions & 5 deletions toolkit/offchain/src/register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use ogmios_client::{
query_ledger_state::{QueryLedgerState, QueryUtxoByUtxoId},
query_network::QueryNetwork,
transactions::Transactions,
types::{OgmiosTx, OgmiosUtxo},
types::OgmiosUtxo,
};
use partner_chains_plutus_data::registered_candidates::{
candidate_registration_to_plutus_data, RegisterValidatorDatum,
Expand All @@ -32,7 +32,7 @@ pub trait Register {
genesis_utxo: UtxoId,
candidate_registration: &CandidateRegistration,
payment_signing_key: MainchainPrivateKey,
) -> Result<Option<OgmiosTx>, OffchainError>;
) -> Result<Option<McTxHash>, OffchainError>;
}

impl<T> Register for T
Expand All @@ -44,7 +44,7 @@ where
genesis_utxo: UtxoId,
candidate_registration: &CandidateRegistration,
payment_signing_key: MainchainPrivateKey,
) -> Result<Option<OgmiosTx>, OffchainError> {
) -> Result<Option<McTxHash>, OffchainError> {
run_register(
genesis_utxo,
candidate_registration,
Expand All @@ -66,7 +66,7 @@ pub async fn run_register<
payment_signing_key: MainchainPrivateKey,
ogmios_client: &C,
await_tx: A,
) -> anyhow::Result<Option<OgmiosTx>> {
) -> anyhow::Result<Option<McTxHash>> {
let ctx = TransactionContext::for_payment_key(payment_signing_key.0, ogmios_client).await?;
let validator = crate::scripts_data::registered_candidates_scripts(genesis_utxo)?;
let validator_address = validator.address_bech32(ctx.network)?;
Expand Down Expand Up @@ -132,7 +132,93 @@ pub async fn run_register<
log::info!("✅ Transaction submitted. ID: {}", hex::encode(result.transaction.id));
await_tx.await_tx_output(ogmios_client, UtxoId::new(tx_id, 0)).await?;

Ok(Some(result.transaction))
Ok(Some(McTxHash(result.transaction.id)))
}

pub trait Deregister {
#[allow(async_fn_in_trait)]
async fn deregister(
&self,
genesis_utxo: UtxoId,
payment_signing_key: MainchainPrivateKey,
stake_ownership_pub_key: MainchainPublicKey,
) -> Result<Option<McTxHash>, OffchainError>;
}

impl<T> Deregister for T
where
T: QueryLedgerState + Transactions + QueryNetwork + QueryUtxoByUtxoId,
{
async fn deregister(
&self,
genesis_utxo: UtxoId,
payment_signing_key: MainchainPrivateKey,
stake_ownership_pub_key: MainchainPublicKey,
) -> Result<Option<McTxHash>, OffchainError> {
run_deregister(
genesis_utxo,
payment_signing_key,
stake_ownership_pub_key,
self,
FixedDelayRetries::two_minutes(),
)
.await
.map_err(|e| OffchainError::InternalError(e.to_string()))
}
}

pub async fn run_deregister<
C: QueryLedgerState + QueryNetwork + QueryUtxoByUtxoId + Transactions,
A: AwaitTx,
>(
genesis_utxo: UtxoId,
payment_signing_key: MainchainPrivateKey,
stake_ownership_pub_key: MainchainPublicKey,
ogmios_client: &C,
await_tx: A,
) -> anyhow::Result<Option<McTxHash>> {
let ctx = TransactionContext::for_payment_key(payment_signing_key.0, ogmios_client).await?;
let validator = crate::scripts_data::registered_candidates_scripts(genesis_utxo)?;
let validator_address = validator.address_bech32(ctx.network)?;
let all_registration_utxos = ogmios_client.query_utxos(&[validator_address]).await?;
let own_registrations = get_own_registrations(
payment_signing_key.to_pub_key_hash(),
stake_ownership_pub_key.clone(),
&all_registration_utxos,
);

if own_registrations.is_empty() {
log::info!("✅ Candidate is not registered.");
return Ok(None);
}

let own_registration_utxos = own_registrations.iter().map(|r| r.0.clone()).collect::<Vec<_>>();
let zero_ex_units = ExUnits::new(&0u64.into(), &0u64.into());
let tx = deregister_tx(&validator, &own_registration_utxos, &ctx, zero_ex_units)?;

let evaluate_response =
ogmios_client.evaluate_transaction(&tx.to_bytes()).await.map_err(|e| {
anyhow!(
"Evaluate candidate deregistration transaction request failed: {}, bytes: {}",
e,
hex::encode(tx.to_bytes())
)
})?;
let validator_redeemer_ex_units = get_first_validator_budget(evaluate_response)?;
let tx = deregister_tx(&validator, &own_registration_utxos, &ctx, validator_redeemer_ex_units)?;
let signed_tx = ctx.sign(&tx).to_bytes();
let result = ogmios_client.submit_transaction(&signed_tx).await.map_err(|e| {
anyhow!(
"Submit candidate deregistration transaction request failed: {}, bytes: {}",
e,
hex::encode(tx.to_bytes())
)
})?;
let tx_id = result.transaction.id;
log::info!("✅ Transaction submitted. ID: {}", hex::encode(result.transaction.id));
await_tx.await_tx_output(ogmios_client, UtxoId::new(tx_id, 0)).await?;

Ok(Some(McTxHash(result.transaction.id)))
}

fn get_own_registrations(
Expand Down Expand Up @@ -210,6 +296,30 @@ fn register_tx(
tx_builder.balance_update_and_build(ctx)
}

fn deregister_tx(
validator: &PlutusScript,
own_registration_utxos: &[OgmiosUtxo],
ctx: &TransactionContext,
validator_redeemer_ex_units: ExUnits,
) -> Result<Transaction, JsError> {
let config = crate::csl::get_builder_config(ctx)?;
let mut tx_builder = TransactionBuilder::new(&config);

{
let mut inputs = TxInputsBuilder::new();
for own_registration_utxo in own_registration_utxos {
inputs.add_script_utxo_input(
own_registration_utxo,
validator,
validator_redeemer_ex_units.clone(),
)?;
}
tx_builder.set_inputs(&inputs);
}

tx_builder.balance_update_and_build(ctx)
}

#[cfg(test)]
mod tests {
use super::register_tx;
Expand Down
2 changes: 1 addition & 1 deletion toolkit/offchain/tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ async fn run_register<T: QueryLedgerState + Transactions + QueryNetwork + QueryU
genesis_utxo: UtxoId,
partnerchain_signature: SidechainSignature,
client: &T,
) -> Option<OgmiosTx> {
) -> Option<McTxHash> {
let eve_utxos = client.query_utxos(&[EVE_ADDRESS.to_string()]).await.unwrap();
let registration_utxo = eve_utxos.first().unwrap().utxo_id();
client
Expand Down
14 changes: 1 addition & 13 deletions toolkit/partner-chains-cli/src/cardano_key.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::IOContext;
use anyhow::anyhow;
use sidechain_domain::{MainchainAddressHash, MainchainPrivateKey};
use sidechain_domain::MainchainPrivateKey;

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
Expand Down Expand Up @@ -45,15 +45,3 @@ pub(crate) fn get_mc_pkey_from_file(
) -> anyhow::Result<MainchainPrivateKey> {
Ok(MainchainPrivateKey(get_key_bytes_from_file(path, context)?))
}

pub(crate) fn get_mc_address_hash_from_pkey(pkey: &MainchainPrivateKey) -> MainchainAddressHash {
let csl_private_key = cardano_serialization_lib::PrivateKey::from_normal_bytes(&pkey.0)
.expect("Conversion is infallible");
let csl_public_key_hash = csl_private_key
.to_public()
.hash()
.to_bytes()
.try_into()
.expect("Bytes represent correct public key hash");
MainchainAddressHash(csl_public_key_hash)
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ fn get_private_key_and_key_hash<C: IOContext>(
let cardano_signig_key_file = config_fields::CARDANO_PAYMENT_SIGNING_KEY_FILE
.prompt_with_default_from_file_and_save(context);
let pkey = cardano_key::get_mc_pkey_from_file(&cardano_signig_key_file, context)?;
let addr_hash = cardano_key::get_mc_address_hash_from_pkey(&pkey);
let addr_hash = pkey.to_pub_key_hash();

Ok((pkey, addr_hash))
}
Expand Down
2 changes: 2 additions & 0 deletions toolkit/primitives/domain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ lazy_static = { workspace = true }
blake2b_simd = { workspace = true }
figment = { workspace = true, optional = true }
thiserror = { workspace = true, optional = true }
cardano-serialization-lib = { workspace = true, optional = true }

[dev-dependencies]
serde_json = { workspace = true }
Expand All @@ -38,6 +39,7 @@ std = [
"num-bigint/std",
"figment",
"thiserror",
"cardano-serialization-lib",
]
serde = [
"dep:serde",
Expand Down
15 changes: 15 additions & 0 deletions toolkit/primitives/domain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,21 @@ impl core::fmt::Debug for MainchainPrivateKey {
}
}

impl MainchainPrivateKey {
#[cfg(feature = "std")]
pub fn to_pub_key_hash(&self) -> MainchainAddressHash {
cardano_serialization_lib::PrivateKey::from_normal_bytes(&self.0)
.expect("Conversion cannot fail on valid MainchainPrivateKey values")
.to_public()
.hash()
.to_bytes()
.as_slice()
.try_into()
.map(MainchainAddressHash)
.expect("Conversion cannot fail as representation is the same")
}
}

impl TryFrom<Vec<u8>> for MainchainPublicKey {
type Error = &'static str;

Expand Down

0 comments on commit 81a1f5f

Please sign in to comment.