Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC for account deletion #51

Draft
wants to merge 62 commits into
base: main
Choose a base branch
from
Draft
Changes from 12 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
2b685f2
account_deletion: skeleton
rbonichon Jun 11, 2024
6f39387
account deletion: extract some contents from PRD
rbonichon Jun 11, 2024
a5ac409
account deletion: design considerations
rbonichon Jun 17, 2024
dffe346
Some details on ledger implementation
rbonichon Jun 18, 2024
cb17c65
Account deletion: some elements of testing
rbonichon Jun 18, 2024
64adacc
Some further notes on ledger API
rbonichon Jun 19, 2024
d0d7794
Precisions
rbonichon Jun 19, 2024
f65b76c
Choice
rbonichon Jun 19, 2024
e90bdbb
some tentative words about transaction logic
rbonichon Jun 20, 2024
db326bd
account deletion: update
rbonichon Jun 25, 2024
007366c
account deletion: update
rbonichon Jun 25, 2024
63fabe7
Reorganized and add sections to follow template more closely
rbonichon Jun 25, 2024
068af92
account deletion : make the meaning `x` more explicit
rbonichon Jun 25, 2024
9c44789
account deletion : preliminary words on account deletion
rbonichon Jun 25, 2024
b7a56c7
account deletion : move sections to rationale and alternatives
rbonichon Jun 25, 2024
bb986f4
account deletion : a word on archive nodes
rbonichon Jun 25, 2024
eac8c7e
account deletion : drawback impact
rbonichon Jun 25, 2024
2fa7195
account deletion : remove redundant definition
rbonichon Jun 25, 2024
303c09a
account deletion : cleaning whitespaces
rbonichon Jun 25, 2024
63f6f02
account deletion : rewording paragraph
rbonichon Jun 25, 2024
8e43116
account deletion : specify behaviors of `remove_` functions
rbonichon Jun 25, 2024
ea15c13
account deletion : typo
rbonichon Jun 25, 2024
12f32f8
account deletion : fix indentation
rbonichon Jun 26, 2024
dbb6368
account deletion: light clarifications
rbonichon Jun 27, 2024
9b1d2f5
account deletion: questions and things to consider
rbonichon Jun 27, 2024
6e61c7c
account deletion: syncing ledgers
rbonichon Jun 28, 2024
bd03179
account deletion: word on code links.
rbonichon Jun 28, 2024
ffca61f
account deletion: inital wording on snark
rbonichon Jun 28, 2024
c485517
account deletion: implementation steps
rbonichon Jun 28, 2024
d1eccdc
account deletion: transaction snark
rbonichon Jun 28, 2024
ce3bd31
account deletion: ledger sync
rbonichon Jun 28, 2024
5afeaaf
account deletion: db encoding for free list
rbonichon Jun 28, 2024
ba838bc
account deletion: free list representation
rbonichon Jul 1, 2024
d0dbc6a
account deletion: deallocation at fill frontier
rbonichon Jul 1, 2024
17efeec
account deletion: other suggestions for free list in db
rbonichon Jul 1, 2024
688cac5
account deletion: permissions
rbonichon Jul 1, 2024
c6733cc
account deletion: update remove function description
rbonichon Jul 1, 2024
0a5612d
account deletion: fix fill frontier index definition
rbonichon Jul 1, 2024
bb88abf
account deletion: deallocation at fill frontier revisited
rbonichon Jul 1, 2024
dea556d
account deletion: serialization of heap
rbonichon Jul 1, 2024
f65aae0
account deletion: additional question
rbonichon Jul 2, 2024
75ab35a
account deletion: on-disk free list handling
rbonichon Jul 2, 2024
1a6e85e
account deletion: JSON encoding of ledger
rbonichon Jul 2, 2024
6aa9057
fixup! account deletion: JSON encoding of ledger
rbonichon Jul 2, 2024
e285613
account deletion: some implementation details for permissions
rbonichon Jul 2, 2024
7ea5241
Punctuation
rbonichon Jul 2, 2024
f2deb25
account deletion: remove empty section
rbonichon Jul 2, 2024
9919fef
account deletion: add detail for permissions in archive nodes
rbonichon Jul 2, 2024
93f7b2b
account deletion: some concepts
rbonichon Jul 2, 2024
21a9503
account deletion: a word about remove account
rbonichon Jul 2, 2024
c0b363e
account deletion: use fill frontier/interval consistently
rbonichon Jul 2, 2024
dd35968
account deletion: example implementation of remove_account
rbonichon Jul 2, 2024
9fc8405
account deletion: modifying deserialization
rbonichon Jul 2, 2024
9b0f96c
account deletion: default value for delete permissions
rbonichon Jul 3, 2024
b597847
account deletion: gain in batch allocation
rbonichon Jul 3, 2024
f65d200
account deletion: parent/child registering and committing
rbonichon Jul 3, 2024
35e8385
account deletion: rephrase
rbonichon Jul 3, 2024
0fa09ac
account deletion: update discussion on data representation
rbonichon Jul 3, 2024
8616655
account deletion: fix discussion
rbonichon Jul 3, 2024
af687d1
account deletion: update on-disk serializing with sets
rbonichon Jul 12, 2024
a017f7a
account deletion: transaction logic update
rbonichon Jul 12, 2024
6851b42
account deletion: implementation steps made clearer
rbonichon Jul 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
367 changes: 367 additions & 0 deletions 00xx-account-deletion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,367 @@
# Account deletion for consumable accounts

## Summary

The capacity to delete accounts is a prerequisite to implement *consumable
accounts* in order to propose the *zkPromise* abstraction in `o1js`.

## Motivation ##

Account deletion is a key protocol feature with the goal to enable the so-called
*zkPromise* abstraction in `o1js`.

Account deletion will allow to create as many accounts as you want, **then**
receive the deposit back when you no longer need them. This is key for zkApps
developers who want to trigger actions on other accounts in a deadlock-safe way
so that they can create a cross-account smart contracts & applications.

## Detailed Design

The support for account deletion relies on adding features at the levels of
transaction logic(s) and storage (ledgers). The latter is a prerequisite of the
former.

### Storage

Within the protocol-related codebase, the lowest-level support lies in having a
working and sound `remove` functions for the different ledger implementations
(`Database`, `Any_ledger`, `Null_ledger`, `Syncable_ledger`) and in masking
mrmr1993 marked this conversation as resolved.
Show resolved Hide resolved
merkle trees.

#### Merkle trees

The current implementation of in-memory ledgers use a fixed-depth [Merkle
tree](https://en.wikipedia.org/wiki/Merkle_tree).

For insertion, the current implementation keeps track of the "fill frontier",
that is, the leftmost empty slot of the tree.

Now, on removal, there are (at least) 2 options:

1. Symbolically mark the location as removed and make this knowledge available
at data structure level such that, upon new insertions, these available
locations are looked at before inserting on the leftmost available
location. This solution extends the datatype of current implementation with a
structure tracking freed locations.

2. Switch the value currently indexed on the frontier of the tree with the
location that will be removed, a little bit like one does when implementing a
heap data structure. This solution keeps the insertion of new data to the
leftmost available locations and thus solution wouldn't change the current
datatype.

Each option would correctly implement the needed support. Let us show how they
differ.

##### Option 1: Tracking freed locations

To illustrate the two options, let's assume we have the following Merkle tree of
depth 2, with an empty free list.

```
H(H(A,B),H(C,x)) free = []
/ \
H(A,B) H(C,x)
/ \ / \
A B C x
```

The free list contains a set of locations bound to the empty account located
between the leftmost leaf and righmost non-empty leaf. The idea is to recycle
these locations upon further addition of data, as we will see shortly.


The insertion of new data `D` results in the following Merkle tree.

```
H(H(A,B),H(C,D)) free = []
/ \
H(A,B) H(C,D)
/ \ / \
A B C D
```

Upon removal of `C`, the structure would evolve as follows, with `x` a
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only mention of this placeholder value. The details here seem important, e.g. we don't want a deleted account to continue to use the same amount of memory, otherwise we shouldn't refund the creation fee.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. Let me be more clear here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you maybe prefer also to use symbol o in place of x since it actually is meant to mark the empty account

placeholder value marking the freed location, which would actually represent to
the empty account in practice.

In this example, locations will be represented as lists of directions for the
sake of readability. In practice, though, they will be represented by other,
usually isomorphic to the list of directions, means, e.g., an integer encoding
the position of the leaf.

The free list now states that the location determined by
sequence of directions `[Right; Left]` is available for reuse for new
insertions.

```
H(H(A,B),H(C,D)) free = [[Right; Left]]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indexes are already dense, shouldn't we use those?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean using the Bigstring view of the same information, that is, Merkle_address ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The actual implementation will use Location.t, which may or may not be a compact representation since we are within the functor's body. The current instance of this functor does use a compact representation for indexes. I chose the Right, Left way of wording the location because I thought it was easier to follow, especially in a tree-like drawing.

/ \
H(A,B) H(x,D)
/ \ / \
A B x D

```

Thus removal incurs a cost proportional to the height of the tree since we have
to recompute the hashes on the Merkle path from the removed data.

Now adding data `E` would result in

```
H(H(A,B),H(E,D)) free = []
/ \
H(A,B) H(E,D)
/ \ / \
A B E D
```

##### Option 2: Maintain insertion on leftmost available location
rbonichon marked this conversation as resolved.
Show resolved Hide resolved

For the sake of completeness, let us consider another option, which aims at
maintaining the fact that any new insertions happens on the leftmost available
location, and that all leaves between the leftmost one and the rightmost
non-empty one are filled.

In this case, removing `C` would not update any free list -- since this solution
does not need this concept. But it would change the location of `D`.

```
H(H(A,B),H(D,x))
/ \
H(A,B) H(D,x)
/ \ / \
A B D x
```

Since the ledger implementation also tracks mapping from account to location as
well as location to accounts, both these mappings would need updating.

Now adding data `E` would result in

```
H(H(A,B),H(D,E))
/ \
H(A,B) H(D,E)
/ \ / \
A B D E
```


##### Implementation choice

We opt to implement the tracking of freed locations.

While this choice implies adding a structure in the masking ledger
implementation to track freed locations, it offers 2 advantages

* the cost of removal is slightly better since we do not need to update location
<-> account mappings;
* account locations in this representation are fixed for the lifetime of the
account: proof of inclusion would not change but for the last element of the
Merkle path.
rbonichon marked this conversation as resolved.
Show resolved Hide resolved


#### On-disk ledger

The `Database` module implements an on-disk ledger that backs up the in-memory data structure.

Deletion support relies on the same techniques as in-memory ledgers, tailored to the on-disk db.
rbonichon marked this conversation as resolved.
Show resolved Hide resolved

There are some further details to be taken care of, such as:

- updating Merkle paths on removal
- updating the `all_accounts` function to avoid iterating over addresses that
have been removed


### Transaction logic

The support for deletion at the ledger level now needs to be lifted within the
transaction logic for account updates.
The key idea here is to handle removal as a specific kind of account update for
rbonichon marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does the transaction logic differ from what's present today? E.g.

  • How should the API to signal that we're deleting an account look?
  • How should the native version of that work?
  • How should the snark version of that work?
  • What happens in the case of failure? Is it already handled?
  • How is the global supply of Mina tracked faithfully?

a given account.

Concretely, the basic support is provided by adding a deletion flag on account
updates, i.e., extend the `Account_update.Body.Stable.t` type with an additional
record field `delete_account`.

This turns the actual definition from
rbonichon marked this conversation as resolved.
Show resolved Hide resolved
```ocaml
type t = Mina_wire_types.Mina_base.Account_update.Body.V1.t =
{ public_key : Public_key.Compressed.Stable.V1.t
; token_id : Token_id.Stable.V2.t
; update : Update.Stable.V1.t
; balance_change :
(Amount.Stable.V1.t, Sgn.Stable.V1.t) Signed_poly.Stable.V1.t
; increment_nonce : bool
; events : Events'.Stable.V1.t
; actions : Events'.Stable.V1.t
; call_data : Pickles.Backend.Tick.Field.Stable.V1.t
; preconditions : Preconditions.Stable.V1.t
; use_full_commitment : bool
; implicit_account_creation_fee : bool
; may_use_token : May_use_token.Stable.V1.t
; authorization_kind : Authorization_kind.Stable.V1.t
}
```

into

```ocaml
rbonichon marked this conversation as resolved.
Show resolved Hide resolved
type t = Mina_wire_types.Mina_base.Account_update.Body.V1.t =
{ public_key : Public_key.Compressed.Stable.V1.t
; token_id : Token_id.Stable.V2.t
; update : Update.Stable.V1.t
; balance_change :
(Amount.Stable.V1.t, Sgn.Stable.V1.t) Signed_poly.Stable.V1.t
; increment_nonce : bool
; events : Events'.Stable.V1.t
; actions : Events'.Stable.V1.t
; call_data : Pickles.Backend.Tick.Field.Stable.V1.t
; preconditions : Preconditions.Stable.V1.t
; use_full_commitment : bool
; implicit_account_creation_fee : bool
; may_use_token : May_use_token.Stable.V1.t
; authorization_kind : Authorization_kind.Stable.V1.t
(* if true, this account update will delete the account *)
; delete_account: bool;
}
```

This extension provides is thus a version 2 of the current versioned type (`V2`).

What about the changes in Mina_wire_Types?
rbonichon marked this conversation as resolved.
Show resolved Hide resolved

Account deletion is also a payment of the initial account
creation fee back to an address with extended with that triggers account
rbonichon marked this conversation as resolved.
Show resolved Hide resolved
deletion.

Clearly, account deletion should not be permitted except for authorized keys, e.g., the
contract that create the consumable account. For that, we need to check this
rbonichon marked this conversation as resolved.
Show resolved Hide resolved
with respect to the `permissions` filed of `Account_update.Update.t`.

<!-- Locations to track
!--
!-- user command snark https://github.com/MinaProtocol/mina/blob/develop/src/lib/transaction_logic/mina_transaction_logic.ml#L1007
!-- zkapp command snark https://github.com/MinaProtocol/mina/blob/develop/src/lib/transaction_logic/zkapp_command_logic.ml#L1232
!-- sparse ledger https://github.com/MinaProtocol/mina/blob/develop/src/lib/mina_base/sparse_ledger_base.ml#L85
!-- mask ledger https://github.com/MinaProtocol/mina/blob/develop/src/lib/merkle_mask/masking_merkle_tree.ml#L973
!-- db ledger https://github.com/MinaProtocol/mina/blob/develop/src/lib/merkle_ledger/database.ml#L559 -->

### Updating the archive node


### Other expected code changes
#### Ledgers

#### Removal

We propose to support removing elements in ledgers through 2 functions :

- `val remove_location: t -> location -> unit`
- `val remove_account: t -> account -> unit`
rbonichon marked this conversation as resolved.
Show resolved Hide resolved

While both are not needed, since one can usually be easily derived from the
other, we argue that it is nicer to have this 2 functions be provided in the
interface.

#### Changes to `last_filled`

The current ledger interface also exposes the following function:
```ocaml
(** for account locations in the ledger, the last (rightmost) filled
location
*)
val last_filled : t -> Location.t option
```

The usage of this function relies on the implicit invariant that data are never
removed, thus the frontier location, as computed by `last_filled` is directly
related to the next location we can allocate.

This assumption breaks upon the introduction of the remove function we sketched
before.

There are two uses of this function

- [sparse_ledger](https://github.com/MinaProtocol/mina/blob/develop/src/lib/mina_base/sparse_ledger_base.ml#L39)
Now that we would have two sources of free locations, we propose to integrate the computation done
here in the `Ledger` interface.
- [util](https://github.com/MinaProtocol/mina/blob/develop/src/lib/merkle_ledger/util.ml#L168)

#### General protocol changes

The update to `Account_update.t`, though seemingly straightforward has a trickle
down effect, due to the impact of versioning.

All types dependent on type `Account_update.t` will need updating. This single
change is relatively massive so that adding the extra field should really be a
single task to help reviewers.



#### o1js
The API used by `o1js` is not expected to change much but for some details.

- *accounts update* : we propose to implement account deletion as a specific
kind of account updates so that users of this API can specify whether to
delete a given account during a transaction.

In its simplest form, this entails the addition of Boolean argument at the API
level stating whether to delete or not. <!-- We propose to add a dedicated, more
!-- explicit, algebraic datatype of the form:
!--
!-- ```ocaml
!-- type account_update = Delete | Update
!-- ``` -->

- *recipient* : upon deletion the interface should allow to specify who should
the recipient of the account creation fee.




## Test plan and functional requirements

- **Testing goals and objectives**:
- Check the correctness of the account deletion feature;
- Validate the absence of impact of the feature on the existing functionalities.
- **Testing approach**:
- Unit tests for the ledger implementations;
- Integration tests for the extension of transaction logics.
- **Testing scope**:
- Testing sequences of intertwined addition/removal commands;
- Testing simple high-level account updates;
- Testing from `o1js` typical use-cases scenarios.
- **Testing requirements**:
- Test in various local setups to simulate different development environments.
- **Testing resources**:
- Simulated environments resembling typical local development setups.


## Drawbacks

The main drawback of account deletion is that it needs a hard fork to be activated on-chain.

While the changes are relatively limited,


## Rationale and alternatives

## Prior art

## Unresolved questions


## Resources - TB removed


> Here are some things I am thinking about, maybe they help you inform the RFC:
>
> - easy API - we need to express account deletion via an easy API in the sdk
rbonichon marked this conversation as resolved.
Show resolved Hide resolved
> - ideally, this should also be efficient (maybe a single "instruction")? How would this look like as a transaction?
> - will we be able to assert that an account has been deleted? eg look up existing accounts, or put a precondition on it that says "account must be deleted"?
> - recover the original account creation fee to a predetermined address or creator
> - What requirements does an account have to fulfill in order to be deleted?
> - Regarding consumable accounts as a whole, how will they look like? Will they be their own specific type of account or a full account as we know currently?