Rand Protocol
الوثائق
المفاهيم الأساسية

الإجماع والنهائية

بروتوكول Chained HotStuff كما تشغّله العقدة: النصابات، والقادة، وقاعدة الإقرار بعد ثلاث دورات (views) متتالية، والمهلات، والحِقَب (epochs)، وما تعنيه كلمة «نهائي» للعميل على testnet.

استخدم هذه الصفحة لمعرفة متى تصبح المعاملة نهائية، وكم عدد المدقّقين الذين يجب أن يكونوا متصلين، وماذا تعني حقول الكتلة التي تراها في RPC وفي المستكشف. الإجماع هو HotStuff BFT المتسلسل مع أنصبة مرجّحة بالحصة؛ وفي السلسلة 20 يعني ذلك 18 من 26 مدقّقًا.

القواعد التي تشغّلها العقدة

القاعدةكما هي منفّذة
النصابشهادة النصاب (QC) هي مجموعة من أصوات Dilithium2 صادرة عن مدقّقين يملكون أكثر من 2/3 بدقة من إجمالي الحصة
جدول القادةتناوب دوري خالص: قائد الجولة v هو validators[v mod n]، والمجموعة مرتّبة حسب العنوان. الحصة ترجّح الأصوات، لا اختيار القائد
الاقتراحيقترح قائد الجولة v كتلة متى امتلك QC للجولة v − 1، أو نصابًا من رسائل NewView للجولة v. الاقتراح الذي يطبّق معاملة غير صالحة يُسقط تلك المعاملة لا الكتلة
التصويت والأمانيصوّت المدقّق مرة واحدة في كل جولة، ولا يصوّت إلا لكتلة آمنة بالنسبة إلى قفله (أي أن QC التبرير فيها أحدث من QC المقفل، أو أن الكتلة تمتد من الكتلة المقفلة). تُكتب حالة الأمان إلى RocksDB بـ fsync قبل أن يغادر الصوت العقدة
نطاق التوقيعفي genesis ذي consensus_domain: 1 (السلسلة 20، وكل سلسلة منذ 15) يغطي كل توقيع صوت ورسالة new-view واقتراح هاش genesis (rand-vote-2 وrand-newview-2 وrand-block-2)، فلا يصحّ توقيع سلسلة ما على أي سلسلة أخرى. أما السلسلة 14 وما قبلها فكانت توقّع بدونه. ويمدّ binding_domain: 1 في السلسلة 20 القاعدة نفسها إلى إثباتات المعاملات والإجراءات الموقّعة (المعاملات)
الاقتراحات الشقيقةتحتفظ النسخة المتماثلة بكتلة واحدة لكل (جولة، قائد)؛ وأي كتلة ثانية مختلفة من ذلك القائد في تلك الجولة تُرفض بوصفها ازدواجية. والاقتراح الذي يتجاوز جولة النسخة المتماثلة نفسها بأكثر من 8 جولات (PROPOSAL_VIEW_WINDOW) يُرفض قبل فحص توقيعه؛ وتُخلي الشجرة التخمينية الممتلئة الكتل الواقعة خارج الفرع قبل أن ترفض أي شيء
تحرير القفللا يُخفَّض القفل تلقائيًا أبدًا (CON-4، v0.6.8). فهو يتنازل فقط لاقتراح تتفوّق QC التبرير فيه عليه، أو لوصول الكتلة المقفلة. أما نصاب من إجابات NotHeld الموقّعة فيُسجَّل لأجل المشغّل ولا يحرّر شيئًا، ويسجّل المدقّق أصواته كي لا يوقّع أبدًا NotHeld على كتلة صوّت لها. وكل كتلة مصدَّقة فوق الرأس تُحفظ وتُستعاد عند إعادة التشغيل، فلا تترك إعادة تشغيل الأسطول كله QC عالقة على كتلة لا يملكها أحد. أما المدقّق الذي فُقدت كتلته المقفلة نهائيًا فيُحرَّر يدويًا وهو غير متصل: rand-node safety release-lock
تجميع QCيجمّع كل مدقّق شهادات QC محليًا من الأصوات المنشورة بالنشر الشائع (gossip)، فتواصل السلسلة الالتزام بالكتل حتى لو كان القائد التالي غير متصل
الالتزامقاعدة الشهادات الثلاث المتسلسلة: تلتزم الكتلة متى تسلسلت فوقها ثلاث شهادات في جولات متتالية v, v+1, v+2 وارتدّ هذا التسلسل إلى الرأس الملتزَم به
المهلات وتغيير الجولاتالأساس --view-timeout-ms يساوي 3,000 ms، ويتضاعف مع كل مهلة متتالية، ويُعاد ضبطه عند أي تقدّم. مؤقّت إيقاع بشهادات المهلة (CH-1، v0.6.8): النسخة المتماثلة التي ينتهي مؤقتها تبقى في جولتها وتوقّع NewView تطلب فيها الجولة التالية. وتدخل جولة ما عند QC للجولة السابقة لها، أو حين يطلب تلك الجولة أو ما بعدها مدقّقون يملكون أكثر من 2/3 من الحصة؛ وتنضم (توقّع طلبها الخاص) حين يطلب أكثر من 1/3 جولة تتجاوز جولتها. ولا تنقل NewView من أي مدقّق منفرد أحدًا. والجولات محدودة (MAX_VIEW_AHEAD = 10⁶)
الإيقاعيقترح القائد كتلة فارغة حين لا يكون هناك شيء معلّق، بعد --block-interval-ms (الافتراضي 1,000 ms) على الأقل من آخر كتلة شوهدت من أي مقترِح
حدود الأصوات والنشرتُتجاهل الأصوات التي تسبق بأكثر من 8 جولات، ولا يُحتسب إلا صوت واحد لكل (جولة، مصوّت)؛ ويُقاس النشر الشائع للإجماع لكل نظير يعيد التمرير (دفعة 256، و64/s)؛ والاقتراح الذي يكون أبوه مجهولًا لا يُحتفظ به يتيمًا إلا إذا كان مقترِحه يقود جولته في مجموعة الحقبة الحالية أو التالية، وكان ضمن حدّ الشجرة من الرأس الملتزَم به، وصحّ حجمه وجذر معاملاته (سقف مجمع اليتامى 64 MiB، ومنذ v0.6.1 سقف 16 يتيمًا لكل مقترِح). ومنذ v0.6.8 لا يُعاد تمرير الاقتراح ولا يُحتفظ به يتيمًا إلا إذا كانت قائمة معاملاته تُجزَّأ إلى الجذر الذي يلتزم به رأسها دون تكرار أي معاملة (GOSSIP-2)
حدود الكتلةmax_block_bytes للسلسلة من بايتات المعاملات (4 MiB افتراضيًا، و20 MiB في genesis السلسلة 20) و2,000 معاملة، ويفرضها المقترِح ثم تفرضها كل نسخة متماثلة مجددًا في Ledger::apply_block
وقت الكتلةفي سلسلة ذات قسم bridge: لا يجوز أن يسبق timestamp_ms للكتلة طابع أبيها (TimestampRewind) ولا أن يتجاوزه بأكثر من 60 s (TimestampLeap)، ويمتنع المدقّق عن التصويت لكتلة تتقدّم ساعته بأكثر من 15 s؛ انظر أدناه

قاعدة الالتزام بثلاث جولات متتالية حاملة للأحمال: فتخفيفها يعيد فتح خلل نهائية متعارضة للعقدة الكاملة اختبار انحدار له. وكل ما يراه العميل عبر RPC ملتزَم به: لا تُقدَّم إلا الكتل الملتزَم بها، ويرسل بثّ WebSocket newHeads إشعارًا واحدًا لكل كتلة ملتزَم بها، بالترتيب.

الجدول هو v0.6.8، الإصدار الذي تشغّله السلسلة 20. ثلاث من قواعده جديدة في v0.6.8 (التدقيق v6) وهي فعّالة منذ بدأت السلسلة 20 العمل في 2026-10-01: مؤقّت الإيقاع بشهادات المهلة (CH-1)، وعدم تحرير القفل تلقائيًا (CON-4)، وفحص قائمة معاملات الاقتراح (GOSSIP-2). أما في السلسلتين 18 و19 (v0.6.7) فكانت NewView موقّعة من مدقّق منفرد تنقل كل نسخة متماثلة إلى جولته، وكان القفل يُخفَّض بإجابات NotHeld موقّعة من نصاب من الحصة. ويغيّر مؤقّت الإيقاع وقاعدة القفل ما يوقّعه المدقّقون ومتى، ولذلك نُشر v0.6.8 بإيقاف الجميع ثم تشغيل الجميع مع قطع السلسلة 20، لا عقدة بعد عقدة.

أرقام الشبكة التجريبية

للسلسلة 20 ستة وعشرون مدقّقًا، لكلٍّ منهم حصة مقدارها 1,000 RAND بالضبط، وجميعهم الـ 26 في genesis بالمفاتيح ومعرّفات النظراء التي شغّلتها السلاسل 17 إلى 19. وفي السلسلة 15 كان ثمانية عشر في genesis على مفاتيح السلسلة 14، وارتبط ثمانية آخرون في 2026-09-27 بتسجيلات v2 وصاروا فعّالين من الحقبة 43. الحصة متساوية، فالنصاب إذن عدّ رؤوس: يجب أن يكون 18 من 26 متصلين كي يلتزم أي شيء. ومع 17 متصلين تتقدّم الجولات ولا يصير شيء نهائيًا. وتحتاج مجموعة من n مدقّقًا متساوين إلى أكثر من 2/3 من الحصة: 2 من 2، و3 من 4، و5 من 6، و13 من 18، و18 من 26. ولا يقيّد سقف الوزن في genesis (3,333 bps لكل مدقّق) مجموعة من حصص متساوية.

تُوقَّت الكتل بالافتراضي 1,000 ms. وفي السلسلة 15 وصلت عمليًا كل 1.2 s تقريبًا (1.18 s بمتوسط ساعاتها التسع والعشرين الأولى)؛ وفي السلسلة 20 أبلغ المستكشف عن متوسط 1.34 s عند الارتفاع 3,270 (2026-10-01، avg_block_time_ms). ويبلغ المستكشف عن متوسط زمن الكتلة الذي يرصده (avg_block_time_ms في GET /stats). وتتقدّم جولة المدقّق على الارتفاع كلما انتهت مهلات الجولات، ولهذا يبلغ rand_getHead عن الاثنين.

الحقب

تُشتق مجموعة المدقّقين من السجل من جديد كل epoch_blocks = 1,000 كتلة (معامل في genesis). epoch(h) = h / 1000؛ وتُشتق مجموعة الحقبة e ≥ 1 من السجل كما هو عند آخر كتلة من الحقبة e − 1: كل صف ذي stake ≥ 1,000 RAND، وأعلى 100 بالحصة، مرتّبة حسب العنوان. وفي سلسلة بلا قسم staking يصير الربط وزنًا عند الحدّ التالي. أما في السلسلة 20 فينتظر حقبتين إضافيتين وميزانية دخول لكل حقبة قدرها 2,500 bps من الوزن المربوط الفعّال عند الحدّ (6,500 RAND ما دامت المجموعة تحمل 26,000)، ويُرفض الربط الذي يسجّل مدقّقًا جديدًا ما لم تكن المجموعة قد صوّتت أولًا على قبول المفتاح بـ AdmitValidator (staking.admission_by_vote). وكانت ميزانية السلسلة 19 ثابتة قدرها 10,000 RAND في الحقبة، ولم يكن لها تصويت قبول. أما فكّ الربط فيكلّف الوزن عند الحدّ التالي، بينما يمهَل المبلغ حقبتين ليصير قابلًا للسحب. ويعيد rand_getEpoch الحقبة وطولها وإسقاطًا للمجموعة التالية. انظر الرهن.

ما تحمله الكتلة

{
  "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 بالدفتر بعد الكتلة. في السلسلة 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)

التزمت السلسلة 19 بالحقول نفسها دون program_state_root، تحت rand-state-7، ودون غلاف.

تعتمد المكوّنات على ملف التكوين الأولي (genesis). السلسلة التي لا تحتوي على أي من الأقسام الاختيارية تلتزم بالمكوّنات الأربعة الأولى تحت rand-state-2. يُضيف قسم bridge الجذر bridge_root. ويُضيف قسم tokens الجذر tokens_root بعده ويعيد تعيين نطاق التجزئة كلها إلى rand-state-4؛ وكل genesis يحتوي على قسم bridge يجب أن يحتوي على قسم tokens، ولذلك تملك السلسلة 20 كليهما. ويُضيف قسم aggregation جذر المجمِّعين (aggregators) تاليًا (تحت rand-state-3 في سلسلة بلا رموز)؛ والسلسلة 20 لا تملكه. ويُضيف قسم staking عدّادَي حقبتَي الصنبور (faucet) وجذر طابور السندات (rand-bond-queue-1) في الأخير، ويعيد تعيين نطاق التجزئة كلها إلى rand-state-5 أيًّا كان ما سواه مفعّلًا؛ السلسلة 14 لم يكن فيها هذا القسم وجرى التجزئة تحت rand-state-4، أما السلاسل من 15 إلى 17 فجرت تجزئتها تحت rand-state-5. ويُضيف قسم vesting جذر سجل الاستحقاق التدريجي بعد ذلك ويعيد تعيين النطاق إلى rand-state-6؛ والسلسلة 20 لا تملكه. ويُضيف قسم gas ذو المتحكم dynamic القيمتين الحيّتين gas_price وbyte_price، كل منهما u64 بترتيب البايتات من الأكبر أهمية (big-endian)، بعد كل ما سواهما، ويعيد تعيين النطاق إلى rand-state-7. كان ذلك نطاق السلسلة 19، وتُبقي السلسلة 20 الأسعار في جذرها: فهي تتحرك مرة واحدة في كل كتلة، بعد معاملات الكتلة وقبل الجذر (الرسوم). أما قسم gas ذو الأسعار الثابتة فليس في الجذر. ويُضيف قسم program_state (RPL-2) جذر خلايا كل برنامج وخزائنه بعد الأسعار ويعيد تعيين النطاق إلى rand-state-8: وهو نطاق السلسلة 20. أما حالة staking في التدقيق v6 فتُدمج حول ذلك لا داخله: ففي ظل staking.admission_by_vote (السلسلة 20) يكون الجذر blake3("rand-state-admitted-1" || inner || admitted_root)، وفي ظل staking.slashing (لا سلسلة بعد) يضيف غلاف ثانٍ مجموعة المسجونين (jailed).

tokens_root هو جذر Merkle لأوراق blake3("rand-token-leaf-1" || bincode(TokenInfo)) مرتبةً بحسب الفهرس، ولذلك فإن البيانات الوصفية لكل رمز وسلطته وmint_nonce وعرضه ومقدار locked في كل غطاء هي كلها حالة إجماع: فإذا اختلفت عقدتان على صف واحد من صفوف رمز واحد فقد اختلفتا على السلسلة. وفي السلسلة 20 يدمج جذر الرموز أيضًا امتداد السجل (سقف max_tokens، ورسم التسجيل المحروق، وحد قيمة الملاحظة (note)، ونوافذ سقف السك المتجددة للجسر) تحت rand-token-registry-3. ويغطي جذر الجسر المُصدِرين (emitters) ومجموعات الحرّاس (guardians) وملخصات الإفادات المُنفقة وتسلسل الحرق ومفاتيح الحرّاس ما بعد الكمّ (PQ) ومفتاح الإيقاف المؤقت وعلم الإيقاف المؤقت ونونس الحوكمة (nonce) الاثنين؛ أما سجل الأصول فقد انتقل منه إلى tokens_root. وللجسر في السلسلة 20 rules_v2، ولذلك يُضاف نونس التدوير ومعاملات السقف العام ويكون النطاق rand-bridge-state-5 (كان نطاق السلسلة 14 rand-bridge-state-4)؛ ثم يُغلَّف ذلك الجذر بحد الإعادة الوارد (rand-bridge-replay-floor-1)، وفي السلسلة 20 أيضًا بقواعد التدوير والتدويرات المعلّقة (rand-bridge-rotation-1) ومجموعة الرسوم (rand-bridge-fees-1). ويُجزَّأ سجل المدققين (validators) في validators_root (نطاق الورقة rand-validator-leaf-4 في السلسلة 20 مع activation_epoch لكل صف، وrand-validator-leaf-2 في سلسلة بلا قسم staking)، ولذلك فإن العقدة التي تختلف على صف واحد من سجل المدققين تختلف على السلسلة.

زمن الكتلة محدود (B2)

في سلسلة مرتبطة بجسر، يحدد الطابع الزمني للكتلة انتهاء صلاحية مجموعة الحرّاس واليوم الذي يُحتسب فيه سقف السك اليومي للغطاء، ولذلك فإن القائد الذي يستطيع تقديم الساعة قد يُنهي مهلة السماح لمجموعة مدوَّرة قبل أوانها أو يتجاوز سقف يوم كامل. تملك السلسلة 20 قسم bridge، ولذلك تسري ثلاث قواعد كلها مشروطة به:

القاعدةالحدالنوع
لا رجوع للخلفtimestamp_ms ≥ parent.timestamp_ms (المساواة مسموحة)، وإلا BlockError::TimestampRewind؛ ويُصدر المقترِح max(now, parent.timestamp_ms)صلاحية، تُفحص عند إعادة التشغيل والمزامنة أيضًا
لا قفز للأمامtimestamp_ms ≤ parent.timestamp_ms + MAX_TIMESTAMP_STEP_MS (60,000 ms)، وإلا BlockError::TimestampLeapصلاحية، تُفحص عند إعادة التشغيل والمزامنة أيضًا
انحراف الساعةيرفض المدقق التصويت لكتلة تتقدم على ساعته بأكثر من MAX_CLOCK_DRIFT_MS (15,000 ms)قاعدة تصويت فقط: تبقى الكتلة في الشجرة التخمينية، ويمكن لنصاب تتفق ساعات أعضائه أن يصدّق عليها، ولا تُطبَّق القاعدة أبدًا عند إعادة تشغيل التاريخ المُلتزَم به

كل مدقق في سلسلة مرتبطة بجسر يحتاج إلى NTP. المدقق الذي تنحرف ساعته بأكثر من 15 ثانية قائدٌ معيب (قد لا تحصل مقترحاته على أي أصوات) وناخبٌ معيب (قد يرفض كتلة قائد أمين جاءت في وقتها). لا يوقف أي منهما السلسلة ما دامت ساعات الأغلبية الأمينة متفقة، لكن الأسطول غير المتزامن يفقد هامش الحيوية (liveness) بلا مقابل.

يجب أن يكون timestamp_ms في genesis قريبًا من وقت الإطلاق الفعلي. فإذا كان في الماضي البعيد تأخر زمن الكتلة عن ساعة الجدار إلى أن يسمح حد الخطوة باللحاق، بحد أقصى 60 s لكل كتلة. وإذا كان في المستقبل بأكثر من 15 s فلن تُلتزم الكتلة 1 إطلاقًا: يُحجب كل صوت، بما فيه صوت القائد نفسه، حتى يبلغ وقت الجدار ذلك الزمن. وبعد توقف طويل قد يدفع اللحاق نفسه يوم سقف السك إلى التقدم أسرع من مرة واحدة لكل يوم حقيقي، بحد أقصى سقف يوم إضافي واحد لكل يوم كامل من التأخر المتراكم.

النهائية بالنسبة إلى العميل

  • تكون المعاملة نهائية عندما تُرجع rand_getTransaction(hash) كتلة. أما قبول rand_sendTransaction فهو مجرد إدخال إلى مجمع المعاملات المعلّقة.
  • الكتلة التي يقدّمها RPC أو يفهرسها المستكشف مُلتزَم بها. لا توجد نافذة نهائية لانتظارها؛ ودليل المستكشف يقول الشيء نفسه.
  • الرأس القادم من rand_subscribe ["newHeads"] رأسٌ لن تفقده العقدة: لا يُرسَل شيء قبل أن تُلتزم الكتلة في التخزين.
  • العقدة التي تتخلف عن الركب تعيد التحقق من كل شيء: كل شهادة، وكل حزمة وإثبات استدعاء، وكل إيصال، قبل أن تقبل كتلة مُزامَنة. ولا يُوثَق أبدًا بتوقيعات النهائية بدلًا من الإثباتات.

ورقة استرشاد سريعة للتعافي، من وثائق العقدة الكاملة نفسها: ارتفاع لا يتقدم مع peer_count منخفض مشكلة في الشبكات؛ وارتفاع لا يتقدم مع سلامة النظراء يعني أن أقل من ثلثي الحصة (stake) متصل؛ وCORRUPT CHAIN عند بدء التشغيل يعني أن العقدة بترت ذيلًا تالفًا وهي تعيد المزامنة.

آخر تحقق مقابل الإيداع c0878c3f في fullnode (السلسلة 21) بتاريخ 2026-10-12. إذا اختلف الكود عن هذه الصفحة فالكود هو الصحيح؛ أبلغونا بذلك.