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: network-parameterized block responses #1106

Merged
merged 5 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/network-primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]

mod traits;
pub use traits::{ReceiptResponse, TransactionResponse};
pub use traits::{BlockResponse, HeaderResponse, ReceiptResponse, TransactionResponse};

mod block;
pub use block::{BlockTransactionHashes, BlockTransactions, BlockTransactionsKind};
83 changes: 83 additions & 0 deletions crates/network-primitives/src/traits.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use alloy_primitives::{Address, BlockHash, Bytes, TxHash, U256};
use alloy_serde::WithOtherFields;

use crate::BlockTransactions;

/// Receipt JSON-RPC response.
pub trait ReceiptResponse {
/// Address of the created contract, or `None` if the transaction was not a deployment.
Expand Down Expand Up @@ -46,6 +48,44 @@ pub trait TransactionResponse {
fn input(&self) -> &Bytes;
}

/// Header JSON-RPC response.
pub trait HeaderResponse {
/// Hash of the block
fn hash(&self) -> Option<BlockHash>;

/// Block number
fn number(&self) -> Option<u64>;

/// Block timestamp
fn timestamp(&self) -> Option<u64>;
Copy link
Member

Choose a reason for hiding this comment

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

I believe it would be reasonable to expect these to exist?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think we have them as options due to pending blocks missing some of the fields? though looks like we have issues with deserializing them anyway:

$ cast block pending --rpc-url mainnet                             
Error: 
deserialization error: invalid type: null, expected 20 bytes, represented as a hex string of length 40, an array of u8, or raw bytes at line 1 column 3009

Copy link
Member

@mattsse mattsse Aug 21, 2024

Choose a reason for hiding this comment

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

{
  "baseFeePerGas": "0x2afc0987",
  "blobGasUsed": "0x40000",
  "difficulty": "0x0",
  "excessBlobGas": "0x120000",
  "extraData": "0xd883010e05846765746888676f312e32322e34856c696e7578",
  "gasLimit": "0x1c9c380",
  "gasUsed": "0x1c9a5ba",
  "hash": null,
  "logsBloom": "0x8660992a000618a00a004414e3d8d3b4340d45000628214c02085c82cc2067b3c209081200821a40a200a219fca201b04e40a8dc1a1040d95083e16d202030606791431110e02501594c0029d01700a8885090002046421c209e02806f2401a700d9680083c522b21a4f455300814d181d4850010120cc616004f9140f8cb22246a080000029402010092308222810dd85e0704021951c08940c3072c772b06e920080a2654432141a3625e11b81c174025134ca002044200c6128a800852480b8c2ca4200060111481316404101d816c0441882d04008344214900209142044009b2042114812069068119000098502c3049412a0206c9c00e0b402c4985c46",
  "miner": null,
  "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "nonce": null,
  "number": "0x139faac",
  "parentHash": "0x765d646630f533da7c231dd1bc53c4ab616df25641b1c4631377dceb7dda04ff",
  "receiptsRoot": "0x1bdeed67943ae3aa143a69b0a9c0420f94ddfea35dc5066c0c132eb8a16ac444",
  "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
  "size": "0x9d37",
  "stateRoot": "0x4f112179f19f5c3a46039a2bc0443ce695857b0263de48810df6e99849013061",
  "timestamp": "0x66c5da95",
  "totalDifficulty": null,
  "transactions": [
    {

Copy link
Member

@mattsse mattsse Aug 21, 2024

Choose a reason for hiding this comment

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

meh, this complicates things a lot because for pending, there's a ton missing

this was for alchemy

so looks like we can't easily embed the header here anyway...


/// Extra data
fn extra_data(&self) -> &Bytes;

/// Base fee per unit of gas (If EIP-1559 is supported)
fn base_fee_per_gas(&self) -> Option<u128>;

/// Blob fee for the next block (if EIP-4844 is supported)
fn next_block_blob_fee(&self) -> Option<u128>;
Comment on lines +62 to +66
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 good start, we can add more functions based on the spec:

https://ethereum.github.io/execution-apis/api-documentation/

}

/// Block JSON-RPC response.
pub trait BlockResponse {
/// Header type
type Header;
/// Transaction type
type Transaction;

/// Block header
fn header(&self) -> &Self::Header;

/// Block transactions
fn transactions(&self) -> &BlockTransactions<Self::Transaction>;

/// Mutable reference to block transactions
fn transactions_mut(&mut self) -> &mut BlockTransactions<Self::Transaction>;
}

impl<T: TransactionResponse> TransactionResponse for WithOtherFields<T> {
fn tx_hash(&self) -> TxHash {
self.inner.tx_hash()
Expand Down Expand Up @@ -89,3 +129,46 @@ impl<T: ReceiptResponse> ReceiptResponse for WithOtherFields<T> {
self.inner.block_number()
}
}

impl<T: BlockResponse> BlockResponse for WithOtherFields<T> {
Copy link
Member

Choose a reason for hiding this comment

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

can we do the same for HeaderResponse, in case the header is WithOtherFields

type Header = T::Header;
type Transaction = T::Transaction;

fn header(&self) -> &Self::Header {
self.inner.header()
}

fn transactions(&self) -> &BlockTransactions<Self::Transaction> {
self.inner.transactions()
}

fn transactions_mut(&mut self) -> &mut BlockTransactions<Self::Transaction> {
self.inner.transactions_mut()
}
}

impl<T: HeaderResponse> HeaderResponse for WithOtherFields<T> {
fn hash(&self) -> Option<BlockHash> {
self.inner.hash()
}

fn number(&self) -> Option<u64> {
self.inner.number()
}

fn timestamp(&self) -> Option<u64> {
self.inner.timestamp()
}

fn extra_data(&self) -> &Bytes {
self.inner.extra_data()
}

fn base_fee_per_gas(&self) -> Option<u128> {
self.inner.base_fee_per_gas()
}

fn next_block_blob_fee(&self) -> Option<u128> {
self.inner.next_block_blob_fee()
}
}
6 changes: 4 additions & 2 deletions crates/network/src/any/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::Network;
use alloy_consensus::TxType;
use alloy_eips::eip2718::Eip2718Error;
use alloy_rpc_types_eth::{AnyTransactionReceipt, Header, Transaction, TransactionRequest};
use alloy_rpc_types_eth::{AnyTransactionReceipt, Block, Header, Transaction, TransactionRequest};
use alloy_serde::WithOtherFields;
use core::fmt;

Expand Down Expand Up @@ -73,5 +73,7 @@ impl Network for AnyNetwork {

type ReceiptResponse = AnyTransactionReceipt;

type HeaderResponse = WithOtherFields<Header>;
type HeaderResponse = Header;
Copy link
Member

Choose a reason for hiding this comment

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

should this remain WithOtherFields<Header> ?

Copy link
Member Author

@klkvr klkvr Aug 21, 2024

Choose a reason for hiding this comment

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

Header is getting flattened into block in RPC, so if block has additional fields those will get consumed by header's others. There's no concept of body/header separation in RPC, so we can't really tell which field belongs to a header or to a body of the block

Copy link
Member

Choose a reason for hiding this comment

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

right, that makes sense


type BlockResponse = WithOtherFields<Block<Self::TransactionResponse>>;
}
2 changes: 2 additions & 0 deletions crates/network/src/ethereum/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,6 @@ impl Network for Ethereum {
type ReceiptResponse = alloy_rpc_types_eth::TransactionReceipt;

type HeaderResponse = alloy_rpc_types_eth::Header;

type BlockResponse = alloy_rpc_types_eth::Block;
}
7 changes: 6 additions & 1 deletion crates/network/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use alloy_consensus::TxReceipt;
use alloy_eips::eip2718::{Eip2718Envelope, Eip2718Error};
use alloy_json_rpc::RpcObject;
use alloy_network_primitives::{BlockResponse, HeaderResponse};
use core::fmt::{Debug, Display};

mod transaction;
Expand Down Expand Up @@ -85,5 +86,9 @@ pub trait Network: Debug + Clone + Copy + Sized + Send + Sync + 'static {
type ReceiptResponse: RpcObject + ReceiptResponse;

/// The JSON body of a header response.
type HeaderResponse: RpcObject;
type HeaderResponse: RpcObject + HeaderResponse;

/// The JSON body of a block response.
type BlockResponse: RpcObject
+ BlockResponse<Transaction = Self::TransactionResponse, Header = Self::HeaderResponse>;
}
3 changes: 2 additions & 1 deletion crates/provider/src/fillers/gas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::{
};
use alloy_json_rpc::RpcError;
use alloy_network::{Network, TransactionBuilder};
use alloy_network_primitives::{BlockResponse, HeaderResponse};
use alloy_rpc_types_eth::BlockNumberOrTag;
use alloy_transport::{Transport, TransportResult};
use futures::FutureExt;
Expand Down Expand Up @@ -150,7 +151,7 @@ impl GasFiller {
.get_block_by_number(BlockNumberOrTag::Latest, false)
.await?
.ok_or(RpcError::NullResp)?
.header
.header()
.next_block_blob_fee()
.ok_or(RpcError::UnsupportedFeature("eip4844"))
}
Expand Down
34 changes: 19 additions & 15 deletions crates/provider/src/provider/trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@ use crate::{
use alloy_eips::eip2718::Encodable2718;
use alloy_json_rpc::{RpcError, RpcParam, RpcReturn};
use alloy_network::{Ethereum, Network};
use alloy_network_primitives::{BlockTransactionsKind, ReceiptResponse};
use alloy_network_primitives::{
BlockResponse, BlockTransactionsKind, HeaderResponse, ReceiptResponse,
};
use alloy_primitives::{
hex, Address, BlockHash, BlockNumber, Bytes, StorageKey, StorageValue, TxHash, B256, U128,
U256, U64,
};
use alloy_rpc_client::{ClientRef, PollerBuilder, RpcCall, WeakClient};
use alloy_rpc_types_eth::{
AccessListResult, Block, BlockId, BlockNumberOrTag, EIP1186AccountProofResponse, FeeHistory,
Filter, FilterChanges, Log, SyncStatus,
AccessListResult, BlockId, BlockNumberOrTag, EIP1186AccountProofResponse, FeeHistory, Filter,
FilterChanges, Log, SyncStatus,
};
use alloy_transport::{BoxTransport, Transport, TransportResult};
use serde_json::value::RawValue;
Expand Down Expand Up @@ -213,8 +215,8 @@ pub trait Provider<T: Transport + Clone = BoxTransport, N: Network = Ethereum>:
self.get_block_by_number(BlockNumberOrTag::Latest, false)
.await?
.ok_or(RpcError::NullResp)?
.header
.base_fee_per_gas
.header()
.base_fee_per_gas()
.ok_or(RpcError::UnsupportedFeature("eip1559"))?
}
};
Expand Down Expand Up @@ -262,7 +264,7 @@ pub trait Provider<T: Transport + Clone = BoxTransport, N: Network = Ethereum>:
&self,
block: BlockId,
kind: BlockTransactionsKind,
) -> TransportResult<Option<Block>> {
) -> TransportResult<Option<N::BlockResponse>> {
match block {
BlockId::Hash(hash) => self.get_block_by_hash(hash.into(), kind).await,
BlockId::Number(number) => {
Expand All @@ -277,21 +279,21 @@ pub trait Provider<T: Transport + Clone = BoxTransport, N: Network = Ethereum>:
&self,
hash: BlockHash,
kind: BlockTransactionsKind,
) -> TransportResult<Option<Block>> {
) -> TransportResult<Option<N::BlockResponse>> {
let full = match kind {
BlockTransactionsKind::Full => true,
BlockTransactionsKind::Hashes => false,
};

let block = self
.client()
.request::<_, Option<Block>>("eth_getBlockByHash", (hash, full))
.request::<_, Option<N::BlockResponse>>("eth_getBlockByHash", (hash, full))
.await?
.map(|mut block| {
if !full {
// this ensures an empty response for `Hashes` has the expected form
// this is required because deserializing [] is ambiguous
block.transactions.convert_to_hashes();
block.transactions_mut().convert_to_hashes();
}
block
});
Expand All @@ -305,16 +307,16 @@ pub trait Provider<T: Transport + Clone = BoxTransport, N: Network = Ethereum>:
&self,
number: BlockNumberOrTag,
hydrate: bool,
) -> TransportResult<Option<Block>> {
) -> TransportResult<Option<N::BlockResponse>> {
let block = self
.client()
.request::<_, Option<Block>>("eth_getBlockByNumber", (number, hydrate))
.request::<_, Option<N::BlockResponse>>("eth_getBlockByNumber", (number, hydrate))
.await?
.map(|mut block| {
if !hydrate {
// this ensures an empty response for `Hashes` has the expected form
// this is required because deserializing [] is ambiguous
block.transactions.convert_to_hashes();
block.transactions_mut().convert_to_hashes();
}
block
});
Expand Down Expand Up @@ -548,7 +550,7 @@ pub trait Provider<T: Transport + Clone = BoxTransport, N: Network = Ethereum>:
}

/// Gets an uncle block through the tag [BlockId] and index [u64].
async fn get_uncle(&self, tag: BlockId, idx: u64) -> TransportResult<Option<Block>> {
async fn get_uncle(&self, tag: BlockId, idx: u64) -> TransportResult<Option<N::BlockResponse>> {
let idx = U64::from(idx);
match tag {
BlockId::Hash(hash) => {
Expand Down Expand Up @@ -725,7 +727,9 @@ pub trait Provider<T: Transport + Clone = BoxTransport, N: Network = Ethereum>:
/// # }
/// ```
#[cfg(feature = "pubsub")]
async fn subscribe_blocks(&self) -> TransportResult<alloy_pubsub::Subscription<Block>> {
async fn subscribe_blocks(
&self,
) -> TransportResult<alloy_pubsub::Subscription<N::BlockResponse>> {
self.root().pubsub_frontend()?;
let id = self.client().request("eth_subscribe", ("newHeads",)).await?;
self.root().get_subscription(id).await
Expand Down Expand Up @@ -1007,7 +1011,7 @@ mod tests {
use alloy_network::AnyNetwork;
use alloy_node_bindings::Anvil;
use alloy_primitives::{address, b256, bytes, keccak256};
use alloy_rpc_types_eth::request::TransactionRequest;
use alloy_rpc_types_eth::{request::TransactionRequest, Block};

fn init_tracing() {
let _ = tracing_subscriber::fmt::try_init();
Expand Down
45 changes: 44 additions & 1 deletion crates/rpc-types-eth/src/block.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Block RPC types.

use crate::{ConversionError, Transaction, Withdrawal};
use alloy_network_primitives::BlockTransactions;
use alloy_network_primitives::{BlockResponse, BlockTransactions, HeaderResponse};
use alloy_primitives::{Address, BlockHash, Bloom, Bytes, B256, B64, U256};
use serde::{ser::Error, Deserialize, Serialize, Serializer};
use std::{collections::BTreeMap, ops::Deref};
Expand Down Expand Up @@ -201,6 +201,32 @@ impl TryFrom<Header> for alloy_consensus::Header {
}
}

impl HeaderResponse for Header {
fn hash(&self) -> Option<BlockHash> {
self.hash
}

fn number(&self) -> Option<u64> {
self.number
}

fn timestamp(&self) -> Option<u64> {
Some(self.timestamp)
}

fn extra_data(&self) -> &Bytes {
&self.extra_data
}

fn base_fee_per_gas(&self) -> Option<u128> {
self.base_fee_per_gas
}

fn next_block_blob_fee(&self) -> Option<u128> {
self.next_block_blob_fee()
}
}

/// Error that can occur when converting other types to blocks
#[derive(Clone, Copy, Debug, thiserror::Error)]
pub enum BlockError {
Expand Down Expand Up @@ -312,6 +338,23 @@ pub struct BlockOverrides {
pub block_hash: Option<BTreeMap<u64, B256>>,
}

impl<T> BlockResponse for Block<T> {
type Transaction = T;
type Header = Header;

fn header(&self) -> &Self::Header {
&self.header
}

fn transactions(&self) -> &BlockTransactions<T> {
&self.transactions
}

fn transactions_mut(&mut self) -> &mut BlockTransactions<Self::Transaction> {
&mut self.transactions
}
}

#[cfg(test)]
mod tests {
use alloy_primitives::keccak256;
Expand Down
Loading