Skip to content

refactor: offchain reception on entity store#24142

Open
mverzilli wants to merge 14 commits into
martin/expose-entity-store-oraclesfrom
martin/offchain-reception-on-entity-store
Open

refactor: offchain reception on entity store#24142
mverzilli wants to merge 14 commits into
martin/expose-entity-store-oraclesfrom
martin/offchain-reception-on-entity-store

Conversation

@mverzilli

@mverzilli mverzilli commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Re-implementation of offchain reception workflows using the new EntityStore machinery

Note this exposed a problem in the EntityStore idempotency semantics: throwing on an attempt at creating an already existing entity is problematic, because Noir doesn't provide error handling mechanisms. In the end, I decided to make entity creation idempotent, with the first attempt at creating a given entity "winning" to avoid inadvertent overwrites. It is then user responsibility to model their workflows properly (there's plenty of alternatives: change how entity id's are derived, check for existence before attempting creation, using facts, etc).

Closes F-682

@mverzilli mverzilli requested a review from nventuro as a code owner June 16, 2026 19:42
@mverzilli mverzilli removed the request for review from nventuro June 16, 2026 19:42
@mverzilli mverzilli changed the base branch from martin/f-738-tx-resolution-oracle to martin/expose-entity-store-oracles June 16, 2026 19:43
Comment thread noir-projects/aztec-nr/aztec/src/messages/processing/offchain/message.nr Outdated
Comment thread noir-projects/aztec-nr/aztec/src/messages/processing/offchain/message.nr Outdated
Comment thread noir-projects/aztec-nr/aztec/src/messages/processing/offchain/message.nr Outdated
Comment thread noir-projects/aztec-nr/aztec/src/messages/processing/offchain/message.nr Outdated
Comment thread noir-projects/aztec-nr/aztec/src/messages/processing/offchain/message.nr Outdated
set_contract_sync_cache_invalid(contract_address, messages.map(|msg| msg.recipient));
}

/// Step function to advance the reception workflow of an offchain message.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It took me a while to understand this function, including reasoning about the correctness of the TTL. I feel like there's an explanation missing of what this entity and workflow are supposed to do, how they operate and how they handle edge cases, from which the FSM states and transitions would be derived and therefore the implementation of this function.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In particular, I don't understand how tx-less messages are handled. The logic seems to be as follows:

A message contains a payload and timestamp, and may be related to a transaction. A message is processed only once, when we see the transaction (this is a fact). (notably tx less messages seem to never get processed)

An unprocessed message expires after the TTL. This is because a transaction known at a time T could not possibly begin to exist after the TTL, because this is larger than the maximum transaction expiration duration (which is not mentioned), therefore it is safe to stop looking for it. We add a 2 hour safety margin.

A processed message also expires after the TTL. This is because we're being a bit lazy - we could instead wait until finalization of the block associated to the tx that caused us to process it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The comment below explains some of these things, but I feel like the explanation already assumes you understand parts of how this works, and it also acts on a sort of 'collapsed' view of the FSM (e.g. the combination of the processed and unprocessed termination conditions, etc.).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I don't understand how tx-less messages are handled

That's because they aren't. I re-wrote these docs to adapt them to the new implementation, but I think along the way I lost that detail. The fact that I lost the history of the file as you pointed out above certainly doesn't help there.

This is at feature parity with the previous implementation, which also didn't. I didn't want to introduce behavior changes at this point, but we can of course plan to add support in a follow up PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The rest of the comments, all fair points. I like the idea of documenting the FSM more explicitly and starting from there.

Comment thread noir-projects/aztec-nr/aztec/src/messages/processing/offchain/mod.nr Outdated
Comment thread noir-projects/aztec-nr/aztec/src/messages/processing/offchain/mod.nr Outdated
Comment thread noir-projects/aztec-nr/aztec/src/messages/processing/offchain/mod.nr Outdated
// Ask PXE to resolve each message's originating tx. We pass the tx hashes in active-reception order, so the
// resolved txs come back aligned with the reception indices and can be matched back positionally.
let resolved_txs = get_resolved_txs(active_receptions.map(|reception: Entity<OffchainMessage>| {
reception.body.tx_hash.unwrap_or(0)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I see now where the 0 hash in a past PR orignated from. This should be an option, with an explanation of why it makes sense to have optional queries (which is obvious here but very non-obvious in TS).

…n-reception-on-entity-store

# Conflicts:
#	yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts
…ull message

Hash the whole serialized OffchainMessage (via its Serialize derive) instead of just
ciphertext + tx hash, so identity covers every field and there are no silently-excluded
fields to reason about.
OffchainMessage, OFFCHAIN_RECEPTION_TYPE and fact_collection_id (now a free function
rather than a method) move into mod.nr; the near-empty message.nr and its round-trip
test are removed.
Comment on lines +204 to +226
/// A message delivered via the `offchain_receive` utility function.
#[derive(Serialize, Deserialize)]
pub struct OffchainMessage {
/// The encrypted message payload.
pub ciphertext: BoundedVec<Field, MESSAGE_CIPHERTEXT_LEN>,
/// The intended recipient of the message.
pub recipient: AztecAddress,
/// The hash of the transaction that produced this message. `Option::none` indicates a tx-less message.
pub tx_hash: Option<Field>,
/// Anchor block timestamp at message emission.
pub anchor_block_timestamp: u64,
}

/// Fact-collection type id shared by every offchain message reception in the fact store.
global OFFCHAIN_RECEPTION_TYPE_ID: Field = sha256_to_field("AZTEC_NR::OFFCHAIN_RECEPTION_TYPE_ID".as_bytes());

/// Computes the fact-collection id that identifies a message's reception workflow in the fact store.
///
/// The id is a hash over the full serialized message. Re-delivering a byte-identical message therefore is idempotent,
/// and a message differing in any of its fields triggers a completely new reception workflow.
fn fact_collection_id(message: OffchainMessage) -> Field {
poseidon2_hash(message.serialize())
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Note: this is a bit of weird placement for these lines in the file. I chose to put them here so as not to scramble the previous review comments

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants