Skip to content
This repository has been archived by the owner on Feb 21, 2024. It is now read-only.

Commit

Permalink
Merge pull request paritytech#365 from subspace/change_piece_distance
Browse files Browse the repository at this point in the history
Change piece distance function to addition and substraction
  • Loading branch information
nazar-pc authored Apr 17, 2022
2 parents e51271f + d6ace91 commit ef8bb6b
Show file tree
Hide file tree
Showing 11 changed files with 125 additions and 48 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

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

4 changes: 2 additions & 2 deletions crates/sc-consensus-subspace/src/verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,8 @@ fn is_within_max_plot(
if total_pieces < max_plot_size {
return true;
}
let max_distance = PieceDistance::MAX / total_pieces * max_plot_size;
PieceDistance::xor_distance(&piece_index.into(), key) <= max_distance
let max_distance_one_direction = PieceDistance::MAX / total_pieces * max_plot_size / 2;
PieceDistance::distance(&piece_index.into(), key) <= max_distance_one_direction
}

pub(crate) struct PieceCheckParams {
Expand Down
7 changes: 2 additions & 5 deletions crates/sp-consensus-subspace/src/digests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,8 @@ impl<AccountId> PreDigest<AccountId> {
pub fn added_weight(&self) -> SubspaceBlockWeight {
let target = u64::from_be_bytes(self.solution.local_challenge.derive_target());
let tag = u64::from_be_bytes(self.solution.tag);
let diff = target.wrapping_sub(tag);
let diff2 = tag.wrapping_sub(target);
// Find smaller diff between 2 directions.
let bidirectional_diff = diff.min(diff2);
u128::from(u64::MAX - bidirectional_diff)

u128::from(u64::MAX - subspace_core_primitives::bidirectional_distance(&target, &tag))
}
}

Expand Down
2 changes: 2 additions & 0 deletions crates/subspace-core-primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ include = [

[dependencies]
hmac = { version = "0.12.1", default-features = false }
num-traits = { version = "0.2", default-features = false }
parity-scale-codec = { version = "3.1.2", default-features = false, features = ["derive"] }
scale-info = { version = "2.1.1", default-features = false, features = ["derive"] }
serde = { version = "1.0.136", optional = true, features = ["derive"] }
Expand All @@ -33,6 +34,7 @@ version = "0.10.2"
default = ["std"]
std = [
"hmac/std",
"num-traits/std",
"parity-scale-codec/std",
"scale-info/std",
"serde/std",
Expand Down
8 changes: 8 additions & 0 deletions crates/subspace-core-primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -513,3 +513,11 @@ impl<AccountId: Clone> Solution<AccountId> {
}
}
}

/// Bidirectional distance metric implemented on top of subtraction
pub fn bidirectional_distance<T: num_traits::WrappingSub + Ord>(a: &T, b: &T) -> T {
let diff = a.wrapping_sub(b);
let diff2 = b.wrapping_sub(a);
// Find smaller diff between 2 directions.
diff.min(diff2)
}
1 change: 1 addition & 0 deletions crates/subspace-farmer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ hex-buffer-serde = "0.3.0"
jsonrpsee = { version = "0.10.1", features = ["client", "macros", "server"] }
log = "0.4.16"
lru = "0.7.5"
num-traits = "0.2"
parity-scale-codec = "3.1.2"
parking_lot = "0.12.0"
rayon = "1.5.2"
Expand Down
106 changes: 72 additions & 34 deletions crates/subspace-farmer/src/plot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod tests;

use event_listener_primitives::{Bag, HandlerId};
use log::{error, info};
use num_traits::{WrappingAdd, WrappingSub};
use rocksdb::DB;
use std::collections::VecDeque;
use std::fs::{self, File, OpenOptions};
Expand Down Expand Up @@ -141,7 +142,7 @@ pub fn retrieve_piece_from_plots(
) -> io::Result<Option<Piece>> {
let piece_index_hash = PieceIndexHash::from(piece_index);
let mut plots = plots.iter().collect::<Vec<_>>();
plots.sort_by_key(|plot| PieceDistance::xor_distance(&piece_index_hash, plot.public_key()));
plots.sort_by_key(|plot| PieceDistance::distance(&piece_index_hash, plot.public_key()));

plots
.iter()
Expand Down Expand Up @@ -443,32 +444,64 @@ impl WeakPlot {
}
}

/// Mapping from piece index hash to piece offset.
///
/// Piece index hashes are transformed in the following manner:
/// - Assume that farmer address is the middle (`2 ^ 255`) of the `PieceDistance` field
/// - Move every piece according to that
#[derive(Debug)]
struct IndexHashToOffsetDB {
inner: DB,
address: PublicKey,
max_distance: Option<PieceDistance>,
max_distance_key: Option<PieceDistance>,
}

impl IndexHashToOffsetDB {
fn open_default(path: impl AsRef<Path>, address: PublicKey) -> Result<Self, PlotError> {
let inner = DB::open_default(path.as_ref()).map_err(PlotError::IndexDbOpen)?;
let max_distance = {
let mut iter = inner.raw_iterator();
iter.seek_to_last();
iter.key().map(PieceDistance::from_big_endian)
};
Ok(Self {
let mut me = Self {
inner,
address,
max_distance,
})
max_distance_key: None,
};
me.update_max_distance();
Ok(me)
}

fn update_max_distance(&mut self) {
self.max_distance_key = try {
let mut iter = self.inner.raw_iterator();
iter.seek_to_first();
let lower_bound = iter.key().map(PieceDistance::from_big_endian)?;
iter.seek_to_last();
let upper_bound = iter.key().map(PieceDistance::from_big_endian)?;

// Pick key which has maximum distance to our key
if subspace_core_primitives::bidirectional_distance(
&lower_bound,
&PieceDistance::MIDDLE,
) < subspace_core_primitives::bidirectional_distance(
&upper_bound,
&PieceDistance::MIDDLE,
) {
upper_bound
} else {
lower_bound
}
};
}

fn get_key(&self, index_hash: &PieceIndexHash) -> PieceDistance {
// We permute distance such that if piece index hash is equal to the `self.address` then it
// lands to the `PieceDistance::MIDDLE`
PieceDistance::from_big_endian(&index_hash.0)
.wrapping_sub(&PieceDistance::from_big_endian(self.address.as_ref()))
.wrapping_add(&PieceDistance::MIDDLE)
}

fn get(&self, index_hash: &PieceIndexHash) -> io::Result<Option<PieceOffset>> {
let distance = PieceDistance::xor_distance(index_hash, &self.address);
self.inner
.get(&distance.to_bytes())
.get(&self.get_key(index_hash).to_bytes())
.map_err(io::Error::other)
.and_then(|opt_val| {
opt_val
Expand All @@ -480,21 +513,23 @@ impl IndexHashToOffsetDB {

/// Returns `true` if piece plot will not result in exceeding plot size and doesn't exist
/// already
fn should_store(&self, index_hash: &PieceIndexHash) -> io::Result<bool> {
Ok(match self.max_distance {
Some(max_distance) => {
PieceDistance::xor_distance(index_hash, &self.address) < max_distance
&& self.get(index_hash)?.is_none()
}
None => false,
})
fn should_store(&self, index_hash: &PieceIndexHash) -> bool {
self.max_distance_key
.map(|max_distance_key| {
subspace_core_primitives::bidirectional_distance(
&max_distance_key,
&PieceDistance::MIDDLE,
) >= PieceDistance::distance(index_hash, self.address)
})
.unwrap_or(true)
}

fn remove_furthest(&mut self) -> io::Result<Option<PieceOffset>> {
let max_distance = match self.max_distance {
let max_distance = match self.max_distance_key {
Some(max_distance) => max_distance,
None => return Ok(None),
};

let result = self
.inner
.get(&max_distance.to_bytes())
Expand All @@ -505,28 +540,31 @@ impl IndexHashToOffsetDB {
.delete(&max_distance.to_bytes())
.map_err(io::Error::other)?;

let mut iter = self.inner.raw_iterator();
iter.seek_to_last();
self.max_distance = iter.key().map(PieceDistance::from_big_endian);
self.update_max_distance();

Ok(result)
}

fn put(&mut self, index_hash: &PieceIndexHash, offset: PieceOffset) -> io::Result<()> {
let distance = PieceDistance::xor_distance(index_hash, &self.address);
let key = self.get_key(index_hash);
self.inner
.put(&distance.to_bytes(), offset.to_le_bytes())
.put(&key.to_bytes(), offset.to_le_bytes())
.map_err(io::Error::other)?;

match self.max_distance {
Some(old_distance) => {
if old_distance < distance {
self.max_distance.replace(distance);
self.max_distance_key = match self.max_distance_key {
Some(max_distance_key) => {
if PieceDistance::distance(index_hash, self.address)
> subspace_core_primitives::bidirectional_distance(
&max_distance_key,
&PieceDistance::MIDDLE,
)
{
Some(key)
} else {
Some(max_distance_key)
}
}
None => {
self.max_distance.replace(distance);
}
None => Some(key),
};

Ok(())
Expand Down Expand Up @@ -676,7 +714,7 @@ impl PlotWorker {
// Check if piece is out of plot range or if it is in the plot
if !self
.piece_index_hash_to_offset_db
.should_store(&piece_index.into())?
.should_store(&piece_index.into())
{
continue;
}
Expand Down
2 changes: 1 addition & 1 deletion crates/subspace-farmer/src/plot/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ async fn partial_plot() {
assert!(!plot.is_empty());

let mut piece_indexes = (0..pieces_to_plot).collect::<Vec<_>>();
piece_indexes.sort_by_key(|i| PieceDistance::xor_distance(&(*i).into(), &address));
piece_indexes.sort_by_key(|i| PieceDistance::distance(&(*i).into(), &address));

// First pieces should be present and equal
for &i in &piece_indexes[..max_plot_pieces as usize] {
Expand Down
1 change: 1 addition & 0 deletions crates/subspace-solving/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ include = [
[dependencies]
log = "0.4.16"
num_cpus = "1.13.0"
num-traits = "0.2"
rayon = "1.5.2"
schnorrkel = "0.9.1"
sloth256-189 = "0.2.2"
Expand Down
32 changes: 26 additions & 6 deletions crates/subspace-solving/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub const SOLUTION_SIGNING_CONTEXT: &[u8] = b"farmer_solution";
mod construct_uint {
//! This module is needed to scope clippy allows
use num_traits::{WrappingAdd, WrappingSub};
use subspace_core_primitives::PieceIndexHash;

uint::construct_uint! {
Expand All @@ -42,18 +43,37 @@ mod construct_uint {
}

impl PieceDistance {
/// Calculates the xor distance metric between piece index hash and farmer address.
pub fn xor_distance(
PieceIndexHash(piece): &PieceIndexHash,
address: impl AsRef<[u8]>,
) -> Self {
Self::from_big_endian(piece) ^ Self::from_big_endian(address.as_ref())
/// Calculates the distance metric between piece index hash and farmer address.
pub fn distance(PieceIndexHash(piece): &PieceIndexHash, address: impl AsRef<[u8]>) -> Self {
let piece = Self::from_big_endian(piece);
let address = Self::from_big_endian(address.as_ref());
subspace_core_primitives::bidirectional_distance(&piece, &address)
}

/// Convert piece distance to big endian bytes
pub fn to_bytes(self) -> [u8; 32] {
self.into()
}

/// The middle of the piece distance field.
/// The analogue of `0b1000_0000` for `u8`.
pub const MIDDLE: Self = {
// TODO: This assumes that numbers are stored little endian,
// should be replaced with just `Self::MAX / 2`, but it is not `const fn` in Rust yet.
Self([u64::MAX, u64::MAX, u64::MAX, u64::MAX / 2])
};
}

impl WrappingAdd for PieceDistance {
fn wrapping_add(&self, other: &Self) -> Self {
self.overflowing_add(*other).0
}
}

impl WrappingSub for PieceDistance {
fn wrapping_sub(&self, other: &Self) -> Self {
self.overflowing_sub(*other).0
}
}
}

Expand Down
7 changes: 7 additions & 0 deletions crates/subspace-solving/tests/integration/main.rs
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
use subspace_solving::PieceDistance;

mod codec;

#[test]
fn piece_distance_middle() {
assert_eq!(PieceDistance::MIDDLE, PieceDistance::MAX / 2);
}

0 comments on commit ef8bb6b

Please sign in to comment.