synor/crates/synor-storage/src/deal.rs
Gulshan Yadav f5bdef2691 feat(storage): add Synor Storage L2 decentralized storage layer
Complete implementation of the Synor Storage Layer (L2) for decentralized
content storage. This enables permanent, censorship-resistant storage of
any file type including Next.js apps, Flutter apps, and arbitrary data.

Core modules:
- cid.rs: Content addressing with Blake3/SHA256 hashing (synor1... format)
- chunker.rs: File chunking for parallel upload/download (1MB chunks)
- erasure.rs: Reed-Solomon erasure coding (10+4 shards) for fault tolerance
- proof.rs: Storage proofs with Merkle trees for verification
- deal.rs: Storage deals and market economics (3 pricing tiers)

Infrastructure:
- node/: Storage node service with P2P networking and local storage
- gateway/: HTTP gateway for browser access with LRU caching
- Docker deployment with nginx load balancer

Architecture:
- Operates as L2 alongside Synor L1 blockchain
- Storage proofs verified on-chain for reward distribution
- Can lose 4 shards per chunk and still recover data
- Gateway URLs: /synor1<cid> for content access

All 28 unit tests passing.
2026-01-10 11:42:03 +05:30

321 lines
9.4 KiB
Rust

//! Storage Deals - Economic layer for storage
//!
//! Users pay storage providers to store their data.
//! Deals are registered on L1 for enforcement.
use crate::cid::ContentId;
use serde::{Deserialize, Serialize};
/// Storage deal status
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DealStatus {
/// Awaiting storage node acceptance
Pending,
/// Deal active, data being stored
Active,
/// Deal completed successfully
Completed,
/// Storage node failed proofs
Failed,
/// Deal cancelled by user
Cancelled,
}
/// Storage deal between user and storage provider
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageDeal {
/// Unique deal ID
pub deal_id: [u8; 32],
/// Content being stored
pub cid: ContentId,
/// Client address (paying for storage)
pub client: [u8; 32],
/// Storage provider node ID
pub provider: Option<[u8; 32]>,
/// Content size in bytes
pub size: u64,
/// Storage duration (in L1 blocks)
pub duration: u64,
/// Price per byte per epoch (in atomic SYNOR units)
pub price_per_byte_epoch: u64,
/// Total collateral locked by provider
pub provider_collateral: u64,
/// Client deposit
pub client_deposit: u64,
/// Deal start block (on L1)
pub start_block: u64,
/// Deal end block (on L1)
pub end_block: u64,
/// Current status
pub status: DealStatus,
/// Replication factor (how many nodes store the data)
pub replication: u8,
/// Failed proof count
pub failed_proofs: u32,
/// Successful proof count
pub successful_proofs: u32,
}
impl StorageDeal {
/// Calculate total cost for the deal
pub fn total_cost(&self) -> u64 {
let epochs = self.duration;
self.size
.saturating_mul(self.price_per_byte_epoch)
.saturating_mul(epochs)
.saturating_mul(self.replication as u64)
}
/// Check if deal is active
pub fn is_active(&self) -> bool {
self.status == DealStatus::Active
}
/// Check if deal has expired
pub fn is_expired(&self, current_block: u64) -> bool {
current_block >= self.end_block
}
/// Calculate provider reward for successful storage
pub fn provider_reward(&self) -> u64 {
// Provider gets paid based on successful proofs
let total_possible_proofs = self.duration / 100; // Assume proof every 100 blocks
if total_possible_proofs == 0 {
return 0;
}
let success_rate = self.successful_proofs as u64 * 100 / total_possible_proofs;
self.total_cost() * success_rate / 100
}
}
/// Request to create a new storage deal
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateDealRequest {
/// Content to store
pub cid: ContentId,
/// Content size
pub size: u64,
/// Desired duration (blocks)
pub duration: u64,
/// Maximum price willing to pay
pub max_price_per_byte_epoch: u64,
/// Desired replication factor
pub replication: u8,
/// Preferred regions (optional)
pub preferred_regions: Vec<String>,
}
/// Storage provider offer
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageOffer {
/// Provider node ID
pub provider: [u8; 32],
/// Available capacity (bytes)
pub available_capacity: u64,
/// Price per byte per epoch
pub price_per_byte_epoch: u64,
/// Minimum deal duration
pub min_duration: u64,
/// Maximum deal duration
pub max_duration: u64,
/// Regions served
pub regions: Vec<String>,
/// Provider stake amount
pub stake: u64,
/// Historical uptime percentage
pub uptime: u8,
/// Success rate (proofs passed / total)
pub success_rate: u8,
}
/// Storage market for matching clients with providers
#[derive(Debug, Default)]
pub struct StorageMarket {
/// Active deals
pub deals: Vec<StorageDeal>,
/// Available offers from providers
pub offers: Vec<StorageOffer>,
}
impl StorageMarket {
/// Create a new storage market
pub fn new() -> Self {
Self::default()
}
/// Find best providers for a deal request
pub fn find_providers(&self, request: &CreateDealRequest) -> Vec<&StorageOffer> {
self.offers
.iter()
.filter(|offer| {
offer.available_capacity >= request.size
&& offer.price_per_byte_epoch <= request.max_price_per_byte_epoch
&& offer.min_duration <= request.duration
&& offer.max_duration >= request.duration
})
.collect()
}
/// Add a storage offer
pub fn add_offer(&mut self, offer: StorageOffer) {
self.offers.push(offer);
}
/// Create a deal from request and accepted offer
pub fn create_deal(
&mut self,
request: CreateDealRequest,
provider: [u8; 32],
client: [u8; 32],
current_block: u64,
) -> StorageDeal {
let deal_id = self.generate_deal_id(&request.cid, &client, current_block);
let offer = self.offers.iter().find(|o| o.provider == provider);
let price = offer.map(|o| o.price_per_byte_epoch).unwrap_or(0);
let deal = StorageDeal {
deal_id,
cid: request.cid,
client,
provider: Some(provider),
size: request.size,
duration: request.duration,
price_per_byte_epoch: price,
provider_collateral: 0, // Set during acceptance
client_deposit: 0, // Set during creation
start_block: current_block,
end_block: current_block + request.duration,
status: DealStatus::Pending,
replication: request.replication,
failed_proofs: 0,
successful_proofs: 0,
};
self.deals.push(deal.clone());
deal
}
fn generate_deal_id(&self, cid: &ContentId, client: &[u8; 32], block: u64) -> [u8; 32] {
let mut input = Vec::new();
input.extend_from_slice(&cid.digest);
input.extend_from_slice(client);
input.extend_from_slice(&block.to_le_bytes());
*blake3::hash(&input).as_bytes()
}
}
/// Pricing tiers for storage
#[derive(Debug, Clone)]
pub struct PricingTier {
/// Tier name
pub name: String,
/// Price per GB per month (in atomic SYNOR)
pub price_per_gb_month: u64,
/// Minimum replication
pub min_replication: u8,
/// SLA uptime guarantee in basis points (10000 = 100%)
pub uptime_sla_bps: u16,
}
impl PricingTier {
/// Standard tier - basic storage
pub fn standard() -> Self {
Self {
name: "Standard".to_string(),
price_per_gb_month: 100_000_000, // 0.1 SYNOR per GB/month
min_replication: 3,
uptime_sla_bps: 9900, // 99.00%
}
}
/// Premium tier - higher redundancy
pub fn premium() -> Self {
Self {
name: "Premium".to_string(),
price_per_gb_month: 250_000_000, // 0.25 SYNOR per GB/month
min_replication: 5,
uptime_sla_bps: 9990, // 99.90%
}
}
/// Permanent tier - one-time payment for ~20 years
pub fn permanent() -> Self {
Self {
name: "Permanent".to_string(),
price_per_gb_month: 2_400_000_000, // 2.4 SYNOR per GB (one-time, ~20 years equiv)
min_replication: 10,
uptime_sla_bps: 9999, // 99.99%
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_deal_cost() {
let deal = StorageDeal {
deal_id: [0u8; 32],
cid: ContentId::from_content(b"test"),
client: [1u8; 32],
provider: Some([2u8; 32]),
size: 1_000_000_000, // 1 GB
duration: 100,
price_per_byte_epoch: 1,
provider_collateral: 0,
client_deposit: 0,
start_block: 0,
end_block: 100,
status: DealStatus::Active,
replication: 3,
failed_proofs: 0,
successful_proofs: 0,
};
let cost = deal.total_cost();
assert_eq!(cost, 1_000_000_000 * 100 * 3);
}
#[test]
fn test_market_find_providers() {
let mut market = StorageMarket::new();
market.add_offer(StorageOffer {
provider: [1u8; 32],
available_capacity: 100_000_000_000, // 100 GB
price_per_byte_epoch: 1,
min_duration: 10,
max_duration: 10000,
regions: vec!["US".to_string()],
stake: 1000,
uptime: 99,
success_rate: 99,
});
let request = CreateDealRequest {
cid: ContentId::from_content(b"test"),
size: 1_000_000,
duration: 100,
max_price_per_byte_epoch: 2,
replication: 3,
preferred_regions: vec![],
};
let providers = market.find_providers(&request);
assert_eq!(providers.len(), 1);
}
#[test]
fn test_pricing_tiers() {
let standard = PricingTier::standard();
let premium = PricingTier::premium();
let permanent = PricingTier::permanent();
assert!(standard.price_per_gb_month < premium.price_per_gb_month);
assert!(premium.price_per_gb_month < permanent.price_per_gb_month);
assert!(standard.min_replication < premium.min_replication);
}
}