Async thunks in transaction builder #197
hayes-mysten
started this conversation in
Technical Discussions
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
What are we adding
Recently, we introduced "transaction thunks", which look like this:
This pattern allows sdks to add give users functions that add commands to transactions without having a reference to that transaction first.
Therefore, an sdk could build something like this:
We've found this pattern to improve type safety, and we believe it makes it easier to understand how transactions ultimately get constructed.
We would like to extend this to allow "async transaction thunks", which would allow developers to add functionality to transactions like this:
Why
Some SDKs need async functionality to build transactions (for example, loading data from the chain to use as a transaction input), but we want to push SDKs towards preserving composability and compatibility with existing transaction building patterns.
The current approach to this is to have SDKs expose async methods that need to be awaited while building transactions:
This patterns works, but presents 2 problems:
More generally, async functions are viral, so once one part of the call stack needs to be
awaited
, every parent function must also become async. Our goal with Transaction construction is to avoid any forced async logic in the critical transaction building flow.Solution
To solve this, we can accept "async thunks" in the transactions
add
method. To make this usable, we will need synchronously return a result that can be used in subsequent transaction commands:We previously added support for similar patterns through a feature called "transaction intents" that work by adding a placeholder command to the transaction, and later replacing that command through a transaction plugin that runs when the transaction is built.
This solves parts of this problem very well, but there is an additional complication: when you have multiple async tasks happening at the same time, execution become non-deterministic. This is a big problem for our transaction builder because it uses indexes as references to command results and indexes. If we just let our async functions execute, they will all race to add commands and inputs to the transaction, and we won't be able to consistently order those commands. The intuitive solution here would be to schedule the async tasks so that they run one at a time, but this has 2 problems: We want to do work in parallel so that things can build as fast as possible, and we don't actually know if a function is async until after we start executing it.
Implementations
We currently have 2 potential implementations for how this can work:
Transaction forking
This solution had a number of issues we discovered during testing and is unlikely to be used
We can pass these async thunks a fork/clone of the transaction. This allows the async function to add/mutate the forked transaction how ever it wants, and we can then deterministically merge the results of all async thunks after they finish executing.
This merging isn't trivial, and we will need to combine inputs, and update references throughout the transaction, but a lot of this was already implemented for transaction intents.
When adding async thunk to a transaction the flow is something like this:
This solution has been implemented here: #195 but has some problems.
With this solution, if you mix inputs/results from one fork with another fork, correctly reconciling the proper order when merging becomes very difficult.
Transaction Sections
Rather than fully forking transactions and merging them back together, we can create proxies/copies of the transaction that mutate the same underlying data, but track which proxy commands and inputs were inserted with. This allows us to re-order the transaction before build to ensure that commands and inputs are added in a deterministic and expected order.
This would work something like:
This solution is implemented here: #198
Beta Was this translation helpful? Give feedback.
All reactions