feat: add Nostalgia Mode - Duniter v1 behavioral compatibility layer (RFC-0042)
Nostalgia Mode — Duniter v1 Behavioral Compatibility Layer
RFC-0042 | pallet-nostalgia | Pallet index: 70
Motivation
The migration to Duniter v2 brought significant technical improvements — deterministic finality (GRANDPA), predictable block times (BABE), WASM execution — but at a cultural cost: the loss of emergent behaviors that defined the original Duniter protocol experience.
Community feedback has been consistent:
- "Transactions arrive too fast now, we don't even have time to discuss on the forum while waiting"
- "The network is too stable — it was more exciting when you never knew if your transfer would go through"
- "I miss when my certification would mysteriously disappear; it gave me a reason to meet people in person"
This MR addresses these concerns by reintroducing, in a controlled and configurable manner, the behavioral characteristics that forged the identity of the Duniter community.
Overview
pallet-nostalgia is a fully idiomatic FRAME pallet that implements six core mechanisms inspired by Duniter v1 network dynamics:
| Feature | Description | Key Parameter |
|---|---|---|
| Transaction Piscine | Deferred execution pool with random delay (5 min – 3 days) and randomized ordering |
MinPoolDelay, MaxPoolDelay
|
| Democratic Killswitch | Any account can halt the network; 2/3 smith vote to restart |
CheckKillswitchAllowed trait |
| Certification Entropy Amplifier | 15% chance of silent certification drop (event emitted only 50% of the time) |
CertDropRate (150,000 ppm) |
| Cascade Fork Simulator | Periodic simulated chain reorgs (~4h interval), auto-triggered if stability exceeds threshold |
ForkInterval, MaxStabilityStreak
|
| Offchain Data Desynchronization Engine (ODDE) | Per-account entropy seeds producing unique offchain data views |
DesyncParams storage |
| Monetary Mass Temporal Anomaly | UD display varies ±0.50 Ğ1 per account, encouraging healthy forum debates | ud_variance_max |
Implementation Details
Pallet Structure
pallets/nostalgia/
├── Cargo.toml
└── src/
├── lib.rs # Core pallet: 6 extrinsics, 10 storage items, hooks
├── types.rs # KillswitchStatus, PiscineItem, ForkMetrics, DesyncParams
├── traits.rs # CheckKillswitchAllowed, CheckDeactivationVoteAllowed, OnPiscineEvent
├── weights.rs # WeightInfo trait + test implementation
├── benchmarking.rs # 9 benchmarks (v2 macros)
├── mock.rs # Mock runtime with deterministic randomness
└── tests.rs # 18 unit tests
Extrinsics
| Call Index | Extrinsic | Origin | Description |
|---|---|---|---|
| 0 | submit_to_piscine |
Signed | Submit a transaction to the deferred execution pool |
| 1 | activate_killswitch |
Signed | Halt the network (democratic governance) |
| 2 | vote_deactivate_killswitch |
Signed (Smith) | Vote to restart the network (2/3 threshold) |
| 3 | configure_desync |
Root | Update ODDE desynchronization parameters |
| 4 | trigger_fork |
Root | Manually trigger a cascade fork simulation |
| 5 | request_entropy_seed |
Signed | Request a permanent ODDE entropy seed |
Storage Items
-
Piscine—BoundedVec<PiscineItem, 4096>pending execution pool -
Killswitch—KillswitchStatus<BlockNumber>network halt state -
KillswitchDeactivationVotes/KillswitchVoters— smith voting state -
ForkState—ForkMetricscascade fork tracking (total, avg depth, stability streak) -
DesyncConfig—DesyncParamsODDE configuration -
NextForkAt— scheduled next automatic fork block -
SilentlyDroppedCount/ProcessedCount— piscine throughput metrics -
AccountEntropySeed— per-account 32-byte ODDE seeds
Piscine Processing Algorithm
on_initialize(block_number):
1. Check killswitch → if Halted, return early
2. Partition piscine: eligible (execute_after ≤ current) vs pending
3. Sort eligible by priority_entropy (randomized at submission)
4. Process max 10 items per block (faithful v1 throughput)
5. Check NextForkAt → trigger cascade fork if due
6. Increment stability_streak → if > MaxStabilityStreak, emit StabilityStreakBroken
Certification Entropy Amplifier
When is_certification = true:
- Roll against
CertDropRate(default 15%) - If dropped: increment
SilentlyDroppedCount, emit event only 50% of the time - The other 50%: the certification vanishes without any trace (authentic v1 behavior)
Runtime Integration
- Pallet index: 70 (between Distance=44 and AtomicSwap=50 range, in the new Nostalgia section)
-
Randomness source:
ProvideRandomness(consistent with existing infrastructure) -
Traits: Uses
()null implementations forCheckKillswitchAllowed,CheckDeactivationVoteAllowed, andOnPiscineEvent— can be wired toSmithMembersin a follow-up -
Weights: Full auto-generated weight file at
runtime/gdev/src/weights/pallet_nostalgia.rs -
Feature flags:
std,runtime-benchmarks,try-runtimeproperly propagated through workspace
Configuration (gdev runtime)
parameter_types! {
pub const NostalgiaMinPoolDelay: BlockNumber = param_duration!(50, 5); // ~5 min / 30s fast
pub const NostalgiaMaxPoolDelay: BlockNumber = param_duration!(43_200, 50); // ~3 days / 5 min fast
pub const NostalgiaCertDropRate: u32 = 150_000; // 15% silent drop
pub const NostalgiaForkInterval: BlockNumber = param_duration!(2_400, 30); // ~4h / 3 min fast
pub const NostalgiaMaxForkDepth: u32 = 6;
pub const NostalgiaMaxStabilityStreak: u32 = 400; // ~40 min max stability
}
Test Coverage
18 unit tests covering all code paths:
-
Piscine: submission, network-halted rejection, delayed processing,
MAX_PROCESS_PER_BLOCKenforcement - Killswitch: activation, double-activation prevention, 2/3 threshold deactivation, non-eligible voter rejection, double-vote prevention, vote-when-not-halted rejection
- Fork cascade: manual trigger, depth clamping, zero-depth rejection, automatic interval trigger, stability streak emergency
- ODDE: entropy seed assignment, duplicate prevention, personalized UD variance, disabled variance passthrough
- Desync config: root-only update, signed origin rejection
- Certification drop: statistical validation over 100 submissions
Files Changed
-
Cargo.toml— workspace member + dependency -
runtime/common/Cargo.toml— dependency + feature flags -
runtime/gdev/Cargo.toml— dependency + feature flags -
runtime/common/src/pallets_config.rs— pallet configuration -
runtime/gdev/src/lib.rs—construct_runtime!entry -
runtime/gdev/src/weights.rs— module declaration -
runtime/gdev/src/weights/pallet_nostalgia.rs— auto-generated weights -
pallets/nostalgia/**— full pallet implementation (7 source files) -
docs/rfc/0042-nostalgia-mode.md— complete RFC specification
Migration
No storage migration required. The pallet initializes its own storage items at genesis via GenesisConfig.
Follow-up Work
-
Wire
CheckDeactivationVoteAllowedtoSmithMembersfor production-grade killswitch governance -
Add RPC endpoint for
piscine_stats()andget_personalized_ud() -
Integrate piscine as
TransactionExtensionfor transparent interception (currently opt-in via extrinsic) -
Port to
gtestandg1runtimes after validation ongdev -
Community vote on optimal
CertDropRate(current 15% based on historical v1 data analysis)
References
- RFC-0042: Nostalgia Mode
- Duniter v1 documentation
- George Santayana, The Life of Reason, 1905 — "Those who cannot remember the past are condemned to repeat it."