feat: optimize GetAccountProof for small accounts#1214
Conversation
323f051 to
c8d0953
Compare
e003c3b to
bce5a37
Compare
crates/store/src/state.rs
Outdated
|
|
||
| let account_ids: Vec<AccountId> = | ||
| account_requests.iter().map(|req| req.account_id).collect(); | ||
| let AccountProofRequest { account_id, block_num: _, details } = account_request; |
There was a problem hiding this comment.
The block_num will be handled once the relevant data is stored #1185 , which is part of step 3, the next PR.
GetAccountProof for small accounts
00637fa to
2b77b09
Compare
|
Hold off for a sec, will push the correct changeset in a few .. |
2a88587 to
5486809
Compare
974b43d to
a800530
Compare
|
@drahnr - could you merge the latest |
Co-authored-by: Mirko <48352201+Mirko-von-Leipzig@users.noreply.github.com>
49f50c9 to
eaac935
Compare
bobbinth
left a comment
There was a problem hiding this comment.
Looks good! Thank you! Not a full review yet, but I left some questions/comments inline (mostly about protobuf files).
5e2cbaf to
3230501
Compare
igamigo
left a comment
There was a problem hiding this comment.
Looks good to me. Left some comments but overall functionality is OK for what we currently use it in the client
| if vault.assets().nth(Self::MAX_RETURN_ENTRIES).is_some() { | ||
| Self::too_many() | ||
| } else { | ||
| Self { | ||
| too_many_assets: false, | ||
| assets: Vec::from_iter(vault.assets()), | ||
| } |
There was a problem hiding this comment.
This iterates over the vault's assets twice, would be nice to do it in one go but not sure there's a nice way to do that either
There was a problem hiding this comment.
We unfortunately don't get access to the underlying data structure, but only in form of an iterator
There was a problem hiding this comment.
I think you are one miden-base commit behind 0xMiden/protocol@b6f679b which merged 0xMiden/protocol#1939. If you can update to that, you could use num_assets.
| } | ||
|
|
||
| // Only include unknown account code blobs, which is equal to a account code digest | ||
| // mismatch. If `None` was requested, don't return any. |
There was a problem hiding this comment.
I think this differs from the previous approach where we did respond with the account code if it was None, right? Because it signaled that the user did not know the code.
Is the idea that if we want the code we send a dummy commitment now? I think that works but also I don't think there's valid usecases for sending None here and not expecting the code back AFAICT.
There was a problem hiding this comment.
The interpretation here changed, can you take a look @bobbinth
There was a problem hiding this comment.
I think this was covered in another comment, but yes, this is slightly different. Now, the client can do the following:
- If the client doesn't know the code send
0x0000...as commitment. This would guarantee that the current code is returned. - If the client has the code, send its commitment, and then the code would be returned only if there is a mismatch.
- There is now also the 3rd option that the client doesn't currently use: send
Noneto indicate that the client is not interested in the code.
09c319f to
b0a0457
Compare
PhilippGackstatter
left a comment
There was a problem hiding this comment.
Looks good. Take this as a light approval - I'm missing some context around the client's need with regards to this API. I mainly looked at the usages of the account-related APIs.
| for StorageMapRequest { slot_index, slot_data } in storage_requests { | ||
| let Some(StorageSlot::Map(storage_map)) = | ||
| details.storage().slots().get(slot_index as usize) | ||
| else { | ||
| return Err(AccountError::StorageSlotNotMap(slot_index).into()); | ||
| }; | ||
| let details = AccountStorageMapDetails::new(slot_index, slot_data, storage_map); | ||
| storage_map_details.push(details); | ||
| } |
There was a problem hiding this comment.
Question out of curiosity: Why does this not return a merkle proof that proves inclusion of the entry in the storage map? I would have thought that the client needed this, but it seems it doesn't.
There was a problem hiding this comment.
I am not fully in the loop how the client uses it CC @igamigo might be able to help here if this is OK, I'll double check tomorrow
There was a problem hiding this comment.
As I see it, the client currently doesn't use the account storage map details, but only the account proofs. So the usage would remain the same.
Do we need the proofs for FPI calls? I'd assume so? CC @bobbinth
I don't necessarily want to block the PR any longer
There was a problem hiding this comment.
There are 3 ways to fulfill these requests and currently we are supporting two of them. The requests types are:
- We request all entries of the storage map: if the number of entries is less than some threshold
$n$ (currently, I think$n = 1000$ but maybe it should be smaller), then we return all entries and the client can reconstruct the SMT locally. - We request specific set of key-value pairs. Here we have two options:
a. The total number of key-value pairs in the SMT is less than$n$ , in which case we return all key-value pairs.
b. The total number of key-value pairs in the SMT is greater than$n$ , in which case we'd need to return Merkle paths for each returned key-value pair (and we can also apply path compaction here).
Case 2b is currently not implemented. Also, looking at the code, I'm not sure the case 2a handled correctly. Specifically, AccountStorageMapDetails::from_slot_data() for now should return all entries if the storage size under a given threshold and return too_many_entries otherwise.
The reason for the above is that if we don't return all entries (and don't return Merkle paths in case of partial entries), the client would have no way to reconstruct the underling SMT - and so the returned data would not be usable.
I think we should do the following:
- Open a small PR to fix handling of case 2a.
- Create a follow-up issue to implement handling of case 2b.
| if vault.assets().nth(Self::MAX_RETURN_ENTRIES).is_some() { | ||
| Self::too_many() | ||
| } else { | ||
| Self { | ||
| too_many_assets: false, | ||
| assets: Vec::from_iter(vault.assets()), | ||
| } |
There was a problem hiding this comment.
I think you are one miden-base commit behind 0xMiden/protocol@b6f679b which merged 0xMiden/protocol#1939. If you can update to that, you could use num_assets.
|
Merging, I'll address any outstanding issues as a follow-up |
bobbinth
left a comment
There was a problem hiding this comment.
Looks good! Thank you! I left a few comments inline. The main one is about fixing how we return storage map data.
| } | ||
|
|
||
| // Only include unknown account code blobs, which is equal to a account code digest | ||
| // mismatch. If `None` was requested, don't return any. |
There was a problem hiding this comment.
I think this was covered in another comment, but yes, this is slightly different. Now, the client can do the following:
- If the client doesn't know the code send
0x0000...as commitment. This would guarantee that the current code is returned. - If the client has the code, send its commitment, and then the code would be returned only if there is a mismatch.
- There is now also the 3rd option that the client doesn't currently use: send
Noneto indicate that the client is not interested in the code.
| for StorageMapRequest { slot_index, slot_data } in storage_requests { | ||
| let Some(StorageSlot::Map(storage_map)) = | ||
| details.storage().slots().get(slot_index as usize) | ||
| else { | ||
| return Err(AccountError::StorageSlotNotMap(slot_index).into()); | ||
| }; | ||
| let details = AccountStorageMapDetails::new(slot_index, slot_data, storage_map); | ||
| storage_map_details.push(details); | ||
| } |
There was a problem hiding this comment.
There are 3 ways to fulfill these requests and currently we are supporting two of them. The requests types are:
- We request all entries of the storage map: if the number of entries is less than some threshold
$n$ (currently, I think$n = 1000$ but maybe it should be smaller), then we return all entries and the client can reconstruct the SMT locally. - We request specific set of key-value pairs. Here we have two options:
a. The total number of key-value pairs in the SMT is less than$n$ , in which case we return all key-value pairs.
b. The total number of key-value pairs in the SMT is greater than$n$ , in which case we'd need to return Merkle paths for each returned key-value pair (and we can also apply path compaction here).
Case 2b is currently not implemented. Also, looking at the code, I'm not sure the case 2a handled correctly. Specifically, AccountStorageMapDetails::from_slot_data() for now should return all entries if the storage size under a given threshold and return too_many_entries otherwise.
The reason for the above is that if we don't return all entries (and don't return Merkle paths in case of partial entries), the client would have no way to reconstruct the underling SMT - and so the returned data would not be usable.
I think we should do the following:
- Open a small PR to fix handling of case 2a.
- Create a follow-up issue to implement handling of case 2b.
Part 2 of #1185 - reworks the
GetAccountProofquery with a new type.The changeset focuses on providing account data / vault entries in the response if they are less than 100 (might be too conservative, for both). @SantiagoPittella