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

Support for yielded execution #519

Merged
merged 33 commits into from
Jun 18, 2024
Merged
Changes from 32 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c23b15c
WIP: yield execution
akhi3030 Nov 17, 2023
1f8dbbd
rename file as per instructions
akhi3030 Nov 17, 2023
0931acd
Add a summary and update some metadata
akhi3030 Nov 17, 2023
5b54b47
minor cleanup
akhi3030 Nov 20, 2023
20c993f
add motivation
akhi3030 Nov 20, 2023
b8d882c
add draft of host function calls
akhi3030 Dec 5, 2023
fcb5230
Apply suggestions from code review
akhi3030 Dec 12, 2023
bc6708b
update the API
akhi3030 Feb 14, 2024
aef122a
add alternatives
akhi3030 Feb 14, 2024
5ce8652
address some review comments
akhi3030 Feb 19, 2024
4cc7d88
add discussion of security implications
akhi3030 Feb 19, 2024
a57d2a5
fix lint issue
akhi3030 Feb 19, 2024
ce2a28d
add a reference implementation
akhi3030 Feb 19, 2024
3fa309a
fix some more lints
akhi3030 Feb 19, 2024
4f0bf49
Add Saketh as author
akhi3030 Feb 22, 2024
48757e4
Update neps/nep-519-yield-execution.md
akhi3030 Feb 28, 2024
60da3e1
Update neps/nep-519-yield-execution.md
akhi3030 Feb 28, 2024
48bd2a9
Update neps/nep-519-yield-execution.md
akhi3030 Feb 28, 2024
64b5fb6
Update neps/nep-519-yield-execution.md
akhi3030 Feb 28, 2024
c058614
Update neps/nep-519-yield-execution.md
akhi3030 Feb 28, 2024
7084473
Update neps/nep-519-yield-execution.md
akhi3030 Feb 28, 2024
8acb8da
specify blocks
akhi3030 Feb 28, 2024
7adce0e
specify blocks again
akhi3030 Feb 28, 2024
09cc2e0
cleanup templates that are not needed
akhi3030 Feb 28, 2024
8facd6b
remove ref impl
akhi3030 Feb 28, 2024
43fa6f3
clarify how the args are available and some formatting
akhi3030 Feb 28, 2024
cd9bc85
minor improv english
akhi3030 Feb 28, 2024
812cff3
apply suggestion from review
akhi3030 Feb 28, 2024
2728727
apply suggestion from review
akhi3030 Feb 28, 2024
577970e
Improve specification
akhi3030 Feb 28, 2024
13ebeb6
Update neps/nep-519-yield-execution.md
akhi3030 Mar 7, 2024
10920a6
some minor improvements to the comments
akhi3030 Mar 7, 2024
77bb83e
apply minor nit comment
akhi3030 Mar 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
191 changes: 191 additions & 0 deletions neps/nep-519-yield-execution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
---
NEP: 519
Title: Yield Execution
Authors: Akhi Singhania <akhi3030@gmail.com>; Saketh Are <saketh@near.org>
Status: Draft
DiscussionsTo: https://github.com/near/NEPs/pull/519
Type: Protocol
Version: 0.0.0
Created: 2023-11-17
LastUpdated: 2023-11-20
---

## Summary

Today, when a smart contract is called by a user or another contract, it has no sensible way to delay responding to the caller till it has observed another future transaction. This proposal introduces this possibility into the NEAR protocol.

## Motivation

There exist some situations where when a smart contract on NEAR is called, it will only be able to provide an answer at some time in the future. The callee needs a way to defer replying to the caller while the response is being prepared.

Examples include a smart contract (`S`) that provides the MPC signing capability. It relies on indexers external to the NEAR protocol for computing the signatures. The rough steps are:

1. Signer contract provides a function `fn sign_payload(Payload, ...)`.
2. When called, the contract defers replying to the caller.
3. External indexers are monitoring the transactions on the contract; they observe the new signing request, compute a signature, and call another function `fn signature_available(Signature, ...)` on the signer contract.
4. The signer contract validates the signature and replies to the original caller.

Today, the NEAR protocol has no sensible way to defer replying to the caller in step 2 above. This proposal proposes adding two following new host functions to the NEAR protocol:

- `promise_yield_create`: allows setting up a continuation function that should only be executed after `promise_yield_resume` is invoked. Together with `promise_return` this allows delaying the reply to the caller;
- `promise_yield_resume`: indicates to the protocol that the continuation to the yield may now be executed.

If these two host functions were available, then `promise_yield_create` would be used to implement step 2 above and `promise_yield_resume` would be used for step 3 of the motivating example above.

## Specification

The proposal is to add the following host functions to the NEAR protocol:


```rust
/// Smart contracts can use this host function along with
/// `promise_yield_resume()` to delay replying to their caller for up to 200
/// blocks. This host function allows the contract to provide a callback to the
/// protocol that will be executed after either contract calls
/// `promise_yield_resume()` or after 200 blocks have been executed. The
/// callback then has the opportunity to either reply to the caller or to delay
/// replying again.
///
/// `method_name_len` and `method_name_ptr`: Identify the callback method that
/// should be executed either after the contract calls `promise_yield_resume()`
/// or after 200 blocks have been executed.
///
/// `arguments_len` and `arguments_ptr` provide an initial blob of arguments
/// that will be passed to the callback. These will be available via the
/// `input` host function.
///
/// `gas`: Similar to the `gas` parameter in
/// [promise_create](https://github.com/near/nearcore/blob/a908de36ab6f75eb130447a5788007e26d05f93e/runtime/near-vm-runner/src/logic/logic.rs#L1281),
/// the `gas` parameter is a prepayment for the gas that would be used to
/// execute the callback.
///
/// `gas_weight`: Similar to the `gas_weight` parameter in
/// [promise_batch_action_function_call_weight](https://github.com/near/nearcore/blob/a908de36ab6f75eb130447a5788007e26d05f93e/runtime/near-vm-runner/src/logic/logic.rs#L1699),
/// this improves the devX for the smart contract. It allows a contract to
/// specify a portion of the remaining gas for executing the callback instead of
/// specifying a precise amount.
///
/// `register_id`: is used to identify the register that will be filled with a
/// unique resumption token. This token is used with `promise_yield_resume` to
/// resolve the continuation receipt set up by this function.
///
/// Return value: u64: Similar to the
/// [promise_create](https://github.com/near/nearcore/blob/a908de36ab6f75eb130447a5788007e26d05f93e/runtime/near-vm-runner/src/logic/logic.rs#L1281)
/// host function, this function also create a promise and returns an index to
/// the promise. This index can be used to create a chain of promises.
pub fn promise_yield_create(
method_name_len: u64,
method_name_ptr: u64,
akhi3030 marked this conversation as resolved.
Show resolved Hide resolved
arguments_len: u64,
arguments_ptr: u64,
gas: u64,
gas_weight: u64,
register_id: u64,
) -> u64;

/// See `promise_yield_create()` for more details. This host function can be
/// used to resolve the continuation that was set up by
/// `promise_yield_create()`. The contract calling this function must be the
/// same contract that called `promise_yield_create()` earlier. This host
/// function cannot be called for the same resumption token twice or if the
/// callback specified in `promise_yield_create()` has already executed.
///
/// `data_id_len` and `data_it_ptr`: Used to pass the unique resumption token
/// that was returned to the smart contract in the `promise_yield_create()`
/// function (via the register).
///
/// `payload_len` and `payload_ptr`: the smart contract can provide an
/// additional optional blob of arguments that should be passed to the callback
/// that will be resumed. These are available via the `promise_result` host
/// function.
///
/// This function can be called multiple times. If it is called successfully
akhi3030 marked this conversation as resolved.
Show resolved Hide resolved
/// multiple times, then the implementation guarantees that the yielded callback
/// will execute with one of the successfully submitted payloads. If submission
/// was successful, then `1` is returned. Otherwise (e.g. if the yield receipt
/// has already timed out or the yielded callback has already been executed) `0`
/// will be returned, indicating that this payload could not be submitted
/// successfully.
pub fn promise_yield_resume(
data_id_len: u64,
data_id_ptr: u64,
payload_len: u64,
payload_ptr: u64,
) -> u32;
```

## Reference Implementation

The reference implementation against the nearcore repository can be found in this [PR](https://github.com/near/nearcore/pull/10415).

## Security Implications

Some potential security issues have been identified and are covered below:

- Smart contracts using this functionality have to be careful not to let just any party trigger a call to `promise_yield_resume`. In the example above, it is possible that a malicious actor may pretend to be an external signer and call the `signature_available()` function with an incorrect signature. Hence contracts should be taking precautions by only letting select callers call the function (by using [this](https://github.com/aurora-is-near/near-plugins/blob/master/near-plugins/src/access_controllable.rs) service for example) and validating the payload before acting upon it.
- This mechanism introduces a new way to create delayed receipts in the protocol. When the protocol is under conditions of congestion, this mechanism could be used to further aggravate the situation. This is deemed as not a terrible issue as the existing mechanisms of using promises and etc. can also be used to further exacerbate the situation.

## Alternatives

Two alternatives have been identified.

### Self calls to delay replying

In the `fn sign_payload(Payload, ...)` function, instead of calling `yield`, the contract can keep calling itself in a loop till external indexer replies with the signature. This would work but would be very fragile and expensive. The contract would have to pay for all the calls and function executions while it is waiting for the response. Also depending on the congestion on the network; if the shard is not busy at all, some self calls could happen within the same block meaning that the contract might not actually wait for as long as it hoped for and if the network is very busy then the call from the external indexer might be arbitrarily delayed.

### Change the flow of calls

The general flow of cross contract calls in NEAR is that a contract `A` sends a request to another contract `B` to perform a service and `B` replies to `A` with the response. This flow could be altered. When a contract `A` calls `B` to perform a service, `B` could respond with a "promise to call it later with the answer". Then when the signature is eventually available, `B` can then send `A` a request with the signature.

There are some problems with this approach though. After the change of flow of calls; `B` is now going to be paying for gas for various executions that `A` should have been paying for. Due to bugs or malicious intent, `B` could forget to call `A` with the signature. If `A` is calling `B` deep in a call tree and `B` replies to it without actually providing an answer, then `A` would need a mechanism to keep the call tree alive while it waits for `B` to call it with the signature in effect running into the same problem that this NEP is attempting to solve.

## Future possibilities

One potential future possibility is to allow contracts to specify how long the protocol should wait (up to a certain limit) for the contract to call `promise_yield_resume`. If contracts specify a smaller value, they would potentially be charged a smaller gas fee. This would make contracts more efficient. This enhancement does lead to a more complex implementation and could even allow malicious contracts to more easily concentrate a lot of callbacks to occur at the same time increasing the congestion on the network. Hence, we decided not to include this feature for the time being.

## Consequences

[This section describes the consequences, after applying the decision. All consequences should be summarized here, not just the "positive" ones. Record any concerns raised throughout the NEP discussion.]

### Positive

- p1

### Neutral

- n1

### Negative

- n1

### Backwards Compatibility

We believe this can be implemented with full backwards compatibility.

## Changelog

### 1.0.0 - Initial Version

> Placeholder for the context about when and who approved this NEP version.

#### Benefits

> List of benefits filled by the Subject Matter Experts while reviewing this version:

- Benefit 1
- Benefit 2

#### Concerns

> Template for Subject Matter Experts review for this version:
> Status: New | Ongoing | Resolved

| # | Concern | Resolution | Status |
| --: | :------ | :--------- | -----: |
| 1 | | | |
| 2 | | | |

## Copyright

Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
Loading