mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat: add IntegerList type Elias-Fano (#111)
* add elias * docs: should not be empty * change models doc * add arbitrary feature * add simple test * add test-utils to primitives * cfg arbitrary
This commit is contained in:
31
Cargo.lock
generated
31
Cargo.lock
generated
@ -58,6 +58,15 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arbitrary"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29d47fbf90d5149a107494b15a7dc8d69b351be2db3bb9691740e88ec17fd880"
|
||||||
|
dependencies = [
|
||||||
|
"derive_arbitrary",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayref"
|
name = "arrayref"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
@ -759,6 +768,17 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_arbitrary"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4903dff04948f22033ca30232ab8eca2c3fc4c913a8b6a34ee5199699814817f"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_more"
|
name = "derive_more"
|
||||||
version = "0.99.17"
|
version = "0.99.17"
|
||||||
@ -2573,6 +2593,7 @@ dependencies = [
|
|||||||
name = "reth-primitives"
|
name = "reth-primitives"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"arbitrary",
|
||||||
"bytes",
|
"bytes",
|
||||||
"crc",
|
"crc",
|
||||||
"ethers-core",
|
"ethers-core",
|
||||||
@ -2583,6 +2604,7 @@ dependencies = [
|
|||||||
"reth-rlp",
|
"reth-rlp",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"sucds",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tiny-keccak 0.3.0",
|
"tiny-keccak 0.3.0",
|
||||||
]
|
]
|
||||||
@ -3319,6 +3341,15 @@ version = "2.4.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sucds"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c1c7f814471a34d2355f9eb25ef3517ec491ac243612b1c83137739998c5444"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.103"
|
version = "1.0.103"
|
||||||
|
|||||||
21
crates/interfaces/src/db/codecs/fuzz/inputs.rs
Normal file
21
crates/interfaces/src/db/codecs/fuzz/inputs.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
//! Curates the input coming from the fuzzer for certain types.
|
||||||
|
|
||||||
|
use reth_primitives::IntegerList;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Makes sure that the list provided by the fuzzer is not empty and pre-sorted
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
||||||
|
pub struct IntegerListInput(pub Vec<u64>);
|
||||||
|
|
||||||
|
impl From<IntegerListInput> for IntegerList {
|
||||||
|
fn from(list: IntegerListInput) -> IntegerList {
|
||||||
|
let mut v = list.0;
|
||||||
|
|
||||||
|
// Empty lists are not supported by `IntegerList`, so we want to skip these cases.
|
||||||
|
if v.is_empty() {
|
||||||
|
return vec![1u64].into()
|
||||||
|
}
|
||||||
|
v.sort();
|
||||||
|
v.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,9 +1,14 @@
|
|||||||
//! Implements fuzzing targets to be used by test-fuzz
|
//! Implements fuzzing targets to be used by test-fuzz
|
||||||
|
|
||||||
|
mod inputs;
|
||||||
|
|
||||||
/// Fuzzer generates a random instance of the object and proceeds to encode and decode it. It then
|
/// Fuzzer generates a random instance of the object and proceeds to encode and decode it. It then
|
||||||
/// makes sure that it matches the original object.
|
/// makes sure that it matches the original object.
|
||||||
macro_rules! impl_fuzzer {
|
///
|
||||||
($($name:tt),+) => {
|
/// Some types like [`IntegerList`] might have some restrictons on how they're fuzzed. For example,
|
||||||
|
/// the list is assumed to be sorted before creating the object.
|
||||||
|
macro_rules! impl_fuzzer_with_input {
|
||||||
|
($(($name:tt, $input_type:tt)),+) => {
|
||||||
$(
|
$(
|
||||||
/// Macro generated module to be used by test-fuzz and `bench` if it applies.
|
/// Macro generated module to be used by test-fuzz and `bench` if it applies.
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
@ -12,8 +17,13 @@ macro_rules! impl_fuzzer {
|
|||||||
use reth_primitives::$name;
|
use reth_primitives::$name;
|
||||||
use crate::db::table;
|
use crate::db::table;
|
||||||
|
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use super::inputs::*;
|
||||||
|
|
||||||
/// Encodes and decodes table types returning its encoded size and the decoded object.
|
/// Encodes and decodes table types returning its encoded size and the decoded object.
|
||||||
pub fn encode_and_decode(obj: $name) -> (usize, $name) {
|
/// This method is used for benchmarking, so its parameter should be the actual type that is being tested.
|
||||||
|
pub fn encode_and_decode(obj: $name) -> (usize, $name)
|
||||||
|
{
|
||||||
let data = table::Encode::encode(obj);
|
let data = table::Encode::encode(obj);
|
||||||
let size = data.len();
|
let size = data.len();
|
||||||
(size, table::Decode::decode(data).expect("failed to decode"))
|
(size, table::Decode::decode(data).expect("failed to decode"))
|
||||||
@ -22,13 +32,14 @@ macro_rules! impl_fuzzer {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[test_fuzz::test_fuzz]
|
#[test_fuzz::test_fuzz]
|
||||||
pub fn fuzz(obj: $name) {
|
pub fn fuzz(obj: $input_type) {
|
||||||
|
let obj: $name = obj.into();
|
||||||
assert!(encode_and_decode(obj.clone()).1 == obj );
|
assert!(encode_and_decode(obj.clone()).1 == obj );
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn test() {
|
pub fn test() {
|
||||||
encode_and_decode($name::default());
|
fuzz($input_type::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,4 +47,16 @@ macro_rules! impl_fuzzer {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fuzzer generates a random instance of the object and proceeds to encode and decode it. It then
|
||||||
|
/// makes sure that it matches the original object.
|
||||||
|
macro_rules! impl_fuzzer {
|
||||||
|
($($name:tt),+) => {
|
||||||
|
$(
|
||||||
|
impl_fuzzer_with_input!(($name, $name));
|
||||||
|
)+
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
impl_fuzzer!(Header, Account);
|
impl_fuzzer!(Header, Account);
|
||||||
|
|
||||||
|
impl_fuzzer_with_input!((IntegerList, IntegerListInput));
|
||||||
22
crates/interfaces/src/db/models/integer_list.rs
Normal file
22
crates/interfaces/src/db/models/integer_list.rs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
//! Implements [`Encode`] and [`Decode`] for [`IntegerList`]
|
||||||
|
|
||||||
|
use crate::db::{
|
||||||
|
error::Error,
|
||||||
|
table::{Decode, Encode},
|
||||||
|
};
|
||||||
|
use bytes::Bytes;
|
||||||
|
use reth_primitives::IntegerList;
|
||||||
|
|
||||||
|
impl Encode for IntegerList {
|
||||||
|
type Encoded = Vec<u8>;
|
||||||
|
|
||||||
|
fn encode(self) -> Self::Encoded {
|
||||||
|
self.to_bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decode for IntegerList {
|
||||||
|
fn decode<B: Into<Bytes>>(value: B) -> Result<Self, Error> {
|
||||||
|
IntegerList::from_bytes(&value.into()).map_err(|e| Error::Decode(eyre::eyre!("{e}")))
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
//! Implements data structures specific to the database
|
//! Implements data structures specific to the database
|
||||||
|
|
||||||
pub mod blocks;
|
pub mod blocks;
|
||||||
|
pub mod integer_list;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
//! Declaration of all Database tables.
|
//! Declaration of all Database tables.
|
||||||
|
|
||||||
use crate::db::models::blocks::{BlockNumHash, HeaderHash, NumTransactions, NumTxesInBlock};
|
use crate::db::models::blocks::{BlockNumHash, HeaderHash, NumTransactions, NumTxesInBlock};
|
||||||
use reth_primitives::{Account, Address, BlockNumber, Header, Receipt};
|
use reth_primitives::{Account, Address, BlockNumber, Header, IntegerList, Receipt};
|
||||||
|
|
||||||
/// Enum for the type of table present in libmdbx.
|
/// Enum for the type of table present in libmdbx.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -97,6 +97,12 @@ table!(Config => ConfigKey => ConfigValue);
|
|||||||
|
|
||||||
table!(SyncStage => StageId => BlockNumber);
|
table!(SyncStage => StageId => BlockNumber);
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Alias Types
|
||||||
|
|
||||||
|
type TxNumberList = IntegerList;
|
||||||
|
type TxNumber = u64;
|
||||||
|
|
||||||
//
|
//
|
||||||
// TODO: Temporary types, until they're properly defined alongside with the Encode and Decode Trait
|
// TODO: Temporary types, until they're properly defined alongside with the Encode and Decode Trait
|
||||||
//
|
//
|
||||||
@ -107,9 +113,7 @@ type ConfigValue = Vec<u8>;
|
|||||||
type BlockNumHashTxNumber = Vec<u8>;
|
type BlockNumHashTxNumber = Vec<u8>;
|
||||||
type RlpTotalDifficulty = Vec<u8>;
|
type RlpTotalDifficulty = Vec<u8>;
|
||||||
type RlpTxBody = Vec<u8>;
|
type RlpTxBody = Vec<u8>;
|
||||||
type TxNumber = u64; // TODO check size
|
|
||||||
type PlainStateKey = Address; // TODO new type will have to account for address_incarna_skey as well
|
type PlainStateKey = Address; // TODO new type will have to account for address_incarna_skey as well
|
||||||
type TxNumberList = Vec<u8>;
|
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
type Address_StorageKey = Vec<u8>;
|
type Address_StorageKey = Vec<u8>;
|
||||||
type AccountBeforeTx = Vec<u8>;
|
type AccountBeforeTx = Vec<u8>;
|
||||||
|
|||||||
@ -25,7 +25,10 @@ maplit = "1"
|
|||||||
bytes = "1.2"
|
bytes = "1.2"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
|
sucds = "0.5.0"
|
||||||
|
arbitrary = { version = "1.1.7", features = ["derive"], optional = true}
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
arbitrary = { version = "1.1.7", features = ["derive"]}
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
hex-literal = "0.3"
|
hex-literal = "0.3"
|
||||||
|
|||||||
13
crates/primitives/src/error.rs
Normal file
13
crates/primitives/src/error.rs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
//! Primitive errors
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// Primitives error type.
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
/// Input provided is invalid.
|
||||||
|
#[error("Input provided is invalid.")]
|
||||||
|
InvalidInput,
|
||||||
|
/// Failed to deserialize data into type.
|
||||||
|
#[error("Failed to deserialize data into type.")]
|
||||||
|
FailedDeserialize,
|
||||||
|
}
|
||||||
127
crates/primitives/src/integer_list.rs
Normal file
127
crates/primitives/src/integer_list.rs
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
use crate::error::Error;
|
||||||
|
use serde::{
|
||||||
|
de::{Unexpected, Visitor},
|
||||||
|
Deserialize, Deserializer, Serialize, Serializer,
|
||||||
|
};
|
||||||
|
use std::ops::Deref;
|
||||||
|
use sucds::{EliasFano, Searial};
|
||||||
|
|
||||||
|
/// Uses EliasFano to hold a list of integers. It provides really good compression with the
|
||||||
|
/// capability to access its elements without decoding it.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct IntegerList(pub EliasFano);
|
||||||
|
|
||||||
|
impl Deref for IntegerList {
|
||||||
|
type Target = EliasFano;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntegerList {
|
||||||
|
/// Creates an IntegerList from a list of integers. `usize` is safe to use since
|
||||||
|
/// [`sucds::EliasFano`] restricts its compilation to 64bits.
|
||||||
|
///
|
||||||
|
/// List should be pre-sorted and not empty.
|
||||||
|
pub fn new<T: AsRef<[usize]>>(list: T) -> Result<Self, Error> {
|
||||||
|
Ok(Self(EliasFano::from_ints(list.as_ref()).map_err(|_| Error::InvalidInput)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserializes a sequence of bytes into a proper [`IntegerList`].
|
||||||
|
pub fn from_bytes(data: &[u8]) -> Result<Self, Error> {
|
||||||
|
Ok(Self(EliasFano::deserialize_from(data).map_err(|_| Error::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(EliasFano::from_ints(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,
|
||||||
|
{
|
||||||
|
serializer.serialize_bytes(&self.to_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 byte array")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: serde::de::Error,
|
||||||
|
{
|
||||||
|
IntegerList::from_bytes(v)
|
||||||
|
.map_err(|_| serde::de::Error::invalid_type(Unexpected::Bytes(v), &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};
|
||||||
|
|
||||||
|
#[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();
|
||||||
|
Ok(Self(EliasFano::from_ints(&nums).map_err(|_| arbitrary::Error::IncorrectFormat)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_integer_list() {
|
||||||
|
let original_list = [1, 2, 3];
|
||||||
|
|
||||||
|
let ef_list = IntegerList::new(&original_list).unwrap();
|
||||||
|
|
||||||
|
assert!(ef_list.iter(0).collect::<Vec<usize>>() == original_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_integer_list_serialization() {
|
||||||
|
let original_list = [1, 2, 3];
|
||||||
|
let ef_list = IntegerList::new(&original_list).unwrap();
|
||||||
|
|
||||||
|
let blist = ef_list.to_bytes();
|
||||||
|
assert!(IntegerList::from_bytes(&blist).unwrap() == ef_list)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,7 +10,9 @@
|
|||||||
mod account;
|
mod account;
|
||||||
mod block;
|
mod block;
|
||||||
mod chain;
|
mod chain;
|
||||||
|
mod error;
|
||||||
mod header;
|
mod header;
|
||||||
|
mod integer_list;
|
||||||
mod jsonu256;
|
mod jsonu256;
|
||||||
mod log;
|
mod log;
|
||||||
mod receipt;
|
mod receipt;
|
||||||
@ -20,6 +22,7 @@ pub use account::Account;
|
|||||||
pub use block::{Block, BlockLocked};
|
pub use block::{Block, BlockLocked};
|
||||||
pub use chain::Chain;
|
pub use chain::Chain;
|
||||||
pub use header::{Header, HeaderLocked};
|
pub use header::{Header, HeaderLocked};
|
||||||
|
pub use integer_list::IntegerList;
|
||||||
pub use jsonu256::JsonU256;
|
pub use jsonu256::JsonU256;
|
||||||
pub use log::Log;
|
pub use log::Log;
|
||||||
pub use receipt::Receipt;
|
pub use receipt::Receipt;
|
||||||
|
|||||||
Reference in New Issue
Block a user