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

Migrating away from Futures #87

Open
sz-piotr opened this issue Jan 25, 2022 · 2 comments
Open

Migrating away from Futures #87

sz-piotr opened this issue Jan 25, 2022 · 2 comments
Labels

Comments

@sz-piotr
Copy link
Contributor

After a conversation with @marekkirejczyk I have decided to revisit code from the early days of Mars to try to remember why Features were introduces, what benefits they bring over promises and how a safe migration away from them could be made.

Some history

When Mars was first envisioned we thought about it as Terraform but for smart contracts. A key idea from that time was to copy the Write, Plan, Apply model - Mars scripts would tell you what they were going to do before sending a single transaction.

In order to achieve this execution plan we couldn't rely on promises, because the concrete steps taken would only be known once the code is run, and the original code didn't have a dry run option.

Benefits of futures

While Futures provide little benefit in terms of things that are possible (because once dry run was implemented you could freely run your code without actually sending any mainnet transactions) their restrictiveness circumvents some possible issues with a Promise approach:

  1. It is hard to implement a for loop based on the value returned from a smart contract, meaning that the scripts are easier to reason about. All contracts deployed in a for loop would require unique names otherwise only one contract would be deployed which is non-obvious.
  2. It is impossible to run code concurrently, which means that we are guaranteed that a transaction finishes execution before sending another one. While this means that the script is slow it also means that the script is safe from problems with nonces or failed transactions which were required to succeed before proceeding.
  3. Because all the code is synchronous all the code can use a shared global context and it can easily be enforced that code has to reside inside a deploy function. Otherwise one might use Mars functions outside of the available context and get unexpected results, because Mars would think they are part of another script run: e.g:
setTimeout(() => contract(Token), 2000)
deploy(() => {
  await contract(Token)
})
  1. When using JS one can achieve a runtime pseudo type safety, because the code produces the execution plan which then is later executed, meaning that if the execution plan cannot be produced then no execution occurs. This in turn prevents sending a few transactions only to discover that the second part of the script actually throws a TypeError.

Mitigations

For loop

The below code will actually only result in a single code deployment, because subsequent calls to contract see that a Token contract with such a name was already deployed. This is unintuitive and can confuse script authors.

for (let i = 0; i < 5; i++) {
  await contract(Token)
}
// or
await contract(Token)
await contract(Token)

I propose that the contract function checks whether or not a particular name has been queried for previously during script execution and throws an error or produces a warning that explains that the user should provide a different name for the contract.

Promise.all

The below code introduces a potential problem where the transactions execute in parallel with nonces and reverts breaking the script logic.

await Promise.all([
   contract('a', Token),
   contract('b', Token),
])

To mitigate this a locking system has to be implemented which detects that an execution is in progress and throws an error explaining to the user that they cannot run code concurrently in Mars.

Global context

With Futures Mars is able to store some parameters in a global context that is only used during the execution planning phase to pass parameters which makes it short lived and relatively safe. With promises the global context would live longer and checks preventing re-initializing the context mid execution have to be put in place.

Execution safety

It would be ideal for Mars to detect if the code of the script changed and refuse to execute without doing a dry run first. This would serve a similar purpose as the plan phase with Futures, evaluating the script in its entirety before sending any real transactions.

@marcin-trust
Copy link
Contributor

marcin-trust commented Jan 26, 2022

Thanks a lot @sz-piotr for input from your memories. For the moment being, I'm coordinating development into Mars. Could you see my ADR #82 that considers 1) modularity improvement and 2) future of the Future?

Meanwhile I'm going to think thoroughly your input.

@marcin-trust
Copy link
Contributor

There's also a hot new feature that just landed here: multisig support. See #85 . Maybe it covers some of the workflows that Futures aimed to support too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants