spacemeshos / SMIPS

Spacemesh Improvement Proposals
https://spacemesh.io
Creative Commons Zero v1.0 Universal
7 stars 1 forks source link

AA Transactions & SVM Integration #53

Closed YaronWittenstein closed 1 year ago

YaronWittenstein commented 2 years ago

Overview

This SMIP will outline the required changes to the platform Transactions as a result of shifting towards the new Account Unification (#49) model and SVM Integration.

Additionally, the upcoming integration of go-spacemesh with SVM will require significant changes for clients as well. Existing clients such as smapp and Process Explorer will have to adapt accordingly.

Goals and motivation

The motivation for this SMIP is to streamline the integration process between go-spacemesh and SVM. Getting everyone up to speed with the same terminology and agreeing on the way things will work is crucial.

In the new Account Unification (#49) model each Transaction will essentially be a Smart-Contract transaction, or SVM transaction if to be more precise. Even simple coins transactions will be implemented as a Smart-Contract.

In SVM there is a distinction between deploying a Smart-Contract spec and creating instances of it. A Smart-Contract specification is named a Template and each live instance is called an Account.

Each Account will have an Address and a balance and Template Address it was spawned from. It will contain more internals which are out of scope for this document.

There are in total 3 types of Transactions SVM v0.3:

This SMIP will detail the fields that each Transaction will consist of without getting into low-level encoding.

High-level design

Each binary (over-the-wire) Transaction will consist of two main parts:

Each Message will be accompanied by the Envelope to form together a complete binary Transaction.

In addition to the explicit data sent over-the-wire, there will be implicit fields inferred from it. One such example is the Transaction Id. For the context of this document, we won't regard these implied fields as part of the Transaction.

Adding to a binary Transaction, there is the Execution Context (or alternatively the Transaction Context or simply Context). The Context structure will contain additional data (alongside the Transaction) to be used by SVM when executing a Transaction. Properties such as Layer or Block Number and the State Root Hash will be part of it. It'll be probably useful to attach implied properties such as Transaction Id as well to sit under the Context.

When SVM will be given a Transaction to execute it'll receive both the Transaction data and its attached Context.

Proposed implementation

Since SVM is implemented in Rust and go-spacemesh in Golang, it's of key importance to make life easier as much as possible when designing the integration.

In order to make the two components talk, they will have to abide the FFI rules. Each successful SVM CI will emit os-specific (macOS/Linux/Windows) binaries.

SVM FFI layer will be wrapped by a project named go-svm, serving as the bridge between Golang and Rust. Any cGo related code will be handled by it. It will expose pure Golang API and mask any FFI related code to the external world. The go-spacemesh will depend on go-svm and know nothing about its internals.

Generally, each call to go-svm for the sake of validation or execution will be equipped with:

1. Envelope

The parsed Envelope as a Golang struct.

Peeling off the binary Envelope out of a binary Transaction is the responsibility of the go-spacemesh.

2. Message

The binary Message is what's left after peeling-off the Envelope out of a binary Transaction. This data is opaque to go-spacemesh and it will be passed as it's forward to the go-svm.

3. Context

The Context will be passed as a Golang struct into go-svm. It will contain inferred data from the binary Transaction (e.g Transaction Id) and execution related data (e.g layer / State Root Hash).

Transactions Fields

Envelope

The exact Encoding of the Envelope will be decided by the go-spacemesh team. It can be XDR or any other encoding. The go-svm expects to receive an already parsed Envelopes in the form of a Golang struct.

Message

Deploy

The Deploy Message will be generated by the Template compiler. Right now it'll be using the SVM SDK, but in the future there might be more ways to achieve that.


 Important: There are no assumptions regarding the order of the Sections.

+----------------+
|                |
|  Code Section  |
|                |
+----------------+
|                |
| Data Section   |
|                |
+----------------+
|                |
| Ctors Section  |
|                |
+----------------+
|                |
| Header Section | (Optional)
|                |
+----------------+
|                |
| Schema Section | (Optional)
|                |
+----------------+
|                |
|  API Section   | (Optional)
|                |
+----------------+
|                |
| Deploy Section | (Optional, will be derived from the `Transaction Envelope` and `Transaction Context`)
|                |
+----------------+

Spawn

Each Spawn Message contain the following fields:

+-----------+-------------+----------------+
|           |             |                |
|  Version  |  Template   |      Name      |
|   (u16)   |  (Address)  |    (String)    |
|           |             |                |
+-----------+-------------+----------------+
|           |                              |
|   Ctor    |          CallData            |
|  (String) |           (Blob)             |
|           |                              |
+-----------+------------------------------+

Call

+-----------+-------------+----------------+
|           |             |                |
|  Version  |   Target    |      Name      |
|   (u16)   |  (Address)  |    (String)    |
|           |             |                |
+-----------+-------------+----------------+
|           |             |                |
|  Function | VerifyData  |    CallData    |
| (String)  |   (Blob)    |     (Blob)     |
|           |             |                |
+-----------+-------------+----------------+

The Encoding of each Call Message will be implemented by SVM which will provide a svm_codec.wasm. Each successful SVM CI build will emit under its Artifacts the svm_codec.wasm.

Dependencies and interactions

Stakeholders and reviewers

@YaronWittenstein @neysofu @noamnelke @sudachen @lrettig @avive

avive commented 2 years ago

I think it is important to add some design regarding signatures to the smip that answers these questions:

  1. Where is the signature in the transaction field?
  2. What transaction fields or sections are signed?
  3. Clarify how signing works - it must be sign on a hash of payload, not a payload so we can support ledger and other hardware wallets signatures requirements.
  4. What signature formats are going to be supported for 0.3? I assume ED and ED++.
  5. How does the use of signature scheme affects the transaction field. For example, in ED++ signatures the public key is not part of the payload but in ED it is.
  6. Need to spec the signature verification algorithm which may not be trivial as it combined contextual information not in the tx payload. e.g. netId.
tal-m commented 2 years ago

@avive There's no specification for what is signed at this level. The signatures are decoded and verified in the verify method of the account --- this can vary from account to account.

I think there should be a separate SMIP (maybe SMIPs) for the specific account types we'll have at launch. Each account type specifies the answers to your questions (Except for q.4, which is common for all of them --- because raw signature verification must be supported in native code on the SVM side --- and possibly q.5 --- see below).

Q.5 actually is a bit tricky, and does relate to this SMIP. We have to keep in mind that sometimes decoding of the envelope is actually transaction-dependent. This is the case, for example, when we're doing public-key extraction. The encoded transaction in this case doesn't have the principal's address. Instead, the address is extracted from the signature.

We said we'd solve this by having an external "decoding" layer, that recognizes specific transaction types and "decodes" the principal address by running the PK extraction. If this decoding is done on the go side, as you suggest in this SMIP, there will be some duplication of code --- both the decoder (in go) and the verify function (in SVM) will need to run ED++ signature verification operations.

Alternatively, we can have the transaction decoding step also take place on the SVM side, which I think is a little cleaner in terms of software design. This would potentially allow us to encode/decode in more optimized ways in the future, and my guess is that it would be more efficient as well (since there's less context switching between go and SVM).

lrettig commented 2 years ago

Transactions Fields Envelope version - For versioning (to ease introducing new changes in the future).

Is this "version" of the transaction object or SVM version? If the latter, maybe we could give this a more generic name such as "vm_type" (since it may not just be a version, it may indeed include both the name of the engine -- svm, evm, rollup-specific engine, whatever -- and the version)

type - The Transaction type (Deploy / Spawn / Call) principal - The Address of the Account paying for the Gas.

paying for the gas and the source of funds for amount (if we include this)

amount(*) - For funding (see more information later under Questions/concerns). gas_limit - Maximum units of Gas to be paid. gas_fee - Fee per Unit of Gas.

gas limit and gas fee semantics here depend upon whether we go with an EIP-1559-like mechanism, which seems likely.

Call target (Account Address) - The Address of the Account which we're calling. function (String) - The function`s identifier to execute.

might want to explain how the identifier is calculated. in EVM it's a hash of the full function signature - I assume we want to do something similar?

YaronWittenstein commented 2 years ago

Transactions Fields Envelope version - For versioning (to ease introducing new changes in the future).

Is this "version" of the transaction object or SVM version? If the latter, maybe we could give this a more generic name such as "vm_type" (since it may not just be a version, it may indeed include both the name of the engine -- svm, evm, rollup-specific engine, whatever -- and the version)

Versioning of the Transaction Object.

type - The Transaction type (Deploy / Spawn / Call) principal - The Address of the Account paying for the Gas.

paying for the gas and the source of funds for amount (if we include this)

amount(*) - For funding (see more information later under Questions/concerns). gas_limit - Maximum units of Gas to be paid. gas_fee - Fee per Unit of Gas.

OK. I'm certain that the amount must be part of the Envelope. We need to be able to tell its value without running SVM by just plain parsing of the Envelope.

gas limit and gas fee semantics here depend upon whether we go with an EIP-1559-like mechanism, which seems likely.

I'll read this EIP.

Call target (Account Address) - The Address of the Account which we're calling. function (String) - The function`s identifier to execute.

might want to explain how the identifier is calculated. in EVM it's a hash of the full function signature - I assume we want to do something similar?

I don't think we need to use any hashing because in Wasm there is no function overloading, the function name is a unique identifier. Also, the compiled Wasm (SVM SDK -> Wasm) will contain very short names for these functions in order to reduce the space of a transaction.

avive commented 2 years ago

If all the Envelope fields are final then next step in this smipp should be providing the data type of each Envelope member and the Envelope binary codec. This will move us towards clients being able to create sm 0.3 transactions (and decode them for display to users) and explorers decoding them for display purposes.

avive commented 2 years ago

Additional things that should be added to this smipp (or to a followup one) that we need in order implement 0.3 transactions:

export interface Call {
    version: number;
    target: string;
    func_name: string;
    verifydata: Uint8Array;
    calldata: Uint8Array;
}

I understand that specing the fields for vault smart contract transaction (spawn and call) is not possible yet until the vault contract is designed but we should be able to spec these fields for a simple coin transaction.