Skip to content

Commit

Permalink
Enable modifying Block Issuer Keys in CLI Wallet (#2235)
Browse files Browse the repository at this point in the history
* Enable adding block issuer keys in cli wallet

* Add Modify Block Issuer Keys in AccountChange

Co-authored-by: Thoralf-M <[email protected]>

* Add remove block issuer key command

* Fix incorrect info message

* Add missing comment on enum variant

* Add missing "for" in doc comment

* Rename `account` -> `account_id`

* Refactor account transition requirement

Co-authored-by: Thoralf-M <[email protected]>

* Update sdk/src/wallet/operations/transaction/high_level/account_block_issuer_keys.rs

Co-authored-by: Thoralf-M <[email protected]>

* Avoid clone of account_id

* Remove required_inputs from begin staking as it's handled by the transition

---------

Co-authored-by: Thoralf-M <[email protected]>
Co-authored-by: Thoralf Müller <[email protected]>
  • Loading branch information
3 people authored Apr 29, 2024
1 parent e046ae5 commit e5e80d3
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 5 deletions.
76 changes: 73 additions & 3 deletions cli/src/wallet_cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use iota_sdk::{
address::{AccountAddress, Bech32Address, ToBech32Ext},
mana::ManaAllotment,
output::{
feature::{BlockIssuerKeySource, MetadataFeature},
feature::{BlockIssuerKeySource, Ed25519PublicKeyHashBlockIssuerKey, MetadataFeature},
unlock_condition::AddressUnlockCondition,
AccountId, BasicOutputBuilder, DelegationId, FoundryId, NativeToken, NativeTokensBuilder, NftId, Output,
OutputId, TokenId,
Expand All @@ -26,8 +26,8 @@ use iota_sdk::{
utils::ConvertTo,
wallet::{
types::OutputData, BeginStakingParams, ConsolidationParams, CreateDelegationParams, CreateNativeTokenParams,
MintNftParams, OutputsToClaim, ReturnStrategy, SendManaParams, SendNativeTokenParams, SendNftParams,
SendParams, SyncOptions, Wallet, WalletError,
MintNftParams, ModifyAccountBlockIssuerKey, OutputsToClaim, ReturnStrategy, SendManaParams,
SendNativeTokenParams, SendNftParams, SendParams, SyncOptions, Wallet, WalletError,
},
U256,
};
Expand Down Expand Up @@ -196,6 +196,22 @@ pub enum WalletCommand {
},
/// Lists the implicit accounts of the wallet.
ImplicitAccounts,
/// Adds a block issuer key to an account.
AddBlockIssuerKey {
/// The account to which the key should be added.
account_id: AccountId,
/// The hex-encoded public key to add.
// TODO: Use the actual type somehow?
block_issuer_key: String,
},
/// Removes a block issuer key from an account.
RemoveBlockIssuerKey {
/// The account from which the key should be removed.
account_id: AccountId,
/// The hex-encoded public key to remove.
// TODO: Use the actual type somehow?
block_issuer_key: String,
},
/// Mint additional native tokens.
MintNativeToken {
/// Token ID to be minted, e.g. 0x087d205988b733d97fb145ae340e27a8b19554d1ceee64574d7e5ff66c45f69e7a0100000000.
Expand Down Expand Up @@ -924,6 +940,46 @@ pub async fn implicit_accounts_command(wallet: &Wallet) -> Result<(), Error> {
Ok(())
}

// `add-block-issuer-key` command
pub async fn add_block_issuer_key(wallet: &Wallet, account_id: AccountId, issuer_key: &str) -> Result<(), Error> {
let issuer_key: [u8; Ed25519PublicKeyHashBlockIssuerKey::LENGTH] = prefix_hex::decode(issuer_key)?;
let params = ModifyAccountBlockIssuerKey {
account_id,
keys_to_add: vec![Ed25519PublicKeyHashBlockIssuerKey::new(issuer_key).into()],
keys_to_remove: vec![],
};

let transaction = wallet.modify_account_output_block_issuer_keys(params, None).await?;

println_log_info!(
"Block issuer key adding transaction sent:\n{:?}\n{:?}",
transaction.transaction_id,
transaction.block_id
);

Ok(())
}

// `remove-block-issuer-key` command
pub async fn remove_block_issuer_key(wallet: &Wallet, account_id: AccountId, issuer_key: &str) -> Result<(), Error> {
let issuer_key: [u8; Ed25519PublicKeyHashBlockIssuerKey::LENGTH] = prefix_hex::decode(issuer_key)?;
let params = ModifyAccountBlockIssuerKey {
account_id,
keys_to_add: vec![],
keys_to_remove: vec![Ed25519PublicKeyHashBlockIssuerKey::new(issuer_key).into()],
};

let transaction = wallet.modify_account_output_block_issuer_keys(params, None).await?;

println_log_info!(
"Block issuer key removing transaction sent:\n{:?}\n{:?}",
transaction.transaction_id,
transaction.block_id
);

Ok(())
}

// `melt-native-token` command
pub async fn melt_native_token_command(wallet: &Wallet, token_id: TokenId, amount: U256) -> Result<(), Error> {
let transaction = wallet.melt_native_token(token_id, amount, None).await?;
Expand Down Expand Up @@ -1580,6 +1636,20 @@ pub async fn prompt_internal(
implicit_account_transition_command(wallet, output_id).await
}
WalletCommand::ImplicitAccounts => implicit_accounts_command(wallet).await,
WalletCommand::AddBlockIssuerKey {
account_id,
block_issuer_key,
} => {
ensure_password(wallet).await?;
add_block_issuer_key(wallet, account_id, &block_issuer_key).await
}
WalletCommand::RemoveBlockIssuerKey {
account_id,
block_issuer_key,
} => {
ensure_password(wallet).await?;
remove_block_issuer_key(wallet, account_id, &block_issuer_key).await
}
WalletCommand::MeltNativeToken { token_id, amount } => {
ensure_password(wallet).await?;
melt_native_token_command(wallet, token_id, amount).await
Expand Down
2 changes: 2 additions & 0 deletions sdk/src/client/api/block_builder/transaction_builder/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ pub enum TransactionBuilderError {
NoAvailableInputsProvided,
#[error("account {0} is not staking")]
NotStaking(AccountId),
#[error("account {0} has no block issuer feature")]
MissingBlockIssuerFeature(AccountId),
/// Required input is not available.
#[error("required input {0} is not available")]
RequiredInputIsNotAvailable(OutputId),
Expand Down
12 changes: 11 additions & 1 deletion sdk/src/client/api/block_builder/transaction_builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,13 @@ impl TransactionBuilder {
// Gets requirements from burn.
self.burn_requirements()?;

// Add requirements from transitions.
if let Some(transitions) = &self.transitions {
for account_id in transitions.accounts().keys() {
self.requirements.push(Requirement::Account(*account_id));
}
}

Ok(())
}

Expand All @@ -331,7 +338,10 @@ impl TransactionBuilder {
// If burn or mana allotments are provided, outputs will be added later, in the other cases it will just
// create remainder outputs.
if !self.provided_outputs.is_empty()
|| (self.burn.is_none() && self.mana_allotments.is_empty() && self.required_inputs.is_empty())
|| (self.burn.is_none()
&& self.mana_allotments.is_empty()
&& self.required_inputs.is_empty()
&& self.transitions.is_none())
{
return Err(TransactionBuilderError::InvalidOutputCount(self.provided_outputs.len()));
}
Expand Down
24 changes: 24 additions & 0 deletions sdk/src/client/api/block_builder/transaction_builder/transition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,24 @@ impl TransactionBuilder {
}
features.retain(|f| !f.is_staking());
}
AccountChange::ModifyBlockIssuerKeys {
keys_to_add,
keys_to_remove,
} => {
if let Some(feature) = features.iter_mut().find(|f| f.is_block_issuer()) {
let block_issuer_feature = feature.as_block_issuer();
let updated_keys = block_issuer_feature
.block_issuer_keys()
.iter()
.filter(|k| !keys_to_remove.contains(k))
.chain(keys_to_add)
.cloned()
.collect::<Vec<BlockIssuerKey>>();
*feature = BlockIssuerFeature::new(block_issuer_feature.expiry_slot(), updated_keys)?.into();
} else {
return Err(TransactionBuilderError::MissingBlockIssuerFeature(account_id));
}
}
}
}

Expand Down Expand Up @@ -308,6 +326,12 @@ pub enum AccountChange {
additional_epochs: u32,
},
EndStaking,
ModifyBlockIssuerKeys {
/// The keys that will be added.
keys_to_add: Vec<BlockIssuerKey>,
/// The keys that will be removed.
keys_to_remove: Vec<BlockIssuerKey>,
},
}

/// A type to specify intended transitions.
Expand Down
1 change: 1 addition & 0 deletions sdk/src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ pub use self::{
},
transaction::{
high_level::{
account_block_issuer_keys::ModifyAccountBlockIssuerKey,
create_account::CreateAccountParams,
delegation::create::{
CreateDelegationParams, CreateDelegationTransaction, PreparedCreateDelegationTransaction,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use serde::{Deserialize, Serialize};

use crate::{
client::{
api::{
transaction_builder::{transition::AccountChange, Transitions},
PreparedTransactionData,
},
secret::SecretManage,
ClientError,
},
types::block::output::{feature::BlockIssuerKey, AccountId},
wallet::{operations::transaction::TransactionOptions, types::TransactionWithMetadata, Wallet, WalletError},
};

/// Params for `modify_account_output_block_issuer_keys()`
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ModifyAccountBlockIssuerKey {
pub account_id: AccountId,
/// The keys that will be added.
pub keys_to_add: Vec<BlockIssuerKey>,
/// The keys that will be removed.
pub keys_to_remove: Vec<BlockIssuerKey>,
}

impl<S: 'static + SecretManage> Wallet<S>
where
WalletError: From<S::Error>,
ClientError: From<S::Error>,
{
pub async fn modify_account_output_block_issuer_keys(
&self,
params: ModifyAccountBlockIssuerKey,
options: impl Into<Option<TransactionOptions>> + Send,
) -> Result<TransactionWithMetadata, WalletError> {
let options = options.into();
let prepared_transaction = self
.prepare_modify_account_output_block_issuer_keys(params, options.clone())
.await?;

self.sign_and_submit_transaction(prepared_transaction, options).await
}

/// Prepares the transaction for [Wallet::create_account_output()].
pub async fn prepare_modify_account_output_block_issuer_keys(
&self,
params: ModifyAccountBlockIssuerKey,
options: impl Into<Option<TransactionOptions>> + Send,
) -> Result<PreparedTransactionData, WalletError> {
log::debug!("[TRANSACTION] prepare_modify_account_output_block_issuer_keys");

let change = AccountChange::ModifyBlockIssuerKeys {
keys_to_add: params.keys_to_add,
keys_to_remove: params.keys_to_remove,
};

let account_id = params.account_id;

let mut options = options.into();
if let Some(options) = options.as_mut() {
if let Some(transitions) = options.transitions.take() {
options.transitions = Some(transitions.add_account(account_id, change));
}
} else {
options.replace(TransactionOptions {
transitions: Some(Transitions::new().add_account(account_id, change)),
..Default::default()
});
}

self.prepare_send_outputs(None, options).await
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::{
},
};

/// Params `create_account_output()`
/// Params for `create_account_output()`
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateAccountParams {
Expand Down
1 change: 1 addition & 0 deletions sdk/src/wallet/operations/transaction/high_level/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

pub(crate) mod account_block_issuer_keys;
pub(crate) mod allot_mana;
pub(crate) mod burning_melting;
pub(crate) mod create_account;
Expand Down

0 comments on commit e5e80d3

Please sign in to comment.