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
12 changes: 4 additions & 8 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,7 @@ jobs:
echo "//Alice" > /tmp/alice-key && chmod 600 /tmp/alice-key
nohup ./target/release/storage-provider-node \
--keyfile /tmp/alice-key --storage-mode inmemory \
--bind-addr 0.0.0.0:3333 --chain-rpc ws://127.0.0.1:2222 \
--enable-checkpoint-coordinator > /tmp/provider-inmemory.log 2>&1 &
--bind-addr 0.0.0.0:3333 --chain-rpc ws://127.0.0.1:2222 > /tmp/provider-inmemory.log 2>&1 &

- name: Wait for provider health
uses: ./.github/actions/wait-for-provider-health
Expand Down Expand Up @@ -340,8 +339,7 @@ jobs:
echo "//Alice" > /tmp/alice-key && chmod 600 /tmp/alice-key
nohup ./target/release/storage-provider-node \
--keyfile /tmp/alice-key --storage-mode inmemory \
--bind-addr 0.0.0.0:3333 --chain-rpc ws://127.0.0.1:2222 \
--enable-checkpoint-coordinator > /tmp/provider.log 2>&1 &
--bind-addr 0.0.0.0:3333 --chain-rpc ws://127.0.0.1:2222 > /tmp/provider.log 2>&1 &

- name: Wait for provider health
uses: ./.github/actions/wait-for-provider-health
Expand Down Expand Up @@ -573,8 +571,7 @@ jobs:
echo "//Alice" > /tmp/alice-key && chmod 600 /tmp/alice-key
nohup ./target/release/storage-provider-node \
--keyfile /tmp/alice-key --storage-mode inmemory \
--bind-addr 0.0.0.0:3333 --chain-rpc ws://127.0.0.1:2222 \
--enable-checkpoint-coordinator > /tmp/provider.log 2>&1 &
--bind-addr 0.0.0.0:3333 --chain-rpc ws://127.0.0.1:2222 > /tmp/provider.log 2>&1 &

- name: Wait for provider health
uses: ./.github/actions/wait-for-provider-health
Expand Down Expand Up @@ -705,8 +702,7 @@ jobs:
echo "//Alice" > /tmp/alice-key && chmod 600 /tmp/alice-key
nohup ./target/release/storage-provider-node \
--keyfile /tmp/alice-key --storage-mode inmemory \
--bind-addr 0.0.0.0:3333 --chain-rpc ws://127.0.0.1:2222 \
--enable-checkpoint-coordinator > /tmp/provider.log 2>&1 &
--bind-addr 0.0.0.0:3333 --chain-rpc ws://127.0.0.1:2222 > /tmp/provider.log 2>&1 &

- name: Wait for provider health
uses: ./.github/actions/wait-for-provider-health
Expand Down
6 changes: 0 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -359,12 +359,6 @@ pub const MinStakePerByte: Balance = 1_000;
pub const ChallengeTimeout: BlockNumber = 48 * RC_HOURS;
pub const SettlementTimeout: BlockNumber = 24 * RC_HOURS;
pub const RequestTimeout: BlockNumber = 6 * RC_HOURS;

// Provider-initiated checkpoint config
pub const DefaultCheckpointInterval: BlockNumber = 100; // anchor blocks (~10 min)
pub const DefaultCheckpointGrace: BlockNumber = 20; // anchor blocks (~2 min)
pub const CheckpointReward: Balance = 1_000_000_000_000; // 1 token
pub const CheckpointMissPenalty: Balance = 500_000_000_000; // 0.5 token
```

### Provider Settings (configured per provider)
Expand Down
2 changes: 1 addition & 1 deletion client/tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ pub async fn dev_discovery() -> Option<DiscoveryClient> {
/// Spawn an in-process provider node on a random port and return its URL.
///
/// Uses `//Alice` as the signing key so endpoints that sign commitments
/// (`/commit`, `/commitment`, `/checkpoint/sign`, `/delete`) work end-to-end.
/// (`/commit`, `/commitment`, `/checkpoint-signature`, `/delete`) work end-to-end.
/// `//Alice` is granted `Admin` on every bucket.
pub async fn start_test_provider() -> String {
let deps = ProviderDeps {
Expand Down
8 changes: 0 additions & 8 deletions crates/pallets/drive-registry/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,6 @@ parameter_types! {
pub const SettlementTimeout: u64 = 50;
pub const RequestTimeout: u64 = 50;
pub TreasuryAccount: u64 = 999; // Treasury account
pub const DefaultCheckpointInterval: u64 = 100;
pub const DefaultCheckpointGrace: u64 = 20;
pub const CheckpointReward: u64 = 1_000_000_000_000;
pub const CheckpointMissPenalty: u64 = 500_000_000_000;
}

impl pallet_storage_provider::Config for Test {
Expand All @@ -107,10 +103,6 @@ impl pallet_storage_provider::Config for Test {
type MaxNonceAge = MaxNonceAge;
type SettlementTimeout = SettlementTimeout;
type RequestTimeout = RequestTimeout;
type DefaultCheckpointInterval = DefaultCheckpointInterval;
type DefaultCheckpointGrace = DefaultCheckpointGrace;
type CheckpointReward = CheckpointReward;
type CheckpointMissPenalty = CheckpointMissPenalty;
// Must be > ChallengeTimeout (100) AND > RequestTimeout (50) per the
// pallet's `integrity_test`. 100 + 50 grace.
type DeregisterAnnouncementPeriod = ConstU64<150>;
Expand Down
8 changes: 0 additions & 8 deletions crates/pallets/s3-registry/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,6 @@ parameter_types! {
pub const MaxNonceAge: u64 = 200;
pub const SettlementTimeout: u64 = 50;
pub const RequestTimeout: u64 = 25;
pub const DefaultCheckpointInterval: u64 = 100;
pub const DefaultCheckpointGrace: u64 = 20;
pub const CheckpointReward: Balance = 1_000_000_000_000;
pub const CheckpointMissPenalty: Balance = 500_000_000_000;
pub TreasuryAccount: u64 = 999;
}

Expand All @@ -107,10 +103,6 @@ impl pallet_storage_provider::Config for Test {
type MaxNonceAge = MaxNonceAge;
type SettlementTimeout = SettlementTimeout;
type RequestTimeout = RequestTimeout;
type DefaultCheckpointInterval = DefaultCheckpointInterval;
type DefaultCheckpointGrace = DefaultCheckpointGrace;
type CheckpointReward = CheckpointReward;
type CheckpointMissPenalty = CheckpointMissPenalty;
// Must be > ChallengeTimeout (100) AND > RequestTimeout (25) per the
// pallet's `integrity_test`. 100 + 50 grace.
type DeregisterAnnouncementPeriod = ConstU64<150>;
Expand Down
154 changes: 0 additions & 154 deletions crates/pallets/storage-provider/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,30 +304,10 @@ mod benchmarks {
deregister_provider(RawOrigin::Signed(provider2));
}

/// Worst case: provider has the maximum representative number of pending
/// `CheckpointRewards` entries, all drained via `iter_prefix` + `take`
/// before stake is unreserved and the record removed.
///
/// The drain count is bounded by `MaxBucketsPerMember` — the closest
/// existing config-derived cap on "how many buckets one entity holds
/// state in." Not a strict semantic match (a provider isn't required to
/// be a bucket member), but it's the right order of magnitude and tracks
/// the runtime's storage-bloat tolerance for the same shape of data.
#[benchmark]
fn complete_deregister() {
let provider = create_provider::<T>(0);

// Seed reward entries across distinct buckets. We poke storage
// directly because exercising the real checkpoint-reward credit path
// requires a full multi-provider checkpoint setup per bucket, which
// would dominate the benchmark and obscure the drain cost we're
// actually measuring.
let reward: BalanceOf<T> = 1_000u32.into();
let n = T::MaxBucketsPerMember::get();
for i in 0..n {
CheckpointRewards::<T>::insert(&provider, i as BucketId, reward);
}

// Announce step.
let _ = Pallet::<T>::deregister_provider(RawOrigin::Signed(provider.clone()).into());

Expand Down Expand Up @@ -734,140 +714,6 @@ mod benchmarks {
extend_checkpoint(RawOrigin::Signed(admin), bucket_id, additional_signatures);
}

#[benchmark]
fn fund_checkpoint_pool() {
let admin = funded_account::<T>("admin", 0);
let bucket_id = setup_bucket::<T>(&admin);
let amount = BalanceOf::<T>::max_value() / 100u32.into();

#[extrinsic_call]
fund_checkpoint_pool(RawOrigin::Signed(admin), bucket_id, amount);
}

// ─────────────────────────────────────────────────────────────────────────
// Provider-Initiated Checkpoints
// ─────────────────────────────────────────────────────────────────────────

/// Worst case: `s` = MaxPrimaryProviders, all signatures verified.
/// After grace period any provider may submit, so provider[0] is the caller.
#[benchmark]
fn provider_checkpoint(s: Linear<1, 5>) {
let admin = funded_account::<T>("admin", 0);
let bucket_id = setup_bucket::<T>(&admin);

let window = 1u64;
let interval = T::DefaultCheckpointInterval::get();
let grace_period = T::DefaultCheckpointGrace::get();
let mmr_root = H256::repeat_byte(0xCD);

// Create s providers with sr25519 keys and primary agreements.
let mut provider_keys: alloc::vec::Vec<(T::AccountId, sp_core::sr25519::Public)> =
alloc::vec::Vec::new();
for i in 0..s {
let provider = create_provider::<T>(i);
let key = register_sr25519_key::<T>(&provider, KEY_TYPE, i);
add_primary_to_bucket::<T>(&admin, &provider, bucket_id, 1_000_000);
provider_keys.push((provider, key));
}

// Fund checkpoint pool so the reward payout path is exercised.
let pool_amount = BalanceOf::<T>::max_value() / 100u32.into();
let _ = Pallet::<T>::fund_checkpoint_pool(
RawOrigin::Signed(admin).into(),
bucket_id,
pool_amount,
);

// Advance past the grace period of window 1 so any primary provider can submit.
let window_start = interval.saturating_mul(window.saturated_into());
let target_block: BlockNumberFor<T> = window_start
.saturating_add(grace_period)
.saturating_add(1u32.into());
set_block_number::<T>(target_block);

// Sign the CheckpointProposal with all s providers.
let proposal =
storage_primitives::CheckpointProposal::new(bucket_id, mmr_root, 0, 10, window);
let encoded_proposal = codec::Encode::encode(&proposal);

let mut signatures: BoundedVec<
(T::AccountId, sp_runtime::MultiSignature),
T::MaxPrimaryProviders,
> = BoundedVec::new();
for (provider, key) in provider_keys.iter() {
let sig = sp_io::crypto::sr25519_sign(KEY_TYPE, key, &encoded_proposal)
.expect("signing should work");
let _ =
signatures.try_push((provider.clone(), sp_runtime::MultiSignature::Sr25519(sig)));
}

// After grace period any provider can submit; use provider[0].
let submitter = provider_keys[0].0.clone();

#[extrinsic_call]
provider_checkpoint(
RawOrigin::Signed(submitter),
bucket_id,
Commitment {
mmr_root,
start_seq: 0,
leaf_count: 10,
},
window,
signatures,
);
}

#[benchmark]
fn configure_checkpoint_window() {
let admin = funded_account::<T>("admin", 0);
let bucket_id = setup_bucket::<T>(&admin);

let interval: BlockNumberFor<T> = 200u32.into();
let grace_period: BlockNumberFor<T> = 50u32.into();

#[extrinsic_call]
configure_checkpoint_window(
RawOrigin::Signed(admin),
bucket_id,
interval,
grace_period,
true,
);
}

#[benchmark]
fn report_missed_checkpoint() {
let admin = funded_account::<T>("admin", 0);
let provider = create_provider::<T>(0);
let bucket_id = setup_primary_agreement::<T>(&admin, &provider, 0);

// Report window 1 — must satisfy anchor_block > window_start_block(window+1, interval).
// Target: interval * (window + 1) + 1
let window = 1u64;
let interval = T::DefaultCheckpointInterval::get();
let next_window_start = interval.saturating_mul((window + 1).saturated_into());
let target_block: BlockNumberFor<T> = next_window_start.saturating_add(1u32.into());
set_block_number::<T>(target_block);

#[extrinsic_call]
report_missed_checkpoint(RawOrigin::Signed(admin), bucket_id, window);
}

#[benchmark]
fn claim_checkpoint_rewards() {
let admin = funded_account::<T>("admin", 0);
let provider = create_provider::<T>(0);
let bucket_id = setup_primary_agreement::<T>(&admin, &provider, 0);

// Directly write rewards to storage
let reward: BalanceOf<T> = 1000u32.into();
CheckpointRewards::<T>::insert(&provider, bucket_id, reward);

#[extrinsic_call]
claim_checkpoint_rewards(RawOrigin::Signed(provider), bucket_id);
}

// ─────────────────────────────────────────────────────────────────────────
// Challenge System
// ─────────────────────────────────────────────────────────────────────────
Expand Down
71 changes: 1 addition & 70 deletions crates/pallets/storage-provider/src/impls/checkpoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
use crate::*;
use frame_support::pallet_prelude::*;
use sp_core::H256;
use sp_runtime::traits::{SaturatedConversion, Saturating};
use storage_primitives::{BucketId, HISTORICAL_ROOT_PRIMES};
use storage_primitives::HISTORICAL_ROOT_PRIMES;

impl<T: Config> Pallet<T> {
pub(crate) fn update_historical_roots(
Expand Down Expand Up @@ -44,72 +43,4 @@ impl<T: Config> Pallet<T> {

Err(Error::<T>::InvalidSyncRoot.into())
}

/// Calculate the checkpoint window number for a given block.
///
/// Window 0 starts at block 0, window 1 at block `interval`, etc.
pub(crate) fn calculate_window(block: BlockNumberFor<T>, interval: BlockNumberFor<T>) -> u64 {
if interval.is_zero() {
return 0;
}
let block_num: u64 = block.saturated_into();
let interval_num: u64 = interval.saturated_into();
block_num / interval_num
}

/// Calculate the start block for a given checkpoint window.
pub(crate) fn window_start_block(
window: u64,
interval: BlockNumberFor<T>,
) -> BlockNumberFor<T> {
let interval_num: u64 = interval.saturated_into();
let start: u64 = window.saturating_mul(interval_num);
start.saturated_into()
}

/// Calculate the leader index for a given bucket and window.
///
/// Uses deterministic selection: blake2_256(bucket_id || window) % num_providers.
/// This ensures all providers can independently calculate who the leader is.
pub(crate) fn calculate_leader_index(
bucket_id: BucketId,
window: u64,
num_providers: u32,
) -> u32 {
if num_providers == 0 {
return 0;
}
// Create deterministic seed from bucket_id and window
let mut data = [0u8; 16];
data[..8].copy_from_slice(&bucket_id.to_le_bytes());
data[8..].copy_from_slice(&window.to_le_bytes());
let hash = sp_io::hashing::blake2_256(&data);
// Take first 4 bytes as u32 and mod by num_providers
let seed = u32::from_le_bytes([hash[0], hash[1], hash[2], hash[3]]);
seed % num_providers
}

/// Get the checkpoint config for a bucket, falling back to defaults.
pub(crate) fn get_checkpoint_config(
bucket_id: BucketId,
) -> storage_primitives::CheckpointWindowConfig<BlockNumberFor<T>> {
CheckpointConfigs::<T>::get(bucket_id).unwrap_or_else(|| {
storage_primitives::CheckpointWindowConfig {
interval: T::DefaultCheckpointInterval::get(),
grace_period: T::DefaultCheckpointGrace::get(),
enabled: true, // Enabled by default
}
})
}

/// Check if the anchor block is within the grace period for a window.
pub(crate) fn is_within_grace_period(
anchor_block: BlockNumberFor<T>,
window: u64,
config: &storage_primitives::CheckpointWindowConfig<BlockNumberFor<T>>,
) -> bool {
let window_start = Self::window_start_block(window, config.interval);
let grace_end = window_start.saturating_add(config.grace_period);
anchor_block <= grace_end
}
}
Loading
Loading