Skip to content

Tracking issue: Historic Data Access #849

@LHerskind

Description

@LHerskind
This tracking issue lays out the high-level overview of the solution, 
but redirects more detailed views to its individual issues if required.

Motivation for issue

  • Many historic trees to keep up to date
  • Need data specific to a block with global variables (Tracking issue: Global Variables #824)
  • Statements about individual historic transactions (and not notes) are painful
  • Unclear how kernel vk tree root could practically be updated if needed
  • Private functions require "access-control-storage"

Connecting the dots from:


Currently the circuits take multiple historic tree roots as public inputs.

export class PrivateHistoricTreeRoots {
constructor(
/**
* Root of the private data tree at the time of when this information was assembled.
*/
public privateDataTreeRoot: Fr,
/**
* Root of the nullifier tree at the time of when this information was assembled.
*/
public nullifierTreeRoot: Fr,
/**
* Root of the contract tree at the time of when this information was assembled.
*/
public contractTreeRoot: Fr,
/**
* Root of the l1 to l2 messages tree at the time of when this information was assembled.
*/
public l1ToL2MessagesTreeRoot: Fr,
/**
* Root of the private kernel vk tree at the time of when this information was assembled.
*/
public privateKernelVkTreeRoot: Fr, // future enhancement
) {}

Each of these inputs require that the sequencer provides a merkle-path to prove inclusion in each of the historic roots trees for every transaction. Furthermore, to keep these historic roots trees up to date, we need to provide the root rollup circuit with sibling paths such that it can insert the new roots in these historic root trees.

Blocks tree

To reduce the overhead in the root rollup and reduce public inputs in the base rollup we suggest to replace the historic roots trees with a single "blocks tree".

The leafs in the blocks tree is to be part of the L2 block header that regards the new block output (so all the current historic roots), as well as global variables specific to the block (chainid, version, timestamps etc).

A new block is to be inserted into the blocks tree at every L2 block (block 3 will be at index 3 etc). See visualization below.

Blocks Tree

The kernel need to output a single block "root" that it is using for the historic data that the kernel requires, and the sequencer can then provide the single merkle path for inclusion of this block in the blocks tree. The work required by the base rollup circuit increases by a few hashes to hash up the contents of the block, but as the block is "small" it requires only few additional hashes.

Kernel VK Tree Root

The Kernel VK Tree Root which is used for verifying the kernels are current part of the historic trees without a way to be changed over time. To support upgrades in the future, I suggest that the values should be passed in from the L1 state transitioner contract, such that the root can be upgraded through contract upgrade instead.

Transaction and receipt trees

Including transaction and receipt trees similar to Ethereum allows users to more easily prove that commitments spent in a "current" transaction originated from tx 1, 2, 3. Practically this could be done based on block sizes without the tree, but much less convenient.

Reasoning behind having two separate trees mainly stems from efficiency, as a leaf in a combined tree might have large pre-image that grow the complexity of the application unnecessarily if only one of the two is needed.

Grandfather tree and getHistoricBlock(blockNumber)

With the blocks tree, we are able to use a specific block as base for our proving. However, it requires help from the sequencer to produce the path for the specified block in the tree. Therefor the block needs to be shared as public input (just the leaf hash), such that the sequencer knows what to generate (exactly as historic roots now). However, this need for assistance puts an upper bound on the practical amount of historic blocks we can query in applications.

Say we have an application where we prove provenance of our notes, e.g., note a generate by tx x, note b by tx y and note c by tx z. Most likely we will need to span different blocks, which put a bound on the number of notes that can be collapsed by this application.

If we borrow the idea from historic roots trees, we can create a separate tree whose leafs are historic roots from the blocks tree. A "historic blocks tree roots tree", referred to as the grandfather tree for brevity.

Grandfather

Every leaf in this Grandfather tree is a historic root from the blocks tree, and includes the values of all blocks before it. Meaning that the leaf at index 3 in the Grandfather tree contains blocks 0, 1, 2, 3.

By having the kernel provide a historic blocks tree root (a leaf in the Grandfather tree), and exposing it to the execution context for the underlying application, an application can access as many historic blocks it desires as long as they are included in the leaf provided.

It now becomes a burden of the application rather than the kernel to "open" the commitments down to the individual transactions (the kernel only see 1 extra root).

Without the grandfather tree, the kernel should as earlier described accept $n$ historic blocks as public input which put an unnecessary upper bound on access to historic data available and increased the burden of applications that does not use this data.

For ergonimics of development. Noir could make the historic blocks accessible as context.getHistoricBlock(blockNumber) similar to blockhash(uint256 blockNumber) in Ethereum and use an oracle to provide the merkle paths.

Access control storage

Enforcing access control is as alluded to in the docs with current storage options an unoptimal experience.

In Accessing historic public state from private functions the idea of a slowly updating tree ("slow-poke") is suggested as a way to support public historic values that are accessible from secret functions and can be used for access control, with the requirement that the root of the slow tree used as public input to the proof MUST be the newest (invalidating pending transactions requiring slow-poke at epoch updates). The idea requires an additional public input and should be explored further to decide if it is the best available option.

Issues

### Refactoring
- [ ] https://github.com/AztecProtocol/aztec-packages/issues/3532
### New Functionality
- [ ] https://github.com/AztecProtocol/aztec-packages/issues/2572
#### Historic Blocks Tree (Grandfather Tree) Subtasks
- [ ] https://github.com/AztecProtocol/aztec-packages/issues/1160
- [ ] https://github.com/AztecProtocol/aztec-packages/issues/1161
- [ ] https://github.com/AztecProtocol/aztec-packages/issues/1162
- [ ] https://github.com/AztecProtocol/aztec-packages/issues/1163
- [ ] https://github.com/AztecProtocol/aztec-packages/issues/3206
- [ ] https://github.com/AztecProtocol/aztec-packages/issues/3207
### Transactions Tree Subtasks
- [ ] https://github.com/AztecProtocol/aztec-packages/issues/1442
- [ ] https://github.com/AztecProtocol/aztec-packages/issues/1443
- [ ] https://github.com/AztecProtocol/aztec-packages/issues/1444
- [ ] https://github.com/AztecProtocol/aztec-packages/issues/1445

Metadata

Metadata

Assignees

No one assigned

    Labels

    T-trackingType: Tracking Issue. This contains tasklists.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions