Skip to content
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(rpc): storage proofs for snos and v0.8.0 #292

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open

Conversation

cchudant
Copy link
Member

@cchudant cchudant commented Oct 1, 2024

Pull Request type

Please add the labels corresponding to the type of changes your PR introduces:

  • Feature

What is the current behavior?

Resolves: #NA

What is the new behavior?

Does this introduce a breaking change?

Yes (deleted some unused rocksdb columns - old database won't open.)

Other information

@antiyro antiyro added the enhancement New feature or request label Oct 2, 2024
@cchudant cchudant marked this pull request as ready for review October 28, 2024 15:06
@HermanObst
Copy link

@antiyro @cchudant hey there! I started reviewing this PR, but I would really appreciate a description that summarize the changes I need to expect in the PR.

crates/client/db/src/bonsai_db.rs Outdated Show resolved Hide resolved
crates/client/db/src/bonsai_db.rs Outdated Show resolved Hide resolved
crates/client/db/src/bonsai_db.rs Outdated Show resolved Hide resolved
crates/client/db/src/bonsai_db.rs Outdated Show resolved Hide resolved
crates/client/db/src/bonsai_db.rs Outdated Show resolved Hide resolved
crates/client/rpc/src/lib.rs Outdated Show resolved Hide resolved
crates/client/rpc/src/lib.rs Outdated Show resolved Hide resolved
crates/client/rpc/src/versions/v0_8_0/get_storage_proof.rs Outdated Show resolved Hide resolved
@cchudant
Copy link
Member Author

cchudant commented Nov 6, 2024

@HermanObst @shamsasari I don't 100% know where to start really, but the basic idea is this:
Bonsai-trie has a trie-log and uses snapshots to get the historical state of the global tries. The way it's handled today in bonsai-trie bothers me quite a lot, and this is the stop-gap solution that uses what's already there in bonsai-trie, with some drawbacks:

  • we can't get a storage proof for a block that is earlier than the block the node restarted at (due to using rocksdb snapshots)
  • the trie logs are not compact at all and handled weirdly in bonsai-trie
  • i had to remove a few features temporarily in bonsai-trie to make all of this work

The snapshot arc thing is a thing that stores a rocksdb Snapshot in an Arc, so that it can be stored in the MadaraBackend. Otherwise, it has an annoying lifetime. See rust-rocksdb/rust-rocksdb#937 and rust-rocksdb/rust-rocksdb#936

That's the hardest part, getting a MerkleTree out of the trie log. Otherwise, generating the merkle proof is as simple as calling getStorageProof which is implemented in my new tree traversal Iterator struct from madara-alliance/bonsai-trie#36

I started reworking the whole trie-log idea in bonsai-trie from scratch in a way that would fix all of the drawbacks & not use rocksdb snapshots. That won't allow us to get rid of our rocksdb fork though, since I will use ArcSnapshot for other things.

Feel free to ask more questions though, this pr is a weird one.

Copy link
Contributor

@shamsasari shamsasari left a comment

Choose a reason for hiding this comment

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

Thanks for the comment, it was very useful. Something of it needs to be in the code as well.

Cargo.toml Outdated Show resolved Hide resolved
Cargo.toml Outdated Show resolved Hide resolved
crates/client/db/src/rocksdb_snapshot.rs Outdated Show resolved Hide resolved
crates/client/db/src/rocksdb_snapshot.rs Show resolved Hide resolved
crates/client/db/src/rocksdb_snapshot.rs Show resolved Hide resolved
crates/client/db/src/snapshots.rs Outdated Show resolved Hide resolved
Comment on lines 105 to 106
// NB: we don't need old value as the trie log is not used :)
// this actually speeds up things quite a lot
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment isn't relevant anymore?

_batch: Option<&mut Self::Batch>,
) -> Result<Option<ByteVec>, Self::DatabaseError> {
self.changed.insert(to_changed_key(key), Some(value.into()));
Ok(None)
Copy link
Contributor

@shamsasari shamsasari Nov 21, 2024

Choose a reason for hiding this comment

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

self.changed.insert gives you the old value. Why is it not returned to the caller?

key: &DatabaseKey,
_batch: Option<&mut Self::Batch>,
) -> Result<Option<ByteVec>, Self::DatabaseError> {
self.changed.insert(to_changed_key(key), None);
Copy link
Contributor

Choose a reason for hiding this comment

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

ditto

}

fn write_batch(&mut self, _batch: Self::Batch) -> Result<(), Self::DatabaseError> {
// TODO: this is not really used yet, this whole abstraction does not really make sense anyway, this needs to be modified
Copy link
Contributor

Choose a reason for hiding this comment

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

Use unreachable! then?

let mut snapshots = self.snapshots.write().expect("Poisoned lock");

if let btree_map::Entry::Vacant(entry) = snapshots.entry(id) {
tracing::debug!("Making snap at {id:?}");
Copy link
Contributor

Choose a reason for hiding this comment

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

snap -> snapshot

@@ -13,4 +13,21 @@ pub struct DbParams {
/// Restore the database at startup from the latest backup version. Use it with `--backup-dir <PATH>`
#[clap(env = "MADARA_RESTORE_FROM_LATEST_BACKUP", long)]
pub restore_from_latest_backup: bool,

/// Maximal number of trie logs saved.
Copy link
Contributor

Choose a reason for hiding this comment

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

Maximum

@@ -13,4 +13,21 @@ pub struct DbParams {
/// Restore the database at startup from the latest backup version. Use it with `--backup-dir <PATH>`
#[clap(env = "MADARA_RESTORE_FROM_LATEST_BACKUP", long)]
pub restore_from_latest_backup: bool,

/// Maximal number of trie logs saved.
Copy link
Contributor

Choose a reason for hiding this comment

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

Will users know what trie logs are?

#[clap(env = "MADARA_DB_MAX_SAVED_TRIE_LOGS", long, default_value_t = 0)]
pub db_max_saved_trie_logs: usize,

/// How many of the latest snapshots are saved, older ones are discarded.
Copy link
Contributor

Choose a reason for hiding this comment

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

snapshots of what? This is not explained.


/// How many of the latest snapshots are saved, older ones are discarded.
/// Higher values cause more database space usage, while lower values prevent the efficient reverting and histoical access for
/// the global state trie at older older blocks.
Copy link
Contributor

Choose a reason for hiding this comment

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

older repeated

pub db_max_saved_snapshots: usize,

/// A database snapshot is created every `db_snapshot_interval` commits.
/// See `db_max_saved_snapshots` to understand what snapshots are used for.
Copy link
Contributor

Choose a reason for hiding this comment

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

See --db-max-saved-snapshots

crates/client/db/src/bonsai_db.rs Show resolved Hide resolved
crates/client/db/src/bonsai_db.rs Show resolved Hide resolved
pub struct Snapshots {
db: Arc<DB>,
snapshots: RwLock<BTreeMap<BasicId, SnapshotRef>>,
max_saved_snapshots: Option<usize>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

If I'm not mistaken we keep a certain amount of snaphots in RAM to be able to fetch historical values much faster than with a simple iterative lookup from the tip of the chain. These are lost on node restart though.

crates/client/db/src/lib.rs Outdated Show resolved Hide resolved
crates/client/db/src/snapshots.rs Outdated Show resolved Hide resolved
@cchudant
Copy link
Member Author

I have modified upstream bonsai-trie and this branch to apply the trie-logs backwards. This is important because it allows us to serve proofs for blocks that are older than the current oldest snapshot

@cchudant
Copy link
Member Author

i also fixed the review comments

Copy link
Collaborator

@Trantorian1 Trantorian1 left a comment

Choose a reason for hiding this comment

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

Seems good, great work 👍

Copy link

@HermanObst HermanObst left a comment

Choose a reason for hiding this comment

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

This is looking great. Good job team!
Im just requiring some documentation for others to have a faster understanding of the code. The rest looks ok!

I would also would like some test on the class trie, but we can left that to other PR

@@ -365,7 +365,7 @@ fn compute_merkle_root<H: StarkHash + Send + Sync>(values: &[Felt]) -> Felt {
let config = bonsai_trie::BonsaiStorageConfig::default();
let bonsai_db = bonsai_trie::databases::HashMapDb::<bonsai_trie::id::BasicId>::default();
let mut bonsai_storage =
bonsai_trie::BonsaiStorage::<_, _, H>::new(bonsai_db, config).expect("Failed to create bonsai storage");
bonsai_trie::BonsaiStorage::<_, _, H>::new(bonsai_db, config, /* max tree height */ 64);

Choose a reason for hiding this comment

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

Im interested about this tree (specifically about the height). What is for?

Copy link
Member Author

Choose a reason for hiding this comment

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

https://docs.starknet.io/architecture-and-concepts/network-architecture/block-structure/#transactions_events_receipts_commitments

in-memory merkle tree within every block
max tree height is 64 since the indices are u64s.
will add a comment about that

if let Some(batch) = batch {
batch.put_cf(&handle, key.as_slice(), value);
} else {
self.db.put_cf_opt(&handle, key.as_slice(), value, &self.write_opt)?;
}
Ok(None)
Ok(old_value.map(Into::into))

Choose a reason for hiding this comment

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

Is this used for something later?

Copy link
Member Author

Choose a reason for hiding this comment

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

what do you mean?

Copy link
Member Author

Choose a reason for hiding this comment

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

no it's used now, the old value will be added to the trie log

Comment on lines 170 to 174
pub struct BonsaiTransaction {
snapshot: SnapshotRef,
changed: BTreeMap<(u8, ByteVec), Option<ByteVec>>,
column_mapping: DatabaseKeyMapping,
}

Choose a reason for hiding this comment

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

Could you add documentation for this struct? It would be helpful to clarify:

Field Descriptions: For example, what does the changed key represent? (I assume u8 represents the column and ByteVec is the database key?) And why is the value optional?

Usage Examples: Providing examples of how this struct is intended to be used would help others understand its purpose and functionality.

In general, adding more documentation would greatly enhance the code's readability and make it easier for others to understand and work with efficiently :)

Copy link
Member Author

Choose a reason for hiding this comment

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

well, most of this file needs to be redone anyway, this abstraction is not that good.
will add some comments, but this is a hack for now

@@ -249,6 +246,19 @@ impl DatabaseExt for DB {
}
}

#[derive(Debug)]

Choose a reason for hiding this comment

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

Link to references/documentation?


/// This struct holds the snapshots. To avoid holding the lock the entire time the snapshot is used, it's behind
/// an Arc. Getting a snapshot only holds the lock for the time of cloning the Arc.
pub struct Snapshots {

Choose a reason for hiding this comment

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

Can we add some docs on how the Snapshots relates to being able to retrieve storage proofs ?
And also slightly document the fields of the struct?

Copy link
Member Author

Choose a reason for hiding this comment

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

mmh, i'm having a hard time figuring out what needs more documentation here

.chain(iter::once(contract_addresses.len()))
.chain(contracts_storage_keys.iter().map(|v| v.storage_keys.len())),
);
if proof_keys > starknet.storage_proof_config.max_keys {

Choose a reason for hiding this comment

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

Is there some limit to this?

Copy link
Member Author

Choose a reason for hiding this comment

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

not really

Comment on lines +130 to +134
let n_tries = saturating_sum(
iter::once(!class_hashes.is_empty() as usize)
.chain(iter::once(!contract_addresses.is_empty() as usize))
.chain(contracts_storage_keys.iter().map(|keys| (!keys.storage_keys.is_empty() as usize))),
);

Choose a reason for hiding this comment

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

In SNOS, the default usage needs to retrieve proofs for all the tries.
Is there a limitation here?

Copy link
Member Author

Choose a reason for hiding this comment

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

not really

if n_tries > starknet.storage_proof_config.max_tries {
return Err(StarknetRpcApiError::ProofLimitExceeded {
kind: StorageProofLimit::MaxUsedTries,
limit: starknet.storage_proof_config.max_tries,

Choose a reason for hiding this comment

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

Is there a limit to this?
Usually several contract storage tries are queried

Copy link
Member Author

Choose a reason for hiding this comment

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

yes, for snos you would just put something very big
these limits are just limits that you put for eg. public rpcs so that they don't get DoSed by having to handle huge requests

Comment on lines +17 to +21
/// This is the number of blocks for which you can get storage proofs using the storage proof endpoints.
/// Blocks older than this limit will not be stored for retrieving historical merkle trie state. By default,
/// the value 0 means that no historical merkle trie state access is allowed.
#[clap(env = "MADARA_DB_MAX_SAVED_TRIE_LOGS", long, default_value_t = 0)]
pub db_max_saved_trie_logs: usize,

Choose a reason for hiding this comment

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

What is the maximum number?

Copy link
Member Author

Choose a reason for hiding this comment

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

there's no maximum number

Comment on lines +154 to +156
/// Limit how many tries can be used within a single storage proof rpc request. Default: 5.
/// The global class trie and global contract tries count each as one, and every contract whose
/// storage is queried count as one each.

Choose a reason for hiding this comment

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

Is there a max value to this?

Copy link
Member Author

Choose a reason for hiding this comment

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

not really

@cchudant
Copy link
Member Author

@HermanObst I am not really sure what you have in mind for the documentation you are asking.
I have made some modifications, but:

  • Do you think the documentation in RpcParams and DbParams (which is user-facing) is enough, or should I copy paste all of that all over the codebase everytime one of those fields pop up again? I would assume people read the user-facing doc before jumping into the code, or if they have questions about these args they would just look at how they're defined and find the user-facing RpcParams/DbParams struct pretty easily. I also think that these should have a dedicated page in the user-facing documentation, when we have that.
  • the snapshots struct in particular looks very self explanatory and I struggle to find any comment that I could add that would explain it better than looking at its code directly. I may be biased as it's my code and I understand it pretty well, but what do you not understand here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
Status: In progress
Development

Successfully merging this pull request may close these issues.

5 participants