master: Add nonce to shreds repairs, add shred data size to header (#10109)
* Add nonce to shreds/repairs * Add data shred size to header Co-authored-by: Carl <carl@solana.com>
This commit is contained in:
		@@ -24,26 +24,31 @@ use std::mem::size_of;
 | 
			
		||||
use std::{sync::Arc, time::Instant};
 | 
			
		||||
use thiserror::Error;
 | 
			
		||||
 | 
			
		||||
pub type Nonce = u32;
 | 
			
		||||
 | 
			
		||||
/// The following constants are computed by hand, and hardcoded.
 | 
			
		||||
/// `test_shred_constants` ensures that the values are correct.
 | 
			
		||||
/// Constants are used over lazy_static for performance reasons.
 | 
			
		||||
pub const SIZE_OF_COMMON_SHRED_HEADER: usize = 83;
 | 
			
		||||
pub const SIZE_OF_DATA_SHRED_HEADER: usize = 3;
 | 
			
		||||
pub const SIZE_OF_DATA_SHRED_HEADER: usize = 5;
 | 
			
		||||
pub const SIZE_OF_CODING_SHRED_HEADER: usize = 6;
 | 
			
		||||
pub const SIZE_OF_SIGNATURE: usize = 64;
 | 
			
		||||
pub const SIZE_OF_SHRED_TYPE: usize = 1;
 | 
			
		||||
pub const SIZE_OF_SHRED_SLOT: usize = 8;
 | 
			
		||||
pub const SIZE_OF_SHRED_INDEX: usize = 4;
 | 
			
		||||
pub const SIZE_OF_NONCE: usize = 4;
 | 
			
		||||
pub const SIZE_OF_DATA_SHRED_IGNORED_TAIL: usize =
 | 
			
		||||
    SIZE_OF_COMMON_SHRED_HEADER + SIZE_OF_CODING_SHRED_HEADER;
 | 
			
		||||
pub const SIZE_OF_DATA_SHRED_PAYLOAD: usize = PACKET_DATA_SIZE
 | 
			
		||||
    - SIZE_OF_COMMON_SHRED_HEADER
 | 
			
		||||
    - SIZE_OF_DATA_SHRED_HEADER
 | 
			
		||||
    - SIZE_OF_DATA_SHRED_IGNORED_TAIL;
 | 
			
		||||
    - SIZE_OF_DATA_SHRED_IGNORED_TAIL
 | 
			
		||||
    - SIZE_OF_NONCE;
 | 
			
		||||
 | 
			
		||||
pub const OFFSET_OF_SHRED_TYPE: usize = SIZE_OF_SIGNATURE;
 | 
			
		||||
pub const OFFSET_OF_SHRED_SLOT: usize = SIZE_OF_SIGNATURE + SIZE_OF_SHRED_TYPE;
 | 
			
		||||
pub const OFFSET_OF_SHRED_INDEX: usize = OFFSET_OF_SHRED_SLOT + SIZE_OF_SHRED_SLOT;
 | 
			
		||||
pub const SHRED_PAYLOAD_SIZE: usize = PACKET_DATA_SIZE - SIZE_OF_NONCE;
 | 
			
		||||
 | 
			
		||||
thread_local!(static PAR_THREAD_POOL: RefCell<ThreadPool> = RefCell::new(rayon::ThreadPoolBuilder::new()
 | 
			
		||||
                    .num_threads(get_thread_count())
 | 
			
		||||
@@ -108,6 +113,7 @@ pub struct ShredCommonHeader {
 | 
			
		||||
pub struct DataShredHeader {
 | 
			
		||||
    pub parent_offset: u16,
 | 
			
		||||
    pub flags: u8,
 | 
			
		||||
    pub size: u16,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// The coding shred header has FEC information
 | 
			
		||||
@@ -169,7 +175,8 @@ impl Shred {
 | 
			
		||||
        version: u16,
 | 
			
		||||
        fec_set_index: u32,
 | 
			
		||||
    ) -> Self {
 | 
			
		||||
        let mut payload = vec![0; PACKET_DATA_SIZE];
 | 
			
		||||
        let payload_size = SHRED_PAYLOAD_SIZE;
 | 
			
		||||
        let mut payload = vec![0; payload_size];
 | 
			
		||||
        let common_header = ShredCommonHeader {
 | 
			
		||||
            slot,
 | 
			
		||||
            index,
 | 
			
		||||
@@ -178,9 +185,13 @@ impl Shred {
 | 
			
		||||
            ..ShredCommonHeader::default()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let size = (data.map(|d| d.len()).unwrap_or(0)
 | 
			
		||||
            + SIZE_OF_DATA_SHRED_HEADER
 | 
			
		||||
            + SIZE_OF_COMMON_SHRED_HEADER) as u16;
 | 
			
		||||
        let mut data_header = DataShredHeader {
 | 
			
		||||
            parent_offset,
 | 
			
		||||
            flags: reference_tick.min(SHRED_TICK_REFERENCE_MASK),
 | 
			
		||||
            size,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if is_last_data {
 | 
			
		||||
@@ -199,9 +210,10 @@ impl Shred {
 | 
			
		||||
            &common_header,
 | 
			
		||||
        )
 | 
			
		||||
        .expect("Failed to write header into shred buffer");
 | 
			
		||||
        let size_of_data_shred_header = SIZE_OF_DATA_SHRED_HEADER;
 | 
			
		||||
        Self::serialize_obj_into(
 | 
			
		||||
            &mut start,
 | 
			
		||||
            SIZE_OF_DATA_SHRED_HEADER,
 | 
			
		||||
            size_of_data_shred_header,
 | 
			
		||||
            &mut payload,
 | 
			
		||||
            &data_header,
 | 
			
		||||
        )
 | 
			
		||||
@@ -219,11 +231,21 @@ impl Shred {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn new_from_serialized_shred(payload: Vec<u8>) -> Result<Self> {
 | 
			
		||||
    pub fn new_from_serialized_shred(mut payload: Vec<u8>) -> Result<Self> {
 | 
			
		||||
        let mut start = 0;
 | 
			
		||||
        let common_header: ShredCommonHeader =
 | 
			
		||||
            Self::deserialize_obj(&mut start, SIZE_OF_COMMON_SHRED_HEADER, &payload)?;
 | 
			
		||||
 | 
			
		||||
        let slot = common_header.slot;
 | 
			
		||||
        let expected_data_size = SHRED_PAYLOAD_SIZE;
 | 
			
		||||
        // Safe because any payload from the network must have passed through
 | 
			
		||||
        // window service,  which implies payload wll be of size
 | 
			
		||||
        // PACKET_DATA_SIZE, and `expected_data_size` <= PACKET_DATA_SIZE.
 | 
			
		||||
        //
 | 
			
		||||
        // On the other hand, if this function is called locally, the payload size should match
 | 
			
		||||
        // the `expected_data_size`.
 | 
			
		||||
        assert!(payload.len() >= expected_data_size);
 | 
			
		||||
        payload.truncate(expected_data_size);
 | 
			
		||||
        let shred = if common_header.shred_type == ShredType(CODING_SHRED) {
 | 
			
		||||
            let coding_header: CodingShredHeader =
 | 
			
		||||
                Self::deserialize_obj(&mut start, SIZE_OF_CODING_SHRED_HEADER, &payload)?;
 | 
			
		||||
@@ -234,11 +256,12 @@ impl Shred {
 | 
			
		||||
                payload,
 | 
			
		||||
            }
 | 
			
		||||
        } else if common_header.shred_type == ShredType(DATA_SHRED) {
 | 
			
		||||
            let size_of_data_shred_header = SIZE_OF_DATA_SHRED_HEADER;
 | 
			
		||||
            let data_header: DataShredHeader =
 | 
			
		||||
                Self::deserialize_obj(&mut start, SIZE_OF_DATA_SHRED_HEADER, &payload)?;
 | 
			
		||||
                Self::deserialize_obj(&mut start, size_of_data_shred_header, &payload)?;
 | 
			
		||||
            if u64::from(data_header.parent_offset) > common_header.slot {
 | 
			
		||||
                return Err(ShredError::InvalidParentOffset {
 | 
			
		||||
                    slot: common_header.slot,
 | 
			
		||||
                    slot,
 | 
			
		||||
                    parent_offset: data_header.parent_offset,
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
@@ -260,7 +283,7 @@ impl Shred {
 | 
			
		||||
        data_header: DataShredHeader,
 | 
			
		||||
        coding_header: CodingShredHeader,
 | 
			
		||||
    ) -> Self {
 | 
			
		||||
        let mut payload = vec![0; PACKET_DATA_SIZE];
 | 
			
		||||
        let mut payload = vec![0; SHRED_PAYLOAD_SIZE];
 | 
			
		||||
        let mut start = 0;
 | 
			
		||||
        Self::serialize_obj_into(
 | 
			
		||||
            &mut start,
 | 
			
		||||
@@ -396,7 +419,9 @@ impl Shred {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn reference_tick_from_data(data: &[u8]) -> u8 {
 | 
			
		||||
        let flags = data[SIZE_OF_COMMON_SHRED_HEADER + SIZE_OF_DATA_SHRED_HEADER - size_of::<u8>()];
 | 
			
		||||
        let flags = data[SIZE_OF_COMMON_SHRED_HEADER + SIZE_OF_DATA_SHRED_HEADER
 | 
			
		||||
            - size_of::<u8>()
 | 
			
		||||
            - size_of::<u16>()];
 | 
			
		||||
        flags & SHRED_TICK_REFERENCE_MASK
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -629,7 +654,7 @@ impl Shredder {
 | 
			
		||||
            let start_index = data_shred_batch[0].common_header.index;
 | 
			
		||||
 | 
			
		||||
            // All information after coding shred field in a data shred is encoded
 | 
			
		||||
            let valid_data_len = PACKET_DATA_SIZE - SIZE_OF_DATA_SHRED_IGNORED_TAIL;
 | 
			
		||||
            let valid_data_len = SHRED_PAYLOAD_SIZE - SIZE_OF_DATA_SHRED_IGNORED_TAIL;
 | 
			
		||||
            let data_ptrs: Vec<_> = data_shred_batch
 | 
			
		||||
                .iter()
 | 
			
		||||
                .map(|data| &data.payload[..valid_data_len])
 | 
			
		||||
@@ -718,7 +743,7 @@ impl Shredder {
 | 
			
		||||
                if missing < first_index_in_fec_set + num_data {
 | 
			
		||||
                    Shred::new_empty_data_shred().payload
 | 
			
		||||
                } else {
 | 
			
		||||
                    vec![0; PACKET_DATA_SIZE]
 | 
			
		||||
                    vec![0; SHRED_PAYLOAD_SIZE]
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
            .collect();
 | 
			
		||||
@@ -733,6 +758,7 @@ impl Shredder {
 | 
			
		||||
        first_code_index: usize,
 | 
			
		||||
        slot: Slot,
 | 
			
		||||
    ) -> std::result::Result<Vec<Shred>, reed_solomon_erasure::Error> {
 | 
			
		||||
        Self::verify_consistent_shred_payload_sizes(&"try_recovery()", &shreds)?;
 | 
			
		||||
        let mut recovered_data = vec![];
 | 
			
		||||
        let fec_set_size = num_data + num_coding;
 | 
			
		||||
 | 
			
		||||
@@ -778,7 +804,7 @@ impl Shredder {
 | 
			
		||||
 | 
			
		||||
            let session = Session::new(num_data, num_coding)?;
 | 
			
		||||
 | 
			
		||||
            let valid_data_len = PACKET_DATA_SIZE - SIZE_OF_DATA_SHRED_IGNORED_TAIL;
 | 
			
		||||
            let valid_data_len = SHRED_PAYLOAD_SIZE - SIZE_OF_DATA_SHRED_IGNORED_TAIL;
 | 
			
		||||
            let coding_block_offset = SIZE_OF_CODING_SHRED_HEADER + SIZE_OF_COMMON_SHRED_HEADER;
 | 
			
		||||
            let mut blocks: Vec<(&mut [u8], bool)> = shred_bufs
 | 
			
		||||
                .iter_mut()
 | 
			
		||||
@@ -823,6 +849,7 @@ impl Shredder {
 | 
			
		||||
    /// Combines all shreds to recreate the original buffer
 | 
			
		||||
    pub fn deshred(shreds: &[Shred]) -> std::result::Result<Vec<u8>, reed_solomon_erasure::Error> {
 | 
			
		||||
        let num_data = shreds.len();
 | 
			
		||||
        Self::verify_consistent_shred_payload_sizes(&"deshred()", shreds)?;
 | 
			
		||||
        let data_shred_bufs = {
 | 
			
		||||
            let first_index = shreds.first().unwrap().index() as usize;
 | 
			
		||||
            let last_shred = shreds.last().unwrap();
 | 
			
		||||
@@ -856,7 +883,7 @@ impl Shredder {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn reassemble_payload(num_data: usize, data_shred_bufs: Vec<&Vec<u8>>) -> Vec<u8> {
 | 
			
		||||
        let valid_data_len = PACKET_DATA_SIZE - SIZE_OF_DATA_SHRED_IGNORED_TAIL;
 | 
			
		||||
        let valid_data_len = SHRED_PAYLOAD_SIZE - SIZE_OF_DATA_SHRED_IGNORED_TAIL;
 | 
			
		||||
        data_shred_bufs[..num_data]
 | 
			
		||||
            .iter()
 | 
			
		||||
            .flat_map(|data| {
 | 
			
		||||
@@ -866,15 +893,43 @@ impl Shredder {
 | 
			
		||||
            .cloned()
 | 
			
		||||
            .collect()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn verify_consistent_shred_payload_sizes(
 | 
			
		||||
        caller: &str,
 | 
			
		||||
        shreds: &[Shred],
 | 
			
		||||
    ) -> std::result::Result<(), reed_solomon_erasure::Error> {
 | 
			
		||||
        if shreds.is_empty() {
 | 
			
		||||
            return Err(reed_solomon_erasure::Error::TooFewShardsPresent);
 | 
			
		||||
        }
 | 
			
		||||
        let slot = shreds[0].slot();
 | 
			
		||||
        for shred in shreds {
 | 
			
		||||
            if shred.payload.len() != SHRED_PAYLOAD_SIZE {
 | 
			
		||||
                error!(
 | 
			
		||||
                    "{} Shreds for slot: {} are inconsistent sizes. Expected: {} actual: {}",
 | 
			
		||||
                    caller,
 | 
			
		||||
                    slot,
 | 
			
		||||
                    SHRED_PAYLOAD_SIZE,
 | 
			
		||||
                    shred.payload.len()
 | 
			
		||||
                );
 | 
			
		||||
                return Err(reed_solomon_erasure::Error::IncorrectShardSize);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn max_ticks_per_n_shreds(num_shreds: u64) -> u64 {
 | 
			
		||||
pub fn max_ticks_per_n_shreds(num_shreds: u64, shred_data_size: Option<usize>) -> u64 {
 | 
			
		||||
    let ticks = create_ticks(1, 0, Hash::default());
 | 
			
		||||
    max_entries_per_n_shred(&ticks[0], num_shreds)
 | 
			
		||||
    max_entries_per_n_shred(&ticks[0], num_shreds, shred_data_size)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn max_entries_per_n_shred(entry: &Entry, num_shreds: u64) -> u64 {
 | 
			
		||||
    let shred_data_size = SIZE_OF_DATA_SHRED_PAYLOAD as u64;
 | 
			
		||||
pub fn max_entries_per_n_shred(
 | 
			
		||||
    entry: &Entry,
 | 
			
		||||
    num_shreds: u64,
 | 
			
		||||
    shred_data_size: Option<usize>,
 | 
			
		||||
) -> u64 {
 | 
			
		||||
    let shred_data_size = shred_data_size.unwrap_or(SIZE_OF_DATA_SHRED_PAYLOAD) as u64;
 | 
			
		||||
    let vec_size = bincode::serialized_size(&vec![entry]).unwrap();
 | 
			
		||||
    let entry_size = bincode::serialized_size(entry).unwrap();
 | 
			
		||||
    let count_size = vec_size - entry_size;
 | 
			
		||||
@@ -892,7 +947,7 @@ pub fn verify_test_data_shred(
 | 
			
		||||
    is_last_in_slot: bool,
 | 
			
		||||
    is_last_in_fec_set: bool,
 | 
			
		||||
) {
 | 
			
		||||
    assert_eq!(shred.payload.len(), PACKET_DATA_SIZE);
 | 
			
		||||
    assert_eq!(shred.payload.len(), SHRED_PAYLOAD_SIZE);
 | 
			
		||||
    assert!(shred.is_data());
 | 
			
		||||
    assert_eq!(shred.index(), index);
 | 
			
		||||
    assert_eq!(shred.slot(), slot);
 | 
			
		||||
@@ -933,6 +988,14 @@ pub mod tests {
 | 
			
		||||
            SIZE_OF_DATA_SHRED_HEADER,
 | 
			
		||||
            serialized_size(&DataShredHeader::default()).unwrap() as usize
 | 
			
		||||
        );
 | 
			
		||||
        let data_shred_header_with_size = DataShredHeader {
 | 
			
		||||
            size: 1000,
 | 
			
		||||
            ..DataShredHeader::default()
 | 
			
		||||
        };
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            SIZE_OF_DATA_SHRED_HEADER,
 | 
			
		||||
            serialized_size(&data_shred_header_with_size).unwrap() as usize
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            SIZE_OF_SIGNATURE,
 | 
			
		||||
            bincode::serialized_size(&Signature::default()).unwrap() as usize
 | 
			
		||||
@@ -952,17 +1015,15 @@ pub mod tests {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn verify_test_code_shred(shred: &Shred, index: u32, slot: Slot, pk: &Pubkey, verify: bool) {
 | 
			
		||||
        assert_eq!(shred.payload.len(), PACKET_DATA_SIZE);
 | 
			
		||||
        assert_eq!(shred.payload.len(), SHRED_PAYLOAD_SIZE);
 | 
			
		||||
        assert!(!shred.is_data());
 | 
			
		||||
        assert_eq!(shred.index(), index);
 | 
			
		||||
        assert_eq!(shred.slot(), slot);
 | 
			
		||||
        assert_eq!(verify, shred.verify(pk));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_data_shredder() {
 | 
			
		||||
    fn run_test_data_shredder(slot: Slot) {
 | 
			
		||||
        let keypair = Arc::new(Keypair::new());
 | 
			
		||||
        let slot = 0x1234_5678_9abc_def0;
 | 
			
		||||
 | 
			
		||||
        // Test that parent cannot be > current slot
 | 
			
		||||
        assert_matches!(
 | 
			
		||||
@@ -1052,6 +1113,11 @@ pub mod tests {
 | 
			
		||||
        assert_eq!(entries, deshred_entries);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_data_shredder() {
 | 
			
		||||
        run_test_data_shredder(0x1234_5678_9abc_def0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_deserialize_shred_payload() {
 | 
			
		||||
        let keypair = Arc::new(Keypair::new());
 | 
			
		||||
@@ -1144,22 +1210,21 @@ pub mod tests {
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_data_and_code_shredder() {
 | 
			
		||||
    fn run_test_data_and_code_shredder(slot: Slot) {
 | 
			
		||||
        let keypair = Arc::new(Keypair::new());
 | 
			
		||||
 | 
			
		||||
        let slot = 0x1234_5678_9abc_def0;
 | 
			
		||||
        // Test that FEC rate cannot be > 1.0
 | 
			
		||||
        assert_matches!(
 | 
			
		||||
            Shredder::new(slot, slot - 5, 1.001, keypair.clone(), 0, 0),
 | 
			
		||||
            Err(ShredError::InvalidFecRate(_))
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let shredder = Shredder::new(0x1234_5678_9abc_def0, slot - 5, 1.0, keypair.clone(), 0, 0)
 | 
			
		||||
        let shredder = Shredder::new(slot, slot - 5, 1.0, keypair.clone(), 0, 0)
 | 
			
		||||
            .expect("Failed in creating shredder");
 | 
			
		||||
 | 
			
		||||
        // Create enough entries to make > 1 shred
 | 
			
		||||
        let num_entries = max_ticks_per_n_shreds(1) + 1;
 | 
			
		||||
        let no_header_size = SIZE_OF_DATA_SHRED_PAYLOAD;
 | 
			
		||||
        let num_entries = max_ticks_per_n_shreds(1, Some(no_header_size)) + 1;
 | 
			
		||||
        let entries: Vec<_> = (0..num_entries)
 | 
			
		||||
            .map(|_| {
 | 
			
		||||
                let keypair0 = Keypair::new();
 | 
			
		||||
@@ -1191,9 +1256,12 @@ pub mod tests {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_recovery_and_reassembly() {
 | 
			
		||||
    fn test_data_and_code_shredder() {
 | 
			
		||||
        run_test_data_and_code_shredder(0x1234_5678_9abc_def0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn run_test_recovery_and_reassembly(slot: Slot) {
 | 
			
		||||
        let keypair = Arc::new(Keypair::new());
 | 
			
		||||
        let slot = 0x1234_5678_9abc_def0;
 | 
			
		||||
        let shredder = Shredder::new(slot, slot - 5, 1.0, keypair.clone(), 0, 0)
 | 
			
		||||
            .expect("Failed in creating shredder");
 | 
			
		||||
 | 
			
		||||
@@ -1203,7 +1271,9 @@ pub mod tests {
 | 
			
		||||
        let entry = Entry::new(&Hash::default(), 1, vec![tx0]);
 | 
			
		||||
 | 
			
		||||
        let num_data_shreds: usize = 5;
 | 
			
		||||
        let num_entries = max_entries_per_n_shred(&entry, num_data_shreds as u64);
 | 
			
		||||
        let no_header_size = SIZE_OF_DATA_SHRED_PAYLOAD;
 | 
			
		||||
        let num_entries =
 | 
			
		||||
            max_entries_per_n_shred(&entry, num_data_shreds as u64, Some(no_header_size));
 | 
			
		||||
        let entries: Vec<_> = (0..num_entries)
 | 
			
		||||
            .map(|_| {
 | 
			
		||||
                let keypair0 = Keypair::new();
 | 
			
		||||
@@ -1442,6 +1512,11 @@ pub mod tests {
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_recovery_and_reassembly() {
 | 
			
		||||
        run_test_recovery_and_reassembly(0x1234_5678_9abc_def0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_shred_version() {
 | 
			
		||||
        let keypair = Arc::new(Keypair::new());
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user