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

Multichain Scripts: Deploy each sequence of transactions sequentially instead of in parallel. #6271

Merged
merged 4 commits into from
Nov 10, 2023

Conversation

ArshanKhanifar
Copy link
Contributor

@ArshanKhanifar ArshanKhanifar commented Nov 9, 2023

Motivation

As explained here foundry first runs a simulation, collects all the transactions then batch runs all the transactions. This causes issues in scenarios where the script has a flow like this:

  1. Fork chain A, deploy contracts to Chain A
  2. Fork chain B, deploy contracts to Chain B
  3. Switch back to Chain A, let contracts in Chain A know of Contracts in Chain B (this is very common where there's cross-chain messaging to be done between the contracts. I.e. Configuring LayerZero to deliver messages to the destination chain.)
  4. Switch to Chain B, etc. etc.

In this case, steps 1 and 3 will generate two sequences of transactions both pertaining to Chain A. From the simulation that was previously run, the nonce of the first transaction in Sequence 3 will be one more than the nonce of the last transaction in Sequence 1. However, since foundry attempts to run both Sequences 1 & 3 in parallel, the nonce returned from the RPC will mismatch the first transaction in Sequence 3, and it'll result in the following error:

EOA nonce changed unexpectedly while sending transactions. Expected 58 got 54 from provider.

I can see that multiple users have also run into this issue: #4701 #6184

Solution

We can still run the transactions in each DeploySequence in parallel and enjoy the performances of batch transactions! However, in between sequence changes we have to wait until the last sequence's transactions are all submitted.

Potential Optimization

We could chunk the neighbouring sequences and execute those in parallel. Consider Chains A, B, C and a deploy script that results in these sequences:

A, B, A, B, B, C, A, B, B, A, A, C, C, C

We could chunk the neighbouring sequences like so:
A, B, A, (B, B), C, A, (B, B), (A, A), (C, C, C)

Although this may already be the case, I'm not sure how these sequences get generated.

@ConejoCapital
Copy link

lgtm

@mattsse mattsse requested a review from Evalir November 9, 2023 19:52
Copy link
Member

@Evalir Evalir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice! looks good, pending nit & ci

return sequence.verify_contracts(config, verify.clone()).await
}
Ok(())
let mut results: Vec<Result<(), Report>> = Vec::new(); // Declare a Vec of Result<(), Report>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for this comment, as the vec is already typed


let errors =
join_all(futs).await.into_iter().filter(|res| res.is_err()).collect::<Vec<_>>();
let errors = results.into_iter().filter(|res| res.is_err()).collect::<Vec<_>>();
Copy link
Member

@Evalir Evalir Nov 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, so I see what's going on:

  • Before, we drove all futures to completion depending on availability (ideally in order), but this wasn't guaranteed—what's guaranteed is that they're stored in the order they were provided.
  • Now, we're waiting for each batch to complete and storing its result, avoiding the race condition

This might mean that complex multichain deployments might take longer though, but i'm willing to take the hit for less failures

Copy link
Contributor Author

@ArshanKhanifar ArshanKhanifar Nov 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, but the potential temporal dependencies between the batches would result in script failures.

I could still see people running into issues if their scripts use blockchain state that changes too quickly even after one block passing: i.e. getting the price of ETH in a ETH/USDC uni pool, and using that as an input to another script.

But for the most part I feel like people just wanna deploy their contracts & maybe do some configurations on them. Not so much use specific chain-state.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and yeah, complex multichain deployments would take longer. I feel like it's a worthwhile tradeoff for the amount of flexibility that's enabled by it.

@Evalir Evalir merged commit 2df7306 into foundry-rs:master Nov 10, 2023
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants