-
Notifications
You must be signed in to change notification settings - Fork 32
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
base: main
Are you sure you want to change the base?
Conversation
@HermanObst @shamsasari I don't 100% know where to start really, but the basic idea is this:
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. |
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.
Thanks for the comment, it was very useful. Something of it needs to be in the code as well.
crates/client/db/src/bonsai_db.rs
Outdated
// NB: we don't need old value as the trie log is not used :) | ||
// this actually speeds up things quite a lot |
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.
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) |
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.
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); |
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.
ditto
crates/client/db/src/bonsai_db.rs
Outdated
} | ||
|
||
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 |
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.
Use unreachable!
then?
crates/client/db/src/snapshots.rs
Outdated
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:?}"); |
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.
snap -> snapshot
crates/node/src/cli/db.rs
Outdated
@@ -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. |
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.
Maximum
crates/node/src/cli/db.rs
Outdated
@@ -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. |
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.
Will users know what trie logs are?
crates/node/src/cli/db.rs
Outdated
#[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. |
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.
snapshots of what? This is not explained.
crates/node/src/cli/db.rs
Outdated
|
||
/// 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. |
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.
older repeated
crates/node/src/cli/db.rs
Outdated
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. |
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.
See --db-max-saved-snapshots
crates/client/db/src/snapshots.rs
Outdated
pub struct Snapshots { | ||
db: Arc<DB>, | ||
snapshots: RwLock<BTreeMap<BasicId, SnapshotRef>>, | ||
max_saved_snapshots: Option<usize>, |
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.
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/rpc/src/versions/user/v0_8_0/methods/read/get_storage_proof.rs
Outdated
Show resolved
Hide resolved
crates/client/rpc/src/versions/user/v0_8_0/methods/read/get_storage_proof.rs
Outdated
Show resolved
Hide resolved
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 |
i also fixed the review comments |
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.
Seems good, great 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.
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); |
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.
Im interested about this tree (specifically about the height). What is for?
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.
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)) |
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 used for something later?
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.
what do you mean?
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.
no it's used now, the old value will be added to the trie log
crates/client/db/src/bonsai_db.rs
Outdated
pub struct BonsaiTransaction { | ||
snapshot: SnapshotRef, | ||
changed: BTreeMap<(u8, ByteVec), Option<ByteVec>>, | ||
column_mapping: DatabaseKeyMapping, | ||
} |
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.
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 :)
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.
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)] |
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.
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 { |
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.
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?
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.
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 { |
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 there some limit to this?
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 really
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))), | ||
); |
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.
In SNOS, the default usage needs to retrieve proofs for all the tries.
Is there a limitation here?
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 really
if n_tries > starknet.storage_proof_config.max_tries { | ||
return Err(StarknetRpcApiError::ProofLimitExceeded { | ||
kind: StorageProofLimit::MaxUsedTries, | ||
limit: starknet.storage_proof_config.max_tries, |
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 there a limit to this?
Usually several contract storage tries are queried
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, 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
/// 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, |
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.
What is the maximum number?
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.
there's no maximum number
/// 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. |
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 there a max value to this?
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 really
@HermanObst I am not really sure what you have in mind for the documentation you are asking.
|
Pull Request type
Please add the labels corresponding to the type of changes your PR introduces:
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