Skip to main content
Pallets โ€ข 2 mins read

Arion Pallet

Arion Pallet

The Arion pallet (pallet-arion) is a Substrate runtime module that manages the on-chain state for Hippius's distributed storage network. It provides CRUSH map placement, miner registration, proof-of-storage attestations, and reputation-based weight calculations.

Overviewโ€‹

The Arion pallet serves as the on-chain coordination layer for the Hippius storage network, enabling:

  • Deterministic data placement via CRUSH (Controlled Replication Under Scalable Hashing)
  • Miner registration with economic incentives and Sybil resistance
  • Proof-of-storage attestations submitted by warden nodes
  • Reputation-based weight calculations for miners and families
  • Third-party verifiability of network state and audit results

Key Conceptsโ€‹

CRUSH Mapโ€‹

The CRUSH map is a deterministic placement algorithm that maps data to storage nodes. The pallet stores:

  • Epoch: Monotonically increasing version of the CRUSH map
  • Miners: List of registered miners with their endpoints and weights
  • Parameters: Configuration like placement group count, erasure coding parameters (k, m)

Each epoch transition publishes a new immutable CRUSH map that validators and clients use for data placement.

Miner Registrationโ€‹

Miners must register on-chain before joining the network:

  • Family Account: The main account that represents a storage provider
  • Child Accounts: Individual miner nodes under a family (proxy relationship)
  • Node ID: Ed25519 public key that identifies each miner node
  • Economic Deposits: Anti-Sybil mechanism with adaptive pricing

Registration Economicsโ€‹

  • First child free: Each family gets one free miner registration
  • Adaptive pricing: After the free slot, a global deposit doubles with each registration
  • Lazy decay: Deposit halves after a configurable period of no registrations
  • Unbonding period: Deposits are locked after deregistration

Proof-of-Storage Attestationsโ€‹

Wardens (audit nodes) periodically challenge miners to prove they're storing data:

  • Challenge: Warden sends a random seed to miner
  • Response: Miner provides merkle proof of storage
  • Attestation: Warden signs the audit result (Passed/Failed/Timeout/InvalidProof)
  • On-chain submission: Attestations are submitted in batches with signature verification

Attestations are used to calculate miner reputation and can trigger penalties for storage failures.

Weight Systemโ€‹

The pallet implements a reputation-based weight system:

  • Node weights: Calculated from bandwidth, storage, uptime, and integrity
  • Family weights: Aggregated from top-N node weights with rank decay
  • Smoothing: Exponential moving average (EMA) to prevent volatility
  • Newcomer grace: Floor weight applied to new families during initial period

Weights determine miner rewards and influence data placement decisions.

Core Featuresโ€‹

1. CRUSH Map Managementโ€‹

Submit new CRUSH maps for each epoch transition:

submit_crush_map(
epoch: u64,
params: CrushParams,
miners: Vec<MinerRecord>
)
  • Enforces epoch monotonicity (no regressions)
  • Validates miner list is sorted by UID
  • Optionally enforces miners are registered on-chain
  • Stores commitment hash for verification

2. Miner Registration & Lifecycleโ€‹

Register a Child Nodeโ€‹

register_child(
family: AccountId,
child: AccountId,
node_id: [u8; 32],
node_sig: [u8; 64]
)

Requirements:

  • Family must be registered in pallet-registration
  • Child must be a proxy of family in pallet-proxy
  • Node signature over (domain, family, child, node_id, nonce)
  • Deposit requirement (free for first child, adaptive pricing after)

Deregister a Child Nodeโ€‹

deregister_child(child: AccountId)
  • Moves child to Unbonding status
  • Releases node ID (with cooldown)
  • Deposit remains reserved until claim_unbonded

Claim Unbonded Depositโ€‹

claim_unbonded(child: AccountId)
  • Available after unbonding period expires
  • Unreserves deposit back to family account

3. Attestation Systemโ€‹

Submit Attestationsโ€‹

submit_attestations(
bucket: u32,
attestations: Vec<AttestationRecord>
)

Each attestation includes:

  • Shard hash (BLAKE3)
  • Miner UID
  • Audit result (Passed/Failed/Timeout/InvalidProof)
  • Challenge seed and timing information
  • Warden public key
  • Ed25519 signature (verified on-chain)

Attestation Commitmentโ€‹

For third-party verification, the pallet stores attestation commitments:

submit_attestation_commitment(
epoch: u64,
arion_content_hash: [u8; 32],
attestation_merkle_root: [u8; 32],
warden_pubkey_merkle_root: [u8; 32],
attestation_count: u32
)

Third parties can:

  1. Query commitment from chain
  2. Download full bundle from Arion using content hash
  3. Verify bundle integrity
  4. Verify individual attestation signatures

Warden Registrationโ€‹

Only registered wardens can submit attestations:

register_warden(warden_pubkey: [u8; 32])    // Admin only
deregister_warden(warden_pubkey: [u8; 32]) // Admin only

4. Weight & Reputation Systemโ€‹

Submit Node Quality Metricsโ€‹

submit_node_quality(
bucket: u32,
updates: Vec<(AccountId, NodeQuality)>
)

Where NodeQuality includes:

  • shard_data_bytes: Total bytes stored
  • bandwidth_bytes: Bytes served in reporting window
  • uptime_permille: Availability (0-1000)
  • strikes: Penalty counter
  • integrity_fails: Failed integrity checks

The pallet deterministically computes node weights using:

  • Concave scoring (log2) to prevent "rich get richer"
  • Weighted combination of bandwidth and storage
  • Uptime multiplier
  • Strike and integrity penalties

Family weights are then computed by:

  • Sorting nodes by weight (descending)
  • Taking top-N nodes
  • Applying rank decay (each rank contributes less)
  • Smoothing with EMA
  • Clamping maximum delta per bucket

5. Stats Aggregationโ€‹

submit_miner_stats(
bucket: u32,
updates: Vec<MinerStatsUpdate>,
network_totals: Option<NetworkTotals>
)

Periodic stats updates for monitoring and analytics:

  • Per-miner shard counts and bytes
  • Network-wide totals
  • Last-seen timestamps

Storage Itemsโ€‹

CRUSH Map Stateโ€‹

StorageTypeDescription
CurrentEpochu64Latest epoch number
EpochParamsMap<u64, CrushParams>CRUSH parameters per epoch
EpochMinersMap<u64, Vec<MinerRecord>>Miner list per epoch
EpochRootMap<u64, H256>Commitment hash per epoch

Registration Stateโ€‹

StorageTypeDescription
ChildRegistrationsMap<AccountId, ChildRegistration>Active/unbonding children
NodeIdToChildMap<[u8; 32], AccountId>Node ID lookup
FamilyChildrenMap<AccountId, Vec<AccountId>>Children per family
GlobalNextDepositBalanceNext registration cost
FamilyCountu32Total families with registrations
TotalActiveChildrenu32Total active miners

Attestation Stateโ€‹

StorageTypeDescription
AttestationsByBucketMap<u32, Vec<AttestationRecord>>Attestations per bucket
EpochAttestationCommitmentsMap<u64, EpochAttestationCommitment>Commitment per epoch
RegisteredWardensMap<[u8; 32], WardenInfo>Authorized wardens
CurrentAttestationBucketu32Latest bucket

Weight Stateโ€‹

StorageTypeDescription
NodeWeightByChildMap<AccountId, u16>Current node weight
NodeQualityByChildMap<AccountId, NodeQuality>Latest quality metrics
FamilyWeightMap<AccountId, u16>Smoothed family weight
FamilyWeightRawMap<AccountId, u16>Unsmoothed family weight
CurrentWeightBucketu32Latest weight bucket

Eventsโ€‹

// CRUSH Map
CrushMapPublished { epoch, miners, root }

// Registration
ChildRegistered { family, child, node_id, deposit }
ChildDeregistered { family, child, node_id, unbonding_end, cooldown_end }
ChildUnbonded { family, child, node_id, amount }

// Attestations
AttestationsSubmitted { bucket, count }
AttestationCommitmentSubmitted { epoch, attestation_count, ... }
WardenRegistered { warden_pubkey, registered_at }
WardenDeregistered { warden_pubkey, deregistered_at }

// Weights
NodeWeightsUpdated { bucket, updates }
FamilyWeightsComputed { bucket, families }
MinerStatsUpdated { bucket, updates }

// Admin
LockupEnabledSet { enabled }
BaseChildDepositSet { deposit }

Configuration Parametersโ€‹

Economic Parametersโ€‹

ParameterDescriptionRecommended
BaseChildDepositFloor for adaptive deposit100 HIPP
GlobalDepositHalvingPeriodBlocksDecay period for deposits14,400 (24h)
UnbondingPeriodBlocksLock period after deregistration100,800 (7 days)
UnregisterCooldownBlocksRe-registration cooldown28,800 (48h)

Capacity Limitsโ€‹

ParameterDescriptionRecommended
MaxFamiliesTotal families allowed256
MaxChildrenTotalTotal miners allowed2,048
MaxChildrenPerFamilyMiners per family32
MaxMinersMiners per CRUSH map2,048

Weight Calculationโ€‹

ParameterDescriptionRecommended
NodeBandwidthWeightPermilleBandwidth contribution700 (70%)
NodeStorageWeightPermilleStorage contribution300 (30%)
NodeScoreScaleScore multiplier512
StrikePenaltyWeight penalty per strike100
IntegrityFailPenaltyWeight penalty per fail50

Family Weight Smoothingโ€‹

ParameterDescriptionRecommended
FamilyTopNNodes counted per family8
FamilyRankDecayPermilleRank decay factor800 (0.8x)
FamilyWeightEmaAlphaPermilleEMA smoothing300 (30% new)
MaxFamilyWeightDeltaPerBucketMax change per bucket500
NewcomerGraceBucketsGrace period buckets100
NewcomerFloorWeightFloor during grace100

Attestation Settingsโ€‹

ParameterDescriptionRecommended
MaxAttestationsAttestations per submission1,000
AttestationRetentionBucketsBuckets to keep before pruning1,000

Admin Operationsโ€‹

Enable/Disable Lockupโ€‹

set_lockup_enabled(enabled: bool)

Controls whether deposits are required for registration. When disabled:

  • No deposits are reserved
  • Unbonding is immediate
  • Useful for testnets or initial rollout

Set Base Depositโ€‹

set_base_child_deposit(deposit: Balance)

Adjust the floor price for miner registration. Global deposit never falls below this value.

Manage Wardensโ€‹

register_warden(warden_pubkey: [u8; 32])
deregister_warden(warden_pubkey: [u8; 32])

Add or remove authorized wardens that can submit attestations.

Security Considerationsโ€‹

Signature Verificationโ€‹

The pallet performs on-chain Ed25519 signature verification for:

  1. Miner registration: Proves ownership of node ID keypair
  2. Attestations: Proves audit results from authorized wardens

Signatures use domain-separated messages to prevent cross-context replay attacks.

Anti-Sybil Mechanismsโ€‹

Multiple layers prevent Sybil attacks:

  1. Family registration: Must be registered in pallet-registration
  2. Proxy verification: Child must be authorized proxy of family
  3. Adaptive deposits: Cost increases with network usage
  4. Cooldown periods: Prevent rapid deregistration/reregistration
  5. Capacity limits: Hard caps on total miners and per-family miners

Replay Protectionโ€‹

  • Node ID nonce: Incremented on each registration/deregistration
  • Cooldown tombstones: Prevent immediate reuse of accounts/node IDs

Weight Calculation Safetyโ€‹

  • Concave scoring: Log2 prevents runaway rewards
  • Capped values: Max node weight, max family weight
  • Delta limits: Prevents sudden weight spikes
  • EMA smoothing: Reduces impact of transient fluctuations
  • Top-N limit: Prevents infinite-children attacks

Integration Guideโ€‹

Runtime Configurationโ€‹

impl pallet_arion::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type ArionAdminOrigin = EnsureRoot<AccountId>;
type MapAuthorityOrigin = EnsureValidatorSet<Self>;
type StatsAuthorityOrigin = EnsureValidatorSet<Self>;
type AttestationAuthorityOrigin = EnsureWardenOrValidator<Self>;
type WeightAuthorityOrigin = EnsureValidatorSet<Self>;

type DepositCurrency = Balances;
type FamilyRegistry = PalletRegistration;
type ProxyVerifier = PalletProxy;

// Economic parameters
type BaseChildDeposit = ConstU128<{ 100 * HIPP }>;
type GlobalDepositHalvingPeriodBlocks = ConstU32<14_400>;
type UnbondingPeriodBlocks = ConstU32<100_800>;
type UnregisterCooldownBlocks = ConstU32<28_800>;

// Capacity limits
type MaxFamilies = ConstU32<256>;
type MaxChildrenTotal = ConstU32<2_048>;
type MaxChildrenPerFamily = ConstU32<32>;
type MaxMiners = ConstU32<2_048>;

// ... other parameters
}

Off-Chain Integrationโ€‹

Validator Serviceโ€‹

  1. Monitor chain state and miner registrations
  2. Build CRUSH map each epoch
  3. Submit via submit_crush_map
  4. Collect quality metrics from miners
  5. Submit via submit_node_quality

Warden Serviceโ€‹

  1. Sample random shards for auditing
  2. Challenge miners with random seeds
  3. Verify merkle proofs from miners
  4. Sign attestations with Ed25519 keypair
  5. Submit via chain-submitter service

Chain Submitterโ€‹

  1. Aggregate attestations from wardens
  2. Submit batches via submit_attestations
  3. Create attestation bundles (SCALE-encoded)
  4. Upload bundles to Arion storage
  5. Submit commitments via submit_attestation_commitment

Miner Registration Flowโ€‹

  1. Family registers in pallet-registration
  2. Family creates child account
  3. Family adds child as proxy in pallet-proxy
  4. Miner generates Ed25519 keypair (node ID)
  5. Miner signs registration message
  6. Family calls register_child (first free, then paid)
  7. Miner joins network with node ID

Usage Examplesโ€‹

Register First Miner (Free)โ€‹

// 1. Ensure family is registered
pallet_registration::register_owner_node(family_origin, ...)?;

// 2. Add proxy relationship
pallet_proxy::add_proxy(family_origin, child_account, ProxyType::NonTransfer, 0)?;

// 3. Register child (off-chain: sign with node keypair)
let message = arion::registration_message(&family, &child, &node_id, nonce);
let signature = node_keypair.sign(&message);

// 4. Submit registration (on-chain)
pallet_arion::register_child(
family_origin,
family,
child,
node_id,
signature
)?;
// No deposit required for first child

Register Additional Miner (Paid)โ€‹

// Same process, but now requires deposit
pallet_arion::register_child(
family_origin,
family,
child2,
node_id2,
signature2
)?;
// Deposit = current GlobalNextDeposit (then doubles)

Deregister Minerโ€‹

// 1. Deregister (begins unbonding)
pallet_arion::deregister_child(family_origin, child)?;
// Status: Active -> Unbonding
// Cooldown starts for re-registration

// 2. Wait for unbonding period (e.g., 7 days)

// 3. Claim deposit
pallet_arion::claim_unbonded(family_origin, child)?;
// Deposit unreserved back to family

Submit Attestation (Warden)โ€‹

// Off-chain: Perform audit
let challenge_seed = random_seed();
let response = miner.challenge(shard_hash, challenge_seed).await?;
let result = verify_merkle_proof(response)?;

// Sign attestation
let attestation = AttestationRecord {
shard_hash,
miner_uid,
result,
challenge_seed,
block_number,
timestamp,
warden_pubkey,
signature: warden_keypair.sign(&message),
merkle_proof_sig_hash,
warden_id,
};

// Submit to chain
pallet_arion::submit_attestations(
warden_origin,
bucket,
vec![attestation]
)?;

Query CRUSH Mapโ€‹

// Get current epoch
let epoch = pallet_arion::CurrentEpoch::<T>::get();

// Get miners for epoch
let miners = pallet_arion::EpochMiners::<T>::get(epoch)?;

// Get parameters
let params = pallet_arion::EpochParams::<T>::get(epoch)?;

// Verify commitment
let root = pallet_arion::EpochRoot::<T>::get(epoch)?;
let computed = compute_hash(&params, &miners);
assert_eq!(root, computed);

Query Miner Registrationโ€‹

// Lookup by child account
let registration = pallet_arion::ChildRegistrations::<T>::get(child)?;

// Lookup by node ID
let child = pallet_arion::NodeIdToChild::<T>::get(node_id)?;
let registration = pallet_arion::ChildRegistrations::<T>::get(child)?;

// Check if active
if registration.status == ChildStatus::Active {
println!("Miner is active, family: {:?}", registration.family);
}

Query Weightsโ€‹

// Get node weight
let node_weight = pallet_arion::NodeWeightByChild::<T>::get(child);

// Get family weight
let family_weight = pallet_arion::FamilyWeight::<T>::get(family);

// Get quality metrics
let quality = pallet_arion::NodeQualityByChild::<T>::get(child)?;
println!("Uptime: {}%, Strikes: {}", quality.uptime_permille / 10, quality.strikes);

Troubleshootingโ€‹

Registration Errorsโ€‹

ErrorCauseSolution
FamilyNotRegisteredFamily not in pallet-registrationRegister family first
ProxyVerificationFailedChild not proxy of familyAdd proxy in pallet-proxy
ChildInCooldownRecently deregisteredWait for cooldown period
NodeIdAlreadyRegisteredNode ID in useUse different keypair
InsufficientDepositNot enough balanceEnsure family has sufficient funds
TooManyChildrenInFamilyHit per-family limitDeregister old miners or use new family

Attestation Errorsโ€‹

ErrorCauseSolution
InvalidAttestationSignatureSignature verification failedCheck signing message format
UnregisteredWardenWarden not authorizedRegister warden via admin
AttestationBucketFullToo many attestations in bucketIncrease MaxAttestations

CRUSH Map Errorsโ€‹

ErrorCauseSolution
EpochRegressionEpoch not increasingEnsure epoch > current
MinerListNotSortedOrNotUniqueMiners not sorted by UIDSort miners before submission
MinerNotRegisteredMiner not on-chain (when enforced)Ensure all miners are registered
  • pallet-registration: Family account registration
  • pallet-proxy: Proxy relationships for child accounts
  • pallet-balances: Currency for deposits

Referencesโ€‹