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: support for additional fields on TransactionReceipt and Block #66

Merged
merged 1 commit into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
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,
}