Skip to content

Commit

Permalink
lib: move Rayon wrappers to lib crate (#380)
Browse files Browse the repository at this point in the history
The Rayon wrapper methods are not specific to the cf-solana crate.
Move them to lib so that any code in the future can easily use them if
necessary.
  • Loading branch information
mina86 authored Aug 29, 2024
1 parent d7b7df6 commit ef5d6aa
Show file tree
Hide file tree
Showing 10 changed files with 853 additions and 120 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion common/cf-solana/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ ibc-core-host.workspace = true
ibc-primitives.workspace = true
ibc-proto.workspace = true
prost = { workspace = true, features = ["prost-derive"] }
rayon = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
solana-program = { workspace = true, optional = true }

Expand All @@ -44,6 +43,7 @@ solana-program-2 = { package = "solana-program", git = "https://github.com/Compo
lib = { workspace = true, features = ["test_utils"] }

[features]
rayon = ["lib/rayon"]
solana-program = [
"dep:solana-program",
"lib/solana-program",
Expand Down
46 changes: 46 additions & 0 deletions common/cf-solana/src/blake3.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
pub use ::blake3::Hasher;

use crate::types::Hash;

/// Calculates Blake3 hash of given byte slice.
///
/// When `solana-program` or `solana-program-2` feature is enabled and
/// building a solana program, this is using Solana’s `sol_blake3` syscall.
/// Otherwise, the calculation is done by `blake3` crate.
#[allow(dead_code)]
pub fn hash(bytes: &[u8]) -> Hash {
if cfg!(target_os = "solana-program") &&
(cfg!(feature = "solana-program") ||
cfg!(feature = "solana-program-2"))
{
hashv(&[bytes])
} else {
Hash(::blake3::hash(bytes).into())
}
}

/// Calculates Blake3 hash of concatenation of given byte slices.
///
/// When `solana` or `solana2` feature is enabled and building a Solana
/// program, this is using Solana’s `sol_blake3` syscall. Otherwise, the
/// calculation is done by `blake3` crate.
#[allow(dead_code)]
pub fn hashv(slices: &[&[u8]]) -> Hash {
#[cfg(all(target_os = "solana-program", feature = "solana-program-2"))]
return Hash(solana_program_2::blake3::hashv(slices).0);
#[cfg(all(
target_os = "solana-program",
feature = "solana-program",
not(feature = "solana-program-2")
))]
return Hash(solana_program::blake3::hashv(slices).0);

#[allow(dead_code)]
{
let mut hasher = Hasher::default();
for bytes in slices {
hasher.update(bytes);
}
hasher.finalize().into()
}
}
2 changes: 1 addition & 1 deletion common/cf-solana/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ extern crate alloc;
#[cfg(any(feature = "std", test))]
extern crate std;

mod blake3;
mod client;
mod consensus;
mod header;
Expand All @@ -14,7 +15,6 @@ pub mod proto;
#[cfg(feature = "serde")]
mod serde_impl;
pub mod types;
mod utils;

pub use client::impls::{CommonContext, Neighbourhood};
pub use client::ClientState;
Expand Down
16 changes: 8 additions & 8 deletions common/cf-solana/src/proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@ pub use cf_guest::proof::{
generate_for_trie, verify_for_trie, GenerateError, IbcProof, VerifyError,
};
use lib::hash::CryptoHash;
#[cfg(all(feature = "rayon", not(miri)))]
use rayon::prelude::*;
use lib::par::prelude::*;

#[cfg(test)]
mod tests;

use crate::types::{Hash, PubKey};
use crate::utils::{blake3, chunks, sort_unstable_by};

/// The fanout of a accounts delta Merkle tree.
///
Expand Down Expand Up @@ -78,7 +76,7 @@ impl MerkleProof {
accounts: &mut [(PubKey, Hash)],
pubkey: &PubKey,
) -> Option<(Hash, MerkleProof)> {
sort_unstable_by(accounts, |a, b| a.0.cmp(&b.0));
lib::par::sort_unstable_by(accounts, |a, b| a.0.cmp(&b.0));

let pos =
accounts.binary_search_by_key(&pubkey, |item| &item.0).ok()?;
Expand Down Expand Up @@ -285,7 +283,9 @@ impl AccountHashData {
pub fn key(&self) -> &PubKey { self.get::<32>(self.0.len() - 32).into() }

/// Returns hash of the account.
pub fn calculate_hash(&self) -> Hash { blake3::hash(self.0.as_slice()) }
pub fn calculate_hash(&self) -> Hash {
crate::blake3::hash(self.0.as_slice())
}

/// Returns `N`-byte long fragment of the account’s hash data starting at
/// index `start`.
Expand Down Expand Up @@ -492,7 +492,7 @@ pub fn hash_account(
return Hash::default();
}

let mut hasher = blake3::Hasher::default();
let mut hasher = crate::blake3::Hasher::default();

// allocate a buffer on the stack that's big enough
// to hold a token account or a stake account
Expand Down Expand Up @@ -530,7 +530,7 @@ pub fn hash_account(
/// we reimplement it because that method takes ownership of hashes which is
/// something we need to keep.
fn compute_merkle_root(accounts: &mut [(PubKey, Hash)]) -> Hash {
let mut hashes: Vec<Hash> = chunks(accounts, MERKLE_FANOUT)
let mut hashes: Vec<Hash> = lib::par::chunks(accounts, MERKLE_FANOUT)
.map(|chunk| {
let mut hasher = CryptoHash::builder();
for item in chunk {
Expand Down Expand Up @@ -593,7 +593,7 @@ fn generate_merkle_proof(
}

fn compute_hashes_at_next_level(hashes: &[Hash]) -> Vec<Hash> {
chunks(hashes, MERKLE_FANOUT)
lib::par::chunks(hashes, MERKLE_FANOUT)
.map(|chunk| {
let mut hasher = CryptoHash::builder();
for hash in chunk {
Expand Down
92 changes: 0 additions & 92 deletions common/cf-solana/src/utils.rs

This file was deleted.

1 change: 1 addition & 0 deletions common/lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ borsh = { workspace = true, optional = true }
bs58 = { workspace = true, optional = true }
bytemuck = { workspace = true, features = ["derive"] }
derive_more.workspace = true
rayon = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
sha2.workspace = true
solana-program = { workspace = true, optional = true }
Expand Down
1 change: 1 addition & 0 deletions common/lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ extern crate alloc;
extern crate std;

pub mod hash;
pub mod par;
#[cfg(any(feature = "test_utils", test))]
pub mod test_utils;
pub mod u3;
71 changes: 71 additions & 0 deletions common/lib/src/par.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#[cfg(all(feature = "rayon", not(miri)))]
use rayon::prelude::*;

pub mod prelude {
#[cfg(all(feature = "rayon", not(miri)))]
pub use rayon::iter::ParallelIterator;
}

#[cfg(all(feature = "rayon", not(miri)))]
pub type Chunks<'a, T> = rayon::slice::Chunks<'a, T>;
#[cfg(any(not(feature = "rayon"), miri))]
pub type Chunks<'a, T> = core::slice::Chunks<'a, T>;

/// Splits array into `count`-element chunks.
///
/// It uses conditional compilation and either uses Rayon’s `par_chunks` method
/// (to allow parallelisation of the chunk processing) or standard `[T]::chunks`
/// method. Specifically, if `rayon` feature is enabled and not building Miri
/// tests, Rayon is used.
///
/// Note that depending on compilation features and target the function is
/// defined as returning `rayon::slice::Chunks` or `core::slice::Chunks`. types.
///
/// # Example
///
/// ```
/// use lib::par::prelude::*;
///
/// let chunks = lib::par::chunks(&[0, 1, 2, 3, 4], 3)
/// .map(|chunk| chunk.to_vec())
/// .collect::<Vec<Vec<u32>>>();
/// assert_eq!(&[vec![0, 1, 2], vec![3, 4]], chunks.as_slice());
/// ```
pub fn chunks<T: Sync>(arr: &[T], count: usize) -> Chunks<'_, T> {
#[cfg(all(feature = "rayon", not(miri)))]
return arr.par_chunks(count);
#[cfg(any(not(feature = "rayon"), miri))]
return arr.chunks(count);
}

#[test]
fn test_chunks() {
let got = chunks(&[1u32, 2, 3, 4, 5], 3)
.map(|chunk| (chunk.len(), chunk.iter().sum::<u32>()))
.collect::<alloc::vec::Vec<(usize, u32)>>();
assert_eq!(&[(3, 6), (2, 9)], got.as_slice());
}


/// Sorts elements of a slice using given comparator.
///
/// It uses conditional compilation and either uses Rayon’s
/// `par_sort_unstable_by` or standard `sort_unstable_by` method. Specifically,
/// if `rayon` feature is enabled and not building Miri tests, Rayon is used.
///
/// # Example
///
/// ```
/// let mut arr = [5, 4, 3, 1, 2, 3];
/// lib::par::sort_unstable_by(&mut arr[..], |a, b| a.cmp(b));
/// assert_eq!(&[1, 2, 3, 3, 4, 5], &arr[..]);
/// ```
pub fn sort_unstable_by<T: Send + Sync>(
arr: &mut [T],
cmp: impl (Fn(&T, &T) -> core::cmp::Ordering) + Sync,
) {
#[cfg(all(feature = "rayon", not(miri)))]
arr.par_sort_unstable_by(cmp);
#[cfg(any(not(feature = "rayon"), miri))]
arr.sort_unstable_by(cmp);
}
Loading

0 comments on commit ef5d6aa

Please sign in to comment.