Rand Protocol
مستندات
مفاهیم اصلی

اجماع و قطعیت

Chained HotStuff آن‌گونه که نود اجرا می‌کند — حدنصاب‌ها، رهبران، قاعدهٔ نهایی‌سازی پس از سه نمای متوالی، مهلت‌های زمانی، دوره‌ها (epochs) — و اینکه «نهایی» برای یک کلاینت در testnet چه معنایی دارد.

از این صفحه بدانید یک تراکنش چه زمانی نهایی می‌شود، چند اعتبارسنج باید فعال باشند و فیلدهای بلاک در RPC و explorer چه معنایی دارند. اجماع از نوع HotStuff BFT زنجیره‌ای با حدنصاب‌های وزن‌دار بر پایه استیک است؛ در chain 20 این یعنی 18 از 26 اعتبارسنج.

قواعدی که نود اجرا می‌کند

قاعدهچگونه پیاده‌سازی شده است
حدنصابگواهی حدنصاب (QC) مجموعه‌ای از رأی‌های Dilithium2 است از اعتبارسنج‌هایی که به‌طور اکید بیش از 2/3 از کل استیک را در اختیار دارند
برنامهٔ رهبریround robin ساده: رهبر نمای v برابر validators[v mod n] است، با مجموعه‌ای که بر اساس آدرس مرتب شده است. استیک وزن رأی‌ها را تعیین می‌کند، نه انتخاب رهبر را
پیشنهاد بلاکرهبر نمای v زمانی بلاک پیشنهاد می‌دهد که یک QC برای v − 1 یا حدنصابی از پیام‌های NewView برای v در اختیار داشته باشد. پیشنهادی که یک تراکنش نامعتبر را اعمال کند، به‌جای کل بلاک آن تراکنش را حذف می‌کند
رأی‌دهی و ایمنیاعتبارسنج در هر نما فقط یک بار رأی می‌دهد و فقط به بلاکی که نسبت به قفل خود ایمن باشد (QC تأییدکنندهٔ آن از QC قفل‌شده جدیدتر است، یا بلاک، بلاک قفل‌شده را گسترش می‌دهد). وضعیت ایمنی پیش از آنکه رأیی از نود خارج شود در RocksDB با fsync ذخیره می‌شود
دامنهٔ امضادر genesis با consensus_domain: 1 (chain 20 و هر زنجیره از 15 به بعد) امضای هر رأی، new-view و پیشنهاد، هش genesis را پوشش می‌دهد (rand-vote-2، rand-newview-2، rand-block-2)، بنابراین امضای یک زنجیره روی هیچ زنجیرهٔ دیگری معتبر نیست. Chain 14 و پیش از آن بدون آن امضا می‌کردند. binding_domain: 1 در chain 20 همین قاعده را به اثبات‌های تراکنش و اقدام‌های امضاشده گسترش می‌دهد (Transactions)
پیشنهادهای هم‌سطحهر replica برای هر (نما، رهبر) یک بلاک نگه می‌دارد؛ بلاک دوم و متفاوت از همان رهبر برای همان نما به‌عنوان equivocation رد می‌شود. پیشنهادی که بیش از 8 نما از نمای خود replica جلوتر باشد (PROPOSAL_VIEW_WINDOW) پیش از بررسی امضایش رد می‌شود؛ درخت گمانه‌زنی پر، پیش از رد کردن هر چیز، بلاک‌های خارج از شاخه را بیرون می‌اندازد
آزادسازی قفلقفل هرگز به‌طور خودکار پایین آورده نمی‌شود (CON-4، v0.6.8). فقط در برابر پیشنهادی که QC تأییدکنندهٔ آن از قفل برتر باشد، یا با رسیدن بلاک قفل‌شده، کنار می‌رود. حدنصابی از پاسخ‌های امضاشدهٔ NotHeld برای اپراتور ثبت می‌شود و چیزی را آزاد نمی‌کند، و اعتبارسنج رأی‌های خود را ثبت می‌کند تا هرگز برای بلاکی که به آن رأی داده NotHeld امضا نکند. هر بلاک گواهی‌شده بالای head ذخیره و هنگام راه‌اندازی مجدد بازیابی می‌شود، به‌طوری که راه‌اندازی مجدد کل ناوگان، QC را روی بلاکی که هیچ‌کس ندارد رها نمی‌کند. اعتبارسنجی که بلاک قفل‌شدهٔ او برای همیشه از دست رفته باشد، دستی و آفلاین آزاد می‌شود: rand-node safety release-lock
تجمیع QCهر اعتبارسنج QCها را به‌صورت محلی از رأی‌های gossip‌شده گرد هم می‌آورد، پس حتی وقتی رهبر بعدی آفلاین است زنجیره به commit ادامه می‌دهد
Commitقاعدهٔ زنجیره‌ای سه‌QC: بلاک زمانی commit می‌شود که سه گواهی در نماهای متوالی v, v+1, v+2 روی آن زنجیر شده باشند و آن زنجیره به head ثبت‌شده برسد
تایم‌اوت و تغییر نمامقدار پایهٔ --view-timeout-ms برابر 3,000 ms است، با دو برابر شدن به ازای هر تایم‌اوت متوالی و بازنشانی با هر پیشرفت. pacemaker مبتنی بر گواهی تایم‌اوت (CH-1، v0.6.8): replicaای که تایمرش پایان یابد در نمای خود می‌ماند و یک NewView امضا می‌کند که نمای بعدی را درخواست می‌کند. این replica با QC نمای پیشین وارد یک نما می‌شود، یا وقتی اعتبارسنج‌هایی با بیش از 2/3 استیک آن نما یا نمایی بعدتر را درخواست کرده باشند؛ و وقتی بیش از 1/3 نمایی فراتر از نمای خودش را درخواست کنند به آن می‌پیوندد (درخواست خودش را امضا می‌کند). NewView هیچ اعتبارسنج منفردی کسی را جابه‌جا نمی‌کند. نماها کران‌دارند (MAX_VIEW_AHEAD = 10⁶)
آهنگ بلاک‌سازیوقتی چیزی در انتظار نباشد، رهبر بلاک خالی پیشنهاد می‌دهد، دست‌کم --block-interval-ms (پیش‌فرض 1,000 ms) پس از آخرین بلاکی که از هر پیشنهاددهنده‌ای دیده شده
کران‌های رأی و gossipرأی‌هایی که بیش از 8 نما جلوتر باشند نادیده گرفته می‌شوند و برای هر (نما، رأی‌دهنده) یک رأی شمرده می‌شود؛ gossip اجماع به‌ازای هر peer فوروارد‌کننده محدود می‌شود (burst 256، 64/s)؛ پیشنهادی که والد آن ناشناخته باشد فقط در صورتی به‌عنوان orphan نگه داشته می‌شود که پیشنهاددهنده‌اش در مجموعهٔ epoch جاری یا بعدی رهبر آن نما باشد، در محدودهٔ کران درخت از head ثبت‌شده قرار گیرد و اندازه و ریشهٔ تراکنش‌هایش درست باشد (سقف مخزن orphan برابر 64 MiB و از v0.6.1 به بعد 16 orphan برای هر پیشنهاددهنده). از v0.6.8 پیشنهاد فقط وقتی relay می‌شود و orphan فقط وقتی نگه داشته می‌شود که فهرست تراکنش‌هایش به ریشه‌ای که سرآیندش به آن متعهد است هش شود و هیچ تراکنشی تکرار نشده باشد (GOSSIP-2)
محدودیت‌های بلاکmax_block_bytes زنجیره برای بایت‌های تراکنش (به‌طور پیش‌فرض 4 MiB، 20 MiB در genesis زنجیرهٔ 20) و 2,000 تراکنش، که پیشنهاددهنده و دوباره هر replica در Ledger::apply_block اعمال می‌کنند
زمان بلاکدر زنجیره‌ای با بخش bridge: timestamp_ms یک بلاک نباید از والدش عقب‌تر باشد (TimestampRewind) و نباید بیش از 60 s از آن جلوتر باشد (TimestampLeap)، و اعتبارسنج از رأی دادن به بلاکی که بیش از 15 s از ساعت خودش جلوتر باشد خودداری می‌کند؛ ادامه را ببینید

قاعدهٔ commit با سه نمای متوالی تکیه‌گاه ساختار است: سست کردن آن یک باگ finality متناقض را دوباره باز می‌کند که fullnode برایش آزمون رگرسیون دارد. هرچه کلاینت از طریق RPC می‌بیند commit شده است: فقط بلاک‌های commit‌شده سرو می‌شوند و جریان newHeads در WebSocket برای هر بلاک commit‌شده به‌ترتیب یک اعلان می‌فرستد.

این جدول مربوط به v0.6.8 است، نسخه‌ای که chain 20 اجرا می‌کند. سه قاعدهٔ آن در v0.6.8 (audit v6) جدید است و از زمان فعال شدن chain 20 در 2026-10-01 زنده‌اند: pacemaker مبتنی بر گواهی تایم‌اوت (CH-1)، نبود آزادسازی خودکار قفل (CON-4) و بررسی فهرست تراکنش‌های پیشنهاد (GOSSIP-2). در زنجیره‌های 18 و 19 (v0.6.7) NewView امضاشدهٔ یک اعتبارسنج منفرد هر replica را به نمای خود می‌برد، و قفل با پاسخ‌های امضاشدهٔ NotHeld از حدنصابی از استیک پایین آورده می‌شد. pacemaker و قاعدهٔ قفل آنچه را اعتبارسنج‌ها امضا می‌کنند و زمان آن را تغییر می‌دهند، بنابراین v0.6.8 با برش chain 20 به‌صورت all-stop/all-start عرضه شد، نه نود به نود.

اعداد testnet

Chain 20 دارای 26 اعتبارسنج است که هر کدام دقیقاً 1,000 RAND استیک دارند، و هر 26 در genesis آن با همان کلیدها و peer idهایی هستند که زنجیره‌های 17 تا 19 اجرا می‌کردند. در chain 15 هجده اعتبارسنج در genesis و با کلیدهای chain 14 بودند، و هشت اعتبارسنج دیگر در 2026-09-27 تحت ثبت‌نام‌های v2 bond کردند و از epoch 43 فعال بودند. استیک یکنواخت است، پس حدنصاب یک شمارش سر است: برای commit کردن هر چیزی باید 18 از 26 آنلاین باشند. با 17 اعتبارسنج فعال، نماها پیش می‌روند و هیچ چیزی نهایی نمی‌شود. مجموعه‌ای از n اعتبارسنج هم‌استیک به بیش از 2/3 استیک نیاز دارد: 2 از 2، 3 از 4، 5 از 6، 13 از 18، 18 از 26. سقف وزن genesis (3,333 bps برای هر اعتبارسنج) برای مجموعه‌ای از استیک‌های برابر محدودیتی ایجاد نمی‌کند.

بلاک‌ها با آهنگ پیش‌فرض 1,000 ms تولید می‌شوند. در chain 15 در عمل تقریباً هر 1.2 s می‌رسیدند (میانگین 1.18 s در 29 ساعت نخست)؛ در chain 20 explorer میانگین 1.34 s را در ارتفاع 3,270 گزارش کرد (2026-10-01، avg_block_time_ms). Explorer میانگین زمان بلاکی را که مشاهده می‌کند گزارش می‌دهد (avg_block_time_ms در GET /stats). هرگاه نماها تایم‌اوت شوند، نمای اعتبارسنج از ارتفاع جلو می‌افتد، و به همین دلیل rand_getHead هر دو را گزارش می‌کند.

Epochها

مجموعهٔ اعتبارسنج‌ها در هر epoch_blocks = 1,000 بلاک (یک پارامتر genesis) از رجیستر دوباره استخراج می‌شود. epoch(h) = h / 1000؛ مجموعهٔ epoch برابر e ≥ 1 از رجیستر در آخرین بلاک epoch برابر e − 1 استخراج می‌شود: هر ردیف با stake ≥ 1,000 RAND، 100 ردیف برتر بر اساس استیک، مرتب‌شده بر اساس آدرس. در زنجیره‌ای بدون بخش staking، یک bond در مرز بعدی وزن می‌شود. در chain 20 دو epoch بیشتر منتظر می‌ماند و تابع یک بودجهٔ ورود در هر epoch است برابر 2,500 bps از وزن bond‌شدهٔ فعال در مرز (6,500 RAND وقتی مجموعه 26,000 دارد)، و bondای که اعتبارسنج جدیدی ثبت کند رد می‌شود مگر آنکه مجموعه ابتدا با AdmitValidator به آن کلید رأی داده باشد (staking.admission_by_vote). بودجهٔ chain 19 مقدار ثابت 10,000 RAND در هر epoch بود و رأی پذیرش نداشت. یک unbond در مرز بعدی از وزن کم می‌کند، در حالی که مبلغ آن دو epoch منتظر می‌ماند تا قابل برداشت شود. rand_getEpoch شمارهٔ epoch، طول epoch و پیش‌بینی مجموعهٔ بعدی را برمی‌گرداند. Staking را ببینید.

آنچه یک بلاک حمل می‌کند

{
  "hash": "647b…", "height": 50, "view": 92, "parent": "2d41…",
  "proposer": "3v3VBJ…", "timestamp_ms": 1788000123456,
  "tx_root": "0000…", "state_root": "a1b2…", "justify_view": 91,
  "tx_count": 0, "transactions": []
}

view نمایی است که بلاک در آن پیشنهاد شده و justify_view نمای QCای است که برای والدش حمل می‌کند. timestamp_ms را پیشنهاددهنده تعیین می‌کند، در محدودهٔ بخش بعدی. state_root به لجر پس از بلاک متعهد است. در chain 20:

inner      = blake3("rand-state-8" || tree_root || nullifier_root || validators_root || programs_root
                                   || bridge_root || tokens_root
                                   || faucet_epoch || faucet_minted_in_epoch || bond_queue_root
                                   || gas_price || byte_price
                                   || program_state_root)
state_root = blake3("rand-state-admitted-1" || inner || admitted_root)

Chain 19 همین فیلدها را بدون program_state_root، تحت rand-state-7 و بدون wrapper commit می‌کرد.

مؤلفه‌ها به پیدایش (genesis) بستگی دارند. زنجیره‌ای که هیچ‌یک از بخش‌های اختیاری را ندارد، چهار مؤلفهٔ نخست را تحت rand-state-2 متعهد می‌کند. بخش bridge مقدار bridge_root را به انتها می‌افزاید. بخش tokens مقدار tokens_root را پس از آن می‌افزاید و دامنهٔ کل هش را به rand-state-4 تغییر می‌دهد؛ پیدایشی که بخش bridge دارد باید بخش tokens هم داشته باشد، بنابراین chain 20 هر دو را دارد. بخش aggregation در ادامه ریشهٔ تجمیع‌کننده‌ها را می‌افزاید (در زنجیره‌ای بدون توکن تحت rand-state-3)؛ chain 20 چنین بخشی ندارد. بخش staking دو شمارندهٔ دوره‌ای faucet و ریشهٔ صف bond (rand-bond-queue-1) را در پایان می‌افزاید و دامنهٔ کل هش را به rand-state-5 تغییر می‌دهد، فارغ از اینکه چه بخش‌های دیگری فعال باشند؛ chain 14 چنین بخشی نداشت و تحت rand-state-4 هش می‌شد، و chainهای 15 تا 17 تحت rand-state-5 هش می‌شدند. بخش vesting ریشهٔ ثبت‌نامهٔ vesting را پس از آن می‌افزاید و دامنهٔ هش را به rand-state-6 تغییر می‌دهد؛ chain 20 چنین بخشی ندارد. بخش gas با کنترلر dynamic مقادیر زندهٔ gas_price و byte_price را، هر یک به‌صورت u64 با ترتیب big-endian، پس از همهٔ موارد دیگر می‌افزاید و دامنهٔ هش را به rand-state-7 تغییر می‌دهد. این دامنهٔ chain 19 بود، و chain 20 قیمت‌ها را در ریشهٔ خود نگه می‌دارد: قیمت‌ها یک‌بار در هر بلوک، پس از تراکنش‌های بلوک و پیش از ریشه، تغییر می‌کنند (Fees). بخش gas با قیمت‌های ثابت در ریشه نیست. بخش program_state (RPL-2) ریشهٔ سلول‌ها و vaultهای هر برنامه را پس از قیمت‌ها می‌افزاید و دامنهٔ هش را به rand-state-8 تغییر می‌دهد: این دامنهٔ chain 20 است. وضعیت staking در audit v6 نه درون این ساختار، بلکه پیرامون آن درج شده است: تحت staking.admission_by_vote (chain 20) ریشه برابر است با blake3("rand-state-admitted-1" || inner || admitted_root)، و تحت staking.slashing (هنوز در هیچ زنجیره‌ای) پوششی دوم مجموعهٔ jailed را اضافه می‌کند. tokens_root ریشهٔ مرکل برگ‌های blake3("rand-token-leaf-1" || bincode(TokenInfo)) به ترتیب اندیس است، بنابراین فراداده، مرجع مجاز، mint_nonce، عرضه و مقدار locked هر پشتوانه برای هر توکن بخشی از وضعیت اجماع است: دو گره‌ای که دربارهٔ یک ردیف توکن اختلاف دارند، دربارهٔ زنجیره اختلاف دارند. در chain 20 ریشهٔ توکن، افزونهٔ ثبت‌نامه (سقف max_tokens، کارمزد ثبت سوخته‌شده، کران مقدار note و پنجره‌های غلتان سقف mint پل) را نیز تحت rand-token-registry-3 در خود می‌گیرد. ریشهٔ پل شامل emitterها، مجموعه‌های guardian، خلاصه‌های attestation مصرف‌شده، توالی burn، کلیدهای PQ guardian، کلید توقف، پرچم توقف و دو nonce حاکمیتی است؛ ثبت‌نامهٔ دارایی‌ها از آن خارج و به tokens_root منتقل شده است. پل chain 20 دارای rules_v2 است، بنابراین nonce چرخش و پارامترهای سقف سراسری افزوده می‌شوند و دامنه rand-bridge-state-5 است (دامنهٔ chain 14 برابر rand-bridge-state-4 بود)؛ سپس این ریشه با کف replay ورودی (rand-bridge-replay-floor-1) و در chain 20 همچنین با قواعد چرخش و چرخش‌های در انتظار (rand-bridge-rotation-1) و گروه کارمزد (rand-bridge-fees-1) پوشانده می‌شود. ثبت‌نامهٔ اعتبارسنج‌ها در validators_root هش می‌شود (دامنهٔ برگ در chain 20 برابر rand-validator-leaf-4 است و activation_epoch هر ردیف را دربردارد؛ در زنجیره‌ای بدون بخش staking برابر rand-validator-leaf-2 است)، بنابراین گره‌ای که دربارهٔ یک ردیف ثبت‌نامه اختلاف دارد، دربارهٔ زنجیره اختلاف دارد.

زمان بلوک محدود است (B2)

در زنجیره‌ای که پل دارد، برچسب زمانی بلوک تعیین می‌کند مجموعهٔ guardian کی منقضی می‌شود و سقف mint روزانهٔ هر پشتوانه به کدام روز تعلق می‌گیرد؛ بنابراین رهبری که بتواند ساعت را جلو بپرد، می‌تواند مهلت تمدید یک مجموعهٔ چرخانده‌شده را زودتر پایان دهد یا از سقف یک روز عبور کند. chain 20 بخش bridge دارد، پس سه قاعده اعمال می‌شود که همگی به آن بخش وابسته‌اند:

قاعدهکراننوع
بدون بازگشت به عقبtimestamp_ms ≥ parent.timestamp_ms (برابری مجاز است)، وگرنه BlockError::TimestampRewind؛ پیشنهاددهنده max(now, parent.timestamp_ms) را صادر می‌کنداعتبار، در replay و همگام‌سازی نیز بررسی می‌شود
بدون جهشtimestamp_ms ≤ parent.timestamp_ms + MAX_TIMESTAMP_STEP_MS (60,000 ms)، وگرنه BlockError::TimestampLeapاعتبار، در replay و همگام‌سازی نیز بررسی می‌شود
انحراف ساعتاعتبارسنج از رأی دادن به بلوکی که بیش از MAX_CLOCK_DRIFT_MS (15,000 ms) جلوتر از ساعت خودش باشد خودداری می‌کندفقط یک قاعدهٔ رأی: بلوک در درخت وضعی (speculative) می‌ماند، حد نصابی که ساعت‌هایش هم‌خوان باشند همچنان می‌تواند آن را گواهی کند، و replay تاریخچهٔ متعهدشده هرگز آن را اعمال نمی‌کند

هر اعتبارسنج در زنجیرهٔ دارای پل به NTP نیاز دارد. اعتبارسنجی که ساعتش بیش از 15 ثانیه اختلاف دارد، هم رهبر معیوب است (پیشنهادهایش ممکن است رأی نگیرند) و هم رأی‌دهندهٔ معیوب (ممکن است بلوک به‌موقع یک رهبر درستکار را رد کند). تا وقتی ساعت‌های اکثریت درستکار هم‌خوان باشند، هیچ‌یک زنجیره را متوقف نمی‌کند، اما ناوگانی هم‌گام‌نشده بی‌دلیل حاشیهٔ پایایی (liveness) را از دست می‌دهد.

timestamp_ms پیدایش باید به زمان واقعی راه‌اندازی نزدیک باشد. اگر بیش از حد در گذشته باشد، زمان بلوک از ساعت دیواری عقب می‌ماند تا کران گام اجازه دهد به آن برسد، حداکثر 60 ثانیه در هر بلوک. اگر بیش از 15 ثانیه در آینده باشد، بلوک 1 اصلاً متعهد نمی‌شود: هر رأی، از جمله رأی خود رهبر، تا زمانی که ساعت دیواری به آن برسد نگه داشته می‌شود. پس از یک توقف طولانی، همین جبران عقب‌ماندگی می‌تواند یک روز سقف mint را سریع‌تر از یک‌بار در هر روز واقعی بچرخاند، با کران یک روز سقف اضافی به ازای هر روز کامل عقب‌ماندگی انباشته.

قطعیت برای یک کلاینت

  • تراکنش وقتی قطعی است که rand_getTransaction(hash) یک بلوک برگرداند. پذیرش توسط rand_sendTransaction فقط ورود به mempool است.
  • بلوکی که RPC ارائه می‌کند یا explorer نمایه‌سازی کرده است، متعهدشده است. پنجرهٔ قطعیتی برای انتظار وجود ندارد؛ راهنمای explorer هم همین را می‌گوید.
  • هِدی که از rand_subscribe با ["newHeads"] می‌رسد، هِدی است که گره آن را از دست نخواهد داد: تا بلوک در ذخیره‌ساز متعهد نشده باشد، چیزی ارسال نمی‌شود.
  • گرهٔ عقب‌مانده همه‌چیز را دوباره راستی‌آزمایی می‌کند: پیش از پذیرش بلوک همگام‌شده، هر گواهی، هر bundle و اثبات فراخوانی و هر رسید. امضاهای قطعیت هرگز جای اثبات‌ها را نمی‌گیرند.

برگهٔ تقلب بازیابی، برگرفته از مستندات خود fullnode: اگر ارتفاع پیش نمی‌رود و peer_count پایین است، مشکل شبکه است؛ اگر ارتفاع پیش نمی‌رود ولی همتاها سالم‌اند، یعنی کمتر از 2/3 stake برخط است؛ و CORRUPT CHAIN در راه‌اندازی یعنی گره انتهای آسیب‌دیده را بریده و در حال همگام‌سازی مجدد است.

آخرین بار با کامیت c0878c3f از fullnode (زنجیرهٔ 21) در تاریخ 2026-10-12 بررسی شد. هرجا کد و این صفحه با هم اختلاف دارند، حق با کد است؛ به ما اطلاع دهید.