Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 52 additions & 47 deletions Cargo.lock

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

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ members = [
"fendermint/actors/ipc_storage_config/shared",

# storage components (netwatch patched for socket2 0.5 compatibility!)
"ipc-storage/erasure-encoding",
"ipc-storage/iroh_manager",
"ipc-storage/ipld",
"ipc-storage/actor_sdk",
Expand Down Expand Up @@ -165,6 +166,7 @@ libp2p-bitswap = { path = "ext/libp2p-bitswap" }
libsecp256k1 = "0.7"
literally = "0.1.3"
log = "0.4"
memmap2 = "0.9"
lru_time_cache = "0.11"
multiaddr = "0.18"
multihash = { version = "0.18.1", default-features = false, features = [
Expand All @@ -184,14 +186,12 @@ quickcheck = "1"
quickcheck_async = "0.1"
quickcheck_macros = "1"
rand = "0.8"
reed-solomon-simd = "3"
rand_chacha = "0.3"
regex = "1"
replace_with = "0.1.7"
statrs = "0.18.0"
reqwest = { version = "0.11.13", features = ["json"] }
# entanglement library
entangler = { package = "recall_entangler", git = "https://github.com/recallnet/entanglement.git", rev = "aee1c675ff05e5cde4771a2e2eb3ac4dab8476bc" }
entangler_storage = { package = "recall_entangler_storage", git = "https://github.com/recallnet/entanglement.git", rev = "aee1c675ff05e5cde4771a2e2eb3ac4dab8476bc" }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe also remove the patched netwatch, as well?

# Objects HTTP API dependencies
warp = "0.3"
uuid = { version = "1.0", features = ["v4"] }
Expand Down
4 changes: 4 additions & 0 deletions fendermint/actors/blobs/shared/src/blobs/blob.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@ pub struct Blob {
pub subscribers: HashMap<SubscriptionId, ChainEpoch>,
/// Blob status.
pub status: BlobStatus,
/// Number of data shards per chunk for erasure encoding (k).
pub data_shards: u16,
/// Number of parity shards per chunk for erasure encoding (m).
pub parity_shards: u16,
}
4 changes: 4 additions & 0 deletions fendermint/actors/blobs/shared/src/blobs/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ pub struct AddBlobParams {
/// Blob time-to-live epochs.
/// If not specified, the current default TTL from the config actor is used.
pub ttl: Option<ChainEpoch>,
/// Number of data shards per chunk for erasure encoding (k).
pub data_shards: u16,
/// Number of parity shards per chunk for erasure encoding (m).
pub parity_shards: u16,
}

/// Params for getting a blob.
Expand Down
71 changes: 63 additions & 8 deletions fendermint/actors/blobs/src/actor/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,12 @@ impl BlobsActor {
)
}

/// Verify aggregated BLS signatures for blob finalization
/// Verify aggregated BLS signatures for blob finalization.
///
/// Only operators assigned to store shards of this blob are required to sign.
/// The set of assigned operators is computed deterministically from the blob hash,
/// blob size, encoding parameters (data_shards, parity_shards), and the active
/// operator list. The quorum threshold is 2/3+ of the assigned operators.
fn verify_blob_signatures(
rt: &impl Runtime,
params: &FinalizeBlobParams,
Expand All @@ -237,7 +242,52 @@ impl BlobsActor {
));
}

// Extract signer indices from bitmap and collect their public keys
// Look up blob to get encoding parameters
let blob = state
.get_blob(rt.store(), params.hash)?
.ok_or_else(|| ActorError::not_found(format!("Blob {} not found", params.hash)))?;

let data_shards = blob.data_shards as usize;
let parity_shards = blob.parity_shards as usize;
let shards_per_chunk = data_shards + parity_shards;

// Compute number of chunks from blob size
const MAX_CHUNK_SIZE: u64 = 16 * 1024 * 1024; // 16 MiB, matches erasure-encoding
let num_chunks = if params.size == 0 {
1
} else {
((params.size + MAX_CHUNK_SIZE - 1) / MAX_CHUNK_SIZE) as usize
};

// Compute the set of unique assigned operator indices using the deterministic
// assignment formula from erasure-encoding:
// rotation_offset = blob_hash (big-endian) % num_nodes
// node_index = (chunk_index * shards_per_chunk + shard_index + rotation_offset) % num_nodes
let rotation_offset = {
let mut remainder: u64 = 0;
for &byte in &params.hash.0 {
remainder = (remainder * 256 + byte as u64) % total_operators as u64;
}
remainder as usize
};

let mut assigned_indices = std::collections::BTreeSet::new();
for chunk_idx in 0..num_chunks {
for shard_idx in 0..shards_per_chunk {
let shard_global = chunk_idx * shards_per_chunk + shard_idx;
let node_index = (shard_global + rotation_offset) % total_operators;
assigned_indices.insert(node_index);
}
}

let assigned_count = assigned_indices.len();
if assigned_count == 0 {
return Err(ActorError::illegal_state(
"No operators assigned to blob".into(),
));
}

// Extract signer indices from bitmap, only counting assigned operators
let mut signer_pubkeys = Vec::new();
let mut signer_count = 0;

Expand All @@ -248,6 +298,11 @@ impl BlobsActor {

// Check if this operator signed (bit is set in bitmap)
if (params.signer_bitmap & (1u128 << index)) != 0 {
// Only count signers that are in the assigned set
if !assigned_indices.contains(&index) {
continue;
}

signer_count += 1;

// Get operator info to retrieve BLS public key
Expand All @@ -274,12 +329,12 @@ impl BlobsActor {
}
}

// Check threshold: need at least 2/3+ of operators
let threshold = (total_operators * 2 + 2) / 3; // Ceiling of 2/3
// Check threshold: need at least 2/3+ of assigned operators
let threshold = (assigned_count * 2 + 2) / 3; // Ceiling of 2/3
if signer_count < threshold {
return Err(ActorError::illegal_argument(format!(
"Insufficient signatures: got {}, need {} out of {}",
signer_count, threshold, total_operators
"Insufficient signatures: got {}, need {} out of {} assigned operators",
signer_count, threshold, assigned_count
)));
}

Expand All @@ -294,7 +349,6 @@ impl BlobsActor {
let messages: Vec<&[u8]> = vec![hash_bytes; signer_count];

// Verify the aggregated signature using verify_messages
// This verifies that the aggregated signature corresponds to the individual signatures
let verification_result = verify_messages(&aggregated_sig, &messages, &signer_pubkeys);

if !verification_result {
Expand All @@ -304,8 +358,9 @@ impl BlobsActor {
}

log::info!(
"BLS signature verified: {} operators signed (threshold: {}/{})",
"BLS signature verified: {}/{} assigned operators signed (threshold: {}, total operators: {})",
signer_count,
assigned_count,
threshold,
total_operators
);
Expand Down
4 changes: 4 additions & 0 deletions fendermint/actors/blobs/src/sol_facade/blobs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ impl AbiCallRuntime for sol::addBlobCall {
size,
ttl,
from,
data_shards: self.dataShards,
parity_shards: self.parityShards,
})
}
fn returns(&self, returns: Self::Returns) -> Self::Output {
Expand Down Expand Up @@ -264,6 +266,8 @@ impl AbiCallRuntime for sol::overwriteBlobCall {
size,
ttl,
from,
data_shards: self.dataShards,
parity_shards: self.parityShards,
},
})
}
Expand Down
Loading
Loading