extern crate alloc;
use alloc::sync::{Arc, Weak};
use alloc::vec::Vec;
use super::{bitvector::BitVector, MAX_EXTRANONCE_LEN};
#[derive(Debug)]
pub struct ExtranoncePrefix {
prefix: Vec<u8>,
upstream_prefix_len: u8,
allocation: Option<Arc<PrefixAllocation>>,
}
#[derive(Debug)]
pub struct AllocatedExtranoncePrefix(ExtranoncePrefix);
#[derive(Debug)]
struct PrefixAllocation {
local_index: u32,
bitmap: Weak<BitVector>,
}
impl ExtranoncePrefix {
#[inline]
pub fn from_wire(prefix: Vec<u8>) -> Result<Self, ExtranoncePrefixError> {
if prefix.len() > MAX_EXTRANONCE_LEN as usize {
return Err(ExtranoncePrefixError::ExceedsMaxLength);
}
let upstream_prefix_len = prefix.len() as u8;
Ok(Self {
prefix,
upstream_prefix_len,
allocation: None,
})
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
&self.prefix
}
#[inline]
pub fn len(&self) -> usize {
self.prefix.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.prefix.is_empty()
}
#[inline]
pub fn upstream_prefix_len(&self) -> u8 {
self.upstream_prefix_len
}
#[inline]
pub(crate) fn preserved_len(&self) -> usize {
self.len() - self.upstream_prefix_len as usize
}
pub fn set_upstream_prefix(
&mut self,
upstream_prefix: &[u8],
) -> Result<(), ExtranoncePrefixError> {
let preserved_bytes = &self.prefix[self.upstream_prefix_len as usize..];
let updated_len = upstream_prefix.len() + preserved_bytes.len();
if updated_len > MAX_EXTRANONCE_LEN as usize {
return Err(ExtranoncePrefixError::ExceedsMaxLength);
}
let mut updated_prefix = Vec::with_capacity(updated_len);
updated_prefix.extend_from_slice(upstream_prefix);
updated_prefix.extend_from_slice(preserved_bytes);
self.prefix = updated_prefix;
self.upstream_prefix_len = upstream_prefix.len() as u8;
Ok(())
}
pub(crate) fn snapshot_for_upstream_update(&self, upstream_prefix: &[u8]) -> Option<Self> {
if upstream_prefix == &self.prefix[..self.upstream_prefix_len as usize]
|| !self.holds_allocator_slot()
{
return None;
}
Some(Self {
prefix: self.prefix.clone(),
upstream_prefix_len: self.upstream_prefix_len,
allocation: self.allocation.clone(),
})
}
fn shares_allocation_with(&self, other: &Self) -> bool {
match (&self.allocation, &other.allocation) {
(Some(left), Some(right)) => Arc::ptr_eq(left, right),
_ => false,
}
}
#[inline]
pub fn holds_allocator_slot(&self) -> bool {
self.allocation
.as_ref()
.is_some_and(|allocation| allocation.bitmap.strong_count() > 0)
}
}
impl AllocatedExtranoncePrefix {
#[inline]
pub(crate) fn from_allocation(
local_index: u32,
upstream_prefix_len: u8,
prefix: Vec<u8>,
bitmap: Weak<BitVector>,
) -> Self {
Self(ExtranoncePrefix {
prefix,
upstream_prefix_len,
allocation: Some(Arc::new(PrefixAllocation {
local_index,
bitmap,
})),
})
}
#[cfg(test)]
#[inline]
pub fn for_test(prefix: Vec<u8>) -> Result<Self, ExtranoncePrefixError> {
Ok(Self(ExtranoncePrefix::from_wire(prefix)?))
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
#[inline]
pub fn len(&self) -> usize {
self.0.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[inline]
pub fn upstream_prefix_len(&self) -> u8 {
self.0.upstream_prefix_len()
}
}
impl From<AllocatedExtranoncePrefix> for ExtranoncePrefix {
#[inline]
fn from(allocated: AllocatedExtranoncePrefix) -> Self {
allocated.0
}
}
impl PartialEq for ExtranoncePrefix {
fn eq(&self, other: &Self) -> bool {
self.prefix == other.prefix
}
}
impl Eq for ExtranoncePrefix {}
impl Drop for PrefixAllocation {
fn drop(&mut self) {
if let Some(bitmap) = self.bitmap.upgrade() {
bitmap.set(self.local_index as usize, false);
}
}
}
#[derive(Debug, Default)]
pub(crate) struct RetiredExtranoncePrefixes(Vec<ExtranoncePrefix>);
impl RetiredExtranoncePrefixes {
pub(crate) fn retire<'a>(
&mut self,
prefix: ExtranoncePrefix,
live_prefixes: impl Iterator<Item = &'a [u8]> + Clone,
) {
self.prune(live_prefixes.clone());
if prefix.holds_allocator_slot()
&& live_prefixes.clone().any(|live| live == prefix.as_bytes())
&& !self
.0
.iter()
.any(|retired| retired == &prefix && retired.shares_allocation_with(&prefix))
{
self.0.push(prefix);
}
}
pub(crate) fn prune<'a>(&mut self, live_prefixes: impl Iterator<Item = &'a [u8]> + Clone) {
self.0.retain(|prefix| {
prefix.holds_allocator_slot()
&& live_prefixes.clone().any(|live| live == prefix.as_bytes())
});
}
#[cfg(test)]
pub(crate) fn len(&self) -> usize {
self.0.len()
}
#[cfg(test)]
pub(crate) fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ExtranoncePrefixError {
ExceedsMaxLength,
}
impl core::fmt::Display for ExtranoncePrefixError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::ExceedsMaxLength => {
write!(f, "extranonce prefix exceeds {MAX_EXTRANONCE_LEN} bytes")
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::extranonce_manager::ExtranonceAllocator;
#[test]
fn upstream_snapshots_release_slot_only_after_last_owner_drops() {
let mut allocator =
ExtranonceAllocator::from_upstream_prefix(vec![0xaa], vec![0xbb], 6, 1).unwrap();
let mut current: ExtranoncePrefix = allocator.allocate_extended(3).unwrap().into();
let first = current.snapshot_for_upstream_update(&[0xcc]).unwrap();
current.set_upstream_prefix(&[0xcc]).unwrap();
let second = current.snapshot_for_upstream_update(&[0xdd]).unwrap();
current.set_upstream_prefix(&[0xdd]).unwrap();
let live_bytes = [first.as_bytes().to_vec(), second.as_bytes().to_vec()];
let mut retired = RetiredExtranoncePrefixes::default();
retired.retire(first, live_bytes.iter().map(Vec::as_slice));
retired.retire(second, live_bytes.iter().map(Vec::as_slice));
assert_eq!(retired.len(), 2);
assert_eq!(allocator.allocated_count(), 1);
drop(current);
retired.prune(live_bytes[..1].iter().map(Vec::as_slice));
assert_eq!(retired.len(), 1);
assert_eq!(allocator.allocated_count(), 1);
retired.prune(core::iter::empty());
assert!(retired.is_empty());
assert_eq!(allocator.allocated_count(), 0);
assert!(allocator.allocate_extended(3).is_ok());
}
#[test]
fn repeated_upstream_snapshots_are_deduplicated_by_allocation_and_bytes() {
let mut allocator =
ExtranonceAllocator::from_upstream_prefix(vec![0xaa], vec![0xbb], 6, 1).unwrap();
let mut current: ExtranoncePrefix = allocator.allocate_extended(3).unwrap().into();
let old_bytes = current.as_bytes().to_vec();
let mut other_bytes = old_bytes.clone();
other_bytes[0] = 0xcc;
let live_bytes = [old_bytes, other_bytes];
let mut retired = RetiredExtranoncePrefixes::default();
for _ in 0..100 {
for upstream in [0xcc, 0xaa] {
let snapshot = current.snapshot_for_upstream_update(&[upstream]).unwrap();
current.set_upstream_prefix(&[upstream]).unwrap();
retired.retire(snapshot, live_bytes.iter().map(Vec::as_slice));
assert!(retired.len() <= 2);
assert_eq!(allocator.allocated_count(), 1);
}
}
assert_eq!(retired.len(), 2);
retired.prune(core::iter::empty());
assert!(retired.is_empty());
assert_eq!(allocator.allocated_count(), 1);
drop(current);
assert_eq!(allocator.allocated_count(), 0);
}
#[test]
fn byte_identical_snapshots_from_distinct_allocators_keep_both_slots() {
let mut first_allocator =
ExtranonceAllocator::from_upstream_prefix(vec![0xaa], vec![0xbb], 6, 1).unwrap();
let mut second_allocator =
ExtranonceAllocator::from_upstream_prefix(vec![0xaa], vec![0xbb], 6, 1).unwrap();
let first: ExtranoncePrefix = first_allocator.allocate_extended(3).unwrap().into();
let second: ExtranoncePrefix = second_allocator.allocate_extended(3).unwrap().into();
assert_eq!(first.as_bytes(), second.as_bytes());
let live_bytes = first.as_bytes().to_vec();
let mut retired = RetiredExtranoncePrefixes::default();
for current in [first, second] {
retired.retire(
current.snapshot_for_upstream_update(&[0xcc]).unwrap(),
core::iter::once(live_bytes.as_slice()),
);
}
assert_eq!(retired.len(), 2);
assert_eq!(first_allocator.allocated_count(), 1);
assert_eq!(second_allocator.allocated_count(), 1);
retired.prune(core::iter::empty());
assert_eq!(first_allocator.allocated_count(), 0);
assert_eq!(second_allocator.allocated_count(), 0);
}
#[test]
fn upstream_snapshots_skip_noops_and_prefixes_without_live_allocators() {
let wire = ExtranoncePrefix::from_wire(vec![0xaa]).unwrap();
assert!(wire.snapshot_for_upstream_update(&[0xcc]).is_none());
let mut allocator =
ExtranonceAllocator::from_upstream_prefix(vec![0xaa], vec![0xbb], 6, 1).unwrap();
let current: ExtranoncePrefix = allocator.allocate_extended(3).unwrap().into();
assert!(current.snapshot_for_upstream_update(&[0xaa]).is_none());
let snapshot = current.snapshot_for_upstream_update(&[0xcc]).unwrap();
drop(allocator);
assert!(!snapshot.holds_allocator_slot());
assert!(current.snapshot_for_upstream_update(&[0xcc]).is_none());
let mut retired = RetiredExtranoncePrefixes::default();
retired.retire(snapshot, core::iter::once(current.as_bytes()));
assert!(retired.is_empty());
}
#[test]
fn preserved_len_includes_local_index_and_standard_padding() {
let mut allocator =
ExtranonceAllocator::from_upstream_prefix(vec![0xaa], vec![0xbb], 6, 256).unwrap();
let extended: ExtranoncePrefix = allocator.allocate_extended(3).unwrap().into();
let standard: ExtranoncePrefix = allocator.allocate_standard().unwrap().into();
let wire = ExtranoncePrefix::from_wire(vec![0xaa, 0xbb]).unwrap();
assert_eq!(extended.preserved_len(), 2);
assert_eq!(standard.preserved_len(), 5);
assert_eq!(wire.preserved_len(), 0);
}
#[test]
fn upstream_prefix_update_preserves_local_regions_and_allocation() {
let mut allocator =
ExtranonceAllocator::from_upstream_prefix(vec![0xaa], vec![0xbb], 6, 256).unwrap();
let mut prefix: ExtranoncePrefix = allocator.allocate_extended(3).unwrap().into();
prefix.set_upstream_prefix(&[0xcc, 0xdd]).unwrap();
assert_eq!(prefix.as_bytes(), &[0xcc, 0xdd, 0xbb, 0x00]);
assert_eq!(prefix.upstream_prefix_len(), 2);
assert_eq!(allocator.allocated_count(), 1);
drop(prefix);
assert_eq!(allocator.allocated_count(), 0);
}
#[test]
fn upstream_prefix_update_replaces_a_wire_sourced_prefix() {
let mut prefix = ExtranoncePrefix::from_wire(vec![0xaa, 0xbb]).unwrap();
assert_eq!(prefix.upstream_prefix_len(), 2);
prefix.set_upstream_prefix(&[0xcc]).unwrap();
assert_eq!(prefix.as_bytes(), &[0xcc]);
assert_eq!(prefix.upstream_prefix_len(), 1);
}
#[test]
fn oversized_upstream_prefix_update_is_transactional() {
let mut allocator =
ExtranonceAllocator::from_upstream_prefix(vec![0xaa], vec![0xbb], 32, 256).unwrap();
let mut prefix: ExtranoncePrefix = allocator.allocate_extended(29).unwrap().into();
assert_eq!(
prefix.set_upstream_prefix(&[0xcc; 31]),
Err(ExtranoncePrefixError::ExceedsMaxLength)
);
assert_eq!(prefix.as_bytes(), &[0xaa, 0xbb, 0x00]);
assert_eq!(prefix.upstream_prefix_len(), 1);
assert_eq!(allocator.allocated_count(), 1);
}
}