Skip to content

Commit

Permalink
feat: support for additional fields on TransactionReceipt and Block (#66
Browse files Browse the repository at this point in the history
)
  • Loading branch information
Evalir authored Dec 11, 2023
1 parent e359e27 commit 46bf17a
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 6 deletions.
15 changes: 10 additions & 5 deletions crates/rpc-types/src/eth/block.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! Contains types that represent ethereum types when used in RPC
use crate::{Transaction, Withdrawal};
use crate::{other::OtherFields, Transaction, Withdrawal};
use alloy_primitives::{
ruint::ParseError, Address, BlockHash, BlockNumber, Bloom, Bytes, B256, B64, U256, U64,
};
Expand Down Expand Up @@ -117,24 +117,27 @@ pub enum BlockError {
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Block {
/// Header of the block
/// Header of the block.
#[serde(flatten)]
pub header: Header,
/// Total difficulty, this field is None only if representing
/// an Uncle block.
#[serde(skip_serializing_if = "Option::is_none")]
pub total_difficulty: Option<U256>,
/// Uncles' hashes
/// Uncles' hashes.
pub uncles: Vec<B256>,
/// Transactions
/// Transactions.
#[serde(skip_serializing_if = "BlockTransactions::is_uncle")]
#[serde(default = "BlockTransactions::uncle")]
pub transactions: BlockTransactions,
/// Integer the size of this block in bytes.
pub size: Option<U256>,
/// Withdrawals in the block
/// Withdrawals in the block.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub withdrawals: Option<Vec<Withdrawal>>,
/// Support for arbitrary additional fields.
#[serde(flatten)]
pub other: OtherFields,
}

impl Block {
Expand Down Expand Up @@ -861,6 +864,7 @@ mod tests {
transactions: BlockTransactions::Hashes(vec![B256::with_last_byte(18)]),
size: Some(U256::from(19)),
withdrawals: Some(vec![]),
other: Default::default(),
};
let serialized = serde_json::to_string(&block).unwrap();
assert_eq!(
Expand Down Expand Up @@ -902,6 +906,7 @@ mod tests {
transactions: BlockTransactions::Hashes(vec![B256::with_last_byte(18)]),
size: Some(U256::from(19)),
withdrawals: None,
other: Default::default(),
};
let serialized = serde_json::to_string(&block).unwrap();
assert_eq!(
Expand Down
1 change: 1 addition & 0 deletions crates/rpc-types/src/eth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod call;
mod fee;
mod filter;
mod log;
pub mod other;
pub mod pubsub;
pub mod raw_log;
pub mod state;
Expand Down
117 changes: 117 additions & 0 deletions crates/rpc-types/src/eth/other.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
//! Support for capturing other fields
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_json::Map;
use std::{
collections::BTreeMap,
ops::{Deref, DerefMut},
};

/// A type that is supposed to capture additional fields that are not native to ethereum but included in ethereum adjacent networks, for example fields the [optimism `eth_getTransactionByHash` request](https://docs.alchemy.com/alchemy/apis/optimism/eth-gettransactionbyhash) returns additional fields that this type will capture
///
/// This type is supposed to be used with [`#[serde(flatten)`](https://serde.rs/field-attrs.html#flatten)
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Default)]
#[serde(transparent)]
pub struct OtherFields {
/// Contains all unknown fields
inner: BTreeMap<String, serde_json::Value>,
}

// === impl OtherFields ===

impl OtherFields {
/// Returns the deserialized value of the field, if it exists.
/// Deserializes the value with the given closure
pub fn get_with<F, V>(&self, key: impl AsRef<str>, with: F) -> Option<V>
where
F: FnOnce(serde_json::Value) -> V,
{
self.inner.get(key.as_ref()).cloned().map(with)
}

/// Returns the deserialized value of the field, if it exists
pub fn get_deserialized<V: DeserializeOwned>(
&self,
key: impl AsRef<str>,
) -> Option<serde_json::Result<V>> {
self.inner.get(key.as_ref()).cloned().map(serde_json::from_value)
}

/// Removes the deserialized value of the field, if it exists
///
/// **Note:** this will also remove the value if deserializing it resulted in an error
pub fn remove_deserialized<V: DeserializeOwned>(
&mut self,
key: impl AsRef<str>,
) -> Option<serde_json::Result<V>> {
self.inner.remove(key.as_ref()).map(serde_json::from_value)
}

/// Removes the deserialized value of the field, if it exists.
/// Deserializes the value with the given closure
///
/// **Note:** this will also remove the value if deserializing it resulted in an error
pub fn remove_with<F, V>(&mut self, key: impl AsRef<str>, with: F) -> Option<V>
where
F: FnOnce(serde_json::Value) -> V,
{
self.inner.remove(key.as_ref()).map(with)
}

/// Removes the deserialized value of the field, if it exists and also returns the key
///
/// **Note:** this will also remove the value if deserializing it resulted in an error
pub fn remove_entry_deserialized<V: DeserializeOwned>(
&mut self,
key: impl AsRef<str>,
) -> Option<(String, serde_json::Result<V>)> {
self.inner
.remove_entry(key.as_ref())
.map(|(key, value)| (key, serde_json::from_value(value)))
}

/// Deserialized this type into another container type
pub fn deserialize_into<T: DeserializeOwned>(self) -> serde_json::Result<T> {
let mut map = Map::with_capacity(self.inner.len());
map.extend(self);
serde_json::from_value(serde_json::Value::Object(map))
}
}

impl Deref for OtherFields {
type Target = BTreeMap<String, serde_json::Value>;

#[inline]
fn deref(&self) -> &BTreeMap<String, serde_json::Value> {
self.as_ref()
}
}

impl DerefMut for OtherFields {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}

impl AsRef<BTreeMap<String, serde_json::Value>> for OtherFields {
fn as_ref(&self) -> &BTreeMap<String, serde_json::Value> {
&self.inner
}
}

impl IntoIterator for OtherFields {
type Item = (String, serde_json::Value);
type IntoIter = std::collections::btree_map::IntoIter<String, serde_json::Value>;

fn into_iter(self) -> Self::IntoIter {
self.inner.into_iter()
}
}

impl<'a> IntoIterator for &'a OtherFields {
type Item = (&'a String, &'a serde_json::Value);
type IntoIter = std::collections::btree_map::Iter<'a, String, serde_json::Value>;

fn into_iter(self) -> Self::IntoIter {
self.as_ref().iter()
}
}
5 changes: 4 additions & 1 deletion crates/rpc-types/src/eth/transaction/receipt.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::Log;
use crate::{other::OtherFields, Log};
use alloy_primitives::{Address, Bloom, B256, U128, U256, U64, U8};
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -51,4 +51,7 @@ pub struct TransactionReceipt {
/// EIP-2718 Transaction type, Some(1) for AccessList transaction, None for Legacy
#[serde(rename = "type")]
pub transaction_type: U8,
/// Support for arbitrary additional fields.
#[serde(flatten)]
pub other: OtherFields,
}

0 comments on commit 46bf17a

Please sign in to comment.