Breaking changes (#5191)

Co-authored-by: Bjerg <onbjerg@users.noreply.github.com>
Co-authored-by: Roman Krasiuk <rokrassyuk@gmail.com>
Co-authored-by: joshieDo <ranriver@protonmail.com>
Co-authored-by: joshieDo <93316087+joshieDo@users.noreply.github.com>
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
Co-authored-by: Oliver Nordbjerg <hi@notbjerg.me>
Co-authored-by: Thomas Coratger <thomas.coratger@gmail.com>
This commit is contained in:
Alexey Shekhirin
2024-02-29 12:37:28 +00:00
committed by GitHub
parent 025fa5f038
commit 6b5b6f7a40
252 changed files with 10154 additions and 6327 deletions

View File

@ -41,7 +41,7 @@ tracing.workspace = true
bytes.workspace = true
byteorder = "1"
clap = { workspace = true, features = ["derive"], optional = true }
derive_more = "0.99"
derive_more.workspace = true
itertools.workspace = true
modular-bitfield = "0.11.2"
num_enum = "0.7"
@ -50,10 +50,10 @@ rayon.workspace = true
serde.workspace = true
serde_json.workspace = true
sha2 = "0.10.7"
sucds = "~0.6"
tempfile.workspace = true
thiserror.workspace = true
zstd = { version = "0.12", features = ["experimental"] }
roaring = "0.10.2"
cfg-if = "1.0.0"
# `test-utils` feature
@ -83,6 +83,9 @@ triehash = "0.8"
hash-db = "~0.15"
plain_hasher = "0.2"
sucds = "0.8.1"
anyhow = "1.0.75"
# necessary so we don't hit a "undeclared 'std'":
# https://github.com/paradigmxyz/reth/pull/177#discussion_r1021172198
criterion.workspace = true
@ -126,3 +129,7 @@ harness = false
name = "trie_root"
required-features = ["arbitrary", "test-utils"]
harness = false
[[bench]]
name = "integer_list"
harness = false

View File

@ -0,0 +1,250 @@
#![allow(missing_docs)]
use criterion::{black_box, criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion};
use rand::prelude::*;
pub fn new_pre_sorted(c: &mut Criterion) {
let mut group = c.benchmark_group("new_pre_sorted");
for delta in [1, 100, 1000, 10000] {
let integers_usize = generate_integers(2000, delta);
assert_eq!(integers_usize.len(), 2000);
let integers_u64 = integers_usize.iter().map(|v| *v as u64).collect::<Vec<_>>();
assert_eq!(integers_u64.len(), 2000);
group.bench_function(BenchmarkId::new("Elias-Fano", delta), |b| {
b.iter(|| elias_fano::IntegerList::new_pre_sorted(black_box(&integers_usize)));
});
group.bench_function(BenchmarkId::new("Roaring Bitmaps", delta), |b| {
b.iter(|| reth_primitives::IntegerList::new_pre_sorted(black_box(&integers_u64)));
});
}
}
pub fn rank_select(c: &mut Criterion) {
let mut group = c.benchmark_group("rank + select");
for delta in [1, 100, 1000, 10000] {
let integers_usize = generate_integers(2000, delta);
assert_eq!(integers_usize.len(), 2000);
let integers_u64 = integers_usize.iter().map(|v| *v as u64).collect::<Vec<_>>();
assert_eq!(integers_u64.len(), 2000);
group.bench_function(BenchmarkId::new("Elias-Fano", delta), |b| {
b.iter_batched(
|| {
let (index, element) =
integers_usize.iter().enumerate().choose(&mut thread_rng()).unwrap();
(elias_fano::IntegerList::new_pre_sorted(&integers_usize).0, index, *element)
},
|(list, index, element)| {
let list = list.enable_rank();
list.rank(element);
list.select(index);
},
BatchSize::PerIteration,
);
});
group.bench_function(BenchmarkId::new("Roaring Bitmaps", delta), |b| {
b.iter_batched(
|| {
let (index, element) =
integers_u64.iter().enumerate().choose(&mut thread_rng()).unwrap();
(
reth_primitives::IntegerList::new_pre_sorted(&integers_u64),
index as u64,
*element,
)
},
|(list, index, element)| {
list.rank(element);
list.select(index);
},
BatchSize::PerIteration,
);
});
}
}
fn generate_integers(n: usize, delta: usize) -> Vec<usize> {
(0..n).fold(Vec::new(), |mut vec, _| {
vec.push(vec.last().map_or(0, |last| {
last + thread_rng().gen_range(delta - delta / 2..=delta + delta / 2)
}));
vec
})
}
criterion_group! {
name = benches;
config = Criterion::default();
targets = new_pre_sorted, rank_select
}
criterion_main!(benches);
/// Implementation from https://github.com/paradigmxyz/reth/blob/cda5d4e7c53ccc898b7725eb5d3b46c35e4da7f8/crates/primitives/src/integer_list.rs
/// adapted to work with `sucds = "0.8.1"`
#[allow(unused, unreachable_pub)]
mod elias_fano {
use std::{fmt, ops::Deref};
use sucds::{mii_sequences::EliasFano, Serializable};
#[derive(Clone, PartialEq, Eq, Default)]
pub struct IntegerList(pub EliasFano);
impl Deref for IntegerList {
type Target = EliasFano;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl fmt::Debug for IntegerList {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let vec: Vec<usize> = self.0.iter(0).collect();
write!(f, "IntegerList {:?}", vec)
}
}
impl IntegerList {
/// Creates an IntegerList from a list of integers. `usize` is safe to use since
/// [`sucds::EliasFano`] restricts its compilation to 64bits.
///
/// # Returns
///
/// Returns an error if the list is empty or not pre-sorted.
pub fn new<T: AsRef<[usize]>>(list: T) -> Result<Self, EliasFanoError> {
let mut builder = EliasFanoBuilder::new(
list.as_ref().iter().max().map_or(0, |max| max + 1),
list.as_ref().len(),
)?;
builder.extend(list.as_ref().iter().copied());
Ok(Self(builder.build()))
}
// Creates an IntegerList from a pre-sorted list of integers. `usize` is safe to use since
/// [`sucds::EliasFano`] restricts its compilation to 64bits.
///
/// # Panics
///
/// Panics if the list is empty or not pre-sorted.
pub fn new_pre_sorted<T: AsRef<[usize]>>(list: T) -> Self {
Self::new(list).expect("IntegerList must be pre-sorted and non-empty.")
}
/// Serializes a [`IntegerList`] into a sequence of bytes.
pub fn to_bytes(&self) -> Vec<u8> {
let mut vec = Vec::with_capacity(self.0.size_in_bytes());
self.0.serialize_into(&mut vec).expect("not able to encode integer list.");
vec
}
/// Serializes a [`IntegerList`] into a sequence of bytes.
pub fn to_mut_bytes<B: bytes::BufMut>(&self, buf: &mut B) {
let len = self.0.size_in_bytes();
let mut vec = Vec::with_capacity(len);
self.0.serialize_into(&mut vec).unwrap();
buf.put_slice(vec.as_slice());
}
/// Deserializes a sequence of bytes into a proper [`IntegerList`].
pub fn from_bytes(data: &[u8]) -> Result<Self, EliasFanoError> {
Ok(Self(
EliasFano::deserialize_from(data).map_err(|_| EliasFanoError::FailedDeserialize)?,
))
}
}
macro_rules! impl_uint {
($($w:tt),+) => {
$(
impl From<Vec<$w>> for IntegerList {
fn from(v: Vec<$w>) -> Self {
let v: Vec<usize> = v.iter().map(|v| *v as usize).collect();
Self::new(v.as_slice()).expect("could not create list.")
}
}
)+
};
}
impl_uint!(usize, u64, u32, u8, u16);
impl Serialize for IntegerList {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let vec = self.0.iter(0).collect::<Vec<usize>>();
let mut seq = serializer.serialize_seq(Some(self.len()))?;
for e in vec {
seq.serialize_element(&e)?;
}
seq.end()
}
}
struct IntegerListVisitor;
impl<'de> Visitor<'de> for IntegerListVisitor {
type Value = IntegerList;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("a usize array")
}
fn visit_seq<E>(self, mut seq: E) -> Result<Self::Value, E::Error>
where
E: SeqAccess<'de>,
{
let mut list = Vec::new();
while let Some(item) = seq.next_element()? {
list.push(item);
}
IntegerList::new(list)
.map_err(|_| serde::de::Error::invalid_value(Unexpected::Seq, &self))
}
}
impl<'de> Deserialize<'de> for IntegerList {
fn deserialize<D>(deserializer: D) -> Result<IntegerList, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_byte_buf(IntegerListVisitor)
}
}
#[cfg(any(test, feature = "arbitrary"))]
use arbitrary::{Arbitrary, Unstructured};
use serde::{
de::{SeqAccess, Unexpected, Visitor},
ser::SerializeSeq,
Deserialize, Deserializer, Serialize, Serializer,
};
use sucds::mii_sequences::EliasFanoBuilder;
#[cfg(any(test, feature = "arbitrary"))]
impl<'a> Arbitrary<'a> for IntegerList {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self, arbitrary::Error> {
let mut nums: Vec<usize> = Vec::arbitrary(u)?;
nums.sort();
Self::new(&nums).map_err(|_| arbitrary::Error::IncorrectFormat)
}
}
/// Primitives error type.
#[derive(Debug, thiserror::Error)]
pub enum EliasFanoError {
/// The provided input is invalid.
#[error(transparent)]
InvalidInput(#[from] anyhow::Error),
/// Failed to deserialize data into type.
#[error("failed to deserialize data into type")]
FailedDeserialize,
}
}

View File

@ -67,7 +67,6 @@ pub static MAINNET: Lazy<Arc<ChainSpec>> = Lazy::new(|| {
)),
base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()),
prune_delete_limit: 3500,
snapshot_block_interval: 500_000,
}
.into()
});
@ -111,7 +110,6 @@ pub static GOERLI: Lazy<Arc<ChainSpec>> = Lazy::new(|| {
)),
base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()),
prune_delete_limit: 1700,
snapshot_block_interval: 1_000_000,
}
.into()
});
@ -159,7 +157,6 @@ pub static SEPOLIA: Lazy<Arc<ChainSpec>> = Lazy::new(|| {
)),
base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()),
prune_delete_limit: 1700,
snapshot_block_interval: 1_000_000,
}
.into()
});
@ -202,7 +199,6 @@ pub static HOLESKY: Lazy<Arc<ChainSpec>> = Lazy::new(|| {
)),
base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()),
prune_delete_limit: 1700,
snapshot_block_interval: 1_000_000,
}
.into()
});
@ -296,7 +292,6 @@ pub static BASE_SEPOLIA: Lazy<Arc<ChainSpec>> = Lazy::new(|| {
.into(),
),
prune_delete_limit: 1700,
snapshot_block_interval: 1_000_000,
..Default::default()
}
.into()
@ -351,7 +346,6 @@ pub static BASE_MAINNET: Lazy<Arc<ChainSpec>> = Lazy::new(|| {
.into(),
),
prune_delete_limit: 1700,
snapshot_block_interval: 1_000_000,
..Default::default()
}
.into()
@ -502,9 +496,6 @@ pub struct ChainSpec {
/// data coming in.
#[serde(default)]
pub prune_delete_limit: usize,
/// The block interval for creating snapshots. Each snapshot will have that much blocks in it.
pub snapshot_block_interval: u64,
}
impl Default for ChainSpec {
@ -519,7 +510,6 @@ impl Default for ChainSpec {
deposit_contract: Default::default(),
base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()),
prune_delete_limit: MAINNET.prune_delete_limit,
snapshot_block_interval: Default::default(),
}
}
}

View File

@ -18,6 +18,7 @@ use proptest::prelude::*;
use reth_codecs::{add_arbitrary_tests, derive_arbitrary, main_codec, Compact};
use serde::{Deserialize, Serialize};
use std::{mem, ops::Deref};
/// Errors that can occur during header sanity checks.
#[derive(Debug, PartialEq)]
pub enum HeaderError {
@ -574,13 +575,14 @@ pub enum HeaderValidationError {
/// A [`Header`] that is sealed at a precalculated hash, use [`SealedHeader::unseal()`] if you want
/// to modify header.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[add_arbitrary_tests(rlp)]
#[main_codec(no_arbitrary)]
#[add_arbitrary_tests(rlp, compact)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SealedHeader {
/// Locked Header fields.
header: Header,
/// Locked Header hash.
hash: BlockHash,
/// Locked Header fields.
header: Header,
}
impl SealedHeader {

View File

@ -1,18 +1,19 @@
use bytes::BufMut;
use roaring::RoaringTreemap;
use serde::{
de::{SeqAccess, Unexpected, Visitor},
ser::SerializeSeq,
Deserialize, Deserializer, Serialize, Serializer,
};
use std::{fmt, ops::Deref};
use sucds::{EliasFano, Searial};
/// Uses EliasFano to hold a list of integers. It provides really good compression with the
/// Uses Roaring Bitmaps to hold a list of integers. It provides really good compression with the
/// capability to access its elements without decoding it.
#[derive(Clone, PartialEq, Eq, Default)]
pub struct IntegerList(pub EliasFano);
#[derive(Clone, PartialEq, Default)]
pub struct IntegerList(pub RoaringTreemap);
impl Deref for IntegerList {
type Target = EliasFano;
type Target = RoaringTreemap;
fn deref(&self) -> &Self::Target {
&self.0
@ -21,53 +22,54 @@ impl Deref for IntegerList {
impl fmt::Debug for IntegerList {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let vec: Vec<usize> = self.0.iter(0).collect();
let vec: Vec<u64> = self.0.iter().collect();
write!(f, "IntegerList {:?}", vec)
}
}
impl IntegerList {
/// Creates an IntegerList from a list of integers. `usize` is safe to use since
/// [`sucds::EliasFano`] restricts its compilation to 64bits.
/// Creates an IntegerList from a list of integers.
///
/// # Returns
///
/// Returns an error if the list is empty or not pre-sorted.
pub fn new<T: AsRef<[usize]>>(list: T) -> Result<Self, EliasFanoError> {
Ok(Self(EliasFano::from_ints(list.as_ref()).map_err(|_| EliasFanoError::InvalidInput)?))
pub fn new<T: AsRef<[u64]>>(list: T) -> Result<Self, RoaringBitmapError> {
Ok(Self(
RoaringTreemap::from_sorted_iter(list.as_ref().iter().copied())
.map_err(|_| RoaringBitmapError::InvalidInput)?,
))
}
// Creates an IntegerList from a pre-sorted list of integers. `usize` is safe to use since
/// [`sucds::EliasFano`] restricts its compilation to 64bits.
// Creates an IntegerList from a pre-sorted list of integers.
///
/// # Panics
///
/// Panics if the list is empty or not pre-sorted.
pub fn new_pre_sorted<T: AsRef<[usize]>>(list: T) -> Self {
pub fn new_pre_sorted<T: AsRef<[u64]>>(list: T) -> Self {
Self(
EliasFano::from_ints(list.as_ref())
.expect("IntegerList must be pre-sorted and non-empty."),
RoaringTreemap::from_sorted_iter(list.as_ref().iter().copied())
.expect("IntegerList must be pre-sorted and non-empty"),
)
}
/// Serializes a [`IntegerList`] into a sequence of bytes.
pub fn to_bytes(&self) -> Vec<u8> {
let mut vec = Vec::with_capacity(self.0.size_in_bytes());
self.0.serialize_into(&mut vec).expect("not able to encode integer list.");
let mut vec = Vec::with_capacity(self.0.serialized_size());
self.0.serialize_into(&mut vec).expect("not able to encode IntegerList");
vec
}
/// Serializes a [`IntegerList`] into a sequence of bytes.
pub fn to_mut_bytes<B: bytes::BufMut>(&self, buf: &mut B) {
let len = self.0.size_in_bytes();
let mut vec = Vec::with_capacity(len);
self.0.serialize_into(&mut vec).unwrap();
buf.put_slice(vec.as_slice());
self.0.serialize_into(buf.writer()).unwrap();
}
/// Deserializes a sequence of bytes into a proper [`IntegerList`].
pub fn from_bytes(data: &[u8]) -> Result<Self, EliasFanoError> {
Ok(Self(EliasFano::deserialize_from(data).map_err(|_| EliasFanoError::FailedDeserialize)?))
pub fn from_bytes(data: &[u8]) -> Result<Self, RoaringBitmapError> {
Ok(Self(
RoaringTreemap::deserialize_from(data)
.map_err(|_| RoaringBitmapError::FailedToDeserialize)?,
))
}
}
@ -76,8 +78,7 @@ macro_rules! impl_uint {
$(
impl From<Vec<$w>> for IntegerList {
fn from(v: Vec<$w>) -> Self {
let v: Vec<usize> = v.iter().map(|v| *v as usize).collect();
Self(EliasFano::from_ints(v.as_slice()).expect("could not create list."))
Self::new_pre_sorted(v.iter().map(|v| *v as u64).collect::<Vec<_>>())
}
}
)+
@ -91,8 +92,8 @@ impl Serialize for IntegerList {
where
S: Serializer,
{
let vec = self.0.iter(0).collect::<Vec<usize>>();
let mut seq = serializer.serialize_seq(Some(self.len()))?;
let vec = self.0.iter().collect::<Vec<u64>>();
let mut seq = serializer.serialize_seq(Some(self.len() as usize))?;
for e in vec {
seq.serialize_element(&e)?;
}
@ -136,21 +137,21 @@ use arbitrary::{Arbitrary, Unstructured};
#[cfg(any(test, feature = "arbitrary"))]
impl<'a> Arbitrary<'a> for IntegerList {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self, arbitrary::Error> {
let mut nums: Vec<usize> = Vec::arbitrary(u)?;
let mut nums: Vec<u64> = Vec::arbitrary(u)?;
nums.sort();
Ok(Self(EliasFano::from_ints(&nums).map_err(|_| arbitrary::Error::IncorrectFormat)?))
Self::new(nums).map_err(|_| arbitrary::Error::IncorrectFormat)
}
}
/// Primitives error type.
#[derive(Debug, thiserror::Error)]
pub enum EliasFanoError {
pub enum RoaringBitmapError {
/// The provided input is invalid.
#[error("the provided input is invalid")]
InvalidInput,
/// Failed to deserialize data into type.
#[error("failed to deserialize data into type")]
FailedDeserialize,
FailedToDeserialize,
}
#[cfg(test)]
@ -161,7 +162,7 @@ mod tests {
fn test_integer_list() {
let original_list = [1, 2, 3];
let ef_list = IntegerList::new(original_list).unwrap();
assert_eq!(ef_list.iter(0).collect::<Vec<usize>>(), original_list);
assert_eq!(ef_list.iter().collect::<Vec<_>>(), original_list);
}
#[test]

View File

@ -37,8 +37,8 @@ mod receipt;
/// Helpers for working with revm
pub mod revm;
pub mod serde_helper;
pub mod snapshot;
pub mod stage;
pub mod static_file;
mod storage;
/// Helpers for working with transactions
pub mod transaction;
@ -73,11 +73,11 @@ pub use net::{
};
pub use peer::{PeerId, WithPeerId};
pub use prune::{
PruneCheckpoint, PruneMode, PruneModes, PruneProgress, PruneSegment, PruneSegmentError,
ReceiptsLogPruneConfig, MINIMUM_PRUNING_DISTANCE,
PruneCheckpoint, PruneMode, PruneModes, PruneProgress, PrunePurpose, PruneSegment,
PruneSegmentError, ReceiptsLogPruneConfig, MINIMUM_PRUNING_DISTANCE,
};
pub use receipt::{Receipt, ReceiptWithBloom, ReceiptWithBloomRef, Receipts};
pub use snapshot::SnapshotSegment;
pub use static_file::StaticFileSegment;
pub use storage::StorageEntry;
#[cfg(feature = "c-kzg")]
@ -92,7 +92,7 @@ pub use transaction::{
AccessList, AccessListItem, FromRecoveredTransaction, IntoRecoveredTransaction,
InvalidTransactionError, Signature, Transaction, TransactionKind, TransactionMeta,
TransactionSigned, TransactionSignedEcRecovered, TransactionSignedNoHash, TxEip1559, TxEip2930,
TxEip4844, TxHashOrNumber, TxLegacy, TxType, TxValue, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID,
TxEip4844, TxHashOrNumber, TxLegacy, TxType, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID,
EIP4844_TX_TYPE_ID, LEGACY_TX_TYPE_ID,
};
pub use withdrawal::{Withdrawal, Withdrawals};

View File

@ -5,9 +5,8 @@ use crate::{
keccak256,
trie::{HashBuilder, Nibbles, TrieAccount},
Address, Header, Receipt, ReceiptWithBloom, ReceiptWithBloomRef, TransactionSigned, Withdrawal,
B256,
B256, U256,
};
use alloy_primitives::U256;
use alloy_rlp::Encodable;
use bytes::{BufMut, BytesMut};
use itertools::Itertools;

View File

@ -6,7 +6,7 @@ mod target;
use crate::{Address, BlockNumber};
pub use checkpoint::PruneCheckpoint;
pub use mode::PruneMode;
pub use segment::{PruneSegment, PruneSegmentError};
pub use segment::{PrunePurpose, PruneSegment, PruneSegmentError};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
pub use target::{PruneModes, MINIMUM_PRUNING_DISTANCE};
@ -53,7 +53,7 @@ impl ReceiptsLogPruneConfig {
// Reminder, that we increment because the [`BlockNumber`] key of the new map should be
// viewed as `PruneMode::Before(block)`
let block = (pruned_block + 1).max(
mode.prune_target_block(tip, PruneSegment::ContractLogs)?
mode.prune_target_block(tip, PruneSegment::ContractLogs, PrunePurpose::User)?
.map(|(block, _)| block)
.unwrap_or_default() +
1,
@ -76,7 +76,7 @@ impl ReceiptsLogPruneConfig {
for (_, mode) in self.0.iter() {
if let PruneMode::Distance(_) = mode {
if let Some((block, _)) =
mode.prune_target_block(tip, PruneSegment::ContractLogs)?
mode.prune_target_block(tip, PruneSegment::ContractLogs, PrunePurpose::User)?
{
lowest = Some(lowest.unwrap_or(u64::MAX).min(block));
}

View File

@ -1,4 +1,4 @@
use crate::{BlockNumber, PruneSegment, PruneSegmentError};
use crate::{prune::segment::PrunePurpose, BlockNumber, PruneSegment, PruneSegmentError};
use reth_codecs::{main_codec, Compact};
/// Prune mode.
@ -15,21 +15,29 @@ pub enum PruneMode {
}
impl PruneMode {
/// Prune blocks up to the specified block number. The specified block number is also pruned.
///
/// This acts as `PruneMode::Before(block_number + 1)`.
pub fn before_inclusive(block_number: BlockNumber) -> Self {
Self::Before(block_number + 1)
}
/// Returns block up to which variant pruning needs to be done, inclusive, according to the
/// provided tip.
pub fn prune_target_block(
&self,
tip: BlockNumber,
segment: PruneSegment,
purpose: PrunePurpose,
) -> Result<Option<(BlockNumber, PruneMode)>, PruneSegmentError> {
let result = match self {
PruneMode::Full if segment.min_blocks() == 0 => Some((tip, *self)),
PruneMode::Full if segment.min_blocks(purpose) == 0 => Some((tip, *self)),
PruneMode::Distance(distance) if *distance > tip => None, // Nothing to prune yet
PruneMode::Distance(distance) if *distance >= segment.min_blocks() => {
PruneMode::Distance(distance) if *distance >= segment.min_blocks(purpose) => {
Some((tip - distance, *self))
}
PruneMode::Before(n) if *n > tip => None, // Nothing to prune yet
PruneMode::Before(n) if tip - n >= segment.min_blocks() => Some((n - 1, *self)),
PruneMode::Before(n) if tip - n >= segment.min_blocks(purpose) => Some((n - 1, *self)),
_ => return Err(PruneSegmentError::Configuration(segment)),
};
Ok(result)
@ -64,7 +72,9 @@ impl Default for PruneMode {
#[cfg(test)]
mod tests {
use crate::{prune::PruneMode, PruneSegment, PruneSegmentError, MINIMUM_PRUNING_DISTANCE};
use crate::{
prune::PruneMode, PrunePurpose, PruneSegment, PruneSegmentError, MINIMUM_PRUNING_DISTANCE,
};
use assert_matches::assert_matches;
use serde::Deserialize;
@ -79,8 +89,8 @@ mod tests {
// Nothing to prune
(PruneMode::Distance(tip + 1), Ok(None)),
(
PruneMode::Distance(segment.min_blocks() + 1),
Ok(Some(tip - (segment.min_blocks() + 1))),
PruneMode::Distance(segment.min_blocks(PrunePurpose::User) + 1),
Ok(Some(tip - (segment.min_blocks(PrunePurpose::User) + 1))),
),
// Nothing to prune
(PruneMode::Before(tip + 1), Ok(None)),
@ -97,7 +107,7 @@ mod tests {
for (index, (mode, expected_result)) in tests.into_iter().enumerate() {
assert_eq!(
mode.prune_target_block(tip, segment),
mode.prune_target_block(tip, segment, PrunePurpose::User),
expected_result.map(|r| r.map(|b| (b, mode))),
"Test {} failed",
index + 1,
@ -106,7 +116,7 @@ mod tests {
// Test for a scenario where there are no minimum blocks and Full can be used
assert_eq!(
PruneMode::Full.prune_target_block(tip, PruneSegment::Transactions),
PruneMode::Full.prune_target_block(tip, PruneSegment::Transactions, PrunePurpose::User),
Ok(Some((tip, PruneMode::Full))),
);
}

View File

@ -7,19 +7,20 @@ use thiserror::Error;
#[main_codec]
#[derive(Debug, Display, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum PruneSegment {
/// Prune segment responsible for the `TxSenders` table.
/// Prune segment responsible for the `TransactionSenders` table.
SenderRecovery,
/// Prune segment responsible for the `TxHashNumber` table.
/// Prune segment responsible for the `TransactionHashNumbers` table.
TransactionLookup,
/// Prune segment responsible for all rows in `Receipts` table.
Receipts,
/// Prune segment responsible for some rows in `Receipts` table filtered by logs.
ContractLogs,
/// Prune segment responsible for the `AccountChangeSet` and `AccountHistory` tables.
/// Prune segment responsible for the `AccountChangeSets` and `AccountsHistory` tables.
AccountHistory,
/// Prune segment responsible for the `StorageChangeSet` and `StorageHistory` tables.
/// Prune segment responsible for the `StorageChangeSets` and `StoragesHistory` tables.
StorageHistory,
/// Prune segment responsible for the `CanonicalHeaders`, `Headers` and `HeaderTD` tables.
/// Prune segment responsible for the `CanonicalHeaders`, `Headers` and
/// `HeaderTerminalDifficulties` tables.
Headers,
/// Prune segment responsible for the `Transactions` table.
Transactions,
@ -27,18 +28,41 @@ pub enum PruneSegment {
impl PruneSegment {
/// Returns minimum number of blocks to left in the database for this segment.
pub fn min_blocks(&self) -> u64 {
pub fn min_blocks(&self, purpose: PrunePurpose) -> u64 {
match self {
Self::SenderRecovery | Self::TransactionLookup | Self::Headers | Self::Transactions => {
0
}
Self::Receipts | Self::ContractLogs | Self::AccountHistory | Self::StorageHistory => {
Self::Receipts if purpose.is_static_file() => 0,
Self::ContractLogs | Self::AccountHistory | Self::StorageHistory => {
MINIMUM_PRUNING_DISTANCE
}
Self::Receipts => MINIMUM_PRUNING_DISTANCE,
}
}
}
/// Prune purpose.
#[derive(Debug, Clone, Copy)]
pub enum PrunePurpose {
/// Prune data according to user configuration.
User,
/// Prune data according to highest static_files to delete the data from database.
StaticFile,
}
impl PrunePurpose {
/// Returns true if the purpose is [`PrunePurpose::User`].
pub fn is_user(self) -> bool {
matches!(self, Self::User)
}
/// Returns true if the purpose is [`PrunePurpose::StaticFile`].
pub fn is_static_file(self) -> bool {
matches!(self, Self::StaticFile)
}
}
/// PruneSegment error type.
#[derive(Debug, Error, PartialEq, Eq, Clone)]
pub enum PruneSegmentError {

View File

@ -200,7 +200,7 @@ where
TransactionKind::Call(to) => TransactTo::Call(to),
TransactionKind::Create => TransactTo::create(),
};
tx_env.value = tx.value.into();
tx_env.value = tx.value;
tx_env.data = tx.input.clone();
tx_env.chain_id = tx.chain_id;
tx_env.nonce = Some(tx.nonce);
@ -216,7 +216,7 @@ where
TransactionKind::Call(to) => TransactTo::Call(to),
TransactionKind::Create => TransactTo::create(),
};
tx_env.value = tx.value.into();
tx_env.value = tx.value;
tx_env.data = tx.input.clone();
tx_env.chain_id = Some(tx.chain_id);
tx_env.nonce = Some(tx.nonce);
@ -239,7 +239,7 @@ where
TransactionKind::Call(to) => TransactTo::Call(to),
TransactionKind::Create => TransactTo::create(),
};
tx_env.value = tx.value.into();
tx_env.value = tx.value;
tx_env.data = tx.input.clone();
tx_env.chain_id = Some(tx.chain_id);
tx_env.nonce = Some(tx.nonce);
@ -262,7 +262,7 @@ where
TransactionKind::Call(to) => TransactTo::Call(to),
TransactionKind::Create => TransactTo::create(),
};
tx_env.value = tx.value.into();
tx_env.value = tx.value;
tx_env.data = tx.input.clone();
tx_env.chain_id = Some(tx.chain_id);
tx_env.nonce = Some(tx.nonce);
@ -287,7 +287,7 @@ where
TransactionKind::Call(to) => tx_env.transact_to = TransactTo::Call(to),
TransactionKind::Create => tx_env.transact_to = TransactTo::create(),
}
tx_env.value = tx.value.into();
tx_env.value = tx.value;
tx_env.data = tx.input.clone();
tx_env.chain_id = None;
tx_env.nonce = None;

View File

@ -1,47 +0,0 @@
//! Snapshot primitives.
mod compression;
mod filters;
mod segment;
use alloy_primitives::BlockNumber;
pub use compression::Compression;
pub use filters::{Filters, InclusionFilter, PerfectHashingFunction};
pub use segment::{SegmentConfig, SegmentHeader, SnapshotSegment};
/// Default snapshot block count.
pub const BLOCKS_PER_SNAPSHOT: u64 = 500_000;
/// Highest snapshotted block numbers, per data part.
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
pub struct HighestSnapshots {
/// Highest snapshotted block of headers, inclusive.
/// If [`None`], no snapshot is available.
pub headers: Option<BlockNumber>,
/// Highest snapshotted block of receipts, inclusive.
/// If [`None`], no snapshot is available.
pub receipts: Option<BlockNumber>,
/// Highest snapshotted block of transactions, inclusive.
/// If [`None`], no snapshot is available.
pub transactions: Option<BlockNumber>,
}
impl HighestSnapshots {
/// Returns the highest snapshot if it exists for a segment
pub fn highest(&self, segment: SnapshotSegment) -> Option<BlockNumber> {
match segment {
SnapshotSegment::Headers => self.headers,
SnapshotSegment::Transactions => self.transactions,
SnapshotSegment::Receipts => self.receipts,
}
}
/// Returns a mutable reference to a snapshot segment
pub fn as_mut(&mut self, segment: SnapshotSegment) -> &mut Option<BlockNumber> {
match segment {
SnapshotSegment::Headers => &mut self.headers,
SnapshotSegment::Transactions => &mut self.transactions,
SnapshotSegment::Receipts => &mut self.receipts,
}
}
}

View File

@ -1,287 +0,0 @@
use crate::{
snapshot::{Compression, Filters, InclusionFilter},
BlockNumber, TxNumber,
};
use derive_more::Display;
use serde::{Deserialize, Serialize};
use std::{ffi::OsStr, ops::RangeInclusive, str::FromStr};
use strum::{AsRefStr, EnumIter, EnumString};
#[derive(
Debug,
Copy,
Clone,
Eq,
PartialEq,
Hash,
Ord,
PartialOrd,
Deserialize,
Serialize,
EnumString,
EnumIter,
AsRefStr,
Display,
)]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
/// Segment of the data that can be snapshotted.
pub enum SnapshotSegment {
#[strum(serialize = "headers")]
/// Snapshot segment responsible for the `CanonicalHeaders`, `Headers`, `HeaderTD` tables.
Headers,
#[strum(serialize = "transactions")]
/// Snapshot segment responsible for the `Transactions` table.
Transactions,
#[strum(serialize = "receipts")]
/// Snapshot segment responsible for the `Receipts` table.
Receipts,
}
impl SnapshotSegment {
/// Returns the default configuration of the segment.
pub const fn config(&self) -> SegmentConfig {
let default_config = SegmentConfig {
filters: Filters::WithFilters(
InclusionFilter::Cuckoo,
super::PerfectHashingFunction::Fmph,
),
compression: Compression::Lz4,
};
match self {
SnapshotSegment::Headers => default_config,
SnapshotSegment::Transactions => default_config,
SnapshotSegment::Receipts => default_config,
}
}
/// Returns the default file name for the provided segment and range.
pub fn filename(
&self,
block_range: &RangeInclusive<BlockNumber>,
tx_range: &RangeInclusive<TxNumber>,
) -> String {
// ATTENTION: if changing the name format, be sure to reflect those changes in
// [`Self::parse_filename`].
format!(
"snapshot_{}_{}_{}_{}_{}",
self.as_ref(),
block_range.start(),
block_range.end(),
tx_range.start(),
tx_range.end(),
)
}
/// Returns file name for the provided segment and range, alongisde filters, compression.
pub fn filename_with_configuration(
&self,
filters: Filters,
compression: Compression,
block_range: &RangeInclusive<BlockNumber>,
tx_range: &RangeInclusive<TxNumber>,
) -> String {
let prefix = self.filename(block_range, tx_range);
let filters_name = match filters {
Filters::WithFilters(inclusion_filter, phf) => {
format!("{}-{}", inclusion_filter.as_ref(), phf.as_ref())
}
Filters::WithoutFilters => "none".to_string(),
};
// ATTENTION: if changing the name format, be sure to reflect those changes in
// [`Self::parse_filename`.]
format!("{prefix}_{}_{}", filters_name, compression.as_ref())
}
/// Parses a filename into a `SnapshotSegment` and its corresponding block and transaction
/// ranges.
///
/// The filename is expected to follow the format:
/// "snapshot_{segment}_{block_start}_{block_end}_{tx_start}_{tx_end}". This function checks
/// for the correct prefix ("snapshot"), and then parses the segment and the inclusive
/// ranges for blocks and transactions. It ensures that the start of each range is less than the
/// end.
///
/// # Returns
/// - `Some((segment, block_range, tx_range))` if parsing is successful and all conditions are
/// met.
/// - `None` if any condition fails, such as an incorrect prefix, parsing error, or invalid
/// range.
///
/// # Note
/// This function is tightly coupled with the naming convention defined in [`Self::filename`].
/// Any changes in the filename format in `filename` should be reflected here.
pub fn parse_filename(
name: &OsStr,
) -> Option<(Self, RangeInclusive<BlockNumber>, RangeInclusive<TxNumber>)> {
let mut parts = name.to_str()?.split('_');
if parts.next() != Some("snapshot") {
return None
}
let segment = Self::from_str(parts.next()?).ok()?;
let (block_start, block_end) = (parts.next()?.parse().ok()?, parts.next()?.parse().ok()?);
let (tx_start, tx_end) = (parts.next()?.parse().ok()?, parts.next()?.parse().ok()?);
if block_start >= block_end || tx_start > tx_end {
return None
}
Some((segment, block_start..=block_end, tx_start..=tx_end))
}
}
/// A segment header that contains information common to all segments. Used for storage.
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
pub struct SegmentHeader {
/// Block range of the snapshot segment
block_range: RangeInclusive<BlockNumber>,
/// Transaction range of the snapshot segment
tx_range: RangeInclusive<TxNumber>,
/// Segment type
segment: SnapshotSegment,
}
impl SegmentHeader {
/// Returns [`SegmentHeader`].
pub fn new(
block_range: RangeInclusive<BlockNumber>,
tx_range: RangeInclusive<TxNumber>,
segment: SnapshotSegment,
) -> Self {
Self { block_range, tx_range, segment }
}
/// Returns the transaction range.
pub fn tx_range(&self) -> &RangeInclusive<TxNumber> {
&self.tx_range
}
/// Returns the block range.
pub fn block_range(&self) -> &RangeInclusive<BlockNumber> {
&self.block_range
}
/// Returns the first block number of the segment.
pub fn block_start(&self) -> BlockNumber {
*self.block_range.start()
}
/// Returns the last block number of the segment.
pub fn block_end(&self) -> BlockNumber {
*self.block_range.end()
}
/// Returns the first transaction number of the segment.
pub fn tx_start(&self) -> TxNumber {
*self.tx_range.start()
}
/// Returns the row offset which depends on whether the segment is block or transaction based.
pub fn start(&self) -> u64 {
match self.segment {
SnapshotSegment::Headers => self.block_start(),
SnapshotSegment::Transactions | SnapshotSegment::Receipts => self.tx_start(),
}
}
}
/// Configuration used on the segment.
#[derive(Debug, Clone, Copy)]
pub struct SegmentConfig {
/// Inclusion filters used on the segment
pub filters: Filters,
/// Compression used on the segment
pub compression: Compression,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_filename() {
let test_vectors = [
(SnapshotSegment::Headers, 2..=30, 0..=1, "snapshot_headers_2_30_0_1", None),
(
SnapshotSegment::Receipts,
30..=300,
110..=1000,
"snapshot_receipts_30_300_110_1000",
None,
),
(
SnapshotSegment::Transactions,
1_123_233..=11_223_233,
1_123_233..=2_123_233,
"snapshot_transactions_1123233_11223233_1123233_2123233",
None,
),
(
SnapshotSegment::Headers,
2..=30,
0..=1,
"snapshot_headers_2_30_0_1_cuckoo-fmph_lz4",
Some((
Compression::Lz4,
Filters::WithFilters(
InclusionFilter::Cuckoo,
crate::snapshot::PerfectHashingFunction::Fmph,
),
)),
),
(
SnapshotSegment::Headers,
2..=30,
0..=1,
"snapshot_headers_2_30_0_1_cuckoo-fmph_zstd",
Some((
Compression::Zstd,
Filters::WithFilters(
InclusionFilter::Cuckoo,
crate::snapshot::PerfectHashingFunction::Fmph,
),
)),
),
(
SnapshotSegment::Headers,
2..=30,
0..=1,
"snapshot_headers_2_30_0_1_cuckoo-fmph_zstd-dict",
Some((
Compression::ZstdWithDictionary,
Filters::WithFilters(
InclusionFilter::Cuckoo,
crate::snapshot::PerfectHashingFunction::Fmph,
),
)),
),
];
for (segment, block_range, tx_range, filename, configuration) in test_vectors {
if let Some((compression, filters)) = configuration {
assert_eq!(
segment.filename_with_configuration(
filters,
compression,
&block_range,
&tx_range
),
filename
);
} else {
assert_eq!(segment.filename(&block_range, &tx_range), filename);
}
assert_eq!(
SnapshotSegment::parse_filename(OsStr::new(filename)),
Some((segment, block_range, tx_range))
);
}
assert_eq!(SnapshotSegment::parse_filename(OsStr::new("snapshot_headers_2_30_3_2")), None);
assert_eq!(SnapshotSegment::parse_filename(OsStr::new("snapshot_headers_2_30_1")), None);
}
}

View File

@ -2,9 +2,8 @@ use crate::{
trie::{hash_builder::HashBuilderState, StoredSubNode},
Address, BlockNumber, B256,
};
use bytes::{Buf, BufMut};
use reth_codecs::{derive_arbitrary, main_codec, Compact};
use serde::{Deserialize, Serialize};
use bytes::Buf;
use reth_codecs::{main_codec, Compact};
use std::ops::RangeInclusive;
/// Saves the progress of Merkle stage.
@ -14,9 +13,6 @@ pub struct MerkleCheckpoint {
pub target_block: BlockNumber,
/// The last hashed account key processed.
pub last_account_key: B256,
// TODO: remove in the next breaking release.
/// The last walker key processed.
pub last_walker_key: Vec<u8>,
/// Previously recorded walker stack.
pub walker_stack: Vec<StoredSubNode>,
/// The hash builder state.
@ -31,13 +27,7 @@ impl MerkleCheckpoint {
walker_stack: Vec<StoredSubNode>,
state: HashBuilderState,
) -> Self {
Self {
target_block,
last_account_key,
walker_stack,
state,
last_walker_key: Vec::default(),
}
Self { target_block, last_account_key, walker_stack, state }
}
}
@ -54,10 +44,6 @@ impl Compact for MerkleCheckpoint {
buf.put_slice(self.last_account_key.as_slice());
len += self.last_account_key.len();
buf.put_u16(self.last_walker_key.len() as u16);
buf.put_slice(&self.last_walker_key[..]);
len += 2 + self.last_walker_key.len();
buf.put_u16(self.walker_stack.len() as u16);
len += 2;
for item in self.walker_stack.into_iter() {
@ -74,10 +60,6 @@ impl Compact for MerkleCheckpoint {
let last_account_key = B256::from_slice(&buf[..32]);
buf.advance(32);
let last_walker_key_len = buf.get_u16() as usize;
let last_walker_key = Vec::from(&buf[..last_walker_key_len]);
buf.advance(last_walker_key_len);
let walker_stack_len = buf.get_u16() as usize;
let mut walker_stack = Vec::with_capacity(walker_stack_len);
for _ in 0..walker_stack_len {
@ -87,16 +69,7 @@ impl Compact for MerkleCheckpoint {
}
let (state, buf) = HashBuilderState::from_compact(buf, 0);
(
MerkleCheckpoint {
target_block,
last_account_key,
last_walker_key,
walker_stack,
state,
},
buf,
)
(MerkleCheckpoint { target_block, last_account_key, walker_stack, state }, buf)
}
}
@ -250,8 +223,8 @@ impl StageCheckpoint {
// TODO(alexey): add a merkle checkpoint. Currently it's hard because [`MerkleCheckpoint`]
// is not a Copy type.
/// Stage-specific checkpoint metrics.
#[derive_arbitrary(compact)]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
#[main_codec]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum StageUnitCheckpoint {
/// Saves the progress of AccountHashing stage.
Account(AccountHashingCheckpoint),
@ -267,39 +240,16 @@ pub enum StageUnitCheckpoint {
IndexHistory(IndexHistoryCheckpoint),
}
/// Generates:
/// 1. [Compact::to_compact] and [Compact::from_compact] implementations for [StageUnitCheckpoint].
/// 2. [StageCheckpoint] getter and builder methods.
#[cfg(test)]
impl Default for StageUnitCheckpoint {
fn default() -> Self {
Self::Account(AccountHashingCheckpoint::default())
}
}
/// Generates [StageCheckpoint] getter and builder methods.
macro_rules! stage_unit_checkpoints {
($(($index:expr,$enum_variant:tt,$checkpoint_ty:ty,#[doc = $fn_get_doc:expr]$fn_get_name:ident,#[doc = $fn_build_doc:expr]$fn_build_name:ident)),+) => {
impl Compact for StageUnitCheckpoint {
fn to_compact<B>(self, buf: &mut B) -> usize
where
B: BufMut + AsMut<[u8]>,
{
match self {
$(
StageUnitCheckpoint::$enum_variant(data) => {
buf.put_u8($index);
1 + data.to_compact(buf)
}
)+
}
}
fn from_compact(buf: &[u8], _len: usize) -> (Self, &[u8]) {
match buf[0] {
$(
$index => {
let (data, buf) = <$checkpoint_ty>::from_compact(&buf[1..], buf.len() - 1);
(Self::$enum_variant(data), buf)
}
)+
_ => unreachable!("Junk data in database: unknown StageUnitCheckpoint variant"),
}
}
}
impl StageCheckpoint {
$(
#[doc = $fn_get_doc]
@ -391,7 +341,6 @@ mod tests {
let checkpoint = MerkleCheckpoint {
target_block: rng.gen(),
last_account_key: rng.gen(),
last_walker_key: B256::random_with(&mut rng).to_vec(),
walker_stack: vec![StoredSubNode {
key: B256::random_with(&mut rng).to_vec(),
nibble: Some(rng.gen()),
@ -405,53 +354,4 @@ mod tests {
let (decoded, _) = MerkleCheckpoint::from_compact(&buf, encoded);
assert_eq!(decoded, checkpoint);
}
#[test]
fn stage_unit_checkpoint_roundtrip() {
let mut rng = rand::thread_rng();
let checkpoints = vec![
StageUnitCheckpoint::Account(AccountHashingCheckpoint {
address: Some(rng.gen()),
block_range: CheckpointBlockRange { from: rng.gen(), to: rng.gen() },
progress: EntitiesCheckpoint {
processed: rng.gen::<u32>() as u64,
total: u32::MAX as u64 + rng.gen::<u64>(),
},
}),
StageUnitCheckpoint::Storage(StorageHashingCheckpoint {
address: Some(rng.gen()),
storage: Some(rng.gen()),
block_range: CheckpointBlockRange { from: rng.gen(), to: rng.gen() },
progress: EntitiesCheckpoint {
processed: rng.gen::<u32>() as u64,
total: u32::MAX as u64 + rng.gen::<u64>(),
},
}),
StageUnitCheckpoint::Entities(EntitiesCheckpoint {
processed: rng.gen::<u32>() as u64,
total: u32::MAX as u64 + rng.gen::<u64>(),
}),
StageUnitCheckpoint::Execution(ExecutionCheckpoint {
block_range: CheckpointBlockRange { from: rng.gen(), to: rng.gen() },
progress: EntitiesCheckpoint {
processed: rng.gen::<u32>() as u64,
total: u32::MAX as u64 + rng.gen::<u64>(),
},
}),
StageUnitCheckpoint::Headers(HeadersCheckpoint {
block_range: CheckpointBlockRange { from: rng.gen(), to: rng.gen() },
progress: EntitiesCheckpoint {
processed: rng.gen::<u32>() as u64,
total: u32::MAX as u64 + rng.gen::<u64>(),
},
}),
];
for checkpoint in checkpoints {
let mut buf = Vec::new();
let encoded = checkpoint.to_compact(&mut buf);
let (decoded, _) = StageUnitCheckpoint::from_compact(&buf, encoded);
assert_eq!(decoded, checkpoint);
}
}
}

View File

@ -3,10 +3,10 @@
/// For custom stages, use [`StageId::Other`]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum StageId {
/// Static File stage in the process.
StaticFile,
/// Header stage in the process.
Headers,
/// Total difficulty stage in the process.
TotalDifficulty,
/// Bodies stage in the process.
Bodies,
/// Sender recovery stage in the process.
@ -36,8 +36,8 @@ pub enum StageId {
impl StageId {
/// All supported Stages
pub const ALL: [StageId; 13] = [
StageId::StaticFile,
StageId::Headers,
StageId::TotalDifficulty,
StageId::Bodies,
StageId::SenderRecovery,
StageId::Execution,
@ -54,8 +54,8 @@ impl StageId {
/// Return stage id formatted as string.
pub fn as_str(&self) -> &str {
match self {
StageId::StaticFile => "StaticFile",
StageId::Headers => "Headers",
StageId::TotalDifficulty => "TotalDifficulty",
StageId::Bodies => "Bodies",
StageId::SenderRecovery => "SenderRecovery",
StageId::Execution => "Execution",
@ -94,8 +94,8 @@ mod tests {
#[test]
fn stage_id_as_string() {
assert_eq!(StageId::StaticFile.to_string(), "StaticFile");
assert_eq!(StageId::Headers.to_string(), "Headers");
assert_eq!(StageId::TotalDifficulty.to_string(), "TotalDifficulty");
assert_eq!(StageId::Bodies.to_string(), "Bodies");
assert_eq!(StageId::SenderRecovery.to_string(), "SenderRecovery");
assert_eq!(StageId::Execution.to_string(), "Execution");

View File

@ -1,6 +1,6 @@
use strum::AsRefStr;
/// Snapshot compression types.
/// Static File compression types.
#[derive(Debug, Copy, Clone, Default, AsRefStr)]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
pub enum Compression {
@ -13,7 +13,7 @@ pub enum Compression {
/// Zstandard (Zstd) compression algorithm with a dictionary.
#[strum(serialize = "zstd-dict")]
ZstdWithDictionary,
/// No compression, uncompressed snapshot.
/// No compression.
#[strum(serialize = "uncompressed")]
#[default]
Uncompressed,

View File

@ -1,16 +1,16 @@
use strum::AsRefStr;
#[derive(Debug, Copy, Clone)]
/// Snapshot filters.
/// Static File filters.
pub enum Filters {
/// Snapshot uses filters with [InclusionFilter] and [PerfectHashingFunction].
/// Static File uses filters with [InclusionFilter] and [PerfectHashingFunction].
WithFilters(InclusionFilter, PerfectHashingFunction),
/// Snapshot doesn't use any filters.
/// Static File doesn't use any filters.
WithoutFilters,
}
impl Filters {
/// Returns `true` if snapshot uses filters.
/// Returns `true` if static file uses filters.
pub const fn has_filters(&self) -> bool {
matches!(self, Self::WithFilters(_, _))
}
@ -18,7 +18,7 @@ impl Filters {
#[derive(Debug, Copy, Clone, AsRefStr)]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
/// Snapshot inclusion filter. Also see [Filters].
/// Static File inclusion filter. Also see [Filters].
pub enum InclusionFilter {
#[strum(serialize = "cuckoo")]
/// Cuckoo filter
@ -27,7 +27,7 @@ pub enum InclusionFilter {
#[derive(Debug, Copy, Clone, AsRefStr)]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
/// Snapshot perfect hashing function. Also see [Filters].
/// Static File perfect hashing function. Also see [Filters].
pub enum PerfectHashingFunction {
#[strum(serialize = "fmph")]
/// Fingerprint-Based Minimal Perfect Hash Function

View File

@ -0,0 +1,54 @@
//! StaticFile primitives.
mod compression;
mod filters;
mod segment;
use alloy_primitives::BlockNumber;
pub use compression::Compression;
pub use filters::{Filters, InclusionFilter, PerfectHashingFunction};
pub use segment::{SegmentConfig, SegmentHeader, SegmentRangeInclusive, StaticFileSegment};
/// Default static file block count.
pub const BLOCKS_PER_STATIC_FILE: u64 = 500_000;
/// Highest static file block numbers, per data part.
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
pub struct HighestStaticFiles {
/// Highest static file block of headers, inclusive.
/// If [`None`], no static file is available.
pub headers: Option<BlockNumber>,
/// Highest static file block of receipts, inclusive.
/// If [`None`], no static file is available.
pub receipts: Option<BlockNumber>,
/// Highest static file block of transactions, inclusive.
/// If [`None`], no static file is available.
pub transactions: Option<BlockNumber>,
}
impl HighestStaticFiles {
/// Returns the highest static file if it exists for a segment
pub fn highest(&self, segment: StaticFileSegment) -> Option<BlockNumber> {
match segment {
StaticFileSegment::Headers => self.headers,
StaticFileSegment::Transactions => self.transactions,
StaticFileSegment::Receipts => self.receipts,
}
}
/// Returns a mutable reference to a static file segment
pub fn as_mut(&mut self, segment: StaticFileSegment) -> &mut Option<BlockNumber> {
match segment {
StaticFileSegment::Headers => &mut self.headers,
StaticFileSegment::Transactions => &mut self.transactions,
StaticFileSegment::Receipts => &mut self.receipts,
}
}
}
/// Each static file has a fixed number of blocks. This gives out the range where the requested
/// block is positioned. Used for segment filename.
pub fn find_fixed_range(block: BlockNumber) -> SegmentRangeInclusive {
let start = (block / BLOCKS_PER_STATIC_FILE) * BLOCKS_PER_STATIC_FILE;
SegmentRangeInclusive::new(start, start + BLOCKS_PER_STATIC_FILE - 1)
}

View File

@ -0,0 +1,435 @@
use crate::{
static_file::{Compression, Filters, InclusionFilter},
BlockNumber, TxNumber,
};
use derive_more::Display;
use serde::{Deserialize, Serialize};
use std::{ops::RangeInclusive, str::FromStr};
use strum::{AsRefStr, EnumIter, EnumString};
#[derive(
Debug,
Copy,
Clone,
Eq,
PartialEq,
Hash,
Ord,
PartialOrd,
Deserialize,
Serialize,
EnumString,
EnumIter,
AsRefStr,
Display,
)]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
/// Segment of the data that can be moved to static files.
pub enum StaticFileSegment {
#[strum(serialize = "headers")]
/// Static File segment responsible for the `CanonicalHeaders`, `Headers`,
/// `HeaderTerminalDifficulties` tables.
Headers,
#[strum(serialize = "transactions")]
/// Static File segment responsible for the `Transactions` table.
Transactions,
#[strum(serialize = "receipts")]
/// Static File segment responsible for the `Receipts` table.
Receipts,
}
impl StaticFileSegment {
/// Returns the segment as a string.
pub const fn as_str(&self) -> &'static str {
match self {
StaticFileSegment::Headers => "headers",
StaticFileSegment::Transactions => "transactions",
StaticFileSegment::Receipts => "receipts",
}
}
/// Returns the default configuration of the segment.
pub const fn config(&self) -> SegmentConfig {
let default_config = SegmentConfig {
filters: Filters::WithFilters(
InclusionFilter::Cuckoo,
super::PerfectHashingFunction::Fmph,
),
compression: Compression::Lz4,
};
match self {
StaticFileSegment::Headers => default_config,
StaticFileSegment::Transactions => default_config,
StaticFileSegment::Receipts => default_config,
}
}
/// Returns the number of columns for the segment
pub const fn columns(&self) -> usize {
match self {
StaticFileSegment::Headers => 3,
StaticFileSegment::Transactions => 1,
StaticFileSegment::Receipts => 1,
}
}
/// Returns the default file name for the provided segment and range.
pub fn filename(&self, block_range: &SegmentRangeInclusive) -> String {
// ATTENTION: if changing the name format, be sure to reflect those changes in
// [`Self::parse_filename`].
format!("static_file_{}_{}_{}", self.as_ref(), block_range.start(), block_range.end())
}
/// Returns file name for the provided segment and range, alongisde filters, compression.
pub fn filename_with_configuration(
&self,
filters: Filters,
compression: Compression,
block_range: &SegmentRangeInclusive,
) -> String {
let prefix = self.filename(block_range);
let filters_name = match filters {
Filters::WithFilters(inclusion_filter, phf) => {
format!("{}-{}", inclusion_filter.as_ref(), phf.as_ref())
}
Filters::WithoutFilters => "none".to_string(),
};
// ATTENTION: if changing the name format, be sure to reflect those changes in
// [`Self::parse_filename`.]
format!("{prefix}_{}_{}", filters_name, compression.as_ref())
}
/// Parses a filename into a `StaticFileSegment` and its expected block range.
///
/// The filename is expected to follow the format:
/// "static_file_{segment}_{block_start}_{block_end}". This function checks
/// for the correct prefix ("static_file"), and then parses the segment and the inclusive
/// ranges for blocks. It ensures that the start of each range is less than or equal to the
/// end.
///
/// # Returns
/// - `Some((segment, block_range))` if parsing is successful and all conditions are met.
/// - `None` if any condition fails, such as an incorrect prefix, parsing error, or invalid
/// range.
///
/// # Note
/// This function is tightly coupled with the naming convention defined in [`Self::filename`].
/// Any changes in the filename format in `filename` should be reflected here.
pub fn parse_filename(name: &str) -> Option<(Self, SegmentRangeInclusive)> {
let mut parts = name.split('_');
if !(parts.next() == Some("static") && parts.next() == Some("file")) {
return None
}
let segment = Self::from_str(parts.next()?).ok()?;
let (block_start, block_end) = (parts.next()?.parse().ok()?, parts.next()?.parse().ok()?);
if block_start > block_end {
return None
}
Some((segment, SegmentRangeInclusive::new(block_start, block_end)))
}
/// Returns `true` if the segment is `StaticFileSegment::Headers`.
pub fn is_headers(&self) -> bool {
matches!(self, StaticFileSegment::Headers)
}
}
/// A segment header that contains information common to all segments. Used for storage.
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Clone)]
pub struct SegmentHeader {
/// Defines the expected block range for a static file segment. This attribute is crucial for
/// scenarios where the file contains no data, allowing for a representation beyond a
/// simple `start..=start` range. It ensures clarity in differentiating between an empty file
/// and a file with a single block numbered 0.
expected_block_range: SegmentRangeInclusive,
/// Block range of data on the static file segment
block_range: Option<SegmentRangeInclusive>,
/// Transaction range of data of the static file segment
tx_range: Option<SegmentRangeInclusive>,
/// Segment type
segment: StaticFileSegment,
}
impl SegmentHeader {
/// Returns [`SegmentHeader`].
pub fn new(
expected_block_range: SegmentRangeInclusive,
block_range: Option<SegmentRangeInclusive>,
tx_range: Option<SegmentRangeInclusive>,
segment: StaticFileSegment,
) -> Self {
Self { expected_block_range, block_range, tx_range, segment }
}
/// Returns the static file segment kind.
pub fn segment(&self) -> StaticFileSegment {
self.segment
}
/// Returns the block range.
pub fn block_range(&self) -> Option<&SegmentRangeInclusive> {
self.block_range.as_ref()
}
/// Returns the transaction range.
pub fn tx_range(&self) -> Option<&SegmentRangeInclusive> {
self.tx_range.as_ref()
}
/// The expected block start of the segment.
pub fn expected_block_start(&self) -> BlockNumber {
self.expected_block_range.start()
}
/// The expected block end of the segment.
pub fn expected_block_end(&self) -> BlockNumber {
self.expected_block_range.end()
}
/// Returns the first block number of the segment.
pub fn block_start(&self) -> Option<BlockNumber> {
self.block_range.as_ref().map(|b| b.start())
}
/// Returns the last block number of the segment.
pub fn block_end(&self) -> Option<BlockNumber> {
self.block_range.as_ref().map(|b| b.end())
}
/// Returns the first transaction number of the segment.
pub fn tx_start(&self) -> Option<TxNumber> {
self.tx_range.as_ref().map(|t| t.start())
}
/// Returns the last transaction number of the segment.
pub fn tx_end(&self) -> Option<TxNumber> {
self.tx_range.as_ref().map(|t| t.end())
}
/// Number of transactions.
pub fn tx_len(&self) -> Option<u64> {
self.tx_range.as_ref().map(|r| (r.end() + 1) - r.start())
}
/// Number of blocks.
pub fn block_len(&self) -> Option<u64> {
self.block_range.as_ref().map(|r| (r.end() + 1) - r.start())
}
/// Increments block end range depending on segment
pub fn increment_block(&mut self) -> BlockNumber {
if let Some(block_range) = &mut self.block_range {
block_range.end += 1;
block_range.end
} else {
self.block_range = Some(SegmentRangeInclusive::new(
self.expected_block_start(),
self.expected_block_start(),
));
self.expected_block_start()
}
}
/// Increments tx end range depending on segment
pub fn increment_tx(&mut self) {
match self.segment {
StaticFileSegment::Headers => (),
StaticFileSegment::Transactions | StaticFileSegment::Receipts => {
if let Some(tx_range) = &mut self.tx_range {
tx_range.end += 1;
} else {
self.tx_range = Some(SegmentRangeInclusive::new(0, 0));
}
}
}
}
/// Removes `num` elements from end of tx or block range.
pub fn prune(&mut self, num: u64) {
match self.segment {
StaticFileSegment::Headers => {
if let Some(range) = &mut self.block_range {
if num > range.end {
self.block_range = None;
} else {
range.end = range.end.saturating_sub(num);
}
};
}
StaticFileSegment::Transactions | StaticFileSegment::Receipts => {
if let Some(range) = &mut self.tx_range {
if num > range.end {
self.tx_range = None;
} else {
range.end = range.end.saturating_sub(num);
}
};
}
};
}
/// Sets a new block_range.
pub fn set_block_range(&mut self, block_start: BlockNumber, block_end: BlockNumber) {
if let Some(block_range) = &mut self.block_range {
block_range.start = block_start;
block_range.end = block_end;
} else {
self.block_range = Some(SegmentRangeInclusive::new(block_start, block_end))
}
}
/// Sets a new tx_range.
pub fn set_tx_range(&mut self, tx_start: TxNumber, tx_end: TxNumber) {
if let Some(tx_range) = &mut self.tx_range {
tx_range.start = tx_start;
tx_range.end = tx_end;
} else {
self.tx_range = Some(SegmentRangeInclusive::new(tx_start, tx_end))
}
}
/// Returns the row offset which depends on whether the segment is block or transaction based.
pub fn start(&self) -> Option<u64> {
match self.segment {
StaticFileSegment::Headers => self.block_start(),
StaticFileSegment::Transactions | StaticFileSegment::Receipts => self.tx_start(),
}
}
}
/// Configuration used on the segment.
#[derive(Debug, Clone, Copy)]
pub struct SegmentConfig {
/// Inclusion filters used on the segment
pub filters: Filters,
/// Compression used on the segment
pub compression: Compression,
}
/// Helper type to handle segment transaction and block INCLUSIVE ranges.
///
/// They can be modified on a hot loop, which makes the `std::ops::RangeInclusive` a poor fit.
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Clone, Copy)]
pub struct SegmentRangeInclusive {
start: u64,
end: u64,
}
impl SegmentRangeInclusive {
/// Creates a new [`SegmentRangeInclusive`]
pub fn new(start: u64, end: u64) -> Self {
Self { start, end }
}
/// Start of the inclusive range
pub fn start(&self) -> u64 {
self.start
}
/// End of the inclusive range
pub fn end(&self) -> u64 {
self.end
}
}
impl std::fmt::Display for SegmentRangeInclusive {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}..={}", self.start, self.end)
}
}
impl From<RangeInclusive<u64>> for SegmentRangeInclusive {
fn from(value: RangeInclusive<u64>) -> Self {
SegmentRangeInclusive { start: *value.start(), end: *value.end() }
}
}
impl From<&SegmentRangeInclusive> for RangeInclusive<u64> {
fn from(value: &SegmentRangeInclusive) -> Self {
value.start()..=value.end()
}
}
impl From<SegmentRangeInclusive> for RangeInclusive<u64> {
fn from(value: SegmentRangeInclusive) -> Self {
(&value).into()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_filename() {
let test_vectors = [
(StaticFileSegment::Headers, 2..=30, "static_file_headers_2_30", None),
(StaticFileSegment::Receipts, 30..=300, "static_file_receipts_30_300", None),
(
StaticFileSegment::Transactions,
1_123_233..=11_223_233,
"static_file_transactions_1123233_11223233",
None,
),
(
StaticFileSegment::Headers,
2..=30,
"static_file_headers_2_30_cuckoo-fmph_lz4",
Some((
Compression::Lz4,
Filters::WithFilters(
InclusionFilter::Cuckoo,
crate::static_file::PerfectHashingFunction::Fmph,
),
)),
),
(
StaticFileSegment::Headers,
2..=30,
"static_file_headers_2_30_cuckoo-fmph_zstd",
Some((
Compression::Zstd,
Filters::WithFilters(
InclusionFilter::Cuckoo,
crate::static_file::PerfectHashingFunction::Fmph,
),
)),
),
(
StaticFileSegment::Headers,
2..=30,
"static_file_headers_2_30_cuckoo-fmph_zstd-dict",
Some((
Compression::ZstdWithDictionary,
Filters::WithFilters(
InclusionFilter::Cuckoo,
crate::static_file::PerfectHashingFunction::Fmph,
),
)),
),
];
for (segment, block_range, filename, configuration) in test_vectors {
let block_range: SegmentRangeInclusive = block_range.into();
if let Some((compression, filters)) = configuration {
assert_eq!(
segment.filename_with_configuration(filters, compression, &block_range,),
filename
);
} else {
assert_eq!(segment.filename(&block_range), filename);
}
assert_eq!(StaticFileSegment::parse_filename(filename), Some((segment, block_range)));
}
assert_eq!(StaticFileSegment::parse_filename("static_file_headers_2"), None);
assert_eq!(StaticFileSegment::parse_filename("static_file_headers_"), None);
}
}

View File

@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
/// Account storage entry.
///
/// `key` is the subkey when used as a value in the `StorageChangeSet` table.
/// `key` is the subkey when used as a value in the `StorageChangeSets` table.
#[derive_arbitrary(compact)]
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
pub struct StorageEntry {

View File

@ -1,5 +1,4 @@
use crate::{Address, B256};
use alloy_primitives::U256;
use crate::{Address, B256, U256};
use alloy_rlp::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper};
use reth_codecs::{main_codec, Compact};
use std::{

View File

@ -1,5 +1,5 @@
use super::access_list::AccessList;
use crate::{keccak256, Bytes, ChainId, Signature, TransactionKind, TxType, TxValue, B256};
use crate::{keccak256, Bytes, ChainId, Signature, TransactionKind, TxType, B256, U256};
use alloy_rlp::{length_of_length, Decodable, Encodable, Header};
use bytes::BytesMut;
use reth_codecs::{main_codec, Compact};
@ -46,7 +46,7 @@ pub struct TxEip1559 {
/// be transferred to the message calls recipient or,
/// in the case of contract creation, as an endowment
/// to the newly created account; formally Tv.
pub value: TxValue,
pub value: U256,
/// The accessList specifies a list of addresses and storage keys;
/// these addresses and storage keys are added into the `accessed_addresses`
/// and `accessed_storage_keys` global sets (introduced in EIP-2929).
@ -188,7 +188,7 @@ impl TxEip1559 {
mem::size_of::<u128>() + // max_fee_per_gas
mem::size_of::<u128>() + // max_priority_fee_per_gas
self.to.size() + // to
mem::size_of::<TxValue>() + // value
mem::size_of::<U256>() + // value
self.access_list.size() + // access_list
self.input.len() // input
}
@ -244,7 +244,7 @@ mod tests {
nonce: 0x42,
gas_limit: 44386,
to: TransactionKind::Call( hex!("6069a6c32cf691f5982febae4faf8a6f3ab2f0f6").into()),
value: 0_u64.into(),
value: U256::ZERO,
input: hex!("a22cb4650000000000000000000000005eee75727d804a2b13038928d36f8b188945a57a0000000000000000000000000000000000000000000000000000000000000000").into(),
max_fee_per_gas: 0x4a817c800,
max_priority_fee_per_gas: 0x3b9aca00,

View File

@ -1,5 +1,5 @@
use super::access_list::AccessList;
use crate::{keccak256, Bytes, ChainId, Signature, TransactionKind, TxType, TxValue, B256};
use crate::{keccak256, Bytes, ChainId, Signature, TransactionKind, TxType, B256, U256};
use alloy_rlp::{length_of_length, Decodable, Encodable, Header};
use bytes::BytesMut;
use reth_codecs::{main_codec, Compact};
@ -34,7 +34,7 @@ pub struct TxEip2930 {
/// be transferred to the message calls recipient or,
/// in the case of contract creation, as an endowment
/// to the newly created account; formally Tv.
pub value: TxValue,
pub value: U256,
/// The accessList specifies a list of addresses and storage keys;
/// these addresses and storage keys are added into the `accessed_addresses`
/// and `accessed_storage_keys` global sets (introduced in EIP-2929).
@ -58,7 +58,7 @@ impl TxEip2930 {
mem::size_of::<u128>() + // gas_price
mem::size_of::<u64>() + // gas_limit
self.to.size() + // to
mem::size_of::<TxValue>() + // value
mem::size_of::<U256>() + // value
self.access_list.size() + // access_list
self.input.len() // input
}
@ -204,7 +204,7 @@ mod tests {
gas_price: 1,
gas_limit: 2,
to: TransactionKind::Create,
value: 3_u64.into(),
value: U256::from(3),
input: Bytes::from(vec![1, 2]),
access_list: Default::default(),
});
@ -227,7 +227,7 @@ mod tests {
gas_price: 1,
gas_limit: 2,
to: TransactionKind::Call(Address::default()),
value: 3_u64.into(),
value: U256::from(3),
input: Bytes::from(vec![1, 2]),
access_list: Default::default(),
});

View File

@ -1,7 +1,7 @@
use super::access_list::AccessList;
use crate::{
constants::eip4844::DATA_GAS_PER_BLOB, keccak256, Bytes, ChainId, Signature, TransactionKind,
TxType, TxValue, B256,
TxType, B256, U256,
};
use alloy_rlp::{length_of_length, Decodable, Encodable, Header};
use bytes::BytesMut;
@ -60,7 +60,7 @@ pub struct TxEip4844 {
/// be transferred to the message calls recipient or,
/// in the case of contract creation, as an endowment
/// to the newly created account; formally Tv.
pub value: TxValue,
pub value: U256,
/// The accessList specifies a list of addresses and storage keys;
/// these addresses and storage keys are added into the `accessed_addresses`
/// and `accessed_storage_keys` global sets (introduced in EIP-2929).
@ -244,7 +244,7 @@ impl TxEip4844 {
mem::size_of::<u128>() + // max_fee_per_gas
mem::size_of::<u128>() + // max_priority_fee_per_gas
self.to.size() + // to
mem::size_of::<TxValue>() + // value
mem::size_of::<U256>() + // value
self.access_list.size() + // access_list
self.input.len() + // input
self.blob_versioned_hashes.capacity() * mem::size_of::<B256>() + // blob hashes size

View File

@ -1,4 +1,4 @@
use crate::{keccak256, Bytes, ChainId, Signature, TransactionKind, TxType, TxValue, B256};
use crate::{keccak256, Bytes, ChainId, Signature, TransactionKind, TxType, B256, U256};
use alloy_rlp::{length_of_length, Encodable, Header};
use bytes::BytesMut;
use reth_codecs::{main_codec, Compact};
@ -33,7 +33,7 @@ pub struct TxLegacy {
/// be transferred to the message calls recipient or,
/// in the case of contract creation, as an endowment
/// to the newly created account; formally Tv.
pub value: TxValue,
pub value: U256,
/// Input has two uses depending if transaction is Create or Call (if `to` field is None or
/// Some). pub init: An unlimited size byte array specifying the
/// EVM-code for the account initialisation procedure CREATE,
@ -51,7 +51,7 @@ impl TxLegacy {
mem::size_of::<u128>() + // gas_price
mem::size_of::<u64>() + // gas_limit
self.to.size() + // to
mem::size_of::<TxValue>() + // value
mem::size_of::<U256>() + // value
self.input.len() // input
}
@ -191,7 +191,7 @@ mod tests {
gas_price: 0xfa56ea00,
gas_limit: 119902,
to: TransactionKind::Call( hex!("06012c8cf97bead5deae237070f9587f8e7a266d").into()),
value: 0x1c6bf526340000u64.into(),
value: U256::from(0x1c6bf526340000u64),
input: hex!("f7d8c88300000000000000000000000000000000000000000000000000000000000cee6100000000000000000000000000000000000000000000000000000000000ac3e1").into(),
});

View File

@ -1,6 +1,6 @@
use crate::{
compression::{TRANSACTION_COMPRESSOR, TRANSACTION_DECOMPRESSOR},
keccak256, Address, BlockHashOrNumber, Bytes, TxHash, B256,
keccak256, Address, BlockHashOrNumber, Bytes, TxHash, B256, U256,
};
use alloy_rlp::{
Decodable, Encodable, Error as RlpError, Header, EMPTY_LIST_CODE, EMPTY_STRING_CODE,
@ -32,7 +32,6 @@ pub use signature::Signature;
pub use tx_type::{
TxType, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, LEGACY_TX_TYPE_ID,
};
pub use tx_value::TxValue;
pub use variant::TransactionSignedVariant;
mod access_list;
@ -48,7 +47,6 @@ mod pooled;
mod sidecar;
mod signature;
mod tx_type;
mod tx_value;
pub(crate) mod util;
mod variant;
@ -192,7 +190,7 @@ impl Transaction {
}
/// Gets the transaction's value field.
pub fn value(&self) -> TxValue {
pub fn value(&self) -> U256 {
*match self {
Transaction::Legacy(TxLegacy { value, .. }) |
Transaction::Eip2930(TxEip2930 { value, .. }) |
@ -481,7 +479,7 @@ impl Transaction {
}
/// This sets the transaction's value.
pub fn set_value(&mut self, value: TxValue) {
pub fn set_value(&mut self, value: U256) {
match self {
Transaction::Legacy(tx) => tx.value = value,
Transaction::Eip2930(tx) => tx.value = value,
@ -1691,7 +1689,7 @@ mod tests {
to: TransactionKind::Call(
Address::from_str("d3e8763675e4c425df46cc3b5c0f6cbdac396046").unwrap(),
),
value: 1000000000000000_u64.into(),
value: U256::from(1000000000000000u64),
input: Bytes::default(),
});
let signature = Signature {
@ -1713,7 +1711,7 @@ mod tests {
to: TransactionKind::Call(Address::from_slice(
&hex!("d3e8763675e4c425df46cc3b5c0f6cbdac396046")[..],
)),
value: 693361000000000_u64.into(),
value: U256::from(693361000000000u64),
input: Default::default(),
});
let signature = Signature {
@ -1734,7 +1732,7 @@ mod tests {
to: TransactionKind::Call(Address::from_slice(
&hex!("d3e8763675e4c425df46cc3b5c0f6cbdac396046")[..],
)),
value: 1000000000000000_u64.into(),
value: U256::from(1000000000000000u64),
input: Bytes::default(),
});
let signature = Signature {
@ -1756,7 +1754,7 @@ mod tests {
to: TransactionKind::Call(Address::from_slice(
&hex!("61815774383099e24810ab832a5b2a5425c154d5")[..],
)),
value: 3000000000000000000_u64.into(),
value: U256::from(3000000000000000000u64),
input: Default::default(),
access_list: Default::default(),
});
@ -1778,7 +1776,7 @@ mod tests {
to: TransactionKind::Call(Address::from_slice(
&hex!("cf7f9e66af820a19257a2108375b180b0ec49167")[..],
)),
value: 1234_u64.into(),
value: U256::from(1234),
input: Bytes::default(),
});
let signature = Signature {

View File

@ -1,4 +1,4 @@
use crate::{Address, Bytes, TransactionKind, TxType, TxValue, B256};
use crate::{Address, Bytes, TransactionKind, TxType, B256, U256};
use alloy_rlp::{
length_of_length, Decodable, Encodable, Error as DecodeError, Header, EMPTY_STRING_CODE,
};
@ -20,7 +20,7 @@ pub struct TxDeposit {
/// The ETH value to mint on L2.
pub mint: Option<u128>,
/// The ETH value to send to the recipient account.
pub value: TxValue,
pub value: U256,
/// The gas limit for the L2 transaction.
pub gas_limit: u64,
/// Field indicating if this transaction is exempt from the L2 gas limit.
@ -38,7 +38,7 @@ impl TxDeposit {
mem::size_of::<Address>() + // from
self.to.size() + // to
mem::size_of::<Option<u128>>() + // mint
mem::size_of::<TxValue>() + // value
mem::size_of::<U256>() + // value
mem::size_of::<u64>() + // gas_limit
mem::size_of::<bool>() + // is_system_transaction
self.input.len() // input
@ -171,7 +171,7 @@ mod tests {
from: Address::default(),
to: TransactionKind::default(),
mint: Some(100),
value: TxValue::default(),
value: U256::default(),
gas_limit: 50000,
is_system_transaction: true,
input: Bytes::default(),
@ -191,7 +191,7 @@ mod tests {
from: Address::default(),
to: TransactionKind::default(),
mint: Some(100),
value: TxValue::default(),
value: U256::default(),
gas_limit: 50000,
is_system_transaction: true,
input: Bytes::default(),
@ -213,7 +213,7 @@ mod tests {
from: Address::default(),
to: TransactionKind::default(),
mint: Some(100),
value: TxValue::default(),
value: U256::default(),
gas_limit: 50000,
is_system_transaction: true,
input: Bytes::default(),

View File

@ -1,128 +0,0 @@
use crate::{ruint::UintTryFrom, U256};
use alloy_rlp::{RlpDecodableWrapper, RlpEncodableWrapper};
use reth_codecs::{add_arbitrary_tests, Compact};
use serde::{Deserialize, Serialize};
/// TxValue is the type of the `value` field in the various Ethereum transactions structs.
///
/// While the field is 256 bits, for many chains it's not possible for the field to use
/// this full precision, hence we use a wrapper type to allow for overriding of encoding.
#[add_arbitrary_tests(compact, rlp)]
#[derive(
Default,
Debug,
Copy,
Clone,
Hash,
PartialEq,
Eq,
Serialize,
Deserialize,
RlpEncodableWrapper,
RlpDecodableWrapper,
)]
pub struct TxValue(U256);
impl From<TxValue> for U256 {
#[inline]
fn from(value: TxValue) -> U256 {
value.0
}
}
impl<T> From<T> for TxValue
where
U256: UintTryFrom<T>,
{
#[inline]
#[track_caller]
fn from(value: T) -> Self {
Self(U256::uint_try_from(value).unwrap())
}
}
/// As ethereum circulation on mainnet is around 120mil eth as of 2022 that is around
/// 120000000000000000000000000 wei we are safe to use u128 for TxValue's encoding
/// as its max number is 340282366920938463463374607431768211455.
/// This optimization should be disabled for chains such as Optimism, where
/// some tx values may require more than 128-bit precision.
impl Compact for TxValue {
#[inline]
fn to_compact<B>(self, buf: &mut B) -> usize
where
B: bytes::BufMut + AsMut<[u8]>,
{
#[cfg(feature = "optimism")]
{
self.0.to_compact(buf)
}
#[cfg(not(feature = "optimism"))]
{
self.0.to::<u128>().to_compact(buf)
}
}
#[inline]
fn from_compact(buf: &[u8], identifier: usize) -> (Self, &[u8]) {
#[cfg(feature = "optimism")]
{
let (i, buf) = U256::from_compact(buf, identifier);
(TxValue(i), buf)
}
#[cfg(not(feature = "optimism"))]
{
let (i, buf) = u128::from_compact(buf, identifier);
(TxValue::from(i), buf)
}
}
}
impl std::fmt::Display for TxValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl PartialEq<U256> for TxValue {
fn eq(&self, other: &U256) -> bool {
self.0.eq(other)
}
}
#[cfg(any(test, feature = "arbitrary"))]
impl<'a> arbitrary::Arbitrary<'a> for TxValue {
#[inline]
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
#[cfg(feature = "optimism")]
{
U256::arbitrary(u).map(Self)
}
#[cfg(not(feature = "optimism"))]
{
u128::arbitrary(u).map(Self::from)
}
}
}
#[cfg(any(test, feature = "arbitrary"))]
impl proptest::arbitrary::Arbitrary for TxValue {
type Parameters = <U256 as proptest::arbitrary::Arbitrary>::Parameters;
#[inline]
fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
use proptest::strategy::Strategy;
#[cfg(feature = "optimism")]
{
proptest::prelude::any::<U256>().prop_map(Self)
}
#[cfg(not(feature = "optimism"))]
{
proptest::prelude::any::<u128>().prop_map(Self::from)
}
}
#[cfg(feature = "optimism")]
type Strategy = proptest::arbitrary::Mapped<U256, Self>;
#[cfg(not(feature = "optimism"))]
type Strategy = proptest::arbitrary::Mapped<u128, Self>;
}