mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
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:
@ -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
|
||||
|
||||
250
crates/primitives/benches/integer_list.rs
Normal file
250
crates/primitives/benches/integer_list.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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};
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
@ -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))),
|
||||
);
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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,
|
||||
@ -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
|
||||
54
crates/primitives/src/static_file/mod.rs
Normal file
54
crates/primitives/src/static_file/mod.rs
Normal 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)
|
||||
}
|
||||
435
crates/primitives/src/static_file/segment.rs
Normal file
435
crates/primitives/src/static_file/segment.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
@ -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::{
|
||||
|
||||
@ -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 call’s 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,
|
||||
|
||||
@ -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 call’s 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(),
|
||||
});
|
||||
|
||||
@ -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 call’s 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
|
||||
|
||||
@ -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 call’s 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(),
|
||||
});
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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>;
|
||||
}
|
||||
Reference in New Issue
Block a user