-
Notifications
You must be signed in to change notification settings - Fork 1
Account Unification #49
Comments
this doused my fire quite a bit. what you said later
i am confused by the wordings here. do you mean
maybe i am looking at this from the wrong perspective, i found the word
assuming the order of execution is below:
under this proposal, it's unclear to me whether we charge any gas for verification phase. the wording here seems to imply yes. if that's true, it makes the name the Ethereum proposal was that no gas paid until it reaches if we are not charging gas for the verification phase, then i misunderstand your words. tho i actually like the idea of paying gas for the verification phase after the signature verifies. however small, it deters the DoS attack. since we are potentially instantiating a SVM for
i had assumed that rewards (mining or otherwise) will be done via internal transfer instead of official transactions |
I don't understand what you mean. The way we verify a transaction is by passing its
Yes, you're right, I fixed this.
The way I think about it, "protected" means "may only be set inside
This is a bit counterintuitive, but by definition you cannot charge gas before I don't see a reason to deviate from the EIP-2938 design here:
We haven't finalized this design yet. That's why I said "possible" exception :) |
like we discussed during the R&D meeting, my end goal is uniform treatment in code and in future design. whether an account has verify()/nonce can be "compressed away" like Tal said, but they will appear homogeneous to devs/users.
what threw me off was this sentence
since |
Q: regarding |
Q: I don't fully understand how the invariant discussed in verification can hold in this use case: both Alice and Bob send a transaction around the same time to the same app (e.g. a vault) to spend all of its coins, each transaction with a different payee. Only one of these transaction will executed and the other will throw an insufficient funds runtime exception. There's no way to validate these transactions without knowing the execution order of the STF and actually trying to execute these transactions in the agreed upon order. I think this is a simpler example than what Tal mentioned. I'm also not sure how the idea of What tal proposes regarding only allowing 1 transaction to a smart contract that can reduce its balance, is quite problematic from usability perspective - popular smart contracts are going to be used frequently by many users, potentially hundreds or thousands of them around the same time. Also, smart contact methods may fail for different reasons based on execution order besides just contract coin balance going to 0. For example, contracts managing tokens, assets, etc... So it should not be about the benchmarks because we don't intend devs to implement Edwards point math in svm code. The benchmarks discussed should be the performance of the EC and crypto precompiled - our provided libs vs. native code. |
Q: what are concrete examples of |
Regarding iddo's 1st argument - in theory, signature checks via SVM should not be much more expensive because the actual computational work is going to be done in context library functions which should be implemented natively - so as long as a developer writes verify() that uses ed25519 scheme (or any other signature scheme we provide support to) there is no issue and the overhead is not in the core elliptical curve ops and should not be significant. As a bonus, with a tight gas limit on verify(), developers will not be able to implement signature verification which is not done efficiently by a core library at all. I think it is safe to assume that we are going to provide primitive hashing and signature scheme libs as svm libs which are optimized and native. We need to do so to make the svm development platform competitive. So tl;dr - you can't implement your own elliptical-curve math ops in verify() because you will go over the verify() gas limit but you can use our svm crypto libs for custom signature schemes. We can also add additional crypto libs to svm core via soft-forks with additional EC functions. Regarding tal's argument about DoS using a smart contract that doesn't use a precompiles - the MAX_GAS on verify() addresses this attack scenario. It is not about pre-compiled contracts we approve, it is about providing a smart-contract EC and crypto libs which are natively compiled that developers use which are part of the runtime. For example, see the eth precompiles. Devs use these as building blocks for custom verification but can't implement EC low-level math in smart contracts. As a reference, here's a list of the precompiled eth methods - they are all computational extensive lib functions related to EC and crypto primitives. https://github.com/ethereum/go-ethereum/blob/bd6879ac518431174a490ba42f7e6e822dcb3ee1/core/vm/contracts.go#L51 |
Regarding the idea to limit transactions per layer to a same contract - I don't like this idea. I think it is better the optimistically assume that some of them will work and just execute them in our pre-defined tx sorting order. There are going to be very popular smart contracts and limiting throughput to a small number of txs per layer is very problematic from the user experience perspective. I think there is no way good way around the issue that you can't tell whether a smart contract transaction is 'valid' without executing all the transactions to the contract in a layer and that we should work under this assumption. I think this is inherent to the state-full smart contracts computation model. The assumptions that this can be done needs to be revisited. |
A note regarding the discussion about state changes and transactions - there is no need for transactions which do not modify state - for this there's an API that reads apps and account state and we can assume there will always be at least one free open api endpoint over the Internet. Smapp will allow to read any app state w/o transactions as well via remote public or local node api. Explorer will also show app state for each spawned app. No rational user will submit a transaction that is not intended to change state as it costs money and reading the state is free. |
The idea here is that "Alice" is a person, not an account. In the terminology of this proposal, she's an "agent," and agents don't have funds, only principals do (agents have private keys that let them sign transactions). Alice stores her funds in a "wallet" which is really a principal account under the hood. If she wants to send funds from this wallet to Bob, she creates, signs, and broadcasts a transaction with the wallet address set as the
Let me rephrase your question: Alice wants to send funds contained in her private (1-of-1) wallet but she wants a different app to pay the fees for the transaction. This is an interesting question. I think this sort of use case is a tiny corner case, that in practice it is not at all common, and that we're probably better off not trying to support this in the beginning. I have to give some thought to whether allowing two sources of funds in a single transaction is secure. Here's one pattern that would work within the proposed framework: Alice grants the app in question permission to spend funds contained in her private wallet. (The permission could be time- or amount-limited, or there could be a whitelisted set of destination addresses, etc.). Then she can submit a transaction to the app, and let the app pay, and the app could call the wallet as a delegate and send the funds to Bob on her behalf.
Assuming by "her account" you mean the sort of private wallet I described above, then the same pattern should work: Alice would first have to delegate authority to the vault to move funds from her private wallet, then the vault could do this on her behalf and pay the fees. This pattern is very similar to how ERC-20 works today. |
In the simplest, and most extreme, case, when one of these transactions is processed, the miner/validator immediately sets the parked balance of the app account to zero (at least until the following layer when all pending transactions have been processed). Then all other transactions with the same
Agree
Agree, this is what precompiles are for. But Wasm is really performant and, if the performance is pretty close, it might make sense to actually perform these checks inside the VM. |
We discussed an example on the call: a vault (multisig) with a per-user daily spending limit, or a vault where we want to be able to change the signatories/keys. If Here's another: a decentralized exchange which charges zero gas if you submit a tx but the price has already moved too far. |
The question isn't about transactions with don't modify state, it's about reading and writing state inside |
Summary: TX format for AA - trying to have a high-level model here first based on the Smipp.
I think it will be useful to think about define how the following basic uses cases going to work with transactions and AA:
|
Q: where are we on the open questions in the smipp the recent post brainstorming session about it, and how do we move forward on providing good answers to them? |
I think the first order of business is to benchmark signature verification inside and outside the SVM, to make sure SVM doesn't add unacceptable overhead. Regarding other questions, we have some discussions going on in the forum. |
Please read my comment above regarding performance - I think you may have missed it. Signature(s) verification is either going to be provided as a native pre-compiles, or using native svm host-functions - or at least the time-consuming parts of it, e.g. the EC and crypto low-level operations. It is clear that you don't want to do these kind of operations in SVM code. Either way, there will not be an unacceptable overhead and so it is should not be a priority for next step on this project. We will provide the low-level EC and crypto functions needed for the various modern signature schemes to enable multi-sig, aggregation and threshold. It should not be feasible to add a scheme that we don't provide such primitive for in svm because we don't want dev to roll-out their own crypto and likely svm is not going to be robust enough or low-level enough to implement such code in an efficient way. I'm more concerned about open issues such as multi-tenancy, the need for another concepts such as 'conservative balance' which have broad implications on the platform design. More generally, are we at a point that we are convinced that AA is doable and we can move forward and specify the transaction format? There's a large body of work needed to be done around transaction singing and processing considering AA, so we need to finalize the transaction model and syntax asap for 0.3 soon. How do we keep moving forward on this project? |
The overhead isn't from running the EC itself, it's from everything around it. Even if we can keep a single SVM instance up (and thus avoid the cost of starting and tearing down an SVM instance), there will be context switches in and out of SVM code, possibly memory allocations and frees, etc. All of these could add a lot compared to a signature verification outside the SVM. My guess is that the overhead will be reasonably low, but I'm not 100% sure. Iddo is 100% sure it will be unacceptably high. This is an unknown, and quite easy to test, so I think it should have very high priority. I think this is the main question mark about whether AA is "doable". If the answer turns out to be that the overhead is too high, we will have to abandon AA (at least for now). If the overhead is low, we can definitely have some version of AA --- in the worst case (e.g., we don't want to support writing state) it will not allow much greater flexibility than we currently have (but will still be slightly more general). Benchmarking code will be needed in any case for 0.3, since that is how we will construct the gas cost table for each operation. So it's not like we'll be doing extra work. It's also work that can be done in parallel to research we are doing on multi-tenancy, nonces, etc. The conservative balance is not due to AA --- it's due to the mesh vs chain construction. It's a requirement even if we don't have smart contracts at all. |
I updated the proposal to factor in our recent discussions and decisions. Please take another look. One fairly important change: I removed |
I've been thinking a lot about nonces lately --- at the moment I don't see a way to fully generalize nonces, much less to fold them into the |
I don't think there needs to be any context switches at all. For standard signature schemes we are going to support for genesis, e.g. ed, e++ and multi-sig smart contracts - in the templates we are going to provide - the implementation of verify can and should be 100% native w/o spawning SVM at all - there is absolutely no reason to and this kind of an obvious optimization and ALL transactions until devs can deploy templates in the future will use these optimized verify. For custom signature schemes that devs are going to implement once we open the platform for devs to submit templates with custom verify code, there will be overhead which is identical to overhead of running a smart contract method which should be factored in the gas cost of the execution. So, in other words, supporting custom dev-provided verified is not any different then supporting a smart contract method that requires svm instantiation and is governed by metered execution. So I still argue this is a non-issue right now compared with having a complete spec that we feel good about and handles all of the use cases and the open issues lane mentioned in this smip. I also don't think that we should consider this performance issue as part of AA decisions - it should be factored in the gas unit computation for svm methods and for verify and for the hard-limit we are going to impose on verify in terms of gas units. |
@avive: If the overhead of SVM is too high to support anything except native-code precompiles, we don't gain anything from account unification (it's not actually unification in this case). The point is that a failed (Also, custom signature schemes are far in the future (if ever) -- that's not what we're talking about at the moment) |
@noamnelke and I have discussed a couple of topics concerning the development efforts for the Accounts Abstraction: 1. Transaction formatFirstly, we've covered the new Transaction format. @noamnelke and others will help with finalizing it. 2. SigningThen, we've moved to talk about signing a Transaction. But we still need to finalize how signing the whole transaction will work. 3. Discarding transactions running in
|
The main topics discussed in the last R&D call were: Explicit Nonce Field Replacement MechanismTal has detailed an Seems we have everything required in order to move forward to writing a SMIP before implementation. Shared Transaction input between functionsTal stressed the need that each running SVM function will have full-access to the input data of the transaction. Limit on #coins transfer allowed while running a functionTal said he believes we need to be able to restrict the amount of coins a running function will be allowed to transfer during its run. Having this information will be leveraged by the Transaction Selection Algorithm. One way of implementing it will be by having |
Overview
We propose an account and transaction model that unifies all accounts into a single smart contract-based account type. Under this proposal, unlike Ethereum today, Spacemesh would have no "EOA" (externally owned account) or "simple", keypair-based account (which cannot contain code). This abstracts or encapsulates the logic that allows nodes to verify transactions (e.g., by ensuring that they have valid signatures and nonces) into smart contract logic as part of a special
verify
method. It means that this verification logic, like all smart contract execution logic, runs in the VM and may in theory be arbitrarily complex (although in practice it will be subject to certain restrictions to prevent denial of service and griefing attacks, and will be optimized).We introduce a subclass of account known as a "Principal" account, so-called because it's able to act as a principal, or source of funds, in a transaction. (The user who generates, signs, and broadcasts the transaction acts as its agent.) The only difference between a Principal and non-Principal account is that a Principal account implements the
verify
method that is executed to verify transactions sent on its behalf, and is thus allowed to act as a source of funds, while a non-Principal account implements no such method and is forbidden from acting as a source of funds. (Equivalently, all accounts can be thought of as Principal accounts, but those that don't explicitly implementverify()
instead have an implicit implementation that always returns false and may therefore never be used as a source of funds.)EOAs are replaced by a "personal wallet" smart contract template with simple, cheap, sanctioned verification logic that is effectively identical to the existing verification logic.
For background reading, please see:
Goals and motivation
Tradeoffs/downsides/challenges
High-level design
address
-> [code
,balance
].sender
field of a transaction is replaced by aprincipal
field. All transactions contain this field, and it must never be nil (with the possible exceptions of coinbase and reward transactions). Theprincipal
is the account whose balance is debited for gas fees and sends (inside or outside the VM). If theprincipal
of a transaction is not set, or is set to the address of an account that is not a Principal account, or if the transaction nonce does not match the Principal account's nonce, that transaction is invalid. If the balance of the Principal account is not sufficient to pay gas and cover all funds transfers contained in the transaction, the transaction fails.code
implements theverify()
method are considered Principal accounts.verify()
can read and write. See "State management", below, for more.code
remains unset). A smart contract may later be deployed at this address, in which case it "inherits" the pre-existing balance. (This allows a user to receive funds into a Principal account "counterfactually", as if the account code were already there, and only requires the user to deploy the account code when they need to transfer or spend the funds.) Note that a Principal account may not serve as a source of funds for any transaction, including deploying code to the account itself, until after that code has been deployed.principal
, and thedestination
account contains funds,verifydata
is passed into the template'sverify()
method, and if it returns true, the template is spawned todestination
anddestination
pays gas for the spawn operation.principal
,destination
,amount
,calldata
,verifydata
) are setprincipal
field is set to the address of a Principal account (i.e., that this account implements theverify()
method) and that the account contains enough gas to verify the transactionverify()
method of the referenced Principal account, passing in the transactionverifydata
. Ensure that, withinMAXVERIFYGAS
gas usage, the method either returns true or else calls aPAYGAS
opcode, and that it does not read or modify state that is not Protected (it may only read its own state, not state from any other account). If the account balance is insufficient to cover the gas spent whenPAYGAS
is called, the transaction is invalidated.verify()
ultimately returns true, the transaction is verified, and if it returns false, the transaction is invalidated.calldata
, thedestination
smart contract is called withcalldata
(as in Ethereum today). Note thatdestination
may or may not be be the same asprincipal
.Prior art
Proposed implementation
PAYGAS
) to SVMverifydata
to a Principal account'sverify()
method and enforce the constraints described aboveTransaction format
The specifics of transaction format are outside the scope of this proposal (see #23, #37). However, Account Unification requires that transactions contain at least the following fields:
principal
(address) // pays the gas fees and source of funds forvalue
, must not be nil (except for self-funded spawn)destination
(Address) // receiver of funds and destination smart contract for method calls (withcalldata
)amount
(Amount) // coin value to be transferred todestination
calldata
(binary) // optional - smart contract method execution call data: method selector, call valuesverifydata
(binary) // passed toverify()
, must not be nil. Typically contains a signature and a nonce.State management and transaction ordering
An important invariant in both Spacemesh 0.1 and Ethereum is that no transaction that originates from sender A can invalidate a transaction from sender B (where A != B). This is due to the fact that a transaction can only be invalidated if the sender account balance is too low to pay the gas, or if the sender nonce doesn't match, and an account's balance can only decrease, and its nonce can only change, as a result of a transaction from the same account. Without this invariant, mempool management and transaction selection become much harder because transactions are more order-dependent: processing any transaction from any sender could invalidate any other transaction from any sender.
If we allow
verify()
to implement arbitrary logic, maintaining this invariant becomes much harder. We propose to start with restrictions onverify()
in an initial phase and relax them somewhat in a later phase, pending further research.Phase I: Stateless verify
In the first phase,
verify()
must be totally stateless. It may only read immutable state such as storage items set when the principal contract was initially deployed. It must not read state from any other contract (since the existence or non-existence of such state is possibly mutable).Certain opcodes that read external state, such as
BLOCKHASH
,COINBASE
,TIMESTAMP
,NUMBER
,DIFFICULTY
,GASLIMIT
,BALANCE
, and those that perform external call/create/state access (CALL
,CALLCODE
,STATICCALL
,CREATE
,CREATE2
,EXTCODESIZE
,EXTCODEHASH
,EXTCODECOPY
,DELEGATECALL
) are also disallowed.Phase II: Limited access to state
Pending further research, in future it should be possible to relax the restrictions on
verify()
somewhat, along the following lines:Level one vs. level two state
In order to verify a transaction with a given
principal
, a node must already read the principal account data from the trie, including its code and balance. If we think of this account data as occupying a single "page" of memory, for some fixed page size, then the additional work associated with reading a small amount of "level one" storage is negligible. Intuitively we can think of this "level one" storage as consisting of a small number of 256-bit storage values: enough to store a few signatures (for a multisig), a nonce, and possibly a per-signature daily spending key.Under this scheme,
verify()
may read and write this level one state for its own account but may not read or write any other state.Protected state
Under this scheme, the account storage of a Principal account is divided into two sections: Protected and Unprotected. Unprotected state may be read and modified by any method other than
verify()
. Protected state may only be modified by theverify()
method, and theverify()
method may only read Protected state. (Any method may read, but not write, Protected state.)Note that we may decide to combine level one state and protected state.
Slow reads and writes
We introduce new opcodes for "slow" read and write of account storage. A slow write only takes effect at the next state checkpoint: typically, the following layer. A slow read returns the value as of the last checkpoint: typically, as of the previous layer. Under this scheme,
verify()
may perform slow reads and writes only, since they can never invalidate other transactions before the next checkpoint.Questions
verify()
method that can read and write state, even state that's protected? (Cheaply calculating conservative balances requires being able to verify transactions in a fashion that is not order-dependent. Any version of AU that is not purely stateless results in transactions whose validity is, at least partially, order-dependent. Sequential nonces help somewhat but not entirely since it's trivial to compute a "matrix" of multiple transactions per nonce such that pathfinding through that matrix is computationally infeasible.)A: yes, see the description of self-funded spawn, above
MAXVERIFYGAS
be set to?principal
of a transaction is implicit via signature extraction rather than explicit)?verify()
inside the VM to verify transactions rather than doing so entirely in native code? Note that the signature check operation itself can still be implemented natively (and called from inside the VM as a "precompile" host function). The cost associated with doing so involves context switching into and out of the VM.verify()
? In particular, can it return or pass data into the "main" contract code to, e.g., indicate which signatures were verified in a multisig/vault?CREATE
andCREATE2
?A: we want both.
CREATE
will set the account address based solely on spawning transaction principal account and nonce.CREATE2
will instead use principal account, salt, and init code (see EIP-1014). This is orthogonal to Account Unification.Dependencies and interactions
Implementation plan
verifydata
, find and runverify()
, and disallow reading/writing of dynamic state as describedStakeholders and reviewers
Testing and performance
tbd
The text was updated successfully, but these errors were encountered: