Skip to content

feat: optimize GetAccountProof for small accounts#1214

Merged
drahnr merged 22 commits intonextfrom
bernhard-1185-acounts-part-2
Oct 7, 2025
Merged

feat: optimize GetAccountProof for small accounts#1214
drahnr merged 22 commits intonextfrom
bernhard-1185-acounts-part-2

Conversation

@drahnr
Copy link
Contributor

@drahnr drahnr commented Sep 9, 2025

Part 2 of #1185 - reworks the GetAccountProof query 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

@drahnr drahnr force-pushed the bernhard-1185-acounts-part-2 branch from 323f051 to c8d0953 Compare September 10, 2025 13:45
drahnr added a commit that referenced this pull request Sep 11, 2025
@drahnr drahnr force-pushed the bernhard-1185-acounts-part-2 branch from e003c3b to bce5a37 Compare September 11, 2025 14:48
@drahnr drahnr changed the base branch from next to bernhard-1185-acounts-part-1 September 11, 2025 14:49

let account_ids: Vec<AccountId> =
account_requests.iter().map(|req| req.account_id).collect();
let AccountProofRequest { account_id, block_num: _, details } = account_request;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The block_num will be handled once the relevant data is stored #1185 , which is part of step 3, the next PR.

@drahnr drahnr changed the title more sophisticated account state query feat: optimize GetAccountProof for small accounts Sep 11, 2025
@drahnr drahnr marked this pull request as ready for review September 11, 2025 17:55
@drahnr drahnr force-pushed the bernhard-1185-acounts-part-2 branch from 00637fa to 2b77b09 Compare September 11, 2025 17:58
@drahnr drahnr marked this pull request as draft September 11, 2025 18:03
@drahnr
Copy link
Contributor Author

drahnr commented Sep 11, 2025

Hold off for a sec, will push the correct changeset in a few ..

@drahnr drahnr marked this pull request as ready for review September 11, 2025 21:36
Base automatically changed from bernhard-1185-acounts-part-1 to next September 12, 2025 11:09
@bobbinth
Copy link
Contributor

@drahnr - could you rebase this from the latest next? Seems like some of the changes from #1211 are not here yet.

@drahnr drahnr force-pushed the bernhard-1185-acounts-part-2 branch from 2a88587 to 5486809 Compare September 14, 2025 20:15
cursor[bot]

This comment was marked as outdated.

@drahnr drahnr force-pushed the bernhard-1185-acounts-part-2 branch from 974b43d to a800530 Compare September 19, 2025 12:17
@bobbinth
Copy link
Contributor

@drahnr - could you merge the latest next into this branch. I think currently it contains some changes that are already in next.

@drahnr drahnr force-pushed the bernhard-1185-acounts-part-2 branch from 49f50c9 to eaac935 Compare October 1, 2025 15:47
Copy link
Contributor

@bobbinth bobbinth left a 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! Not a full review yet, but I left some questions/comments inline (mostly about protobuf files).

@drahnr drahnr force-pushed the bernhard-1185-acounts-part-2 branch from 5e2cbaf to 3230501 Compare October 2, 2025 14:29
Copy link
Collaborator

@igamigo igamigo left a comment

Choose a reason for hiding this comment

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

Looks good to me. Left some comments but overall functionality is OK for what we currently use it in the client

Comment on lines +352 to +358
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()),
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We unfortunately don't get access to the underlying data structure, but only in form of an iterator

Copy link
Contributor

Choose a reason for hiding this comment

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

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.
Copy link
Collaborator

Choose a reason for hiding this comment

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

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The interpretation here changed, can you take a look @bobbinth

Copy link
Contributor

Choose a reason for hiding this comment

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

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 None to indicate that the client is not interested in the code.

@drahnr drahnr force-pushed the bernhard-1185-acounts-part-2 branch from 09c319f to b0a0457 Compare October 6, 2025 12:02
Copy link
Contributor

@PhilippGackstatter PhilippGackstatter left a comment

Choose a reason for hiding this comment

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

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.

Comment on lines +921 to 929
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);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Contributor Author

@drahnr drahnr Oct 6, 2025

Choose a reason for hiding this comment

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

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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

Copy link
Contributor

Choose a reason for hiding this comment

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

There are 3 ways to fulfill these requests and currently we are supporting two of them. The requests types are:

  1. 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.
  2. 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.

Comment on lines +352 to +358
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()),
}
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

@drahnr
Copy link
Contributor Author

drahnr commented Oct 7, 2025

Merging, I'll address any outstanding issues as a follow-up

@drahnr drahnr merged commit 40529f1 into next Oct 7, 2025
6 checks passed
@drahnr drahnr deleted the bernhard-1185-acounts-part-2 branch October 7, 2025 16:05
Copy link
Contributor

@bobbinth bobbinth left a 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! 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.
Copy link
Contributor

Choose a reason for hiding this comment

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

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 None to indicate that the client is not interested in the code.

Comment on lines +921 to 929
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);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

There are 3 ways to fulfill these requests and currently we are supporting two of them. The requests types are:

  1. 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.
  2. 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.

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.

5 participants