Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Runtime triggers #516

@bowenwang1996

Description

@bowenwang1996

It is sometimes useful for smart contract to subscribe to some event and perform an action based on the result of an event. One simple example is cronjob. Currently smart contracts have no way of scheduling an action that is periodically triggered (in this case the event that triggers the action is time) and as a result, if someone wants to achieve this, they need to rely on offchain infrastructure that periodically calls the smart contract, which is quite cumbersome. A more complex use case is chain signatures, which needs the execution to resume once the signature is generated by validators.

In terms of effect, this should roughly be equivalent to if the smart contract has a method that constantly calls itself

fn trigger(&self) -> Promise {
  if condition {
     do_something
  }
  Self::ext(env::current_account_id()).trigger()
}

However, a function like this is not practical as is because of gas limit associated with each specific function call. However, we can extend the mechanism of callbacks to allow them be not triggered immediately, but rather when a specific condition is met. More specifically, we can introduce a new type of Promise DelayedPromise that generates a postponed receipt, similar to what callbacks do today. However, the postponed receipt is stored globally in the shard, instead of under a specific account (at least conceptually, in practice a separate index could be created if that helps), along with the condition that triggers the delayed promise. Then, during the execution of every chunk, the runtime first checks whether any of the delayed promise should be triggered and execute them if the triggering condition is met. Roughly this would allow us to rewrite the example above into

fn trigger_condition(&self) -> bool {
   // some condition specified by the contract
   condition
}

fn trigger(&self) -> Promise {
  if condition {
     do_something
  }
  // specify the trigger condition, callback and arguments to the callback. This is almost a call to self with callback, except that it also specifies the trigger condition.
  DelayedPromise::new(env::current_account_id(), trigger_condition, trigger, {})
}

For this idea to work, a few issues need to be resolved:

  • How the delayed promises are paid for. In NEAR's execution model, it is always the signer of the transaction who pays for the entire execution chain, regardless of the number of receipts emitted during the execution. However, in the case of a delayed promise, the situation may get more complex. There are two possible options here:
    1. a delayed promise can only be triggered once, with no possibility of scheduling further delayed promises. In this simple mode, we can reuse the existing gas model and continue to ask the signer of the transaction to pay for the promise without having to worry about issues like running out of gas.
    2. a delayed promise can be triggered multiple times, or it can schedule delayed promises within its execution. Under this assumption, the existing gas model does not work because then we would run into a similar problem described at the beginning - there won't be enough gas to execute the delayed promise because of the single function call gas limit (300Tgas). One possible idea to address this issue is to say that the contract which schedules the delayed promise has to pay for its execution and it is the responsibility of the smart contract to figure out how to transfer such cost to users if needed. Then, if the smart contract is not able to pay for the cost of the delayed promise, its execution would fail and no new promises would be scheduled.
  • How triggers are specified. The trigger should be specified by the contract and can depend on the execution context (VMContext). One idea is that we could introduce a special type of function that a smart contract can implement to specify the behavior of a trigger it plans to use. The function must return a boolean value and must consume little gas (the exact threshold needs to be defined). Otherwise a malicious attacker could run an infinite loop to cause validators to do a lot more work and slow down the network as a result. The cost of running the trigger should be priced into the cost of a delayed promise so that this cost is accounted for.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    Shipped 🚀

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions