-
Notifications
You must be signed in to change notification settings - Fork 118
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
Validate note commitment trees in non-finalized state #2425
Comments
After studying this and #2458 I see two options for the overall flow of the finalized state:
I think the first approach is better since it wastes less memory and the additional computation it requires is small. But it may lead to some code repetition / refactoring since we need to recalculate stuff that was already calculated in the Chain. I'll explore the first option for now. Additional questions:
|
I think the first option is fine - feel free to open a ticket for later optimisations!
I'm not sure, here are some benefits and drawbacks of waiting until Sprout, Sapling, or NU5 activation: Benefits of waiting until activation:
Drawbacks of waiting until activation:
Based on those tradeoffs, I would prefer waiting - because verification correctness is a high priority. |
The code becomes a bit awkward yes, and I think it will make testing a bit difficult since it will be more difficult to "fake" some stuff. For example, with the finalized state, we will need to create a full chain with Sapling and Orchard "fake" activations in order to test it properly. I'm not sure if the current tests already do something similar, I need to check. Other than that, I think it's a good idea. I've started to use |
It might be worth looking at the nullifier tests from #2497, and the UTXO tests in one of my upcoming PRs. (Which has been delayed because I have to work out how to make other tests pass the UTXO checks.) The nullifier tests commit the genesis block, then modify blocks 1 & 2 so they contain the nullifiers I want. For the UTXO tests, I'll need to add test methods to the finalized and non-finalized states which add fake UTXOs. We should be able to do something similar for the anchors, and just have test methods which add fake state? |
@teor2345: my concern is, for example, if we only create the Sapling tree in the Sapling activation height, then in order to test it in the finalized state then we will need to create more than 100K blocks which is unfeasible. From what I understand in the nullifiers test this doesn't happen because there is no check anywhere the test flow regarding the block height. Regarding using
After trying to use |
There is a check that the heights are in order, which is why all the tests that use the state service or finalized state start at a genesis block. But you're right, there's no check for any absolute activation block height.
We already validate orchard activation in the zebra/zebra-consensus/src/transaction.rs Lines 336 to 343 in 6df17ff
Zebra's V5 transactions are the only transaction version that contains orchard actions, so we won't be asking for orchard roots until the v5 transaction check passes. Sprout was active from genesis, and sapling is active as of Zebra's
The DB is cached in RAM, so we don't need to limit writes in this PR. (Unless it's really easy.)
I don't think special-case handling is a good idea - we'd have to get it right for both block validation and mempool validation. And test it for both cases.
The correct activation height is actually the commit of the block before the activation block, because the anchors need to be available to mempool transactions and transactions in the activation block: This needs a spec clarification.
I agree, I think we'd be effectively duplicating validation we've done already in the |
I'm fine with instantiating all the trees from the beginning, as teor said we have several checks in place to ensure they will not be populated (besides the Default::default() / empty tree) until needed, and I think that's fine, if any accidental queries to them come back with a result besides the root of the empty tree, that should correctly fail, unless that is the first sprout/sapling/orchard transaction correctly looking for the root of the empty tree. |
It looks like we have implemented all tasks in this issue. |
Done! |
Is your feature request related to a problem?
In order to maintain shielded note commitment trees, we will need to update our implementation of Zebra state for non-finalized chains, keep those trees up to date in the non-finalized state as we validate blocks, and save down the serialized trees to finalized state for the tip.
Questions:
Unanswered questions about the finalized and non-finalized state:
What keys do we need to be able to look up quickly? For transaction validation? For treestate updates?
This changes how we index treestates in the state.
The state already has sprout and sapling anchors, and sprout, sapling, and orchard nullifiers.
Are there any rules about which treestates can be used for which transactions?
This might change how we maintain (Sprout?) treestates in the non-finalized state.
For example, can Sprout, Sapling, and Orchard transactions use the previous block's treestate?
(There are a lot of references to "finalized" treestates in the rest of the RFC, but I'm not sure if they are the same as the "finalized" state.)
The rest of the RFC mentions that Sprout transactions can use the treestate of any previous transaction in the block.
Why do we need to store a separate Sprout treestate for every block? Can we skip blocks without Sprout transactions? Are there parts of the treestate data that don't change?
This impacts the indexes, storage, and lookups for those treestates.
What are the details of a Sprout treestate update?
This might change how we maintain (Sprout?) treestates in the non-finalized or finalized state.
Referencing a root that we haven't seen before, because when we're verifying blocks in parallel, is that ok? Cryptographically ?
Non-finalized state
Maintaining note commitment trees
Do the
sprout
implementations for the non-finalized stateAdd
(sprout|sapling|orchard)_note_commitment_tree: (sprout|sapling|orchard)::tree::NoteCommitmentTree
data field to theChain
typeWhen adding a block to a non-finalized tip:
JoinSplit
s orOutput
s orAction
s in the transactions in the block, they must be appended to the respective note commitment tree in-orderNoteCommitmentTree.append()
When forking a new side-chain:
Update
Chain.update_chain_state_with()
self.update_chain_state_with(joinsplit_data)
to append anysprout::NoteCommitment
sself.update_chain_state_with(sapling_shielded_data_per_spend_anchor)
to append anysapling::NoteCommitment
sself.update_chain_state_with(sapling_shielded_data_shared_anchor)
to append anysapling::NoteCommitment
sself.update_chain_state_with(orchard_shielded_data)
to append anyorchard::NoteCommitment
sFinalized state
FinalizedBlock
Do the
sprout
implementations for the finalized stateAdd
(sprout|sapling|orchard)_note_commitment_tree
fieldsChain
, the output treestates as of the last transaction in the blockDatabase format:
sprout_incremental
|BE32(height)
|sprout::tree::NoteCommitmentTree
| Delete |sapling_incremental
|BE32(height)
|sapling::tree::NoteCommitmentTree
| Delete |orchard_incremental
|BE32(height)
|orchard::tree::NoteCommitmentTree
| Delete |Additional context
See #2091 (comment) for further details
The text was updated successfully, but these errors were encountered: