-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
[Fix] Relax ordering checks for futures #2356
Conversation
To make sure I understand, could this perhaps say: |
@vicsn Thank you. I have revised it to make it more clear. |
46f37b2
to
0570e9e
Compare
0570e9e
to
cc6d9cb
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left some some comments.
TLDR - The enforcement that futures must be consumed in the order they are produced is too strict. It forces developers to commit in advance to the order in which on-chain code is finalized and eliminates any opportunity to react dynamically to on-chain state. This ability to conditionally order external function calls is assumed on all standard programmable blockchains. By allowing the user to await futures in an arbitrary order, we can restore feature parity in this domain with other ecosystems, reduce developer experience friction and provide more intuitive syntax, all while preserving the existing intended security assumptions.
Motivation
Background
The functions in Aleo programs are split into two categories: offline and online. Offline functions are executed offline and are subsequently represented by a proof of the executions correctness. Online finalize functions must be re-executed and are subject to change depending on the state of the ledger. Although offline and online functions are linked in the sense that they share a name (think "function foo()" and the "finalize foo()" that follows), the offline function can never interact with the online state, whereas the online function receives input parameters from the offline call.
Earlier this year, the implementation of the asyncs feature made calls to external transitions produce futures, which must in turn be awaited inside the current function's corresponding finalize block. The intention of this feature was to give a more intuitive depiction of how the online and offline state interact by leveraging familiar async-await-future syntax. Additionally the feature allowed users to interleave additional instructions between the awaiting of external futures, which was impossible to do beforehand.
Problem 1: Conceptual
One constraint imposed in this feature was that the order in which external transition functions are called must be equivalent to the order in which the corresponding external finalize functions are awaited. This is problematic, as it gives the illusion that the ordering of asynchronous execution can be governed inside a synchronous block of code, when by definition, it is impossible to control the ordering in which asynchronous queries are executed.
Consider a simple example in which a developer wants to query the state of the same website twice in a row. Since the internet is asynchronous, if they use a protocol like UDP, there is no guarantee that the first request captures an earlier state of the website than the second request. The only way for the developer to ensure that the first one viewed the state before the second one is if they block in between the queries. This is not an option in Aleo programs because as soon as we block to wait for an on-chain execution to happen, we can no longer do more offline execution and subsequently generate a proof. We would have to wait for the asynchronous code to execute, attest to it, and then proceed. But this is also impossible as our attestation would not necessarily be correct as someone else could have modified the state in between our first finalize execution and our second finalize execution.
Problem 2: Loss of functionality
Under the current model, a developer is unable to re-order the awaiting of futures in a finalize block. In this way they are unable to write code that reacts to on-chain state, and conditionally choose the order to await futures. This functionality is a default assumption on all existing programmable blockchains.
Consider the abstract case in which program
A.aleo
importsB.aleo
andC.aleo
, andB.aleo
importsC.aleo
. Now supposeA.aleo/foo()
externally callsB.aleo/boo()
which in turn externally callsC.aleo/woo()
. It is now impossible to write finalize logic forA.aleo/foo
in which it first reads some on-chain state fromC.aleo
and depending on the result chooses whether to awaitB.aleo/boo.future
orC.aleo/woo.future
first. This would be beneficial in a case whereC.aleo/woo()
modifies on-chain state, such as its own mappings.Testing
In addition to passing CI, I have added a new more complicated test to make sure nothing funny happens when futures are re-ordered. Also the tests asserting that all futures must be awaited, and that all futures must be propagated to an async call both still pass.