feat(rpc): add partial eth_getProof implementation#1515
feat(rpc): add partial eth_getProof implementation#1515gakonst merged 25 commits intoparadigmxyz:mainfrom
eth_getProof implementation#1515Conversation
The stages crate depends on the provider crate. As this struct was needed for further development of the eth_getProof endpoint, it was moved down on the dependency chain. Maybe it could be in its own crate. Co-authored-by: lambdaclass-user <github@lambdaclass.com>
Co-authored-by: lambdaclass-user <github@lambdaclass.com>
Co-authored-by: lambdaclass-user <github@lambdaclass.com>
Co-authored-by: lambdaclass-user <github@lambdaclass.com>
Co-authored-by: lambdaclass-user <github@lambdaclass.com>
Codecov Report
📣 This organization is not using Codecov’s GitHub App Integration. We recommend you install it so Codecov can continue to function properly for your repositories. Learn more @@ Coverage Diff @@
## main #1515 +/- ##
===========================================
- Coverage 72.95% 42.13% -30.82%
===========================================
Files 391 391
Lines 46575 46869 +294
===========================================
- Hits 33978 19749 -14229
- Misses 12597 27120 +14523
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 197 files with indirect coverage changes Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. |
Co-authored-by: lambdaclass-user <github@lambdaclass.com>
|
@mattsse On another note, the |
|
cc @joshieDo given you've been looking at Merkle stage and patricia tree in general |
|
My impression is that we need to change the way we insert into the trie - and use a cursor instead (included that remark in the other PR). this would allow us to have more control over what we commit. For this kind of unwinding, we have to "force" some hashing stages to use their incremental branch instead of batched/scratch which don't commit the db tx iirc |
|
There could be only one write tx, this could block other write transactions. |
Previously only HistorySP was returned and we didn't use LatestSP at all. Co-authored-by: lambdaclass-user <github@lambdaclass.com>
Co-authored-by: lambdaclass-user <github@lambdaclass.com>
|
Update: I left the implementation for the latest state provider only. If the block requested isn't the latest, it returns a "not implemented" error via RPC. As part of this, I modified the state access helpers to allow returning any of both the latest and history state providers. I've noticed that geth uses multiple databases for caching previous states, and use this for the endpoint backend. Other implementations seem to have partial (only for the latest or cached) or no support for the endpoint.
Because of limitations with cita_trie and the need for cursor mutability, this would require a
That's right for the |
Co-authored-by: lambdaclass-user <github@lambdaclass.com>
|
blocked by #1497 |
eth_getProof implementationeth_getProof implementation
Also, fix: was hashing received and already hashed address Co-authored-by: lambdaclass-user <github@lambdaclass.com>
Co-authored-by: lambdaclass-user <github@lambdaclass.com>
|
Needs merge conflict post #1474, sorry about that. |
|
Fixed! |
| // Transparent wrapper to enable state access helpers | ||
| // returning latest state provider when appropiate | ||
| pub(crate) enum SP<'a, H, L> { | ||
| History(H), | ||
| Latest(L), | ||
| _Unreachable(&'a ()), // like a PhantomData for 'a |
There was a problem hiding this comment.
Let's please avoid really short names like this
There was a problem hiding this comment.
Yes. I was having a bit of trouble coming up with a fitting name. I was thinking of StateProviderType, or maybe GenericSP. wdyt?
There was a problem hiding this comment.
I would call it StateProvider and do use xyz::StateProvider as StateProviderTrait
crates/rpc/rpc/src/eth/api/server.rs
Outdated
| let res = EthApi::get_proof(self, address, keys, block_number); | ||
|
|
||
| Ok(res.map_err(|e| match e { | ||
| EthApiError::InvalidBlockRange => internal_rpc_err("unimplemented"), |
There was a problem hiding this comment.
| EthApiError::InvalidBlockRange => internal_rpc_err("unimplemented"), | |
| EthApiError::InvalidBlockRange => internal_rpc_err("eth_getProof is unimplemented for historical blocks"), |
| &self, | ||
| block_id: Option<BlockId>, | ||
| ) -> Result<Option<<Client as StateProviderFactory>::HistorySP<'_>>> { | ||
| ) -> Result<Option<HistoryOrLatest<'_, Client>>> { |
There was a problem hiding this comment.
Why is this change needed? latest_state() did the same before, no?
There was a problem hiding this comment.
Yes, but this way the consumer of the Factory doesn't need to explicitly use LatestSP, which would need an additional check so he knows if the BlockId received references the latest block of the chain or not. This check wasn't being done before, and we were always using the HistorySP by default.
crates/rpc/rpc/src/eth/api/state.rs
Outdated
| EIP1186AccountProofResponse { address, code_hash: KECCAK_EMPTY, ..Default::default() }; | ||
|
|
||
| if let Some(account) = state.basic_account(address)? { | ||
| let (account_proof, storage_hash, stg_proofs) = state.proof(address, keys.clone())?; |
There was a problem hiding this comment.
| let (account_proof, storage_hash, stg_proofs) = state.proof(address, keys.clone())?; | |
| let (account_proof, storage_hash, stg_proofs) = state.proof(address, &keys)?; |
| fn proof( | ||
| &self, | ||
| _address: Address, | ||
| _keys: Vec<H256>, |
There was a problem hiding this comment.
| _keys: Vec<H256>, | |
| _keys: &[H256], |
can't do like this?
There was a problem hiding this comment.
Yes, you're right.
| .ok_or(ProviderError::Header { number: 0 })? | ||
| .1 | ||
| .state_root; | ||
| let (acc_proof, stg_root) = loader |
There was a problem hiding this comment.
let's use full words, storage_proof, account_proof etc.
| fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> { | ||
| let mut cursor = self.tx.cursor_dup_read::<tables::StoragesTrie>()?; | ||
| Ok(cursor.seek_by_key_subkey(self.key, H256::from_slice(key))?.map(|entry| entry.node)) | ||
| } |
| pub fn generate_acount_proof<'tx, 'itx>( | ||
| &self, | ||
| tx: &'tx impl DbTx<'itx>, | ||
| root: H256, |
There was a problem hiding this comment.
This is a weird API due to requiring the root to be passed by the user every time. Can we avoid that somehow?
There was a problem hiding this comment.
Maybe we can make the DBTrieLoader receive the current root on instantiation in a similar way to the database wrappers we use inside the module (i.e. with new(root) and empty() methods).
There was a problem hiding this comment.
Opened #1743. Should be a small change, and a good first issue.
| storage_root: H256, | ||
| address: H256, | ||
| keys: Vec<H256>, | ||
| ) -> Result<Vec<Vec<Vec<u8>>>, TrieError> { |
There was a problem hiding this comment.
Vec<Vec<Vec<_>>> looks weird, can we introduce a new type for this?
There was a problem hiding this comment.
Added a type alias for Vec<Vec<u8>> named MerkleProof. That should make this less weird (now Vec<MerkleProof>).
| #[test] | ||
| fn get_proof() { |
There was a problem hiding this comment.
let's please add a test for something that has multiple storage slots as well
Co-authored-by: lambdaclass-user <github@lambdaclass.com>
Co-authored-by: lambdaclass-user <github@lambdaclass.com>
Co-authored-by: lambdaclass-user <github@lambdaclass.com>
Co-authored-by: lambdaclass-user <github@lambdaclass.com>
Co-authored-by: lambdaclass-user <github@lambdaclass.com>
| // Transparent wrapper to enable state access helpers | ||
| // returning latest state provider when appropiate | ||
| pub(crate) enum SP<'a, H, L> { | ||
| History(H), | ||
| Latest(L), | ||
| _Unreachable(&'a ()), // like a PhantomData for 'a |
There was a problem hiding this comment.
I would call it StateProvider and do use xyz::StateProvider as StateProviderTrait
crates/rpc/rpc/src/eth/api/state.rs
Outdated
| let mut proof = | ||
| EIP1186AccountProofResponse { address, code_hash: KECCAK_EMPTY, ..Default::default() }; |
There was a problem hiding this comment.
Does Geth return an empty proof like this when an account that is not found is requested?
There was a problem hiding this comment.
No. It returns empty storage proofs when the account doesn't exist. I misread the docs 😅
| pub fn generate_acount_proof<'tx, 'itx>( | ||
| &self, | ||
| tx: &'tx impl DbTx<'itx>, | ||
| root: H256, |
Co-authored-by: lambdaclass-user <github@lambdaclass.com>
Co-authored-by: lambdaclass-user <github@lambdaclass.com>
gakonst
left a comment
There was a problem hiding this comment.
OK - seems fine at this point. Let's merge and we can iterate if needed.
Closes: #1507
This PR adds the implementation for the
eth_getProofRPC call, along with additional methods inDBTrieLoaderfor generating Merkle proofs.