Skip to content

Commit e6f2bc6

Browse files
authored
forge: split sending and waiting for receipts + buffered (#1824)
* split sending and waiting for receipts + buffered * run the local identifier before the etherscan one * retryclient + sequential sending * make gas price estimation before starting to send * update cargo * fix progress label * change location of progress macros * send & wait in batches of 50 * sign and send_raw_transaction on fn broadcast * increase buffer from receipts to 20 * increase send_transaction batch to 100 * add buffered to send_tx and [non]aggressive retryclient * sort receipts before verifying * set chain id once * add SignerClient type alias * show progress bar even if there's only one receipt * ask gas estimation for arbitrum network before sending * don't panic if it cannot recognize chain * dont panic on printing the receipt * print_receipt does not return result anymore * change gas price retrieval * hide Paid from print_receipt if there's no gas_price
1 parent 9b479b1 commit e6f2bc6

File tree

15 files changed

+342
-157
lines changed

15 files changed

+342
-157
lines changed

Cargo.lock

Lines changed: 13 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ strsim = "0.10.0"
7373
bytes = "1.1.0"
7474
strum = { version = "0.24", features = ["derive"] }
7575
thiserror = "1.0.30"
76+
indicatif = "0.17.0-rc.11"
7677

7778
[dev-dependencies]
7879
anvil = { path = "../anvil" }

cli/src/cast.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ mod utils;
77

88
use cast::{Cast, SimpleCast, TxBuilder};
99
use foundry_config::Config;
10+
use utils::get_http_provider;
1011
mod opts;
1112
use crate::{cmd::Cmd, utils::consume_config_rpc_url};
1213
use cast::InterfacePath;
@@ -262,9 +263,10 @@ async fn main() -> eyre::Result<()> {
262263
resend,
263264
} => {
264265
let config = Config::from(&eth);
265-
let provider = Provider::try_from(
266-
config.eth_rpc_url.unwrap_or_else(|| "http://localhost:8545".to_string()),
267-
)?;
266+
let provider = get_http_provider(
267+
&config.eth_rpc_url.unwrap_or_else(|| "http://localhost:8545".to_string()),
268+
false,
269+
);
268270
let chain_id = Cast::new(&provider).chain_id().await?;
269271
let chain = Chain::try_from(chain_id.as_u64()).unwrap_or(eth.chain);
270272
let sig = sig.unwrap_or_default();

cli/src/cmd/forge/create.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ use crate::{
44
cmd::{forge::build::CoreBuildArgs, Cmd, RetryArgs},
55
compile,
66
opts::{forge::ContractInfo, EthereumOpts, WalletType},
7-
utils::{parse_ether_value, parse_u256},
7+
utils::{get_http_provider, parse_ether_value, parse_u256},
88
};
99
use clap::{Parser, ValueHint};
1010
use ethers::{
1111
abi::{Abi, Constructor, Token},
12-
prelude::{artifacts::BytecodeObject, ContractFactory, Http, Middleware, Provider},
12+
prelude::{artifacts::BytecodeObject, ContractFactory, Middleware},
1313
solc::utils::RuntimeOrHandle,
1414
types::{transaction::eip2718::TypedTransaction, Chain, U256},
1515
};
@@ -164,9 +164,10 @@ impl CreateArgs {
164164

165165
// Add arguments to constructor
166166
let config = Config::from(&self.eth);
167-
let provider = Provider::<Http>::try_from(
168-
config.eth_rpc_url.unwrap_or_else(|| "http://localhost:8545".to_string()),
169-
)?;
167+
let provider = get_http_provider(
168+
&config.eth_rpc_url.unwrap_or_else(|| "http://localhost:8545".to_string()),
169+
false,
170+
);
170171
let params = match abi.constructor {
171172
Some(ref v) => {
172173
let constructor_args =

cli/src/cmd/forge/debug.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ impl DebugArgs {
6262
etherscan_api_key: None,
6363
verify: false,
6464
json: false,
65+
with_gas_price: None,
6566
};
6667
script.run_script().await
6768
}

cli/src/cmd/forge/script/broadcast.rs

Lines changed: 158 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,43 @@
11
use crate::{
2-
cmd::{forge::script::receipts::wait_for_receipts, ScriptSequence, VerifyBundle},
2+
cmd::{
3+
forge::script::receipts::wait_for_receipts, has_different_gas_calc, ScriptSequence,
4+
VerifyBundle,
5+
},
6+
init_progress,
37
opts::WalletType,
8+
update_progress,
49
utils::get_http_provider,
510
};
611
use ethers::{
7-
prelude::{SignerMiddleware, TxHash},
12+
prelude::{Signer, SignerMiddleware, TxHash},
813
providers::Middleware,
9-
types::{transaction::eip2718::TypedTransaction, Chain, TransactionReceipt},
14+
types::{transaction::eip2718::TypedTransaction, Chain},
1015
};
11-
use std::fmt;
16+
use futures::StreamExt;
17+
use indicatif::{ProgressBar, ProgressStyle};
18+
use std::{cmp::min, fmt};
1219

1320
use super::*;
1421

1522
impl ScriptArgs {
23+
/// Sends the transactions which haven't been broadcasted yet.
1624
pub async fn send_transactions(
1725
&self,
1826
deployment_sequence: &mut ScriptSequence,
1927
fork_url: &str,
2028
) -> eyre::Result<()> {
21-
let provider = get_http_provider(fork_url);
29+
let provider = get_http_provider(fork_url, true);
30+
let already_broadcasted = deployment_sequence.receipts.len();
2231

23-
if deployment_sequence.receipts.len() < deployment_sequence.transactions.len() {
32+
if already_broadcasted < deployment_sequence.transactions.len() {
2433
let required_addresses = deployment_sequence
2534
.transactions
2635
.iter()
27-
.skip(deployment_sequence.receipts.len())
36+
.skip(already_broadcasted)
2837
.map(|tx| *tx.from().expect("No sender for onchain transaction!"))
2938
.collect();
3039

31-
let local_wallets = self.wallets.find_all(&provider, required_addresses).await?;
40+
let local_wallets = self.wallets.find_all(provider.clone(), required_addresses).await?;
3241
if local_wallets.is_empty() {
3342
eyre::bail!("Error accessing local wallet when trying to send onchain transaction, did you set a private key, mnemonic or keystore?")
3443
}
@@ -38,31 +47,111 @@ impl ScriptArgs {
3847
// otherwise.
3948
let sequential_broadcast = local_wallets.len() != 1 || self.slow;
4049

41-
let transactions = deployment_sequence.transactions.clone();
50+
// Make a one-time gas price estimation
51+
let (gas_price, eip1559_fees) = {
52+
match deployment_sequence.transactions.front().unwrap() {
53+
TypedTransaction::Legacy(_) | TypedTransaction::Eip2930(_) => {
54+
(provider.get_gas_price().await.ok(), None)
55+
}
56+
TypedTransaction::Eip1559(_) => {
57+
(None, provider.estimate_eip1559_fees(None).await.ok())
58+
}
59+
}
60+
};
4261

4362
// Iterate through transactions, matching the `from` field with the associated
4463
// wallet. Then send the transaction. Panics if we find a unknown `from`
45-
let sequence = transactions.into_iter().map(|tx| {
46-
let from = *tx.from().expect("No sender for onchain transaction!");
47-
let signer = local_wallets.get(&from).expect("`find_all` returned incomplete.");
48-
(tx, signer)
49-
});
64+
let sequence = deployment_sequence
65+
.transactions
66+
.iter()
67+
.skip(already_broadcasted)
68+
.map(|tx| {
69+
let from = *tx.from().expect("No sender for onchain transaction!");
70+
let signer = local_wallets.get(&from).expect("`find_all` returned incomplete.");
5071

51-
let mut future_receipts = vec![];
72+
let mut tx = tx.clone();
5273

53-
println!("##\nSending transactions.");
54-
for (tx, signer) in sequence {
55-
let receipt = self.send_transaction(tx, signer, sequential_broadcast, fork_url);
74+
tx.set_chain_id(signer.chain_id());
5675

57-
if sequential_broadcast {
58-
wait_for_receipts(vec![receipt], deployment_sequence).await?;
59-
} else {
60-
future_receipts.push(receipt);
76+
if let Some(gas_price) = self.with_gas_price {
77+
tx.set_gas_price(gas_price);
78+
} else {
79+
// fill gas price
80+
match tx {
81+
TypedTransaction::Eip2930(_) | TypedTransaction::Legacy(_) => {
82+
tx.set_gas_price(gas_price.expect("Could not get gas_price."));
83+
}
84+
TypedTransaction::Eip1559(ref mut inner) => {
85+
let eip1559_fees =
86+
eip1559_fees.expect("Could not get eip1559 fee estimation.");
87+
inner.max_fee_per_gas = Some(eip1559_fees.0);
88+
inner.max_priority_fee_per_gas = Some(eip1559_fees.1);
89+
}
90+
}
91+
}
92+
93+
(tx, signer)
94+
})
95+
.collect::<Vec<_>>();
96+
97+
let pb = init_progress!(deployment_sequence.transactions, "txes");
98+
99+
// We send transactions and wait for receipts in batches of 100, since some networks
100+
// cannot handle more than that.
101+
let batch_size = 100;
102+
let mut index = 0;
103+
104+
for (batch_number, batch) in sequence.chunks(batch_size).map(|f| f.to_vec()).enumerate()
105+
{
106+
let mut pending_transactions = vec![];
107+
108+
println!(
109+
"##\nSending transactions [{} - {}].",
110+
batch_number * batch_size,
111+
batch_number * batch_size + min(batch_size, batch.len()) - 1
112+
);
113+
for (tx, signer) in batch.into_iter() {
114+
let tx_hash = self.send_transaction(tx, signer, sequential_broadcast, fork_url);
115+
116+
if sequential_broadcast {
117+
update_progress!(pb, (index + already_broadcasted));
118+
index += 1;
119+
120+
wait_for_receipts(
121+
vec![tx_hash.await?],
122+
deployment_sequence,
123+
provider.clone(),
124+
)
125+
.await?;
126+
} else {
127+
pending_transactions.push(tx_hash);
128+
}
61129
}
62-
}
63130

64-
if !sequential_broadcast {
65-
wait_for_receipts(future_receipts, deployment_sequence).await?;
131+
if !pending_transactions.is_empty() {
132+
let mut buffer = futures::stream::iter(pending_transactions).buffered(7);
133+
134+
let mut tx_hashes = vec![];
135+
136+
while let Some(tx_hash) = buffer.next().await {
137+
update_progress!(pb, (index + already_broadcasted));
138+
index += 1;
139+
let tx_hash = tx_hash?;
140+
deployment_sequence.add_pending(tx_hash);
141+
tx_hashes.push(tx_hash);
142+
}
143+
144+
// Checkpoint save
145+
deployment_sequence.save()?;
146+
147+
if !sequential_broadcast {
148+
println!("##\nWaiting for receipts.");
149+
wait_for_receipts(tx_hashes, deployment_sequence, provider.clone()).await?;
150+
}
151+
}
152+
153+
// Checkpoint save
154+
deployment_sequence.save()?;
66155
}
67156
}
68157

@@ -80,7 +169,7 @@ impl ScriptArgs {
80169
signer: &WalletType,
81170
sequential_broadcast: bool,
82171
fork_url: &str,
83-
) -> Result<(TransactionReceipt, U256), BroadcastError> {
172+
) -> Result<TxHash, BroadcastError> {
84173
let from = tx.from().expect("no sender");
85174

86175
if sequential_broadcast {
@@ -118,26 +207,27 @@ impl ScriptArgs {
118207
if let Some(txs) = transactions {
119208
if script_config.evm_opts.fork_url.is_some() {
120209
let (gas_filled_txs, create2_contracts) =
121-
self.execute_transactions(txs, script_config, decoder)
122-
.await
123-
.map_err(|_| eyre::eyre!("One or more transactions failed when simulating the on-chain version. Check the trace by re-running with `-vvv`"))?;
210+
self.execute_transactions(txs, script_config, decoder).await.map_err(|_| {
211+
eyre::eyre!(
212+
"One or more transactions failed when simulating the
213+
on-chain version. Check the trace by re-running with `-vvv`"
214+
)
215+
})?;
216+
124217
let fork_url = self.evm_opts.fork_url.as_ref().unwrap().clone();
125218

126-
let provider = get_http_provider(&fork_url);
127-
let chain = provider.get_chainid().await?.as_u64();
219+
let chain = get_http_provider(&fork_url, false).get_chainid().await?.as_u64();
128220
let is_legacy = self.legacy ||
129221
Chain::try_from(chain).map(|x| Chain::is_legacy(&x)).unwrap_or_default();
130222

131223
let txes = gas_filled_txs
132224
.into_iter()
133225
.map(|tx| {
134-
let mut tx = if is_legacy {
226+
if is_legacy {
135227
TypedTransaction::Legacy(tx.into())
136228
} else {
137229
TypedTransaction::Eip1559(tx.into())
138-
};
139-
tx.set_chain_id(chain);
140-
tx
230+
}
141231
})
142232
.collect();
143233

@@ -189,31 +279,44 @@ impl fmt::Display for BroadcastError {
189279
/// transaction hash that can be used on a later run with `--resume`.
190280
async fn broadcast<T, U>(
191281
signer: &SignerMiddleware<T, U>,
192-
legacy_or_1559: TypedTransaction,
193-
) -> Result<(TransactionReceipt, U256), BroadcastError>
282+
mut legacy_or_1559: TypedTransaction,
283+
) -> Result<TxHash, BroadcastError>
194284
where
195-
SignerMiddleware<T, U>: Middleware,
285+
T: Middleware,
286+
U: Signer,
196287
{
197288
tracing::debug!("sending transaction: {:?}", legacy_or_1559);
198-
let nonce = *legacy_or_1559.nonce().unwrap();
199-
let pending = signer
200-
.send_transaction(legacy_or_1559, None)
289+
290+
if has_different_gas_calc(signer.signer().chain_id()) {
291+
match legacy_or_1559 {
292+
TypedTransaction::Legacy(ref mut inner) => inner.gas = None,
293+
TypedTransaction::Eip1559(ref mut inner) => inner.gas = None,
294+
TypedTransaction::Eip2930(ref mut inner) => inner.tx.gas = None,
295+
};
296+
297+
legacy_or_1559.set_gas(
298+
signer
299+
.estimate_gas(&legacy_or_1559)
300+
.await
301+
.map_err(|err| BroadcastError::Simple(err.to_string()))?,
302+
);
303+
}
304+
305+
// Signing manually so we skip `fill_transaction` and its `eth_createAccessList` request.
306+
let signature = signer
307+
.sign_transaction(
308+
&legacy_or_1559,
309+
*legacy_or_1559.from().expect("Tx should have a `from`."),
310+
)
201311
.await
202312
.map_err(|err| BroadcastError::Simple(err.to_string()))?;
203313

204-
let tx_hash = pending.tx_hash();
314+
// Submit the raw transaction
315+
let pending = signer
316+
.provider()
317+
.send_raw_transaction(legacy_or_1559.rlp_signed(&signature))
318+
.await
319+
.map_err(|err| BroadcastError::Simple(err.to_string()))?;
205320

206-
let receipt = match pending.await {
207-
Ok(receipt) => match receipt {
208-
Some(receipt) => receipt,
209-
None => {
210-
return Err(BroadcastError::ErrorWithTxHash(
211-
format!("Didn't receive a receipt for {}", tx_hash),
212-
tx_hash,
213-
))
214-
}
215-
},
216-
Err(err) => return Err(BroadcastError::ErrorWithTxHash(err.to_string(), tx_hash)),
217-
};
218-
Ok((receipt, nonce))
321+
Ok(pending.tx_hash())
219322
}

0 commit comments

Comments
 (0)