diff --git a/ledger/src/blockstore.rs b/ledger/src/blockstore.rs index bd961f59d12be7..e8f55aa0a412af 100644 --- a/ledger/src/blockstore.rs +++ b/ledger/src/blockstore.rs @@ -3788,18 +3788,25 @@ impl Blockstore { .expect("fetch from DuplicateSlots column family failed") } - // `new_shred` is assumed to have slot and index equal to the given slot and index. - // Returns the existing shred if `new_shred` is not equal to the existing shred at the - // given slot and index as this implies the leader generated two different shreds with - // the same slot and index - pub fn is_shred_duplicate(&self, new_shred: &Shred) -> Option> { - let (slot, index, shred_type) = new_shred.id().unpack(); - let existing_shred = match shred_type { - ShredType::Data => self.get_data_shred(slot, index as u64), - ShredType::Code => self.get_coding_shred(slot, index as u64), + /// Returns the shred already stored in blockstore if it has a different + /// payload than the given `shred` but the same (slot, index, shred-type). + /// This implies the leader generated two different shreds with the same + /// slot, index and shred-type. + /// The payload is modified so that it has the same retransmitter's + /// signature as the `shred` argument. + pub fn is_shred_duplicate(&self, shred: &Shred) -> Option> { + let (slot, index, shred_type) = shred.id().unpack(); + let mut other = match shred_type { + ShredType::Data => self.get_data_shred(slot, u64::from(index)), + ShredType::Code => self.get_coding_shred(slot, u64::from(index)), } .expect("fetch from DuplicateSlots column family failed")?; - (existing_shred != *new_shred.payload()).then_some(existing_shred) + if let Ok(signature) = shred.retransmitter_signature() { + if let Err(err) = shred::layout::set_retransmitter_signature(&mut other, &signature) { + error!("set retransmitter signature failed: {err:?}"); + } + } + (&other != shred.payload()).then_some(other) } pub fn has_duplicate_shreds_in_slot(&self, slot: Slot) -> bool { diff --git a/ledger/src/shred.rs b/ledger/src/shred.rs index 68c8186c89703a..39a3f3911769f6 100644 --- a/ledger/src/shred.rs +++ b/ledger/src/shred.rs @@ -355,6 +355,7 @@ impl Shred { dispatch!(pub(crate) fn erasure_shard_as_slice(&self) -> Result<&[u8], Error>); // Returns the shard index within the erasure coding set. dispatch!(pub(crate) fn erasure_shard_index(&self) -> Result); + dispatch!(pub(crate) fn retransmitter_signature(&self) -> Result); dispatch!(pub fn into_payload(self) -> Vec); dispatch!(pub fn merkle_root(&self) -> Result); @@ -751,6 +752,34 @@ pub mod layout { .map(Hash::new) } + pub(crate) fn set_retransmitter_signature( + shred: &mut [u8], + signature: &Signature, + ) -> Result<(), Error> { + let offset = match get_shred_variant(shred)? { + ShredVariant::LegacyCode | ShredVariant::LegacyData => Err(Error::InvalidShredVariant), + ShredVariant::MerkleCode { + proof_size, + chained, + resigned, + } => { + merkle::ShredCode::get_retransmitter_signature_offset(proof_size, chained, resigned) + } + ShredVariant::MerkleData { + proof_size, + chained, + resigned, + } => { + merkle::ShredData::get_retransmitter_signature_offset(proof_size, chained, resigned) + } + }?; + let Some(buffer) = shred.get_mut(offset..offset + SIZE_OF_SIGNATURE) else { + return Err(Error::InvalidPayloadSize(shred.len())); + }; + buffer.copy_from_slice(signature.as_ref()); + Ok(()) + } + // Minimally corrupts the packet so that the signature no longer verifies. #[cfg(test)] pub(crate) fn corrupt_packet( diff --git a/ledger/src/shred/merkle.rs b/ledger/src/shred/merkle.rs index 60ebba92766567..9baa9a612c4e07 100644 --- a/ledger/src/shred/merkle.rs +++ b/ledger/src/shred/merkle.rs @@ -332,6 +332,39 @@ impl ShredData { let node = get_merkle_node(shred, SIZE_OF_SIGNATURE..proof_offset).ok()?; get_merkle_root(index, node, proof).ok() } + + pub(super) fn retransmitter_signature(&self) -> Result { + let offset = self.retransmitter_signature_offset()?; + self.payload + .get(offset..offset + SIZE_OF_SIGNATURE) + .map(|bytes| <[u8; SIZE_OF_SIGNATURE]>::try_from(bytes).unwrap()) + .map(Signature::from) + .ok_or(Error::InvalidPayloadSize(self.payload.len())) + } + + fn retransmitter_signature_offset(&self) -> Result { + let ShredVariant::MerkleData { + proof_size, + chained, + resigned, + } = self.common_header.shred_variant + else { + return Err(Error::InvalidShredVariant); + }; + Self::get_retransmitter_signature_offset(proof_size, chained, resigned) + } + + pub(super) fn get_retransmitter_signature_offset( + proof_size: u8, + chained: bool, + resigned: bool, + ) -> Result { + if !resigned { + return Err(Error::InvalidShredVariant); + } + let proof_offset = Self::get_proof_offset(proof_size, chained, resigned)?; + Ok(proof_offset + usize::from(proof_size) * SIZE_OF_MERKLE_PROOF_ENTRY) + } } impl ShredCode { @@ -524,6 +557,39 @@ impl ShredCode { let node = get_merkle_node(shred, SIZE_OF_SIGNATURE..proof_offset).ok()?; get_merkle_root(index, node, proof).ok() } + + pub(super) fn retransmitter_signature(&self) -> Result { + let offset = self.retransmitter_signature_offset()?; + self.payload + .get(offset..offset + SIZE_OF_SIGNATURE) + .map(|bytes| <[u8; SIZE_OF_SIGNATURE]>::try_from(bytes).unwrap()) + .map(Signature::from) + .ok_or(Error::InvalidPayloadSize(self.payload.len())) + } + + fn retransmitter_signature_offset(&self) -> Result { + let ShredVariant::MerkleCode { + proof_size, + chained, + resigned, + } = self.common_header.shred_variant + else { + return Err(Error::InvalidShredVariant); + }; + Self::get_retransmitter_signature_offset(proof_size, chained, resigned) + } + + pub(super) fn get_retransmitter_signature_offset( + proof_size: u8, + chained: bool, + resigned: bool, + ) -> Result { + if !resigned { + return Err(Error::InvalidShredVariant); + } + let proof_offset = Self::get_proof_offset(proof_size, chained, resigned)?; + Ok(proof_offset + usize::from(proof_size) * SIZE_OF_MERKLE_PROOF_ENTRY) + } } impl<'a> ShredTrait<'a> for ShredData { diff --git a/ledger/src/shred/shred_code.rs b/ledger/src/shred/shred_code.rs index 067d7edaf437eb..f1625c132256e7 100644 --- a/ledger/src/shred/shred_code.rs +++ b/ledger/src/shred/shred_code.rs @@ -106,6 +106,13 @@ impl ShredCode { } } } + + pub(super) fn retransmitter_signature(&self) -> Result { + match self { + Self::Legacy(_) => Err(Error::InvalidShredVariant), + Self::Merkle(shred) => shred.retransmitter_signature(), + } + } } impl From for ShredCode { diff --git a/ledger/src/shred/shred_data.rs b/ledger/src/shred/shred_data.rs index ac409376370420..3aa97ebde9e4e1 100644 --- a/ledger/src/shred/shred_data.rs +++ b/ledger/src/shred/shred_data.rs @@ -143,6 +143,13 @@ impl ShredData { Self::Merkle(_) => panic!("Not Implemented!"), } } + + pub(super) fn retransmitter_signature(&self) -> Result { + match self { + Self::Legacy(_) => Err(Error::InvalidShredVariant), + Self::Merkle(shred) => shred.retransmitter_signature(), + } + } } impl From for ShredData {