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

feat: deprecate --unsigned flag in favor of --default-signer for forc-deploy/run #4957

Merged
merged 4 commits into from
Aug 16, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
16 changes: 11 additions & 5 deletions docs/book/src/forc/plugins/forc_client/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ After you have created a wallet, you can derive a new account by running `forc w

To submit the transactions created by `forc deploy` or `forc run`, you need to sign them first (unless you are using a client without UTXO validation). To sign a transaction you can use `forc-wallet` CLI. This section is going to walk you through the whole signing process.

By default `fuel-core` runs without UTXO validation, which means you can run unsigned transactions. This allows you to send invalid inputs to emulate different conditions.
By default `fuel-core` runs without UTXO validation, this allows you to send invalid inputs to emulate different conditions.

If you want to run `fuel-core` with UTXO validation, you can pass `--utxo-validation` to `fuel-core run`. If UTXO validation is enabled, unsigned transactions will return an error.
If you want to run `fuel-core` with UTXO validation, you can pass `--utxo-validation` to `fuel-core run`.

To install `forc-wallet` please refer to `forc-wallet`'s [github repo](https://github.com/FuelLabs/forc-wallet#forc-wallet).

Expand All @@ -38,16 +38,22 @@ forc wallet accounts
forc wallet account <account_index>
```

> If you don't want to sign the transaction generated by `forc-deploy` or `forc-run` you can pass `--unsigned` to them.
> If you want to sign the transaction generated by `forc-deploy` or `forc-run` with an account funded by default once you start your local node, you can pass `--default-signer` to them. Please note that this will only work against your local node.
>
> ```sh
> forc-deploy --unsigned
> forc-deploy --default-signer
> ```
>
> ```sh
> forc-run --unsigned
> forc-run --default-signer
> ```

By default `--default-signer` flag would sign your transactions with the following private-key:

```sh
0xde97d8624a438121b86a1956544bd72ed68cd69f2c99555b08b1e8c51ffd511c
```

## Interacting with the testnet

While using `forc-deploy` or `forc-run` to interact with the testnet you need to pass the testnet end point with `--node-url`
Expand Down
5 changes: 4 additions & 1 deletion forc-plugins/forc-client/src/cmd/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ pub struct Command {
/// to [`crate::default::NODE_URL`].
#[clap(long, env = "FUEL_NODE_URL")]
pub node_url: Option<String>,
/// Do not sign the transaction
/// Sign the transaction with default signer that is pre-funded by fuel-core. Useful for testing against local node.
#[clap(long)]
pub default_signer: bool,
/// Deprecated in favor of `--default-signer`.
#[clap(long)]
pub unsigned: bool,
/// Set the key to be used for signing.
Expand Down
5 changes: 4 additions & 1 deletion forc-plugins/forc-client/src/cmd/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ pub struct Command {
/// in the node's view of the blockchain, (i.e. it does not affect the chain state).
#[clap(long)]
pub simulate: bool,
/// Do not sign the transaction
/// Sign the transaction with default signer that is pre-funded by fuel-core. Useful for testing against local node.
#[clap(long)]
pub default_signer: bool,
/// Deprecated in favor of `--default-signer`.
#[clap(long)]
pub unsigned: bool,
/// Set the key to be used for signing.
Expand Down
10 changes: 7 additions & 3 deletions forc-plugins/forc-client/src/op/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use std::time::Duration;
use std::{collections::BTreeMap, path::PathBuf};
use sway_core::language::parsed::TreeType;
use sway_core::BuildTarget;
use tracing::info;
use tracing::{info, warn};

#[derive(Debug)]
pub struct DeployedContract {
Expand Down Expand Up @@ -78,7 +78,11 @@ fn validate_and_parse_salts<'a>(
///
/// When deploying a single contract, only that contract's ID is returned.
pub async fn deploy(command: cmd::Deploy) -> Result<Vec<DeployedContract>> {
let command = apply_target(command)?;
let mut command = apply_target(command)?;
if command.unsigned {
warn!(" Warning: --unsigned flag is deprecated, please prefer using --default-signer. Assuming `--default-signer` is passed. This means your transaction will be signed by an account that is funded by fuel-core by default for testing purposes.");
command.default_signer = true;
}
let mut contract_ids = Vec::new();
let curr_dir = if let Some(ref path) = command.pkg.path {
PathBuf::from(path)
Expand Down Expand Up @@ -234,7 +238,7 @@ pub async fn deploy_pkg(
.add_output(Output::contract_created(contract_id, state_root))
.finalize_signed(
client.clone(),
command.unsigned,
command.default_signer,
command.signing_key,
wallet_mode,
)
Expand Down
9 changes: 7 additions & 2 deletions forc-plugins/forc-client/src/op/run/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use std::{path::PathBuf, str::FromStr};
use sway_core::language::parsed::TreeType;
use sway_core::BuildTarget;
use tokio::time::timeout;
use tracing::info;
use tracing::{info, warn};

use self::encode::ScriptCallHandler;

Expand All @@ -32,6 +32,11 @@ pub struct RanScript {
///
/// When running a single script, only that script's receipts are returned.
pub async fn run(command: cmd::Run) -> Result<Vec<RanScript>> {
let mut command = command;
if command.unsigned {
warn!(" Warning: --unsigned flag is deprecated, please prefer using --default-signer. Assuming `--default-signer` is passed. This means your transaction will be signed by an account that is funded by fuel-core by default for testing purposes.");
command.default_signer = true;
}
let mut receipts = Vec::new();
let curr_dir = if let Some(path) = &command.pkg.path {
PathBuf::from(path)
Expand Down Expand Up @@ -110,7 +115,7 @@ pub async fn run_pkg(
.add_contracts(contract_ids)
.finalize_signed(
client.clone(),
command.unsigned,
command.default_signer,
command.signing_key,
wallet_mode,
)
Expand Down
193 changes: 100 additions & 93 deletions forc-plugins/forc-client/src/util/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ use forc_wallet::{
/// The maximum time to wait for a transaction to be included in a block by the node
pub const TX_SUBMIT_TIMEOUT_MS: u64 = 30_000u64;

/// Default PrivateKey to sign transactions submitted to local node.
pub const DEFAULT_PRIVATE_KEY: &str =
"0xde97d8624a438121b86a1956544bd72ed68cd69f2c99555b08b1e8c51ffd511c";

#[derive(PartialEq, Eq)]
pub enum WalletSelectionMode {
ForcWallet,
Manual,
Expand Down Expand Up @@ -161,101 +166,114 @@ impl<Tx: Buildable + SerializableVec + field::Witnesses + Send> TransactionBuild
async fn finalize_signed(
&mut self,
client: FuelClient,
unsigned: bool,
default_sign: bool,
signing_key: Option<SecretKey>,
wallet_mode: WalletSelectionMode,
) -> Result<Tx> {
let params = client.chain_info().await?.consensus_parameters.into();
let mut signature_witness_index = 0u8;
let signing_key = if !unsigned {
let key = match (wallet_mode, signing_key) {
(WalletSelectionMode::ForcWallet, None) => {
// TODO: This is a very simple TUI, we should consider adding a nice TUI
// capabilities for selections and answer collection.
let wallet_path = default_wallet_path();
if !wallet_path.exists() {
let question = format!("Could not find a wallet at {wallet_path:?}, would you like to create a new one? [y/N]: ");
let accepted = ask_user_yes_no_question(&question)?;
if accepted {
new_wallet_cli(&wallet_path)?;
println!("Wallet created successfully.")
} else {
anyhow::bail!("Refused to create a new wallet. If you don't want to use forc-wallet, you can sign this transaction manually with --manual-signing flag.")
}
let signing_key = match (wallet_mode, signing_key, default_sign) {
(WalletSelectionMode::ForcWallet, None, false) => {
// TODO: This is a very simple TUI, we should consider adding a nice TUI
// capabilities for selections and answer collection.
let wallet_path = default_wallet_path();
if !wallet_path.exists() {
let question = format!("Could not find a wallet at {wallet_path:?}, would you like to create a new one? [y/N]: ");
let accepted = ask_user_yes_no_question(&question)?;
if accepted {
new_wallet_cli(&wallet_path)?;
println!("Wallet created successfully.")
} else {
anyhow::bail!("Refused to create a new wallet. If you don't want to use forc-wallet, you can sign this transaction manually with --manual-signing flag.")
}
let prompt = format!(
}
let prompt = format!(
"\nPlease provide the password of your encrypted wallet vault at {wallet_path:?}: "
);
let password = rpassword::prompt_password(prompt)?;
let verification = AccountVerification::Yes(password.clone());
let accounts = collect_accounts_with_verification(&wallet_path, verification)?;
let provider = Provider::new(client.clone(), params);
let account_balances = collect_account_balances(&accounts, &provider).await?;
let password = rpassword::prompt_password(prompt)?;
let verification = AccountVerification::Yes(password.clone());
let accounts = collect_accounts_with_verification(&wallet_path, verification)?;
let provider = Provider::new(client.clone(), params);
let account_balances = collect_account_balances(&accounts, &provider).await?;

let total_balance = account_balances
.iter()
.flat_map(|account| account.values())
.sum::<u64>();
if total_balance == 0 {
// TODO: Point to latest test-net faucet with account info added to the
// link as a parameter.
anyhow::bail!("Your wallet does not have any funds to pay for the deployment transaction.\
let total_balance = account_balances
.iter()
.flat_map(|account| account.values())
.sum::<u64>();
if total_balance == 0 {
// TODO: Point to latest test-net faucet with account info added to the
// link as a parameter.
anyhow::bail!("Your wallet does not have any funds to pay for the deployment transaction.\
\nIf you are deploying to a testnet consider using the faucet.\
\nIf you are deploying to a local node, consider providing a chainConfig which funds your account.")
}
print_account_balances(&accounts, &account_balances);
}
print_account_balances(&accounts, &account_balances);

print!("\nPlease provide the index of account to use for signing: ");
std::io::stdout().flush()?;
let mut account_index = String::new();
std::io::stdin().read_line(&mut account_index)?;
let account_index = account_index.trim().parse::<usize>()?;
print!("\nPlease provide the index of account to use for signing: ");
std::io::stdout().flush()?;
let mut account_index = String::new();
std::io::stdin().read_line(&mut account_index)?;
let account_index = account_index.trim().parse::<usize>()?;

let secret_key = derive_secret_key(&wallet_path, account_index, &password).map_err(|e| {
let secret_key = derive_secret_key(&wallet_path, account_index, &password)
.map_err(|e| {
if e.to_string().contains("Mac Mismatch") {
anyhow::anyhow!("Failed to access forc-wallet vault. Please check your password")
}else {
anyhow::anyhow!(
"Failed to access forc-wallet vault. Please check your password"
)
} else {
e
}
})?;

// TODO: Do this via forc-wallet once the functinoality is exposed.
let public_key = PublicKey::from(&secret_key);
let hashed = public_key.hash();
let bech32 = Bech32Address::new(FUEL_BECH32_HRP, hashed);
let question = format!(
"Do you agree to sign this transaction with {}? [y/N]: ",
bech32
);
let accepted = ask_user_yes_no_question(&question)?;
if !accepted {
anyhow::bail!("User refused to sign");
}

Some(secret_key)
// TODO: Do this via forc-wallet once the functinoality is exposed.
let public_key = PublicKey::from(&secret_key);
let hashed = public_key.hash();
let bech32 = Bech32Address::new(FUEL_BECH32_HRP, hashed);
let question = format!(
"Do you agree to sign this transaction with {}? [y/N]: ",
bech32
);
let accepted = ask_user_yes_no_question(&question)?;
if !accepted {
anyhow::bail!("User refused to sign");
}
(WalletSelectionMode::ForcWallet, Some(key)) => {
tracing::warn!(
"Signing key is provided while requesting to sign with forc-wallet. Using signing key"

Some(secret_key)
}
(WalletSelectionMode::ForcWallet, Some(key), _) => {
tracing::warn!(
"Signing key is provided while requesting to sign with forc-wallet or with default signer. Using signing key"
);
Some(key)
}
(WalletSelectionMode::Manual, None) => None,
(WalletSelectionMode::Manual, Some(key)) => Some(key),
};
// Get the address
let address = if let Some(key) = key {
Address::from(*key.public_key().hash())
} else {
Address::from(prompt_address()?)
};
Some(key)
}
(WalletSelectionMode::Manual, None, false) => None,
(WalletSelectionMode::Manual, Some(key), false) => Some(key),
(_, None, true) => {
// Generate a `SecretKey` to sign this transaction from a default private key used
// by fuel-core.
let secret_key = SecretKey::from_str(DEFAULT_PRIVATE_KEY)?;
Some(secret_key)
}
(WalletSelectionMode::Manual, Some(key), true) => {
tracing::warn!(
"Signing key is provided while requesting to sign with a default signer. Using signing key"
);
Some(key)
}
};
// Get the address
let address = if let Some(key) = signing_key {
Address::from(*key.public_key().hash())
} else {
Address::from(prompt_address()?)
};

// Insert dummy witness for signature
signature_witness_index = self.witnesses().len().try_into()?;
self.add_witness(Witness::default());
// Insert dummy witness for signature
let signature_witness_index = self.witnesses().len().try_into()?;
self.add_witness(Witness::default());

// Add input coin and output change
self.fund(
// Add input coin and output change
self.fund(
address,
Provider::new(client, params),
signature_witness_index,
Expand All @@ -265,29 +283,18 @@ impl<Tx: Buildable + SerializableVec + field::Witnesses + Send> TransactionBuild
} else {
e
})?;
key
} else {
None
};

let mut tx = self.finalize_without_signature_inner();

if !unsigned {
let signature = if let Some(signing_key) = signing_key {
// Safety: `Message::from_bytes_unchecked` is unsafe because
// it can't guarantee that the provided bytes will be the product
// of a cryptographically secure hash. However, the bytes are
// coming from `tx.id()`, which already uses `Hasher::hash()`
// to hash it using a secure hash mechanism.
let message = Message::from_bytes(*tx.id(&params.chain_id));
Signature::sign(&signing_key, &message)
} else {
prompt_signature(tx.id(&params.chain_id))?
};
let signature = if let Some(signing_key) = signing_key {
let message = Message::from_bytes(*tx.id(&params.chain_id));
Signature::sign(&signing_key, &message)
} else {
prompt_signature(tx.id(&params.chain_id))?
};

let witness = Witness::from(signature.as_ref());
tx.replace_witness(signature_witness_index, witness);
}
let witness = Witness::from(signature.as_ref());
tx.replace_witness(signature_witness_index, witness);
tx.precompute(&params.chain_id)?;

Ok(tx)
Expand Down