Skip to content

feat(rpc): add partial eth_getProof implementation#1515

Merged
gakonst merged 25 commits intoparadigmxyz:mainfrom
lambdaclass:rpc-eth-getproof
Mar 14, 2023
Merged

feat(rpc): add partial eth_getProof implementation#1515
gakonst merged 25 commits intoparadigmxyz:mainfrom
lambdaclass:rpc-eth-getproof

Conversation

@MegaRedHand
Copy link
Contributor

Closes: #1507

This PR adds the implementation for the eth_getProof RPC call, along with additional methods in DBTrieLoader for generating Merkle proofs.

MegaRedHand and others added 5 commits February 22, 2023 15:13
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-commenter
Copy link

codecov-commenter commented Feb 22, 2023

Codecov Report

Merging #1515 (5c63be8) into main (043098a) will decrease coverage by 30.82%.
The diff coverage is 4.04%.

📣 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     
Flag Coverage Δ
integration-tests 16.88% <0.00%> (-3.48%) ⬇️
unit-tests 35.49% <4.04%> (-32.24%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
crates/executor/src/executor.rs 65.53% <0.00%> (-28.44%) ⬇️
crates/interfaces/src/provider.rs 0.00% <ø> (ø)
crates/primitives/src/account.rs 12.50% <0.00%> (-75.38%) ⬇️
crates/rpc/rpc/src/eth/api/call.rs 0.00% <0.00%> (ø)
crates/rpc/rpc/src/eth/api/mod.rs 17.77% <0.00%> (-46.60%) ⬇️
crates/rpc/rpc/src/eth/api/server.rs 0.00% <0.00%> (-95.43%) ⬇️
crates/rpc/rpc/src/eth/api/state.rs 0.00% <0.00%> (-100.00%) ⬇️
crates/storage/provider/src/lib.rs 100.00% <ø> (ø)
...storage/provider/src/providers/state/historical.rs 0.00% <0.00%> (-92.00%) ⬇️
...tes/storage/provider/src/providers/state/latest.rs 5.17% <0.00%> (-52.90%) ⬇️
... and 7 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>
@MegaRedHand
Copy link
Contributor Author

@mattsse
I'm having a bit of trouble with this. As it stands, the HistoricalStateProvider would need to unwind the hashed account and storage tables to the past state before updating the trie and generating the proof. All of this would be really expensive and needs to be done while leaving the current tables untouched. I was thinking of just doing this in the transaction without committing (so changes are dropped), but this wouldn't work as it is read-only. Do you know of something that could be used for this?

On another note, the LatestStateProvider doesn't seem to be used aside from on tests.

@gakonst
Copy link
Member

gakonst commented Feb 22, 2023

cc @joshieDo given you've been looking at Merkle stage and patricia tree in general

@joshieDo
Copy link
Collaborator

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

@rakita
Copy link
Collaborator

rakita commented Feb 23, 2023

There could be only one write tx, this could block other write transactions.

MegaRedHand and others added 3 commits February 23, 2023 15:15
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>
@MegaRedHand
Copy link
Contributor Author

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.

My impression is that we need to change the way we insert into the trie - and use a cursor instead

Because of limitations with cita_trie and the need for cursor mutability, this would require a Mutex<RefCell<_>> inside the database wrapper. I can add this if it seems like a good enough tradeoff, but maybe we should first add the stage benchmarks and check it doesn't drop performance.

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

That's right for the execute part, but the unwind doesn't commit changes. Also, the transaction given to the provider is read-only so we can't make changes to the DB and we can't make this RW because of what rakita said.

@MegaRedHand MegaRedHand marked this pull request as ready for review February 23, 2023 20:49
Co-authored-by: lambdaclass-user <github@lambdaclass.com>
@joshieDo
Copy link
Collaborator

blocked by #1497

@MegaRedHand MegaRedHand changed the title feat(rpc): add eth_getProof implementation feat(rpc): add partial eth_getProof implementation Feb 24, 2023
MegaRedHand and others added 2 commits February 24, 2023 17:11
Also, fix: was hashing received and already hashed address

Co-authored-by: lambdaclass-user <github@lambdaclass.com>
@onbjerg onbjerg added C-enhancement New feature or request A-rpc Related to the RPC implementation labels Feb 27, 2023
MegaRedHand and others added 2 commits March 1, 2023 10:24
Co-authored-by: lambdaclass-user <github@lambdaclass.com>
@gakonst
Copy link
Member

gakonst commented Mar 1, 2023

Needs merge conflict post #1474, sorry about that.

@MegaRedHand
Copy link
Contributor Author

Fixed!

Copy link
Member

@gakonst gakonst left a comment

Choose a reason for hiding this comment

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

good work - mainly nits

Comment on lines +100 to +105
// 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
Copy link
Member

Choose a reason for hiding this comment

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

Let's please avoid really short names like this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. I was having a bit of trouble coming up with a fitting name. I was thinking of StateProviderType, or maybe GenericSP. wdyt?

Copy link
Member

Choose a reason for hiding this comment

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

I would call it StateProvider and do use xyz::StateProvider as StateProviderTrait

let res = EthApi::get_proof(self, address, keys, block_number);

Ok(res.map_err(|e| match e {
EthApiError::InvalidBlockRange => internal_rpc_err("unimplemented"),
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
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>>> {
Copy link
Member

Choose a reason for hiding this comment

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

Why is this change needed? latest_state() did the same before, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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.

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())?;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
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>,
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
_keys: Vec<H256>,
_keys: &[H256],

can't do like this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, you're right.

.ok_or(ProviderError::Header { number: 0 })?
.1
.state_root;
let (acc_proof, stg_root) = loader
Copy link
Member

Choose a reason for hiding this comment

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

let's use full words, storage_proof, account_proof etc.

Comment on lines +261 to +264
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))
}
Copy link
Member

Choose a reason for hiding this comment

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

cc @joshieDo @onbjerg

the reason why I don't like the Cita Trie lib is because it doesn't let us reuse our cursors when we're walking thru the entire table, feels a bit gross to re-instantiate a cursor on every get

pub fn generate_acount_proof<'tx, 'itx>(
&self,
tx: &'tx impl DbTx<'itx>,
root: H256,
Copy link
Member

Choose a reason for hiding this comment

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

This is a weird API due to requiring the root to be passed by the user every time. Can we avoid that somehow?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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).

Copy link
Member

Choose a reason for hiding this comment

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

Yeah I like that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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> {
Copy link
Member

Choose a reason for hiding this comment

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

Vec<Vec<Vec<_>>> looks weird, can we introduce a new type for this?

Copy link
Contributor Author

@MegaRedHand MegaRedHand Mar 10, 2023

Choose a reason for hiding this comment

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

Added a type alias for Vec<Vec<u8>> named MerkleProof. That should make this less weird (now Vec<MerkleProof>).

Comment on lines +823 to +824
#[test]
fn get_proof() {
Copy link
Member

Choose a reason for hiding this comment

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

let's please add a test for something that has multiple storage slots as well

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added

MegaRedHand and others added 6 commits March 10, 2023 11:05
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>
Copy link
Member

@gakonst gakonst left a comment

Choose a reason for hiding this comment

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

Good from me - would like @joshieDo to approve before merging, cc @mattsse given proximity to RPC-related code

Comment on lines +100 to +105
// 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
Copy link
Member

Choose a reason for hiding this comment

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

I would call it StateProvider and do use xyz::StateProvider as StateProviderTrait

Comment on lines +70 to +71
let mut proof =
EIP1186AccountProofResponse { address, code_hash: KECCAK_EMPTY, ..Default::default() };
Copy link
Member

Choose a reason for hiding this comment

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

Does Geth return an empty proof like this when an account that is not found is requested?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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,
Copy link
Member

Choose a reason for hiding this comment

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

Yeah I like that.

MegaRedHand and others added 3 commits March 13, 2023 10:25
Co-authored-by: lambdaclass-user <github@lambdaclass.com>
Co-authored-by: lambdaclass-user <github@lambdaclass.com>
Copy link
Member

@gakonst gakonst left a comment

Choose a reason for hiding this comment

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

OK - seems fine at this point. Let's merge and we can iterate if needed.

@gakonst gakonst merged commit 54aab53 into paradigmxyz:main Mar 14, 2023
@gakonst gakonst deleted the rpc-eth-getproof branch March 14, 2023 00:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-rpc Related to the RPC implementation C-enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add eth_getProof impl

6 participants