Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable modifying Block Issuer Keys in CLI Wallet #2235

Merged
merged 12 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
PhilippGackstatter marked this conversation as resolved.
Show resolved Hide resolved
/// 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,
DaughterOfMars marked this conversation as resolved.
Show resolved Hide resolved
},
/// 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: account_id.clone(),
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: account_id.clone(),
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
PhilippGackstatter marked this conversation as resolved.
Show resolved Hide resolved
}
WalletCommand::RemoveBlockIssuerKey {
account_id,
block_issuer_key,
} => {
ensure_password(wallet).await?;
remove_block_issuer_key(wallet, &account_id, &block_issuer_key).await
PhilippGackstatter marked this conversation as resolved.
Show resolved Hide resolved
}
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_block_issuer_key()`
PhilippGackstatter marked this conversation as resolved.
Show resolved Hide resolved
#[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
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use alloc::collections::BTreeSet;

use serde::{Deserialize, Serialize};

use crate::{
Expand Down Expand Up @@ -72,6 +74,12 @@ where
} else {
options.replace(TransactionOptions {
transitions: Some(Transitions::new().add_account(params.account_id, change)),
required_inputs: BTreeSet::from([self
.get_account_output(params.account_id)
.await
.ok_or(WalletError::AccountNotFound)?
.1
.output_id]),
..Default::default()
});
}
Expand Down