WEB3-644: WEB3-560: feat: Helpers for queries over multiple blocks of the same chain - #46
WEB3-644: WEB3-560: feat: Helpers for queries over multiple blocks of the same chain#46Wollac wants to merge 52 commits into
Conversation
nategraf
left a comment
There was a problem hiding this comment.
I think this looks pretty good, and a definite improvement over the version I put together. I think this will be quite nice for consumers, and should avoid them needing to use SteelVerifier directly. The token-stats example looks quite nice with this.
| .call(); | ||
| let envs = input.into_env(Ð_MAINNET_CHAIN_SPEC); | ||
|
|
||
| // Execute the view calls on each EVM state. |
There was a problem hiding this comment.
As written, the host can provide any number of rates, with any interval, and the guest will report the average. I'm not exactly sure what the right way to handle this is, but I do think we should show some constraint or reporting of the time frame here, as this may be a common pitfall for people using the multiblock features.
One simple and accurate idea would be to report all block numbers that were considered as a list in the journal. Its a bit inelegant though.
There was a problem hiding this comment.
Fair enough. I agree, but that's exactly how the example was before... I'm not sure how to improve this example though without distracting from its core.
There was a problem hiding this comment.
I have changed the example so that the environments must be exactly 7,200 blocks (24 hours) apart, and it commits to the number of such intervals.
| /// While this pattern requires `#[allow(private_bounds)]` on a public method that use it, | ||
| /// the benefit of improved code structure and maintainability is a worthwhile trade-off | ||
| /// for this internal abstraction. | ||
| pub(super) trait InputBuilder<D, F: EvmFactory> { |
There was a problem hiding this comment.
Its worth considering making this public. Without it being public, a user may have significant difficulty in implementing some kinds of generic functionality in their code. E.g. it would be difficult to have a single function that works with two kinds of commitments, to switch between using Anvil and live chains like Eth Mainnet and OP.
There was a problem hiding this comment.
I have made the InputBuilder trait public and moved it to host/builder.rs
| // ignore the builder and use the available env with block commitment directly | ||
| env.into_input().await |
There was a problem hiding this comment.
This does seem odd. It seems your intention is essentially to take the EVM data (e.g. storage and event inclusion proofs) and combine it with the commitment provided by self. However, both in this case and the other implementations, self and env both contain the EVM data and the commitment. This on in particular is a bit awkward in that any EVM data on self will be dropped, but that feels more like a symptom than the problem itself.
Would it be reasonable to drop the env arg here? It seems like it would require an alterred usage pattern and I can imagine how the type propagation for the commit might be a bit painful.
There was a problem hiding this comment.
It seems your intention is essentially to take the EVM data (e.g. storage and event inclusion proofs) and combine it with the commitment provided by self.
Exactly. Use the builder self to attach the correct commitment to the evm data, and also convert it into a type EvmImput, that does not depend on an generics.
However, both in this case and the other implementations, self and env both contain the EVM data and the commitment.
No, self is a builder, so by definition it does not contain any EVM data but just config and env is a HostEvmEnv<D, F, ()>, while the final () actually assures that it does not contain any specific commitment.
I agree that the entire InputBuilder design is not ideal. However, it attaches different commitments to EVM data and tries to minimize code duplication, which is exactly what it is supposed to do.
There was a problem hiding this comment.
I thought about this design quite a bit and I still believe it is the cleanest way to create a generic commitment for an HostEvmEnv<D, F, ()> which is exactly what the Multiblock stuff needs.
To make it more clear, I have added corresponding documentation to the InputBuilder trait.
| /// Creates a new [HostMultiblockEvmEnv] from the given [EvmEnvBuilder]. | ||
| /// | ||
| /// This ignores any potential EVM execution block set in the builder, but all other options | ||
| /// specified with this builder are incorporated when creating the individual [EvmEnv]. | ||
| pub fn from_builder(builder: EvmEnvBuilder<P, F, &'a ChainSpec<F::SpecId>, B>) -> Self { | ||
| Self { | ||
| builder, | ||
| env: MultiblockEvmEnv(BTreeMap::new()), | ||
| } | ||
| } |
There was a problem hiding this comment.
When I implemented it this way, it felt like a bit of a hack. My main motivation for doing so was to avoid duplicating all the methods on the EvmEnvBuilder struct here. It may be better to do that though now that we are looking to make this a stable API. I think the required tweaks to the API could result in a cleaner factoring, as the builder cloning is kind of a the root of some of awkwardness I believe.
There was a problem hiding this comment.
Honestly, I like this design because we get the staged builder pattern from the builder for free. Of course, we can make HostMultiblockEvmEnv the builder, but then we'd have to still double all these different config steps as well as the different commitment types. Ii would probably be worth it if this would allows us to get rid of the InputBuilder trait but I dont see that.
There was a problem hiding this comment.
In order to make the HostMultiblockEvmEnv a sufficient builder, we would need to add all the following
provider: P,
provider_config: ProviderConfig,
block: BlockId,
chain_spec: S,
commitment_config: B,
phantom: PhantomData<F>,and also implement staged builder pattern for this. This would lead to big code duplication.
I also think that the pattern
let builder = EthEvmEnv::builder().provider(p).chain_spec(Ð_MAINNET_CHAIN_SPEC);
let mut envs = HostMultiblockEvmEnv::from_builder(builder);is closer to our existing pattern of how to build a HostEvmEnv. Thus, I have kept this design.
| /// Returns a copy of the builder with elided commitment config and set EVM execution block. | ||
| pub(crate) fn to_block(&self, block: impl Into<BlockId>) -> EvmEnvBuilder<P, F, S, ()> | ||
| where | ||
| P: Clone, | ||
| S: Clone, | ||
| { | ||
| EvmEnvBuilder { | ||
| provider: self.provider.clone(), | ||
| provider_config: self.provider_config.clone(), | ||
| block: block.into(), | ||
| chain_spec: self.chain_spec.clone(), | ||
| commitment_config: (), | ||
| phantom: PhantomData, | ||
| } | ||
| } |
There was a problem hiding this comment.
Looking at the callsite for this function in multiblock, it was not obvious to me that this clones rather than consuming self like many of the other methods do. I might suggest making this consume self, and adding an explicit clone in front of the calls.
There was a problem hiding this comment.
According to the Rust naming conventions to_ is borrowed -> owned for non-Copy types, like Path::to_str or str::to_lowercase().
So the alternative would be to rename this to an into_ and combine it will an explicit clone, I'll check whether that might help readability, even though it will always be combined with a clone.
Co-authored-by: Victor Snyder-Graf <victor@risczero.com>
# Conflicts: # CHANGELOG.md
# Conflicts: # crates/steel/src/lib.rs
- warn instead of silently building an input with no recorded state access; the explicit guard was removed because multiblock legitimately builds commitment-only envs and the parent-hash fast path records none - document why the EIP-2935 spacing check inspects current_env (storage is not backfilled at activation, so a pre-fork hash is unretrievable) - drop the dead .context() on the infallible build_input! merge so both merge sites use a bare ? - token-stats: fix stale README output, use .expect for the guest rate conversion, and consolidate the 7200 block interval into a shared BLOCK_INTERVAL const
Alchemy currently fails all eth_getProof requests on base-mainnet, breaking the preflight of the op examples in CI. OP Mainnet (as well as both Sepolia networks) serve proofs fine, so move the l2 and l2-to-l1 examples to OP Mainnet. This keeps L1 on Ethereum Mainnet, so the shared L1_RPC_URL and the l1-to-l2 example are unaffected.
- reject the zero value returned by the EIP-2935 history storage and EIP-4788 beacon roots contracts for ring buffer slots that were never written; block commitments with an unset slot fall back to BLOCKHASH verification within the 256-block window - tighten EvmEnv::merge back to a single commitment type; the cross-type state merge needed by multiblock is now pub(crate) merge_state - reject blocks at or after an explicit commitment target in get_or_build before any RPC, and warn when build_multi ignores a configured block - document the beacon chain-head and commitment-target constraints; clarify the beacon "no child block" error for the multiblock case - downgrade the forgotten-preflight host error to a warning and make the guest MPT panic self-diagnosing; restore the no_preflight test - unify commitment version rendering via Commitment::version_name - token-stats: require >= 2 samples and rename the journal field to numDays to avoid the Solidity reserved keyword
No description provided.