Account abstraction #388
Replies: 11 comments 23 replies
-
In general, I really like this proposal. One concern I have is message validation in pubsub could turn into a DoS vector. Unfortunately, an invalid message can still cause us to load an actor & execute the validate method, with nobody to pay the cost. However, there are a few possible mitigations:
|
Beta Was this translation helpful? Give feedback.
-
One thing that's possible in EIP 4337 and not here is creating an abstract account from scratch without having to upgrade an existing account. An awkward part of this proposal, for Filecoin/EVM compatibility at least, is that the account owner must send at least one filecoin message to transition the account before they're able to send EVM messages as the account. |
Beta Was this translation helpful? Give feedback.
-
@anorth I'm not sure I understand this part:
If actor X calls method M1 on account actor A, then M1 must be read-only. If so, then any methods called by M1 on A must also be read-only. Therefore, I don't see how an account actor can modify its own state through a call sequence via other actors. (and I think this is what we want, because otherwise we could have A's state change without A's nonce changing, thus risking validity changing). Am I misunderstanding something? |
Beta Was this translation helpful? Give feedback.
-
A recent write-up from someone enjoying AA on starknet gives a good outline of the value of some of the flexibility offered by this abstraction. https://hackmd.io/@s0lness/BJUb16Yo9 |
Beta Was this translation helpful? Give feedback.
-
Under Since it was straightforward to implement at the moment, I've implemented a function that will set dynamic runtime check (that has read access to everything in Kernel) per syscall that will either continue normally or abort early from denied access. This approach would have its benefits as there can be extremely fined grained and programmable access control, which would lend future extensions a hand when giving/removing access to syscalls. mriise/ref-fvm@73c0f25 Personally though I would prefer to not link syscalls the kernel doesnt have access to at all, though that would mean adding a new type of kernel inside of the FVM (may be some workarounds though). thoughts? One thing that I am curious about is this line here |
Beta Was this translation helpful? Give feedback.
-
Abort with what? |
Beta Was this translation helpful? Give feedback.
-
@mriise and I have been talking about this for a bit:
Proposal: For now, forbid mutation of abstract accounts after instantiation. We also talked about future solutions here for allowing mutation, if desired (although I'm not convinced we actually need it). One solution would be some form of "self mutation" bit in off-chain messages. Blocks would only be able to include at most one message with this bit set. However, we'll need to get rid of tipsets first. |
Beta Was this translation helpful? Give feedback.
-
Proposal: remove the Basically
This means that the user can always:
The downside of letting cc @anorth |
Beta Was this translation helpful? Give feedback.
-
So, even if we only allow one message per block, an attacker could effectively "spend" all gas in a block without paying for it:
With high likelihood ( The main issue with this attack is that anyone on the network would be able to pull it off for little cost (potentially attacking all blocks in a tipset at the same time). Unfortunately, limiting abstract accounts to single messages, or even getting rid of tipsets, doesn't help. One possible solution is to not allow spends from on-chain sends, only from off-chain sends originating from the abstract account. Unfortunately, this severely limits some very valid abstract account use-cases. Another solution is to set some form of "minimum balance" for abstract accounts where abstract accounts would be required to maintain enough funds for at least one off-chain send. But that's pretty terrible from a UX perspective (an account would appear to have funds, but would not be able to spend them). Another is to introduce multi-stage tipset validation where:
As complex as it is, I'm leaning towards 3 because it means that there's absolutely no way for execution to affect the validity of a message (allowing things like mutation, abstract account iniitalization, etc.). |
Beta Was this translation helpful? Give feedback.
-
Proposal from @mriise: Time Travel In this version, we can:
In this version, when we validate a message during block validation, we:
Analysis:
|
Beta Was this translation helpful? Give feedback.
-
Interactions with f2 address generation Currently, we assume that accounts have a "key address" and use this to derive a new f2 address. However, an abstract account may only have an In general, the "correct" address to use for abstract accounts is the We have two options:
This gets complicated by the fact that we want to also store the f1/f3/f4 addresses in the actor state as the actor's "predictable" address (or "chosen" address). Honestly, I don't know what the right solution here is. I think much of the complexity comes down to the fact that f4 addresses are optional. |
Beta Was this translation helpful? Give feedback.
-
This is a proposal to introduce account abstraction into Filecoin. Account abstraction allows a user-programmed actor to be the top level entry point that initiates a transaction and pays fees.
Filecoin has a unique opportunity to implement account abstraction before a host of other actors and systems come to rely on the assumption that EOAs and actors are distinct.
Motivation
Concrete, protocol-defined message validation limits innovation in a few important areas:
The key goal of account abstraction is to allow users to use smart contract wallets containing arbitrary verification logic instead of externally-owned accounts as their primary account. A transaction can "start from" a smart contract, not only an EOA. Account abstraction decouples the ownership of assets (the wallet) from the authority to transfer them (wallet-specific policy).
Examples of the kind of validation logic such a smart contract wallet might implement include:
To explore multisig wallets as an example, with account abstraction:
Some other account abstraction proposals also target additional goals, which are discussed in the "Alternatives & extensions" section below.
Proposal
Message validation invokes the VM
The on-chain
Message
type is unchanged. All fields still have the same meanings, except that the gas values are a suggestion to the sending actor, rather than definitive. The sending actor will have the opportunity to specify different gas values during message validation.Instead of validating a messages signature using a hardcoded algorithm (e.g. Secp256k1), message signature invokes the VM and delegates checking to the actor identified in a message’s
From
address.Call sequence number checking remains unchanged: a message’s sequence number continues to be checked separately to signature validation, and the
From
actor’sCallSeqNum
is incremented automatically after a message is validly included in the chain.VM entry point for message validation
A new actor entry point is added to the VM (separate from
invoke
):validate(Message, Signature)-> GasSpec
The message and signature parameters are the blockchain structures. An actor implementing this entry point is an account actor.
This entry point is invoked by the mempool and transaction relayers to validate a message, taking the place of existing message signature and gas limit validation. Validation either aborts (signalling the message is invalid) or returns the gas limit and price for the message.
Validation is expected to verify that the signature data authorises the message for execution by the account actor. It may check a cryptographic signature but may also inspect the message and perform arbitrary local logic according to policies expressed in the actor’s code or state. Validation is subject to a fixed gas limit of
VALIDATION_GAS_LIMIT = TODO
.If
validate
returns successfully, then prior to execution the VM sets the gas limit and deducts the returnedGasLimit * GasFeeCap
from the actor’s balance (failing if balance is insufficient) before proceeding. That the execution ofvalidate
itself incurs a gas cost, which consumes part of the message’s gas limit. The VM then invoke the receiver of the message as usual.Note that the
Message
parameter tovalidate
includes gas parameters specified from the outside. The actor may pass these values through or return different ones: those from the message may be thought of as advisory from the end user.Restricted runtime
The key challenge for miners and validators is to determine if a transaction will pay the fee once included, with a small, fixed amount of processing that is robust against DOS attacks.
The
validate
method has a restricted runtime. It cannot inspect any environmental variables (e.g. current epoch), check its own balance, nor send a message to any other actor. Any attempt to do so must abort. The actor can read its own state, but must in effect be a pure function of that state and the message and signature.Restrictions on receiving
A message to an account actor from any other actor is read-only, and cannot modify the account actor’s state (edit: including spending of native FIL tokens). Any attempt to replace the actor’s state root must abort. The exception to this is that a message originating from an account actor can modify its own state (including through a call sequence via other actors).
Upgradeability
An account actor is expected to be upgradeable in-place via future FVM code upgradability mechanisms or proxy-delegate patterns. This compatibility should be confirmed prior to finalising either mechanism.
Block and message validity
Messages
The validity of a message with a particular sequence number depends purely on the actor state at that sequence number. Since that state is immutable except via calls originating from the actor itself, the validity for any message bearing that sequence number is fixed, and may be cached by mempools and message relayers. A node may use the cached validity result (including gas parameters and gas units used for validation) when executing the message in a block: it does not need to re-validate.
The validity of any message with a more distant sequence number is not fixed: an earlier message could arbitrarily alter the actor’s validation logic. Any change to the actor’s state would necessitate revalidation of all subsequent messages. Thus, in the basic case, a mempool should keep only one message originating from any actor at one time. Heuristics could support retaining longer message chains in some situations (e.g. the sending code CID is known to be an immutable, non-upgradeable contract; or analysis shows that earlier messages can’t invalidate later ones).
This restriction on message chains is mitigated by the introduction of multicalls (below).
Blocks
A block may include only valid messages, according to each actor’s validity method. Since message validity depends only on that actor’s state, which is immutable except via calls originating with the actor itself, the ordering of messages from different actors cannot affect the validity of any one of them.
Multiple messages from one actor may be included in one block. Each must be valid against the state after the immediately preceding message.
Note that a message may still fail due to the sending actor having insufficient balance to cover the gas limit (which the validation function cannot check). This results in a penalty to the block provider, as today.
Gas charges
The base message fee is reduced to reflect that no signature validation happens. But gas is charged for the
validate
method.Upgrade the built-in account actor (optional)
The built-in account actor code is upgraded in place to become an account actor according to this proposal, with equivalent behaviour to today. The
validate
method checks the message’s signature via theVerifySignature
syscall and passes through the message’s gas parameters.The built-in account actor should also gain an upgradeability hook, so that it may be replaced with a different contract after initial creation.
This upgrade means that all account actors utilise account abstraction and may be upgraded. The only distinction that a built-in account actor has is that it has an address that was derived from a public key (but not necessarily the public key now in use for validation).
Upgrading the built-in account isn't strictly necessary, especially if we need to leave non-abstract validation in place for BLS accounts. But it would be nice for there to be only one way that messages are validated.
Discussion
Multicalls
A multicall is a single blockchain message that makes multiple calls to other actors, which can succeed or fail as a unit. Multicalls are great for the UX of doing multiple things without waiting for block inclusion (e.g. “approve token and then make a transfer”) and also more efficient, because message overheads (especially signature validation) are amortized over more work.
Account abstraction supports implementing multicalls within the account actors. We just add an exported method
Execute(calls: List<Call>)
and then send a message from the account to itself with the list of calls to execute. It’s also possible to parameterise policy around atomicity, etc. We could upgrade the built-in account actor to support this for everyone!This
Execute
method should probably be specced as an FRC for more general delegated execution, too.Replay and non-malleability
Note that actors may not have cross-chain replay protection unless they build it in manually, or Filecoin adds such protection in the base protocol.
A poorly designed abstract account could break transaction non-malleability (i.e that transaction can't be modified in-flight and remain valid). It's the account contract author's responsibility to ensure message can't be tampered with.
Hosted VMs (EVM)
Account abstraction could present a natural mechanism to implement entry points for VMs hosted inside the FVM, such as the planned EVM compatibility layer. See https://github.com/filecoin-project/fvm-specs/issues/92
Open questions
I'm not sure how to make this compatible with BLS accounts that use the block-level signature aggregation. At worst this just means that BLS keys can't enjoy abstraction, but that's a bit of a shame.
Implementation notes
We'll need a new signature type to allow for signatures that are opaque to the Filecoin protocol, but validated by user-programmed actors. We can't have a fixed whitelist of formats at the protocol level.
We need to work out mechanics of returning a tuple from a VM entry point.
Alternatives & extensions
Minimum gas bounds allowing state mutation from external calls
Rather than being immutable, allow mutation if a minimum gas amount is spent (some fraction of the fixed cost of re-validating the next message). See https://eips.ethereum.org/EIPS/eip-2938.
Immutability is really unfortunate as it limits the possibility of things like token receiver hooks. But expensive token receiver hooks aren't much better.
Abstract over call sequence number (nonce)
Delegate nonce checking to
validate()
too, allowing more flexible schemes than linear (e.g. host an EVM inside an actor, and use EVM address nonces).This can allow a message CID to be valid more than once, breaking uniqueness. Is that bad? We do deduplicate on CID within tipsets though (and can require unique within a block).
A message could become invalid in a tipset because a prior one bumped the nonce (each miner only held one in mempool, but different ones). This is analogous to situation today where we penalise miner for “wrong” nonce. But implication is that we need to re-run
validate()
as part of tipset execution, whereas original proposal doesn’t. Is that too bad?Paymasters
One feature that other account abstraction proposals sometimes aim for is paymasters or sponsored transactions. This refers to an ability for one contract to pay fees for certain messages that originate at another. A canonical example is an on-chain exchange paying gas fees for its users. This proposal doesn't enable paymasters or sponsorship. See https://eips.ethereum.org/EIPS/eip-3074 for an Ethereum proposal that does.
Pay with tokens
Another candidate feature is the ability to pay fees with user-programmed tokens, by swapping tokens for the native asset (FIL) during transaction execution. The goal is to not need any of the native token held in the wallet/account. This proposal doesn't enable that yet, mainly because the validation function cannot call other contracts.
Beta Was this translation helpful? Give feedback.
All reactions