-
Notifications
You must be signed in to change notification settings - Fork 29
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
feat: Add Chain Data tables #63
feat: Add Chain Data tables #63
Conversation
src/store/store.sql
Outdated
-- Create chain mmr nodes | ||
CREATE TABLE chain_mmr_nodes( | ||
id INTEGER, -- in-order index of the internal MMR node (autoincrements) | ||
node BLOB NOT NULL, -- internal node value (hash) | ||
PRIMARY KEY (id) | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bobbinth you mentioned that we would probably need to refactor ChainMmr in Miden base to have it work on a partial MMR (I'm guessing this TODO is related).
Right now the RPC returns partial MMR deltas AFAIK, and it should be enough to maintain a full MMR on the client side by adding new leaves, correct? Can you expand a little bit more on the motivation and how it could work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe depending on how frequently the client makes requests, we may not get all the leaves and the client won't be able to build the full MMR.
There is an issue to fix this 0xPolygonMiden/miden-base#112 - I'll work on this and it'll be ready by Monday.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you! I left a few comments inline explaining in more detail how the overall structure should work. It may require a bit more explanation though - so, let happy to talk through it.
src/store/chain_data.rs
Outdated
impl Store { | ||
// CHAIN DATA | ||
// -------------------------------------------------------------------------------------------- | ||
pub fn insert_block_header(&mut self, block_header: BlockHeader) -> Result<(), StoreError> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned in the previous comments, this will need to take an additional parameter for chain_mmr_peaks
(this would be Vec<Digest>
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now thinking about this, we might also need to pass the forest
(u64
) to this as well because our goal is to be able to reconstruct PartialMmr that the client keeps track of. This requires:
forest
:u64
- this would be in theblock_headers
table.peaks
:Vec<RpoDigest>
- this would also be in theblock_headers
table.nodes
:BTreeMap<InOrderIndex, RpoDigest>
- this would come from thechain_mmr_nodes
table.track_latest
:bool
- not sure where this should come from yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this still needed? forest
can be derived from peaks and the PartialMmr
constructor takes only the peaks
src/store/chain_data.rs
Outdated
pub fn insert_chain_mmr_node(&mut self, chain_mmr: ChainMmr) -> Result<(), StoreError> { | ||
let node = serialize_chain_mmr(chain_mmr)?; | ||
|
||
const QUERY: &str = "INSERT INTO chain_mmr_nodes (node) VALUES (?)"; | ||
|
||
self.db | ||
.execute(QUERY, params![node]) | ||
.map_err(StoreError::QueryError) | ||
.map(|_| ()) | ||
} | ||
|
||
pub fn get_chain_mmr_hash_by_id(&self, id: u64) -> Result<ChainMmr, StoreError> { | ||
const QUERY: &str = "SELECT id, node FROM chain_mmr_nodes WHERE id = ?"; | ||
self.db | ||
.prepare(QUERY) | ||
.map_err(StoreError::QueryError)? | ||
.query_map(params![id as i64], parse_chain_mmr_nodes_columns) | ||
.map_err(StoreError::QueryError)? | ||
.map(|result| { | ||
result | ||
.map_err(StoreError::ColumnParsingError) | ||
.and_then(parse_chain_mmr_nodes) | ||
}) | ||
.next() | ||
.ok_or(StoreError::ChainMmrNodeNotFound(id))? | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure these methods are correct. chain_mmr_nodes
table is meant to store nodes from PartialMmr
struct. Basically, every row in this table would be a single entry in the nodes
map of PartialMmr
.
We probably don't want to insert the whole partial MMR every time - but rather only insert the resulting from each update.
So, the methods here should probably be a bit lower-level. Something like:
pub fn insert_chain_mmr_nodes(&mut self, nodes: Vec<(InOrderIndex, Digest)>) -> Result<(), StoreError>` {
}
/// Returns all nodes in the table.
pub fn get_chain_mmr_nodes(&mut self) -> Result<BTreeMap<InOrderIndex, Digest>, StoreError> {
}
/// Gets a list of nodes required to reconstruct authentication paths for the specified blocks.
///
/// This will be used for `get_transaction_data()` method of `DataStore`.
pub fn get_chain_mmr_paths(
&mut self,
block_numbers: &[u32]
) -> Result<Vec<(InOrderIndex, Digest)>, StoreError> {
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, to check that I undestand correctly:
insert_chain_mmr_nodes
should receive only the nodes
derived from each update, iterating over them, inserting each pair (InOrderIndex
, Digest
) in the table.
get_chain_mmr_nodes
retrieves all rows from the table, and puts them inside a BTreeMap
.
get_chain_mmr_paths
only retrieves the rows on the table which InOrderIndex
matches any of the elements of block_numbers
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
insert_chain_mmr_nodes
should receive only thenodes
derived from each update, iterating over them, inserting each pair (InOrderIndex
,Digest
) in the table.
This is correct. In the naive implementation this should be pretty simple as we can just take everything that was added to the nodes
map after the last inserted node (new nodes would always have a bigger index).
get_chain_mmr_nodes
retrieves all rows from the table, and puts them inside aBTreeMap
.
Correct.
get_chain_mmr_paths
only retrieves the rows on the table whichInOrderIndex
matches any of the elements ofblock_numbers
.
It is a bit more involved as for each block number we'll need to return all nodes in the path from the block to the root of the corresponding peak. Let's leave this for another PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at the implementation of InOrderIndex
it seems like there's no way to serialize this type, and neither access it's inner usize
, am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, currently missing but I'm adding it in 0xPolygonMiden/crypto#238.
src/client/mod.rs
Outdated
self.store | ||
.apply_state_sync(new_block_num, new_nullifiers) | ||
.apply_state_sync(new_block_num, new_nullifiers, new_block_header) | ||
.map_err(ClientError::StoreError)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A general comment about sync_state
function: my original intent was for it to work slightly differently. Specifically:
The sync_state
request to the node gives us the next block containing requested data. It also gives us chain_tip
which is the latest block number in the chain. So, unless response.block_header.block_num == response.chain_tip
we haven't synced to the tip of the chain yet.
The idea was that we'd make these requests in a loop until response.block_header.block_num == response.chain_tip
, at which point we know that we've fully synchronized with the chain.
Each request also brings us info about new notes, nullifiers etc. created. It also returns Chain MMR delta that we can use to update the state of Chain MMR. This includes both chain MMR peaks and chain MMR nodes.
A naive way to update chain MMR is to load full PartialMmr
at the beginning of this method and then call apply
on it for every response. There is a better way to do it - though, the details require more thought.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can add the naive implementation to leave it in a working state, and change it a better one if we can come up with some.
A few more comments about the overall syncing procedure (some applicable to this PR, but many probably for future PRs): First, as already mentioned in one of the comments above, the idea is that we make requests to the
For every response we get, we do the following:
|
FYI, I'm handling some of Bobbin's comments on the syncing procedure on #68 (except the account hash checks and maintaining the MMR which are more related to this PR) |
Pushed some changes that use the newer changes of the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! Thank you!
from #5
Add
block_headers
andchain_mmr_nodes
tables with their needed insertion and getter methods to interact with theStore
.