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

refactor(Notes): Rename Singleton.getNote to convey "update" #3123

Closed
Tracked by #2783
benesjan opened this issue Oct 30, 2023 · 14 comments
Closed
Tracked by #2783

refactor(Notes): Rename Singleton.getNote to convey "update" #3123

benesjan opened this issue Oct 30, 2023 · 14 comments
Labels
S-needs-discussion Status: Still needs more discussion before work can start.

Comments

@benesjan
Copy link
Contributor

benesjan commented Oct 30, 2023

ImmutableSingleton.getNote modifies state and I assumed it doesn't which made me use it unconstrained context and waste time debugging it. We should either rename it to clarify that it modifies state, throw an error message showing that calling the function from unconstrained context is prohibited or, as Santiago proposed here, allow for calling of constrained function from unconstrained context.

@rahul-kothari
Copy link
Contributor

throughout aztec-nr, we use get_note() which actually creates a nullifier and view_note()/fetch_note() otherwise.
https://aztecprotocol.slack.com/archives/C03P17YHVK8/p1698052507877489?thread_ts=1697534405.457239&cid=C03P17YHVK8

Maybe we shoukd just refactor the name altogether in aztec-nr to what mike suggested. What do you think?

@rahul-kothari rahul-kothari added the S-needs-discussion Status: Still needs more discussion before work can start. label Oct 31, 2023
@benesjan
Copy link
Contributor Author

benesjan commented Nov 2, 2023

@rahul-kothari I think Mike's names are definitely better but I can also appreciate the consistency of view_note() naming with view() call.

The issue here is that if you don't know the API that well it's easy to do the mistake of using get_notes in unconstrained context. I think probably the best solution would be to improve the error message. Currently we show someting like "brillig trap hit". If we instead manage to show "calling a constrained function get_note() from an unconstrained context is prohibited" and it pointed to the line there would be no issue. Even a much simpler message of "calling a constrained function from an unconstrained context is prohibited" would suffice although it would make sense to invest time in this because I think this will be quite a common issue.

@sirasistant do you know if it would be tricky to improve the error message here and point to the line where the constrained function was called?

@sirasistant
Copy link
Contributor

The error itself should have a noir callstack showing all the calls that lead to the blow up, If that's not the case, there is a bug :(
Regarding the error message itself, it could be improved if assertion messages could be non-literals, but that's complicated at the compiler side. Maybe we can just modify

    pub fn get_note(self) -> Note {
        let context = self.context.unwrap();
        let storage_slot = self.storage_slot;
        get_note(context, storage_slot, self.note_interface)
    }

to check if is_some() and if not assert with an appropiate error message?

@benesjan
Copy link
Contributor Author

benesjan commented Nov 2, 2023

@sirasistant this is what the error looks like when I incorrectly use the get_note method:
image

so it correctly shows the callstack.

As you propose, we could either add there the assert with a nice error message or maybe the best solution of all would be implementing this issue Santiago proposed.

@rahul-kothari
Copy link
Contributor

Potential name for getNote abd viewNote is: get_note_as_mutable AND get_note_as_read_only

@LHerskind
Copy link
Contributor

Tested on current version.

Tx with:

  • ImmutableSingleton.getNote()
    • emits 0 note hashes and 1 nullifier.
    • The nullifier is the tx nullifier.
  • Singleton.getNote()
    • emits 1 note hash and 2 nullifiers.
    • A nullifier for the tx, one for the read and then a note hash for the insertion.

The immutable get_note seems to behave as planned now. But naming of the singleton could be fine to take a look at.

What do people think about get_and_refresh? In my mind, refresh encapsulate (nullify, noteHash).

On the part of naming. Anyone know how come we are using read and write for public but get and replace for private? (singletons and public state).

@benesjan you ok with me altering the name of your issue to be singleton instead since


Related, but the immutable here is also just application-enforced and not protocol-enforced.

@benesjan
Copy link
Contributor Author

@LHerskind Sure, feel free to update the issue as necessary.

@LHerskind LHerskind changed the title Rename ImmutableSingleton.getNote Refactor: Rename Singleton.getNote to convey "update" Jan 18, 2024
@LHerskind LHerskind added this to the Execution Environment milestone Jan 22, 2024
@rahul-kothari
Copy link
Contributor

Another task in the issue can be to throw error when someone calls get_note() in an unconstrained function.

read and write was probably added to be rust like.read_note() makes sense to me too!

@iAmMichaelConnor: what are your thoughts on the proposed names?
get_note() -> get_and_refresh
view_note() -> read_note or view_note (due to the consistency with "view" call in PXE) or get_note_as_ready_only

@nventuro
Copy link
Contributor

nventuro commented Jan 31, 2024

Going over all of this it seems like there's maybe two issues being conflated: the unhelpful error message when calling get in an unconstrained context, and the importance of get emitting a nullfier due to the non-nullification guarantee.

The first item (@benesjan's original issue) is perhaps better tackled by simply improving the error message. Note that this is not just about singletons: sets also feature a pair of get/set functions which can result the same frustrating debugging scenario. As suggested here we can test the context option to emit an appropiate error message. (it'd actually be great if an error message could be passed to unwrap directly - maybe we should open an issue about this?)

The second item has to do with the safety guarantees that get carries. It seems like singletons emitting a nullifier on read is surprising to many people. This might come from an API mismatch that exists with sets, the other big state variable. In sets, get does not emit any nullifiers: it instead expects for the user to manually remove any notes that were consumed. While this comes naturally when dealing with e.g. token balances, it is not necessarily evident for other kinds of sets.


get_note being this different in terms of what it guarantees seems like a big problem. I created to #4346 discuss that separately. In terms of this issue, for now I'd just add error messages that guide the developer towards what they need to do.

@rahul-kothari
Copy link
Contributor

rahul-kothari commented Feb 2, 2024

it'd actually be great if an error message could be passed to unwrap directly - maybe we should open an issue about this?)

Actually @kevaundray is working on it rn!

@rahul-kothari
Copy link
Contributor

We decided that view_note shall now be called get_notes_offchain

@LHerskind LHerskind changed the title Refactor: Rename Singleton.getNote to convey "update" refactor(Notes): Rename Singleton.getNote to convey "update" Mar 9, 2024
@LHerskind LHerskind moved this from Todo to Blocked in A3 Mar 11, 2024
@rahul-kothari
Copy link
Contributor

@LHerskind is this sitll blocked on Noir or anything?

@LHerskind
Copy link
Contributor

@LHerskind is this sitll blocked on Noir or anything?
@rahul-kothari was this ever blocked by noir? It was more a api naming issue?

spalladino pushed a commit that referenced this issue May 21, 2024
Closes #5886.

This removes the `Context` struct and makes all state variables generic
over a new `Context` type parameter, with each state variable providing
implementations for `PrivateContext`, `PublicContext` or `()` (used to
marked `unconstarined` - more on this later).

The end result is that we get compile-time errors when calling functions
that are unavailable in the current context, reduced wrapping and
unwrapping, and no obscure `explicit trap hit in brilling
'self._is_some'` runtime errors, such as in
#3123.

## How

The implementation is prety much as described in #5886, except instead
of using traits we specialize the type directly for the contexts we
know.

```rust
struct MyStateVar<Context> { context: Context }

impl MyStateVar<&mut PrivateContext> { fn private_read() { } }
```

This works because there's only a couple of them, and users are not
expected to extend them.

The macros were altered so that instead of wrapping the context object
in `Context` and then passing that, we simply pass the raw object to the
`Storage::init` function. This means that `Storage` itself is now also
generic, resulting in some unfortunate new boilerplate in the struct
declaration.

All instances of `self.context.private.unwrap()` and friends were
removed: each function is now available under the corresponding `impl`
block that is specialized for the corresponding context. We could even
rename some since `read_public` and `read_private` is no longer
required: both impls can have `read` functions since they are
effectively methods for different types, so the shared name is a
non-issue.

## Oddities

This change revelead a number of small bugs in the codebase, in the form
of uncallable functions. These were undetected since no test called
them. I'll do a pass over the entire PR and leave comments where
relevant.

## Top-level unconstrained

This PR continues the formalization of what I've been calling 'top-level
unconstrained' (i.e. an unconstrained contract function) as a
fundamental Aztec.nr concept and third execution environment, alongside
private and public execution. So far we've been accessing oracles in
these unconstrained functions without much care (sometimes for
perfomance reasons - see
#5911), but the new
stricter compile-time checks force us to be a bit more careful.

In my mind, we are arriving at the following model:
- public execution: done by the sequencer, can be simulated locally with
old data, not unlike the evm
- private execution: able to produce valid private kernel proofs with
side effects collected in the context
- top-level unconstrained execution: local computation using both
private and old public data, with certain restrictions from private exec
lifted (e.g. unbounded loops), unable to produce any kind of proofs or
reason about state changes. only useful for computing values doing
arbitrary computation over both private and public state, with zero
validation and guarantees of correctness

Private execution requires a context object a it needs to collect side
effects, but public notably does not - it simply calls oracles and gets
them to do things. In this sense, the `PublicContext` type is acting as
a marker of the current execution environment in order to prevent
developers from accidentally doing things that are invalid in public,
which would otherwise result in either transpilation error or inability
to create public kernel proofs.

This means that we may want a third `UnconstrainedContext` to act as a
similar marker for this third type (where we can e.g. call `view_notes`,
read old public state, etc.). It currently doesn't exist: we simply have
`Context::none()`, and it is defined as the absense of one of the other
contexts. Because of this, I chose to temporarily use the unit type
(`()`) to mark this environment.

Note that in some cases the different execution environments share code
paths: `view_notes` is simply `get_notes` without any constraints, and
public storage reads are performed by calling the same oracles in both
public and unconstrained. I imagine small differences will arise in the
future, specially as work on the AVM continues.

---------

Co-authored-by: esau <152162806+sklppy88@users.noreply.github.com>
Co-authored-by: thunkar <gregojquiros@gmail.com>
AztecBot pushed a commit to AztecProtocol/aztec-nr that referenced this issue May 22, 2024
Closes #5886.

This removes the `Context` struct and makes all state variables generic
over a new `Context` type parameter, with each state variable providing
implementations for `PrivateContext`, `PublicContext` or `()` (used to
marked `unconstarined` - more on this later).

The end result is that we get compile-time errors when calling functions
that are unavailable in the current context, reduced wrapping and
unwrapping, and no obscure `explicit trap hit in brilling
'self._is_some'` runtime errors, such as in
AztecProtocol/aztec-packages#3123.

## How

The implementation is prety much as described in #5886, except instead
of using traits we specialize the type directly for the contexts we
know.

```rust
struct MyStateVar<Context> { context: Context }

impl MyStateVar<&mut PrivateContext> { fn private_read() { } }
```

This works because there's only a couple of them, and users are not
expected to extend them.

The macros were altered so that instead of wrapping the context object
in `Context` and then passing that, we simply pass the raw object to the
`Storage::init` function. This means that `Storage` itself is now also
generic, resulting in some unfortunate new boilerplate in the struct
declaration.

All instances of `self.context.private.unwrap()` and friends were
removed: each function is now available under the corresponding `impl`
block that is specialized for the corresponding context. We could even
rename some since `read_public` and `read_private` is no longer
required: both impls can have `read` functions since they are
effectively methods for different types, so the shared name is a
non-issue.

## Oddities

This change revelead a number of small bugs in the codebase, in the form
of uncallable functions. These were undetected since no test called
them. I'll do a pass over the entire PR and leave comments where
relevant.

## Top-level unconstrained

This PR continues the formalization of what I've been calling 'top-level
unconstrained' (i.e. an unconstrained contract function) as a
fundamental Aztec.nr concept and third execution environment, alongside
private and public execution. So far we've been accessing oracles in
these unconstrained functions without much care (sometimes for
perfomance reasons - see
AztecProtocol/aztec-packages#5911), but the new
stricter compile-time checks force us to be a bit more careful.

In my mind, we are arriving at the following model:
- public execution: done by the sequencer, can be simulated locally with
old data, not unlike the evm
- private execution: able to produce valid private kernel proofs with
side effects collected in the context
- top-level unconstrained execution: local computation using both
private and old public data, with certain restrictions from private exec
lifted (e.g. unbounded loops), unable to produce any kind of proofs or
reason about state changes. only useful for computing values doing
arbitrary computation over both private and public state, with zero
validation and guarantees of correctness

Private execution requires a context object a it needs to collect side
effects, but public notably does not - it simply calls oracles and gets
them to do things. In this sense, the `PublicContext` type is acting as
a marker of the current execution environment in order to prevent
developers from accidentally doing things that are invalid in public,
which would otherwise result in either transpilation error or inability
to create public kernel proofs.

This means that we may want a third `UnconstrainedContext` to act as a
similar marker for this third type (where we can e.g. call `view_notes`,
read old public state, etc.). It currently doesn't exist: we simply have
`Context::none()`, and it is defined as the absense of one of the other
contexts. Because of this, I chose to temporarily use the unit type
(`()`) to mark this environment.

Note that in some cases the different execution environments share code
paths: `view_notes` is simply `get_notes` without any constraints, and
public storage reads are performed by calling the same oracles in both
public and unconstrained. I imagine small differences will arise in the
future, specially as work on the AVM continues.

---------

Co-authored-by: esau <152162806+sklppy88@users.noreply.github.com>
Co-authored-by: thunkar <gregojquiros@gmail.com>
@benesjan
Copy link
Contributor Author

benesjan commented Aug 9, 2024

Outdated issue

@benesjan benesjan closed this as completed Aug 9, 2024
@github-project-automation github-project-automation bot moved this from Blocked to Done in A3 Aug 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-needs-discussion Status: Still needs more discussion before work can start.
Projects
Archived in project
Development

No branches or pull requests

5 participants