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: add transaction timeout config #8669

Merged
merged 3 commits into from
Aug 22, 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
16 changes: 13 additions & 3 deletions crates/cast/bin/cmd/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ pub struct SendTxArgs {
#[arg(long, requires = "from")]
unlocked: bool,

/// Timeout for sending the transaction.
#[arg(long, env = "ETH_TIMEOUT")]
pub timeout: Option<u64>,

#[command(flatten)]
tx: TransactionOpts,

Expand Down Expand Up @@ -98,6 +102,7 @@ impl SendTxArgs {
command,
unlocked,
path,
timeout,
} = self;

let blob_data = if let Some(path) = path { Some(std::fs::read(path)?) } else { None };
Expand Down Expand Up @@ -126,6 +131,8 @@ impl SendTxArgs {
.await?
.with_blob_data(blob_data)?;

let timeout = timeout.unwrap_or(config.transaction_timeout);

// Case 1:
// Default to sending via eth_sendTransaction if the --unlocked flag is passed.
// This should be the only way this RPC method is used as it requires a local node
Expand All @@ -152,7 +159,7 @@ impl SendTxArgs {

let (tx, _) = builder.build(config.sender).await?;

cast_send(provider, tx, cast_async, confirmations, to_json).await
cast_send(provider, tx, cast_async, confirmations, timeout, to_json).await
// Case 2:
// An option to use a local signer was provided.
// If we cannot successfully instantiate a local signer, then we will assume we don't have
Expand All @@ -171,7 +178,7 @@ impl SendTxArgs {
.wallet(wallet)
.on_provider(&provider);

cast_send(provider, tx, cast_async, confirmations, to_json).await
cast_send(provider, tx, cast_async, confirmations, timeout, to_json).await
}
}
}
Expand All @@ -181,6 +188,7 @@ async fn cast_send<P: Provider<T, AnyNetwork>, T: Transport + Clone>(
tx: WithOtherFields<TransactionRequest>,
cast_async: bool,
confs: u64,
timeout: u64,
to_json: bool,
) -> Result<()> {
let cast = Cast::new(provider);
Expand All @@ -191,7 +199,9 @@ async fn cast_send<P: Provider<T, AnyNetwork>, T: Transport + Clone>(
if cast_async {
println!("{tx_hash:#x}");
} else {
let receipt = cast.receipt(format!("{tx_hash:#x}"), None, confs, false, to_json).await?;
let receipt = cast
.receipt(format!("{tx_hash:#x}"), None, confs, Some(timeout), false, to_json)
.await?;
println!("{receipt}");
}

Expand Down
2 changes: 1 addition & 1 deletion crates/cast/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ async fn main() -> Result<()> {
println!(
"{}",
Cast::new(provider)
.receipt(tx_hash, field, confirmations, cast_async, json)
.receipt(tx_hash, field, confirmations, None, cast_async, json)
.await?
);
}
Expand Down
5 changes: 4 additions & 1 deletion crates/cast/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ use std::{
path::PathBuf,
str::FromStr,
sync::atomic::{AtomicBool, Ordering},
time::Duration,
};
use tokio::signal::ctrl_c;

Expand Down Expand Up @@ -690,7 +691,7 @@ where
/// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?;
/// let cast = Cast::new(provider);
/// let tx_hash = "0xf8d1713ea15a81482958fb7ddf884baee8d3bcc478c5f2f604e008dc788ee4fc";
/// let receipt = cast.receipt(tx_hash.to_string(), None, 1, false, false).await?;
/// let receipt = cast.receipt(tx_hash.to_string(), None, 1, None, false, false).await?;
/// println!("{}", receipt);
/// # Ok(())
/// # }
Expand All @@ -700,6 +701,7 @@ where
tx_hash: String,
field: Option<String>,
confs: u64,
timeout: Option<u64>,
cast_async: bool,
to_json: bool,
) -> Result<String> {
Expand All @@ -716,6 +718,7 @@ where
} else {
PendingTransactionBuilder::new(self.provider.root(), tx_hash)
.with_required_confirmations(confs)
.with_timeout(timeout.map(Duration::from_secs))
.get_receipt()
.await?
}
Expand Down
4 changes: 4 additions & 0 deletions crates/config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,9 @@ pub struct Config {
/// Whether to enable Alphanet features.
pub alphanet: bool,

/// Timeout for transactions in seconds.
pub transaction_timeout: u64,

/// Warnings gathered when loading the Config. See [`WarningsProvider`] for more information
#[serde(rename = "__warnings", default, skip_serializing)]
pub warnings: Vec<Warning>,
Expand Down Expand Up @@ -2143,6 +2146,7 @@ impl Default for Config {
extra_args: vec![],
eof_version: None,
alphanet: false,
transaction_timeout: 120,
_non_exhaustive: (),
}
}
Expand Down
51 changes: 44 additions & 7 deletions crates/forge/bin/cmd/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,19 @@ use foundry_common::{
fmt::parse_tokens,
};
use foundry_compilers::{artifacts::BytecodeObject, info::ContractInfo, utils::canonicalize};
use foundry_config::{
figment::{
self,
value::{Dict, Map},
Metadata, Profile,
},
merge_impl_figment_convert, Config,
};
use serde_json::json;
use std::{borrow::Borrow, marker::PhantomData, path::PathBuf, sync::Arc};

merge_impl_figment_convert!(CreateArgs, opts, eth);

/// CLI arguments for `forge create`.
#[derive(Clone, Debug, Parser)]
pub struct CreateArgs {
Expand Down Expand Up @@ -65,6 +75,10 @@ pub struct CreateArgs {
#[arg(long, requires = "verify")]
show_standard_json_input: bool,

/// Timeout to use for broadcasting transactions.
#[arg(long, env = "ETH_TIMEOUT")]
pub timeout: Option<u64>,

#[command(flatten)]
opts: CoreBuildArgs,

Expand All @@ -84,8 +98,9 @@ pub struct CreateArgs {
impl CreateArgs {
/// Executes the command to create a contract
pub async fn run(mut self) -> Result<()> {
let config = self.try_load_config_emit_warnings()?;
// Find Project & Compile
let project = self.opts.project()?;
let project = config.project()?;

let target_path = if let Some(ref mut path) = self.contract.path {
canonicalize(project.root().join(path))?
Expand Down Expand Up @@ -114,7 +129,6 @@ impl CreateArgs {
};

// Add arguments to constructor
let config = self.eth.try_load_config_emit_warnings()?;
let provider = utils::get_provider(&config)?;
let params = match abi.constructor {
Some(ref v) => {
Expand All @@ -138,15 +152,17 @@ impl CreateArgs {
if self.unlocked {
// Deploy with unlocked account
let sender = self.eth.wallet.from.expect("required");
self.deploy(abi, bin, params, provider, chain_id, sender).await
self.deploy(abi, bin, params, provider, chain_id, sender, config.transaction_timeout)
.await
} else {
// Deploy with signer
let signer = self.eth.wallet.signer().await?;
let deployer = signer.address();
let provider = ProviderBuilder::<_, _, AnyNetwork>::default()
.wallet(EthereumWallet::new(signer))
.on_provider(provider);
self.deploy(abi, bin, params, provider, chain_id, deployer).await
self.deploy(abi, bin, params, provider, chain_id, deployer, config.transaction_timeout)
.await
}
}

Expand Down Expand Up @@ -207,6 +223,7 @@ impl CreateArgs {
}

/// Deploys the contract
#[allow(clippy::too_many_arguments)]
async fn deploy<P: Provider<T, AnyNetwork>, T: Transport + Clone>(
self,
abi: JsonAbi,
Expand All @@ -215,12 +232,13 @@ impl CreateArgs {
provider: P,
chain: u64,
deployer_address: Address,
timeout: u64,
) -> Result<()> {
let bin = bin.into_bytes().unwrap_or_else(|| {
panic!("no bytecode found in bin object for {}", self.contract.name)
});
let provider = Arc::new(provider);
let factory = ContractFactory::new(abi.clone(), bin.clone(), provider.clone());
let factory = ContractFactory::new(abi.clone(), bin.clone(), provider.clone(), timeout);

let is_args_empty = args.is_empty();
let mut deployer =
Expand Down Expand Up @@ -367,6 +385,20 @@ impl CreateArgs {
}
}

impl figment::Provider for CreateArgs {
fn metadata(&self) -> Metadata {
Metadata::named("Create Args Provider")
}

fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
let mut dict = Dict::default();
if let Some(timeout) = self.timeout {
dict.insert("transaction_timeout".to_string(), timeout.into());
}
Ok(Map::from([(Config::selected_profile(), dict)]))
}
}

/// `ContractFactory` is a [`DeploymentTxFactory`] object with an
/// [`Arc`] middleware. This type alias exists to preserve backwards
/// compatibility with less-abstract Contracts.
Expand Down Expand Up @@ -414,6 +446,7 @@ pub struct Deployer<B, P, T> {
abi: JsonAbi,
client: B,
confs: usize,
timeout: u64,
_p: PhantomData<P>,
_t: PhantomData<T>,
}
Expand All @@ -428,6 +461,7 @@ where
abi: self.abi.clone(),
client: self.client.clone(),
confs: self.confs,
timeout: self.timeout,
_p: PhantomData,
_t: PhantomData,
}
Expand Down Expand Up @@ -504,6 +538,7 @@ pub struct DeploymentTxFactory<B, P, T> {
client: B,
abi: JsonAbi,
bytecode: Bytes,
timeout: u64,
_p: PhantomData<P>,
_t: PhantomData<T>,
}
Expand All @@ -517,6 +552,7 @@ where
client: self.client.clone(),
abi: self.abi.clone(),
bytecode: self.bytecode.clone(),
timeout: self.timeout,
_p: PhantomData,
_t: PhantomData,
}
Expand All @@ -532,8 +568,8 @@ where
/// Creates a factory for deployment of the Contract with bytecode, and the
/// constructor defined in the abi. The client will be used to send any deployment
/// transaction.
pub fn new(abi: JsonAbi, bytecode: Bytes, client: B) -> Self {
Self { client, abi, bytecode, _p: PhantomData, _t: PhantomData }
pub fn new(abi: JsonAbi, bytecode: Bytes, client: B, timeout: u64) -> Self {
Self { client, abi, bytecode, timeout, _p: PhantomData, _t: PhantomData }
}

/// Create a deployment tx using the provided tokens as constructor
Expand Down Expand Up @@ -567,6 +603,7 @@ where
abi: self.abi,
tx,
confs: 1,
timeout: self.timeout,
_p: PhantomData,
_t: PhantomData,
})
Expand Down
1 change: 1 addition & 0 deletions crates/forge/tests/cli/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ forgetest!(can_extract_config_values, |prj, cmd| {
extra_args: vec![],
eof_version: None,
alphanet: false,
transaction_timeout: 120,
_non_exhaustive: (),
};
prj.write_config(input.clone());
Expand Down
18 changes: 16 additions & 2 deletions crates/script/src/broadcast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,14 @@ impl BundledState {
.map(|(sequence_idx, sequence)| async move {
let rpc_url = sequence.rpc_url();
let provider = Arc::new(get_http_provider(rpc_url));
progress_ref.wait_for_pending(sequence_idx, sequence, &provider).await
progress_ref
.wait_for_pending(
sequence_idx,
sequence,
&provider,
self.script_config.config.transaction_timeout,
)
.await
})
.collect::<Vec<_>>();

Expand Down Expand Up @@ -368,7 +375,14 @@ impl BundledState {
self.sequence.save(true, false)?;
sequence = self.sequence.sequences_mut().get_mut(i).unwrap();

progress.wait_for_pending(i, sequence, &provider).await?
progress
.wait_for_pending(
i,
sequence,
&provider,
self.script_config.config.transaction_timeout,
)
.await?
}
// Checkpoint save
self.sequence.save(true, false)?;
Expand Down
7 changes: 7 additions & 0 deletions crates/script/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@ pub struct ScriptArgs {
)]
pub with_gas_price: Option<U256>,

/// Timeout to use for broadcasting transactions.
#[arg(long, env = "ETH_TIMEOUT")]
pub timeout: Option<u64>,

#[command(flatten)]
pub opts: CoreBuildArgs,

Expand Down Expand Up @@ -453,6 +457,9 @@ impl Provider for ScriptArgs {
figment::value::Value::from(etherscan_api_key.to_string()),
);
}
if let Some(timeout) = self.timeout {
dict.insert("transaction_timeout".to_string(), timeout.into());
}
Ok(Map::from([(Config::selected_profile(), dict)]))
}
}
Expand Down
8 changes: 6 additions & 2 deletions crates/script/src/progress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ impl ScriptProgress {
sequence_idx: usize,
deployment_sequence: &mut ScriptSequence,
provider: &RetryProvider,
timeout: u64,
) -> Result<()> {
if deployment_sequence.pending.is_empty() {
return Ok(());
Expand All @@ -180,8 +181,11 @@ impl ScriptProgress {

trace!("Checking status of {count} pending transactions");

let futs =
deployment_sequence.pending.clone().into_iter().map(|tx| check_tx_status(provider, tx));
let futs = deployment_sequence
.pending
.clone()
.into_iter()
.map(|tx| check_tx_status(provider, tx, timeout));
let mut tasks = futures::stream::iter(futs).buffer_unordered(10);

let mut errors: Vec<String> = vec![];
Expand Down
3 changes: 2 additions & 1 deletion crates/script/src/receipts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ impl From<AnyTransactionReceipt> for TxStatus {
pub async fn check_tx_status(
provider: &RetryProvider,
hash: TxHash,
timeout: u64,
) -> (TxHash, Result<TxStatus, eyre::Report>) {
// We use the inner future so that we can use ? operator in the future, but
// still neatly return the tuple
Expand All @@ -40,7 +41,7 @@ pub async fn check_tx_status(

loop {
if let Ok(receipt) = PendingTransactionBuilder::new(provider, hash)
.with_timeout(Some(Duration::from_secs(120)))
.with_timeout(Some(Duration::from_secs(timeout)))
.get_receipt()
.await
{
Expand Down
Loading