refactor: forbid globally scoped capsules#21614
refactor: forbid globally scoped capsules#21614mverzilli wants to merge 44 commits intomartin/scoped-capsulesfrom
Conversation
| let inbox: CapsuleArray<PendingOffchainMsg> = | ||
| CapsuleArray::at(contract_address, OFFCHAIN_INBOX_SLOT, msg.recipient); |
There was a problem hiding this comment.
This is not super elegant but it doesn't seem like a costly abstraction, so leaving it like that
| node, | ||
| contractStore, | ||
| noteStore, | ||
| () => keyStore.getAccounts(), |
There was a problem hiding this comment.
I would like to kill the "ALL_SCOPES" idea and get rid of this
There was a problem hiding this comment.
I support you in this endeavor. We got to ALL_SCOPES through "evolution" and not through designing.
| // Resolve ALL_SCOPES to actual registered accounts, since sync_state must be called once per account. | ||
| const scopeAddresses = scopes === 'ALL_SCOPES' ? await this.getRegisteredAccounts() : scopes; | ||
|
|
||
| await Promise.all([ |
There was a problem hiding this comment.
ideally we could get rid of plural scopes throughout the systems, which would let us simplify this and the code below
There was a problem hiding this comment.
"get rid of plural scopes throughout the system" is too much, there are legitimate use cases (such as getting notes scoped to multiple accounts at once).
| let context_resolution_requests: CapsuleArray<Field> = | ||
| CapsuleArray::at_global_scope(address, OFFCHAIN_CONTEXT_REQUESTS_SLOT); | ||
| CapsuleArray::at(contract_address, OFFCHAIN_CONTEXT_REQUESTS_SLOT, scope); | ||
| let resolved_contexts: CapsuleArray<Option<MessageTxContext>> = | ||
| CapsuleArray::at_global_scope(address, OFFCHAIN_CONTEXT_RESPONSES_SLOT); | ||
| CapsuleArray::at(contract_address, OFFCHAIN_CONTEXT_RESPONSES_SLOT, scope); | ||
| let ready_to_process: CapsuleArray<OffchainMessageWithContext> = | ||
| CapsuleArray::at_global_scope(address, OFFCHAIN_READY_MESSAGES_SLOT); | ||
| CapsuleArray::at(contract_address, OFFCHAIN_READY_MESSAGES_SLOT, scope); |
There was a problem hiding this comment.
These could technically be global scope capsules if we left the scope param out of resolve_message_contexts, but given there is no legacy here I preferred to scope them and gain some extra isolation points.
We should eventually replace this with volatile arrays which are the right idea to use here
…arrays-scope-up-1
…s' into martin/capsule-arrays-scope-up-1
|
|
||
| /// Receives offchain messages into this contract's offchain inbox for subsequent processing. | ||
| /// | ||
| /// Each message is routed to the inbox scoped to its `recipient` field. |
There was a problem hiding this comment.
This is one decision open to debate, I went for this initially because I had the "scoped sync_state" refactor ahead of me, but now I'm wondering if offchain_receive shouldn't explicitly receive an array of scopes and place each message in each scope regardless of who is each message's specific recipient
There was a problem hiding this comment.
I see scopes as an implementation detail of PXE. The message was sent to X, it is processed as received by X. How it is stored in the db etc feels divorced from this.
There was a problem hiding this comment.
Aren't messages generally destined for 1 scope?
There was a problem hiding this comment.
Yes, each message has one recipient, but as we've been discussing in other chats, there's a bit of chaos to tame here:
offchain_receiveis a utility- it needs to be run with
from, which will become its scope - but it can receive multiple messages, each with its own recipient
- that opens the door to multiple possible behaviors. if the recipients != scope, what do we do? ignore? throw? route them to their inbox? something else?
moreover, there's the concept of additionalScopes when running private functions:
- Will we ever want to support
additionalScopesfrom utilities as well? - How does that behave in combination with message recipients (same questions as above)?
There was a problem hiding this comment.
Why is it a problem to have multiple recipients? Are we not allowed to store something in a scope that is not currently open?
To clarify what I mean, the from in an utility fn is a mistake, it's just a simple way to specify the scopes to use. We use from to not expose this complexity to the app, and the wallet does from -> scopes. But that's just a shortcut.
Conceptually there is no problem in procesing multiple messages to different recipients, though if they are not in scope then msg processing may fail (e.g. we may not be able to read their keys).
There was a problem hiding this comment.
There is no inherent problem, just a degree of freedom that I haven't seen in other of our contract APIs so I thought worth explicitly mentioning
|
|
||
| pub(crate) unconstrained fn process_private_event_msg( | ||
| contract_address: AztecAddress, | ||
| recipient: AztecAddress, |
There was a problem hiding this comment.
another debatable change: I removed the recipient params from all process_*_msg variants, though it might in the future be useful to have access to that info when processing. I chose to remove them nevertheless because it's not used, YMMV.
There was a problem hiding this comment.
The recipient is the scope here so you should always have that info during processing just by the fact that you are able to load the thing from the correct scope, no?
There was a problem hiding this comment.
Yes, correct. When I wrote this I wasn't 100% sure that always the scope is the recipient
There was a problem hiding this comment.
Whoa I'm not sure about this. Why does the scope pop up here? It is because of the capsule oracle? If so this is news conceptually - the fact that the scope is the recipient used to be a pxe detail, but now this leaks into nr.
|
|
||
| /// Receives offchain messages into this contract's offchain inbox for subsequent processing. | ||
| /// | ||
| /// Each message is routed to the inbox scoped to its `recipient` field. |
There was a problem hiding this comment.
Aren't messages generally destined for 1 scope?
| pub(crate) unconstrained fn get_private_logs(contract_address: AztecAddress) -> CapsuleArray<PendingTaggedLog> { | ||
| /// | ||
| /// Note that eventually it would make sense to make this function accept an array of addresses as of scopes, since it | ||
| /// might be useful to integrate all private logs known to a set of accounts. However, this function is currently only |
There was a problem hiding this comment.
integrate all private logs known to a set of accounts
WDYM by this? Loading logs "into" multiple scopes at once?
There was a problem hiding this comment.
Yes, maybe this is too much philosophizing on my end. This function as is just fetches logs, one could imagine a version of this that fetches logs from multiple scopes at once and does something with them, but currently there's no use for that. Maybe I should just remove this comment which ends up adding more noise than clarity
|
|
||
| pub(crate) unconstrained fn process_private_event_msg( | ||
| contract_address: AztecAddress, | ||
| recipient: AztecAddress, |
There was a problem hiding this comment.
The recipient is the scope here so you should always have that info during processing just by the fact that you are able to load the thing from the correct scope, no?
| process_custom_message, | ||
| message_plaintext, | ||
| message_context, | ||
| message_context.recipient, |
There was a problem hiding this comment.
Somewhat unrelated but above we have this comment:
// Real messages would also have a recipient, a concept which is currently half-supported in `TestEnvironment`
// as events are properly scoped by recipient, but notes use a global scope instead. We therefore simply set
// the zero address as the recipient if one is not supplied, which PXE accepts as a scope despite this not
// being a registered account.The zero address seems to act here as somewhat of a global scope. Do you know if there is some plan to purge that? And do you know if the notes truly still use global scope? I am worried that we will forget about this.
There was a problem hiding this comment.
Will review that comment. I think notes no longer use global scope, especially after this change
There was a problem hiding this comment.
I think we should remove that recipient=ZERO line and make sure we're using a proper recipient. I'm surprised this hasn't caused issues so far.
There was a problem hiding this comment.
Cool cool, please link the issues here then. Thanks
| .simulate({ from: recipient }); | ||
|
|
||
| // Force a block so PXE re-syncs and processes the offchain-delivered message | ||
| await forceEmptyBlock(); |
There was a problem hiding this comment.
Doesn't this indicate that the current offchain_receive flow is cumbersome in the real world as we might want to have a flow in which we would want to fully process an offchain message and then we would need to wait for a new block? Shouldn't we have the ability to trigger re-sync at the same anchor block somehow exposed?
There was a problem hiding this comment.
I see there is already this issue for it. Would link it in the comment of forceEmptyBlock
There was a problem hiding this comment.
Will do. There's actually a new oracle to invalidate the cache for the given contract+scope combination, I just need to use it for this hack to be gone.
| // At the same time, we don't want allow any global scope access other than where backwards compatibility forces | ||
| // us to, so we need scope to be here artificially. |
There was a problem hiding this comment.
| // At the same time, we don't want allow any global scope access other than where backwards compatibility forces | |
| // us to, so we need scope to be here artificially. | |
| // At the same time, we don't want to allow any global scope access other than where backwards compatibility forces | |
| // us to. Hence the scope here is "artifical". |
The wording seemed cumbersome.
| node, | ||
| contractStore, | ||
| noteStore, | ||
| () => keyStore.getAccounts(), |
There was a problem hiding this comment.
I support you in this endeavor. We got to ALL_SCOPES through "evolution" and not through designing.
| @@ -141,6 +143,7 @@ pub unconstrained fn do_sync_state<CustomMessageHandlerEnv>( | |||
| process_custom_message, | |||
| message_ciphertext, | |||
| pending_tagged_log.context, | |||
There was a problem hiding this comment.
Maybe for an upcoming refactor:
pending_tagged_log.contextis aMessageContext.MessageContexthas arecipient.- The recipient is, by definition, the
scope, because that's what we're filtering by in theget_private_logscall above
This is a remnant from before introducing scope to sync_state, when get_private_logs was expected to include messages aimed at different recipients.
Carrying the recipient through this introduces some noise, since now process_message_ciphertext will have the scope or the recipient to pick from.
-> We should remove recipient from MessageContext.
There was a problem hiding this comment.
Decided to tackle said refactor now, because it impacts oracle interfaces, so it's better to include it in the next release to avoid unnecessary version bumps
|
|
||
| /// Receives offchain messages into this contract's offchain inbox for subsequent processing. | ||
| /// | ||
| /// Each message is routed to the inbox scoped to its `recipient` field. |
There was a problem hiding this comment.
I see scopes as an implementation detail of PXE. The message was sent to X, it is processed as received by X. How it is stored in the db etc feels divorced from this.
Depends on #21588
Uses the new scoped functionalities of capsules from as many places in the codebase as possible
Note: this PR makes the distinction between stored capsules and volatile arrays even more painfully clear, as capsules used just for rpc comms between Aztec.nr and PXE don't really need a scope but at the same time would be better off not being global to remove concurrency constraints
Closes F-459