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 22 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
384 changes: 384 additions & 0 deletions 00xx-account-deletion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,384 @@
# 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.

#### <a name="merkle_trees"></a> 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. Since removal is not possible, the
leaves between the leftmost leaf of the Merkle tree and the rightmost-filled
leaf are *all* filled with data (i.e., non-empty acccounts).

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.

We will only discuss option 1 here and delay the discussion about option 2 in
[the alternative section](#alternative).


#### Example scenario

To illustrate option 1, let us assume we have the following Merkle tree of depth
2, with an empty free list. This Merkle tree has one location marked with `x` -
this marks the empty account.
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this is the design we want in practice: if we just replace it with the empty account, we don't actually save any space. We would really like to delete the account entirely (at least, on disk).

Copy link
Author

Choose a reason for hiding this comment

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

I see your point. We don't really need to store the empty account though, we just need to act as if it were stored in the leaf. I will rephrase that bit.



```
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` (the empty
account) marking the freed location.

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
```

##### Merkelizing the free list

The view of the free list above is a simplification.

The data contained in the free list needs to be Merkelized as well. This means,
rbonichon marked this conversation as resolved.
Show resolved Hide resolved
assuming a hash function $H$ and locations $loc_1, ..., loc_n$ being inserted in
the free list in that order, that the actual data in the free list would be:

$$
free = [ (loc_n, h_n = H(loc_n, h_{n-1})), ..., (loc_2, h_2 = H(loc_2, h_1)); (loc_1, h_1 = H(loc_1, nil))]
$$





#### 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 current definition into
```ocaml
rbonichon marked this conversation as resolved.
Show resolved Hide resolved
(* This will update the version of the correspond Mina_wire_types definition too).
type t = Mina_wire_types.Mina_base.Account_update.Body.V2.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 therefore provides a version 2 of the current versioned type (`V2`).

This simple change trickles down into updating the transitive closure of all
type definitions depending on `Account_update`, including
`Mina_wire_types.Mina_transaction`.

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` field 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

The addition of a field in `Account_update` will need to reflected in table
`zkapp_account_update_body` with the addition of a `delete_account` column.
rbonichon marked this conversation as resolved.
Show resolved Hide resolved

The migration of the current database needs to set a `false` value in this new
column for all already registered rows.



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

#### Removal

We propose to support removing elements in ledgers through 2 functions, that we specify below:

- `val remove_location: t -> location -> unit`: remove the account found at the
given `location` from the ledger, don't do anything if `location` is occupied
by the empty account
- `val remove_account: t -> account -> unit`: remove `account` from the ledger,
rbonichon marked this conversation as resolved.
Show resolved Hide resolved
don't do anything if `account` does not exist.

While both are not needed, since one can usually be easily derived from the
other, we argue that it is nicer to have these 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 implementing account deletion is that it needs a hard fork
to be activated on-chain.

On the surface, the changes might seem trivial (adding a removal function,
adding a field in a data structure) but they they actually have a widespread
impact on the codebase (50+ files are touched just for adding the aforementioned
field addition so that the our codebase compiles). We thus need to be extra
careful both in the coding *and* the reviewing phases.

## <a name="alternatives"></a>Rationale and alternatives

### Merkle trees

For the sake of completeness, let us consider another option for implementing
removal at the level of Merkle trees, 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 as illustrate in [this section](#merkle_trees).

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 a single element of the
Merkle path.


## Prior art

## Unresolved questions

- Merkelization of ledger + free list and interface for interacting with this data structure


## 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?