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: AWS kms signer support for forc-client #6578

Merged
merged 21 commits into from
Sep 25, 2024
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
373 changes: 371 additions & 2 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ anyhow = "1.0"
assert-json-diff = "2.0"
async-trait = "0.1"
atty = "0.2"
aws-config = "1.5"
aws-sdk-kms = "1.44"
kayagokalp marked this conversation as resolved.
Show resolved Hide resolved
byte-unit = "5.1"
bytecount = "0.6"
bytes = "1.7"
Expand Down Expand Up @@ -158,6 +160,7 @@ indoc = "2.0"
insta = "1.40"
ipfs-api-backend-hyper = "0.6"
itertools = "0.13"
k256 = "0.13"
lazy_static = "1.4"
libp2p-identity = "0.2"
libtest-mimic = "0.7"
Expand Down
3 changes: 3 additions & 0 deletions forc-plugins/forc-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ repository.workspace = true
[dependencies]
anyhow.workspace = true
async-trait.workspace = true
aws-config.workspace = true
aws-sdk-kms.workspace = true
chrono = { workspace = true, features = ["std"] }
clap = { workspace = true, features = ["derive", "env"] }
devault.workspace = true
Expand All @@ -32,6 +34,7 @@ fuels-accounts.workspace = true
fuels-core.workspace = true
futures.workspace = true
hex.workspace = true
k256.workspace = true
rand.workspace = true
rpassword.workspace = true
serde.workspace = true
Expand Down
4 changes: 4 additions & 0 deletions forc-plugins/forc-client/src/cmd/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,8 @@ pub struct Command {
/// Disable the "new encoding" feature
#[clap(long)]
pub no_encoding_v1: bool,

/// AWS KMS signer arn. If present forc-deploy will automatically use AWS KMS signer instead of forc-wallet.
#[clap(long)]
pub aws_kms_signer: Option<String>,
}
61 changes: 28 additions & 33 deletions forc-plugins/forc-client/src/op/deploy.rs

Large diffs are not rendered by default.

83 changes: 54 additions & 29 deletions forc-plugins/forc-client/src/op/run/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,26 @@ use crate::{
cmd,
constants::TX_SUBMIT_TIMEOUT_MS,
util::{
gas::get_script_gas_used,
node_url::get_node_url,
pkg::built_pkgs,
tx::{prompt_forc_wallet_password, TransactionBuilderExt, WalletSelectionMode},
tx::{prompt_forc_wallet_password, select_account, SignerSelectionMode},
},
};
use anyhow::{anyhow, bail, Context, Result};
use forc_pkg::{self as pkg, fuel_core_not_running, PackageManifestFile};
use forc_tracing::println_warning;
use forc_util::tx_utils::format_log_receipts;
use fuel_core_client::client::FuelClient;
use fuel_tx::{ContractId, Transaction, TransactionBuilder};
use fuels_accounts::provider::Provider;
use fuel_tx::{ContractId, Transaction};
use fuels::{
programs::calls::{traits::TransactionTuner, ScriptCall},
types::{
bech32::Bech32ContractId,
transaction::TxPolicies,
transaction_builders::{BuildableTransaction, VariableOutputPolicy},
},
};
use fuels_accounts::{provider::Provider, Account};
use pkg::{manifest::build_profile::ExperimentalFlags, BuiltPackage};
use std::time::Duration;
use std::{path::PathBuf, str::FromStr};
Expand Down Expand Up @@ -51,10 +58,10 @@ pub async fn run(command: cmd::Run) -> Result<Vec<RanScript>> {
let build_opts = build_opts_from_cmd(&command);
let built_pkgs_with_manifest = built_pkgs(&curr_dir, &build_opts)?;
let wallet_mode = if command.default_signer || command.signing_key.is_some() {
WalletSelectionMode::Manual
SignerSelectionMode::Manual
} else {
let password = prompt_forc_wallet_password()?;
WalletSelectionMode::ForcWallet(password)
SignerSelectionMode::ForcWallet(password)
};
for built in built_pkgs_with_manifest {
if built
Expand All @@ -77,13 +84,34 @@ pub async fn run(command: cmd::Run) -> Result<Vec<RanScript>> {
Ok(receipts)
}

fn tx_policies_from_cmd(cmd: &cmd::Run) -> TxPolicies {
let mut tx_policies = TxPolicies::default();
if let Some(max_fee) = cmd.gas.max_fee {
tx_policies = tx_policies.with_max_fee(max_fee);
}
if let Some(script_gas_limit) = cmd.gas.script_gas_limit {
tx_policies = tx_policies.with_script_gas_limit(script_gas_limit);
}
tx_policies
}

pub async fn run_pkg(
command: &cmd::Run,
manifest: &PackageManifestFile,
compiled: &BuiltPackage,
wallet_mode: &WalletSelectionMode,
signer_mode: &SignerSelectionMode,
) -> Result<RanScript> {
let node_url = get_node_url(&command.node, &manifest.network)?;
let provider = Provider::connect(node_url.clone()).await?;
let tx_count = 1;
let account = select_account(
signer_mode,
command.default_signer || command.unsigned,
command.signing_key,
&provider,
tx_count,
)
.await?;

let script_data = match (&command.data, &command.args) {
(None, Some(args)) => {
Expand Down Expand Up @@ -116,31 +144,28 @@ pub async fn run_pkg(
})
.collect::<Result<Vec<ContractId>>>()?;

let mut tb = TransactionBuilder::script(compiled.bytecode.bytes.clone(), script_data);
tb.maturity(command.maturity.maturity.into())
.add_contracts(contract_ids);

let provider = Provider::connect(node_url.clone()).await?;

let script_gas_limit = if compiled.bytecode.bytes.is_empty() {
0
} else if let Some(script_gas_limit) = command.gas.script_gas_limit {
script_gas_limit
// Dry run tx and get `gas_used`
} else {
get_script_gas_used(tb.clone().finalize_without_signature_inner(), &provider).await?
JoshuaBatty marked this conversation as resolved.
Show resolved Hide resolved
let script_binary = compiled.bytecode.bytes.clone();
let external_contracts = contract_ids
.into_iter()
.map(Bech32ContractId::from)
.collect::<Vec<_>>();
let call = ScriptCall {
script_binary,
encoded_args: Ok(script_data),
inputs: vec![],
outputs: vec![],
external_contracts,
};
tb.script_gas_limit(script_gas_limit);

let tx = tb
.finalize_signed(
Provider::connect(node_url.clone()).await?,
command.default_signer,
command.signing_key,
wallet_mode,
)
let tx_policies = tx_policies_from_cmd(command);
let mut tb = call
.transaction_builder(tx_policies, VariableOutputPolicy::EstimateMinimum, &account)
.await?;

account.add_witnesses(&mut tb)?;
account.adjust_for_fee(&mut tb, 0).await?;

let tx = tb.build(provider).await?;

if command.dry_run {
info!("{:?}", tx);
Ok(RanScript { receipts: vec![] })
Expand Down
87 changes: 87 additions & 0 deletions forc-plugins/forc-client/src/util/account.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use async_trait::async_trait;
use fuel_crypto::{Message, Signature};
use fuels::{
prelude::*,
types::{coin_type_id::CoinTypeId, input::Input},
};
use fuels_accounts::{wallet::WalletUnlocked, Account};

use super::aws::AwsSigner;

#[derive(Clone, Debug)]
/// Set of different signers available to be used with `forc-client` operations.
pub enum ForcClientAccount {
kayagokalp marked this conversation as resolved.
Show resolved Hide resolved
/// Local signer where the private key owned locally. This can be
/// generated through `forc-wallet` integration or manually by providing
/// a private-key.
Wallet(WalletUnlocked),
/// A KMS Signer specifically using AWS KMS service. The signing key
/// is managed by another entity for KMS signers. Messages are
kayagokalp marked this conversation as resolved.
Show resolved Hide resolved
/// signed by the KMS entity. Signed transactions are retrieved
/// and submitted to the node by `forc-client`.
KmsSigner(AwsSigner),
}

#[async_trait]
impl Account for ForcClientAccount {
async fn get_asset_inputs_for_amount(
&self,
asset_id: AssetId,
amount: u64,
excluded_coins: Option<Vec<CoinTypeId>>,
) -> Result<Vec<Input>> {
match self {
ForcClientAccount::Wallet(wallet) => {
wallet
.get_asset_inputs_for_amount(asset_id, amount, excluded_coins)
.await
}
ForcClientAccount::KmsSigner(account) => {
account
.get_asset_inputs_for_amount(asset_id, amount, excluded_coins)
.await
}
}
}

fn add_witnesses<Tb: TransactionBuilder>(&self, tb: &mut Tb) -> Result<()> {
tb.add_signer(self.clone())?;

Ok(())
}
}

impl ViewOnlyAccount for ForcClientAccount {
fn address(&self) -> &Bech32Address {
match self {
ForcClientAccount::Wallet(wallet) => wallet.address(),
ForcClientAccount::KmsSigner(account) => {
fuels_accounts::ViewOnlyAccount::address(account)
}
}
}

fn try_provider(&self) -> Result<&Provider> {
match self {
ForcClientAccount::Wallet(wallet) => wallet.try_provider(),
ForcClientAccount::KmsSigner(account) => Ok(account.provider()),
}
}
}

#[async_trait]
impl Signer for ForcClientAccount {
async fn sign(&self, message: Message) -> Result<Signature> {
match self {
ForcClientAccount::Wallet(wallet) => wallet.sign(message).await,
ForcClientAccount::KmsSigner(account) => account.sign(message).await,
}
}

fn address(&self) -> &Bech32Address {
match self {
ForcClientAccount::Wallet(wallet) => wallet.address(),
ForcClientAccount::KmsSigner(account) => fuels_core::traits::Signer::address(account),
}
}
}
Loading
Loading