Skip to content

Commit

Permalink
Merge pull request #2078 from subspace/disallow-block-solutions-in-votes
Browse files Browse the repository at this point in the history
Disallow block solutions in votes
  • Loading branch information
nazar-pc authored Oct 10, 2023
2 parents 9dccf66 + f7fd322 commit e7ed38e
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 47 deletions.
32 changes: 24 additions & 8 deletions crates/pallet-subspace/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
// limitations under the License.
#![doc = include_str!("../README.md")]
#![cfg_attr(not(feature = "std"), no_std)]
#![feature(assert_matches, const_option, let_chains)]
#![feature(array_chunks, assert_matches, const_option, let_chains, portable_simd)]
#![warn(unused_must_use, unsafe_code, unused_variables, unused_must_use)]

extern crate alloc;
Expand Down Expand Up @@ -92,7 +92,9 @@ impl EraChangeTrigger for NormalEraChange {

#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo)]
struct VoteVerificationData {
/// Block solution range, vote must not reach it
solution_range: SolutionRange,
vote_solution_range: SolutionRange,
current_slot: Slot,
parent_slot: Slot,
}
Expand Down Expand Up @@ -1307,6 +1309,11 @@ fn current_vote_verification_data<T: Config>(is_block_initialized: bool) -> Vote
let solution_ranges = SolutionRanges::<T>::get();
VoteVerificationData {
solution_range: if is_block_initialized {
solution_ranges.current
} else {
solution_ranges.next.unwrap_or(solution_ranges.current)
},
vote_solution_range: if is_block_initialized {
solution_ranges.voting_current
} else {
solution_ranges
Expand Down Expand Up @@ -1339,6 +1346,7 @@ enum CheckVoteError {
UnknownSegmentCommitment,
InvalidHistorySize,
InvalidSolution(String),
QualityTooHigh,
InvalidProofOfTime,
InvalidFutureProofOfTime,
DuplicateVote,
Expand All @@ -1360,6 +1368,7 @@ impl From<CheckVoteError> for TransactionValidityError {
CheckVoteError::UnknownSegmentCommitment => InvalidTransaction::Call,
CheckVoteError::InvalidHistorySize => InvalidTransaction::Call,
CheckVoteError::InvalidSolution(_) => InvalidTransaction::Call,
CheckVoteError::QualityTooHigh => InvalidTransaction::Call,
CheckVoteError::InvalidProofOfTime => InvalidTransaction::Future,
CheckVoteError::InvalidFutureProofOfTime => InvalidTransaction::Call,
CheckVoteError::DuplicateVote => InvalidTransaction::Call,
Expand Down Expand Up @@ -1530,12 +1539,12 @@ fn check_vote<T: Config>(
.segment_index(),
);

if let Err(error) = verify_solution(
match verify_solution(
solution.into(),
slot.into(),
(&VerifySolutionParams {
proof_of_time: *proof_of_time,
solution_range: vote_verification_data.solution_range,
solution_range: vote_verification_data.vote_solution_range,
piece_check_params: Some(PieceCheckParams {
max_pieces_in_sector: T::MaxPiecesInSector::get(),
segment_commitment,
Expand All @@ -1548,11 +1557,18 @@ fn check_vote<T: Config>(
})
.into(),
) {
debug!(
target: "runtime::subspace",
"Vote verification error: {error:?}"
);
return Err(CheckVoteError::InvalidSolution(error));
Ok(solution_distance) => {
if solution_distance <= vote_verification_data.solution_range {
return Err(CheckVoteError::QualityTooHigh);
}
}
Err(error) => {
debug!(
target: "runtime::subspace",
"Vote verification error: {error:?}"
);
return Err(CheckVoteError::InvalidSolution(error));
}
}

// Cheap proof of time verification is possible here because proof of time must have already
Expand Down
47 changes: 40 additions & 7 deletions crates/pallet-subspace/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,25 +39,27 @@ use sp_runtime::testing::{Digest, DigestItem, Header, TestXt};
use sp_runtime::traits::{Block as BlockT, Header as _, IdentityLookup};
use sp_runtime::{BuildStorage, Perbill};
use sp_weights::Weight;
use std::iter;
use std::marker::PhantomData;
use std::num::{NonZeroU32, NonZeroU64, NonZeroUsize};
use std::simd::Simd;
use std::sync::{Once, OnceLock};
use std::{iter, mem};
use subspace_archiving::archiver::{Archiver, NewArchivedSegment};
use subspace_core_primitives::crypto::kzg::{embedded_kzg_settings, Kzg};
use subspace_core_primitives::crypto::Scalar;
use subspace_core_primitives::{
ArchivedBlockProgress, ArchivedHistorySegment, Blake3Hash, BlockNumber, HistorySize,
LastArchivedBlock, Piece, PieceOffset, PosSeed, PotOutput, PublicKey, Record,
RecordedHistorySegment, SegmentCommitment, SegmentHeader, SegmentIndex, SlotNumber, Solution,
SolutionRange, REWARD_SIGNING_CONTEXT,
RecordedHistorySegment, SectorId, SegmentCommitment, SegmentHeader, SegmentIndex, SlotNumber,
Solution, SolutionRange, REWARD_SIGNING_CONTEXT,
};
use subspace_erasure_coding::ErasureCoding;
use subspace_farmer_components::auditing::audit_sector;
use subspace_farmer_components::plotting::{plot_sector, PieceGetterRetryPolicy};
use subspace_farmer_components::FarmerProtocolInfo;
use subspace_proof_of_space::shim::ShimTable;
use subspace_proof_of_space::{Table, TableGenerator};
use subspace_verification::is_within_solution_range;

type PosTable = ShimTable;

Expand Down Expand Up @@ -427,6 +429,7 @@ pub fn create_signed_vote(
archived_history_segment: &ArchivedHistorySegment,
reward_address: <Test as frame_system::Config>::AccountId,
solution_range: SolutionRange,
vote_solution_range: SolutionRange,
) -> SignedVote<u64, <Block as BlockT>::Hash, <Test as frame_system::Config>::AccountId> {
let kzg = kzg_instance();
let erasure_coding = erasure_coding_instance();
Expand Down Expand Up @@ -466,13 +469,15 @@ pub fn create_signed_vote(
))
.unwrap();

let global_challenge = proof_of_time
.derive_global_randomness()
.derive_global_challenge(slot.into());

let maybe_audit_result = audit_sector(
&public_key,
sector_index,
&proof_of_time
.derive_global_randomness()
.derive_global_challenge(slot.into()),
solution_range,
&global_challenge,
vote_solution_range,
&plotted_sector_bytes,
&plotted_sector.sector_metadata,
);
Expand All @@ -492,6 +497,34 @@ pub fn create_signed_vote(
.unwrap()
.unwrap();

let sector_id = SectorId::new(
PublicKey::from(keypair.public.to_bytes()).hash(),
solution.sector_index,
);
let sector_slot_challenge = sector_id.derive_sector_slot_challenge(&global_challenge);
let masked_chunk = (Simd::from(solution.chunk.to_bytes())
^ Simd::from(solution.proof_of_space.hash()))
.to_array();
// Extract audit chunk from masked chunk
let audit_chunk = SolutionRange::from_le_bytes(
*masked_chunk
.array_chunks::<{ mem::size_of::<SolutionRange>() }>()
.nth(usize::from(solution.audit_chunk_offset))
.unwrap(),
);

// Check that solution quality is not too high
if is_within_solution_range(
&global_challenge,
audit_chunk,
&sector_slot_challenge,
solution_range,
)
.is_some()
{
continue;
}

let vote = Vote::<u64, <Block as BlockT>::Hash, _>::V0 {
height,
parent_hash,
Expand Down
Loading

0 comments on commit e7ed38e

Please sign in to comment.