Async chain state transition using messages

I propose that we change how confidential requests result in suave chain state transitions.

Currently the result of the execution of a confidential request overrides the calldata used in chain transition. I propose that instead users request suave chain transitions via a new library call Suave.emit_chain_sideeffect, providing it the calldata which should be executed on chain.

Changelog:

  • 5th Dec: renamed onchain to chain_callback to emit_chain_sideeffect
  • 5th Dec: added a function that returns the log to be emitted prepare_chain_sideeffect. This makes it impossible to confuse the call for being blocking
library Suave {
    event OnchainSideeffect(/* ... */)
    function prepare_chain_sideeffect(address to, bytes data) pure returns OnchainSideeffect {}
    function emit_chain_sideeffect(address to, bytes data) pure {
        emit prepare_chain_sideeffect(to, data);
    }
}

This change has two important benefits:

  1. Explicit, structured way to execute side effects on suave chain from confidential execution. The structure is easy to extend to accommodate future needs, say attestations.
  2. We no longer have to limit to a single on chain side effect as with a return statement. Complex applications that require multiple chain state transitions will be much simpler to develop. In particular this allows modular applications that handle their own chain state transitions, even if parts of the code of the application are not known or the developer does not control them.

There are some less important, but still nice benefits:

  • You can request state change from a deployment transaction, not possible previously (since constructors have no returns!)
  • You can deploy contracts from confidential transactions, since adding the to address is trivial
  • Once we introduce SGX, attestations become transparent as all callback messages can be attested to on the fly. Something similar can be done while using result for the calldata, but it’s not as simple and elegant because of the need for aggregation of both the results as well as the attestations .

Usage

contract Suapp {
    int _remainder
    function update_rem(int remainder) {
        _remainder = remainder;
    }

    /* New */
    function run_confidential(int x) {
        emit Suave.prepare_chain_sideeffect(address(this),
          abi.encodeWithSelector(update_rem.selector, x % 50),
        );
    }

    /* Old */
    function run_confidential(int x) {
        return abi.encodeWithSelector(update_rem.selector, x % 50);
    }
}

The change seems underwhelming at first, but now imagine that this Suapp is a part of a bigger application. Using Suave.emit_chain_sideeffect embedding this Suapp is trivial - does not require any changes. Using return to overwrite the calldata embedding even this simple Suapp is already impractical because we have to aggregate the intermediate returns:

contract ParentSuapp {
    Suapp s

    function emit_x(int x) {
        emit x;
    }

    /* New */
    function run_confidential(int x) {
        s.run_confidential(x * 17);
        Suave.emit_chain_sideeffect(address(this), abi.encodeWithSelector(emit_x.selector, x));
    }

    /* Old */
    function run_confidential(int x) {
        bytes suappUpdate = s.run_confidential(x * 17);
        return abi.encodeWithSelector(emit_and_run_s.selector, suappUpdate, x);
    }
    function emit_and_run_s(bytes suappUpdateBytes, int x) {
        address(s).call(suappUpdateBytes);
        emit_x(x);
    }
}

The Suave.emit_chain_sideeffect method of requesting chain state transitions is clearly superior in this still trivial example. Developing a large application in the current return-oriented way quickly becomes a UX nightmare.

Specs

For the implementation, I propose that the library call emits an event with the required fields, and the kettle executing the confidential request captures those events (logs) and puts them into the resulting SuaveTransaction as messages, which are later executed on chain.
Spec PR.

2 Likes

This is a summary of the convo I had with @mateusz:

First, from the DevEx perspective, the Suave.chain_callback API call looks like a synchronous query. Developers might think that the Suapp blocks at that point of the execution to make an on-chain call somewhere (they might even think it is calling the Ethereum L1).

Two possible solutions we discussed for this would be either to make the name extremely unambiguous or leverage the emit X syntax from Solidity to emit a certain event type.

Second, by making it such that any arbitrary Suapps during the confidential execution can make attestations at any time we open the spectrum for possible attack vectors that we have not even thought about.

Third, I think we should start with the same semantics that we have right now (one attestation/callback per confidential request) and expand the design space from there as we find new use cases for Suapps and get more external feedback.

1 Like

I don’t have a strong opinion on the first issue - chain_callback seeming blocking, so I’d say we can rename it back to onchain and make it return the event that is supposed to be emitted, and have the user emit it.
Also open to suggestions on an alternative name. Maybe emit_chain_callback? emit_chain_sideeffect?

Second, by making it such that any arbitrary Suapps during the confidential execution can make attestations at any time we open the spectrum for possible attack vectors that we have not even thought about.

As far as attestations go, this is actually the safest way to go about as you can’t easily call functions from your attested functions - each function that you want to call you need to pass an attestation to and verify it each time. Hence this change is a big benefit to attestations UX. We’ll cross that bridge once we get there, but from what I’ve gathered this is the right thing.
Additionally, attestations are context dependent, and you have to make them in a specific place - you can’t do one attestation at the end or anything like that.

Third, I think we should start with the same semantics that we have right now (one attestation/callback per confidential request) and expand the design space from there as we find new use cases for Suapps and get more external feedback.

I don’t see why not, although I also don’t see why we would restrict it up front

1 Like

Could you motivate for me why we would want to attest to different portions of the state transition? Is it that some attestations may take longer? Is it so we could parallelize attestations in the event they dont touch overlapping state? Is it possible some parts of the state transition gets attested to but not others?

Edit: on parallelization, could we also add state access lists to these side-effect requests? I believe we should be able to with assembly?

1 Like

Still grokking, but one question that comes to mind is how does the MEVM process Messages []core.Message as the naive way of processing each individually would inhibit onchain atomicity between messages I believe?

This may not be a large concern though, and the reason for that is additionally why I am beginning to be in favor of this approach: you can replicate the old approach within the new approach.

But overall, if I am understanding this correctly, then I am a fan! The parentSuapp example is particularly motivating.

On UX: side effects was an initially confusing term for me, could we call it something like request_state_update or trigger_state_update? This would fit well with our internal thinking about multiple different states within a kettle, the two most obvious being local and global. Actually, an even cooler idea, perhaps we could eventually have multiple “DA layers” (slight term abuse sorry) and you could chose which is updated via your side effect/update!

Great post! I’m really happy we’re thinking about how info leakage works. Sorry I’m slow to the party - missed this somehow.

Two observations about the implications of the decisions we are making now:

  • one function calling another, may want to control what information is emitted during the subcall. (E.g. in the M.O.S.S. idea the build() function needs to know info isn’t being leaked by the bundle function

  • by processing SUAVE chain state transitions based on TEE signatures (verified pre-contract execution as opposed to by the contracts themselves), we are giving the whole of suave-chain the same security guarantees as a TEE. This might be fine and something we choose to do for computational efficiency reasons, but it certainly has tradeoffs. If we let these TEE signatures be verified at the application layer then apps could choose whether to rely on that security model or not.

This makes sense to me. I don’t see why we should break the abstraction of “info leaving the TEE” by providing different routes for info to leave the TEE.

this feels like there’s some hidden cool competitive dynamics that can come from this

Agree with the name emit_chain_sideeffect being confusing. Side-effect almost implies it’s something unwanted.

I much prefer trigger_state_update as it’s much clearer what’s the intent and what happens. @lthibault might also have some thoughts on the naming here ^^

1 Like

Hey everyone! Thanks for pointing me to this thread, @metachris!

One thing I keep noticing is how close we are to having an algebraic effect system. I’ve spoken with several of you about this idea, and I would like to propose a couple of surface-level tweaks that edge us closer in that direction.

library Suave {
    event Effect(address to, bytes data)

    function newEffect(address to, bytes data) pure returns Effect {}
    function perform(e Effect) pure {
        emit e;
    }
}
contract Suapp {
    int _remainder
    function update_rem(int remainder) {
        _remainder = remainder;
    }

    /* New -- inline calls unpacked for clarity */
    function run_confidential(int x) {
        bytes input = abi.encodeWithSelector(update_rem.selector, x % 50);
        Effect e = Suave.newEffect(address(this), input);
        Suave.perform(e);
    }

    /* Old */
    function run_confidential(int x) {
        return abi.encodeWithSelector(update_rem.selector, x % 50);
    }
}

For the avoidance of doubt, this is functionally equivalent to @mateusz’s proposal, but I think this refactoring and nomenclature are the “correct” way of looking a this problem. What do I mean by “correct”? Essentially, it sets us up to model on-chain effects as an algebraic effect system. From the link:

In computing, an effect system is a formal system that describes the computational effects of computer programs, such as side effects. An effect system can be used to provide a check of the possible effects of the program.

In plain English: this offers us a mechanism for making the MEVM “aware” of the distinction between in-band (on-chain) computation and out of band (off-chain) computation [0]. I’ll argue for the importance of an effect system separately, as the current proposal is really just a function refactor / nomenclature change. In the meantime, I’d like to draw your attention to the possibility of a small refactor in the future:

interface IEffectful {
    function perform() pure;
}

library Suave {
    function perform(e IEffectful) pure {
        e.perform();
    }
}

The main benefit of this refactor is to open us to type-switching or pattern-matching against effects, which in turn allows us to introduce a notion of (potentially asynchronous) effect handlers:

// I'm a little shaky on the syntax here, so please interpret this loosely.
// The point is that `handle` registers a callback.
Suave.handle(address(SomeEffect), someMethodPointerOrAddressToWASM);

Key point: different applications can specify different, namespaced, scoped effects. Only effects can … well… effect changes to out-of-band systems.

I don’t want to get too far over my skis, so I’ll stop here for the time being. Happy to flesh this out in a separate communication. In the meantime, here’s a fairly accessible introduction to the topic of algebraic effects. I think it does a good job of showing how these systems offer a unifying abstraction over

  • exceptions
  • task scheduling
  • context switching (including band-switching)
  • asynchronous state changes

all of which seem directly relevant to SUAVE’s design goals.

Cheers,
Louis

P.S.: this is my first post on the forum :slight_smile:

Footnotes:

  • [0] As an aside, I’m quite fond of the terms “in-band” and “out-of-band” rather than “public” and “confidential” to describe computation in SUAVE; confidentiality is but one of several uses of out-of-band computation. I digress…
3 Likes