ملخص
تمر المعاملات عبر 8 مراحل: الاستقبال، والتحقق من التوقيع، والتعقيم، وفحوصات الميزانية/العمر، والتحقق من دافع الرسوم، وتحميل الحساب، وتنفيذ التعليمات، والتثبيت.
خط معالجة المعاملات
عندما تصل معاملة إلى validator، فإنها تمر عبر سلسلة من مراحل التحقق والتنفيذ. يصف ما يلي خط المعالجة الكامل من الاستقبال إلى التثبيت، مع مراجع ملفات المصدر في عميل validator agave.
1. الاستقبال وإلغاء التسلسل
يستقبل validator بايتات المعاملة عبر UDP/QUIC. يجب أن تتناسب البايتات الخام ضمن
حزمة واحدة (PACKET_DATA_SIZE = 1,232 بايت). يتم إلغاء تسلسل البايتات إلى
VersionedTransaction، والذي يحتوي على مصفوفة التوقيعات و
VersionedMessage (إما قديم أو v0).
2. التحقق من التوقيع (sigverify)
يتم التحقق من التوقيعات في
مرحلة sigverify
قبل دخول المعاملة إلى مرحلة البنوك. لكل توقيع عند الفهرس i، يتحقق المدقق من
Ed25519(signatures[i]، account_keys[i]، message_bytes). إذا كان أي توقيع
غير صالح، يتم تجاهل الحزمة.
يتم التحقق بشكل متوازٍ: يقسم validator دفعات الحزم إلى أجزاء من
VERIFY_PACKET_CHUNK_SIZE
(128) ويعالجها بشكل متوازٍ.
3. التعقيم
يتم تعقيم المعاملة التي تم إلغاء تسلسلها لإنتاج SanitizedTransaction (أو
RuntimeTransaction).
يتحقق التعقيم من الثوابت الهيكلية:
- عدد التوقيعات يطابق
num_required_signaturesفي الرأس - جميع
program_id_indexوaccount_indicesللتعليمات ضمن الحدود - دافع الرسوم (فهرس الحساب 0) هو موقّع قابل للكتابة
يخزن غلاف RuntimeTransaction البيانات الوصفية المحسوبة مسبقاً من
TransactionMeta:
تجزئة الرسالة، وعلامة معاملة التصويت، وعدد توقيعات التجميع المسبق
(Ed25519/secp256k1/secp256r1)، وتفاصيل تعليمات ميزانية الحوسبة، وإجمالي طول
بيانات التعليمات.
4. التحقق من ميزانية الحوسبة والعمر وذاكرة التخزين المؤقت للحالة
تقوم طريقة
check_transactions
بإجراء عدة فحوصات لكل معاملة:
ميزانية الحوسبة: يتم تحليل تعليمات ميزانية الحوسبة الخاصة بالمعاملة والتحقق
من صحتها أولاً. يتم حساب تفاصيل الرسوم من حدود الميزانية ورسوم الأولوية. إذا
كانت ميزانية الحوسبة غير صالحة أو متعارضة، تفشل المعاملة مع أخطاء تحليل ميزانية
الحوسبة مثل DuplicateInstruction، أو
InstructionError(..., InvalidInstructionData)، أو
InvalidLoadedAccountsDataSizeLimit.
عمر تجزئة الكتلة: يتم البحث عن recent_blockhash الخاص بالمعاملة في
BlockhashQueue.
إذا تم العثور على التجزئة وكان عمرها ضمن MAX_PROCESSING_AGE (150 فتحة)،
تستمر المعاملة. إذا لم يتم العثور عليها، يتحقق المدقق من وجود
nonce دائم صالح.
ذاكرة التخزين المؤقت للحالة: يتم التحقق من تجزئة رسالة المعاملة مقابل ذاكرة
التخزين المؤقت للحالة. إذا تم العثور عليها، يتم رفض المعاملة مع
AlreadyProcessed.
5. التحقق من صحة nonce ودافع الرسوم
تتعامل طريقة
validate_transaction_nonce_and_fee_payer
في SVM مع عمليتي تحقق:
التحقق من صحة nonce (إن وجد): بالنسبة لمعاملات nonce، يقوم المدقق بتحميل حساب nonce والتحقق من:
- أن الحساب مملوك لبرنامج النظام (System Program)
- أنه يتم تحليله كـ
State::Initialized - أن nonce الدائم المخزن يطابق
recent_blockhashالخاص بالمعاملة - أنه يمكن تقديم nonce (يختلف nonce الدائم الحالي عن nonce الدائم التالي، أي أن nonce لم يتم استخدامه بالفعل في الكتلة الحالية)
- أن سلطة nonce قد وقعت على المعاملة
إذا كان صالحاً، يتم تقديم nonce إلى قيمة nonce الدائم التالية. راجع
validate_transaction_nonce.
التحقق من صحة دافع الرسوم: يتم تحميل حساب دافع الرسوم (دائماً الفهرس 0)
والتحقق منه بواسطة
validate_fee_payer:
- يجب أن يكون الحساب موجوداً (lamports > 0)، وإلا
AccountNotFound - يجب أن يكون الحساب حساب نظام أو حساب nonce، وإلا
InvalidAccountForFee - يجب أن تغطي lamports قيمة
min_balance + total_fee، حيثmin_balanceيساوي 0 لحسابات النظام أوrent.minimum_balance(NonceState::size())لحسابات nonce؛ وإلاInsufficientFundsForFee - بعد خصم الرسوم، يجب أن يظل الحساب معفياً من الإيجار (لا يمكن الانتقال من معفى من الإيجار إلى دافع للإيجار)
يتم خصم الرسوم من دافع الرسوم في هذه المرحلة. يتم حفظ لقطة من دافع الرسوم بعد
خصم الرسوم (والـ nonce المتقدم، إن وُجد) كـ RollbackAccounts، وهي الحسابات
التي يتم تثبيتها حتى في حالة فشل التنفيذ.
6. تحميل الحسابات
تقوم
load_transaction
بتحميل جميع الحسابات المشار إليها في المعاملة. تقوم
AccountLoader
بتغليف مخزن الحسابات الخارجي وتحافظ على ذاكرة تخزين مؤقتة محلية للدفعة بحيث تكون
الحسابات المعدلة بواسطة المعاملات السابقة في نفس الدفعة مرئية للمعاملات اللاحقة.
لكل حساب غير دافع الرسوم، يقوم المحمّل بـ:
- جلب الحساب من الذاكرة المؤقتة أو قاعدة بيانات الحسابات
- تحديث حالة الإعفاء من الإيجار إذا لزم الأمر
- تجميع حجم بيانات الحساب نحو
loaded_accounts_data_size_limit(الافتراضي 64 ميجابايت). يتحمل كل حساب عبئاً أساسياً قدرهTRANSACTION_ACCOUNT_BASE_SIZE(64 بايت) بالإضافة إلى طول بياناته
لكل برنامج يتم استدعاؤه بواسطة تعليمات المعاملة، يتحقق المحمّل من أن حساب
البرنامج موجود ومملوك لمحمّل صالح (NativeLoader أو أحد PROGRAM_OWNERS).
تفشل البرامج غير الصالحة مع ProgramAccountNotFound أو
InvalidProgramForExecution.
تقوم برامج LoaderV3 (القابلة للترقية) بتحميل حساب programdata المرتبط بها ضمنياً، والذي يُحتسب أيضاً ضمن حد حجم البيانات المحملة.
إذا فشل تحميل الحساب ولكن تم التحقق من صحة دافع الرسوم بنجاح، تصبح المعاملة
نتيجة
FeesOnly:
يتم تحصيل الرسوم ولكن لا يتم تنفيذ أي تعليمات.
7. تنفيذ التعليمات
تقوم
execute_loaded_transaction
بإنشاء TransactionContext مع جميع الحسابات المحملة وتستدعي
process_message.
يتم تنفيذ التعليمات بشكل تسلسلي بالترتيب الذي تظهر به في الرسالة. ينشئ كل
استدعاء تعليمة InvokeContext ويستدعي البرنامج المستهدف.
تفاصيل معالجة التعليمات
تقوم دالة
process_message
في وقت التشغيل بالتكرار عبر كل تعليمة وتستدعي البرنامج المستهدف:
- لكل تعليمة، يستدعي وقت التشغيل
prepare_next_top_level_instruction، والذي يبنيInstructionContext. يحتوي هذا السياق على مراجع لحسابات التعليمة (المحلولة من الفهارس المجمعة)، وبيانات التعليمة، وفهرس حساب البرنامج. - يتحقق وقت التشغيل مما إذا كان البرنامج مجمّعاً مسبقاً (Ed25519، Secp256k1، Secp256r1). يتم التحقق من البرامج المجمّعة مسبقاً مباشرة دون استدعاء BPF VM.
- لجميع البرامج الأخرى، يستدعي وقت التشغيل
process_instruction، والذي يحمّل البرنامج من ذاكرة التخزين المؤقت وينفذه في الجهاز الافتراضي BPF. - بعد اكتمال التعليمة، يقوم وقت التشغيل
بالتحقق
من أن إجمالي رصيد lamport عبر جميع حسابات التعليمة لم يتغير (فحص
UnbalancedInstruction). - إذا فشلت أي تعليمة، يتم التراجع عن المعاملة بأكملها. لا يتم تثبيت أي تغييرات حالة وسيطة.
تزيد كل تعليمة من تتبع التعليمات. يتضمن التتبع كلاً من التعليمات ذات المستوى
الأعلى وأي CPIs تستدعيها. لا يمكن أن يتجاوز إجمالي طول التتبع
(التعليمات ذات المستوى الأعلى بالإضافة إلى جميع CPIs المتداخلة) 64
(MAX_INSTRUCTION_TRACE_LENGTH). يؤدي تجاوز هذا الحد إلى إرجاع
InstructionError::MaxInstructionTraceLengthExceeded.
بعد التنفيذ، يتحقق وقت التشغيل من أن:
- مجموع lamports عبر جميع الحسابات لم يتغير
- لم ينتقل أي حساب من معفي من الإيجار إلى دافع للإيجار
8. التثبيت أو التراجع
إذا نجح التنفيذ، تُكتب حالات الحسابات المعدلة من TransactionContext مرة أخرى
إلى ذاكرة التخزين المؤقت المحلية للدفعة في AccountLoader. إذا فشل التنفيذ،
تُكتب فقط RollbackAccounts (دافع الرسوم مع خصم الرسوم والتقدم في nonce)
مرة أخرى. لا يزال يتم تحصيل الرسوم، ولكن يتم تجاهل جميع تغييرات الحسابات الأخرى.
ملخص خط الأنابيب
Receive packet (UDP/QUIC)--> Deserialize into VersionedTransaction--> Sigverify (parallel Ed25519 verification)--> Sanitize (structural validation, metadata extraction)--> Parse compute budget, calculate fees--> Check blockhash age (or verify nonce account)--> Check status cache (dedup)--> Validate nonce authority and advanceability (if nonce transaction)--> Validate fee payer (load, check balance, deduct fee)--> Load all accounts (with data size limits)--> Load programs (verify loaders)--> Execute instructions sequentially--> Verify post-conditions (lamport balance, rent state)--> Commit account changes (or rollback on failure)
مرجع أخطاء المعاملات
يسرد الجدول التالي جميع متغيرات
TransactionError
وفي أي مرحلة من خط الأنابيب تحدث:
| الخطأ | المرحلة | السبب |
|---|---|---|
AccountInUse | الجدولة | الحساب مقفل بالفعل بواسطة معاملة أخرى في نفس الدفعة |
AccountLoadedTwice | الجدولة | يظهر pubkey مرتين في account_keys الخاص بالمعاملة |
AccountNotFound | التحقق من دافع الرسوم | حساب دافع الرسوم غير موجود |
ProgramAccountNotFound | تحميل الحساب | البرنامج المستدعى غير موجود |
InsufficientFundsForFee | التحقق من دافع الرسوم | دافع الرسوم لا يمكنه تغطية الرسوم + الحد الأدنى المعفى من rent |
InvalidAccountForFee | التحقق من دافع الرسوم | دافع الرسوم ليس حساب نظام أو حساب nonce |
AlreadyProcessed | ذاكرة التخزين المؤقت للحالة | تمت معالجة المعاملة بالفعل |
BlockhashNotFound | فحص العمر | Blockhash غير موجود في قائمة الانتظار وليس nonce صالحاً |
InstructionError | التنفيذ | حدث خطأ أثناء معالجة تعليمة (يتضمن فهرس التعليمة و InstructionError المحدد) |
CallChainTooDeep | تحميل الحساب | سلسلة استدعاء المحمل عميقة جداً |
MissingSignatureForFee | التعقيم | المعاملة تتطلب رسوماً لكن لا يوجد توقيع |
InvalidAccountIndex | التعقيم | المعاملة تحتوي على مرجع حساب غير صالح |
SignatureFailure | التحقق من التوقيع | توقيع Ed25519 لا يتحقق (يتم تجاهل الحزمة) |
InvalidProgramForExecution | تحميل الحساب | البرنامج غير مملوك بواسطة محمل صالح |
SanitizeFailure | التعقيم | فشلت المعاملة في تعقيم إزاحات الحسابات بشكل صحيح |
ClusterMaintenance | الجدولة | المعاملات معطلة حالياً بسبب صيانة الشبكة |
AccountBorrowOutstanding | التنفيذ | معالجة المعاملة تركت حساباً مع مرجع مستعار معلق |
WouldExceedMaxBlockCostLimit | الجدولة | المعاملة ستتجاوز الحد الأقصى لتكلفة الكتلة |
UnsupportedVersion | التعقيم | إصدار المعاملة غير مدعوم |
InvalidWritableAccount | تحميل الحساب | المعاملة تحمل حساباً قابلاً للكتابة لا يمكن الكتابة عليه |
WouldExceedMaxAccountCostLimit | الجدولة | المعاملة ستتجاوز الحد الأقصى لتكلفة الحساب داخل الكتلة |
WouldExceedAccountDataBlockLimit | الجدولة | المعاملة ستتجاوز حد بيانات الحساب داخل الكتلة |
TooManyAccountLocks | الجدولة | المعاملة قفلت عدداً كبيراً جداً من الحسابات |
AddressLookupTableNotFound | تحميل الحساب | حساب جدول البحث عن العناوين غير موجود |
InvalidAddressLookupTableOwner | تحميل الحساب | جدول البحث عن العناوين مملوك بواسطة برنامج خاطئ |
InvalidAddressLookupTableData | تحميل الحساب | جدول البحث عن العناوين يحتوي على بيانات غير صالحة |
InvalidAddressLookupTableIndex | تحميل الحساب | البحث في جدول العناوين يستخدم فهرساً غير صالح |
InvalidRentPayingAccount | الفحص بعد التنفيذ | انتقل الحساب من معفى من rent إلى دافع rent |
WouldExceedMaxVoteCostLimit | الجدولة | المعاملة ستتجاوز الحد الأقصى لتكلفة التصويت |
WouldExceedAccountDataTotalLimit | الجدولة | المعاملة ستتجاوز الحد الإجمالي لبيانات الحساب |
DuplicateInstruction | تحليل ميزانية الحوسبة | متغير تعليمة ميزانية الحوسبة مكرر في نفس المعاملة |
InsufficientFundsForRent | الفحص بعد التنفيذ | الحساب لا يحتوي على lamports كافية لتغطية rent لحجم بياناته |
MaxLoadedAccountsDataSizeExceeded | تحميل الحساب | إجمالي البيانات المحملة يتجاوز حد 64 ميجابايت |
InvalidLoadedAccountsDataSizeLimit | تحليل ميزانية الحوسبة | SetLoadedAccountsDataSizeLimit تم تعيينه إلى 0 |
ResanitizationNeeded | التعقيم | المعاملة اختلفت قبل/بعد تفعيل الميزة وتحتاج إعادة تعقيم |
ProgramExecutionTemporarilyRestricted | تحميل الحساب | تنفيذ البرنامج مقيد مؤقتاً على الحساب المشار إليه |
UnbalancedTransaction | الفحص بعد التنفيذ | إجمالي رصيد lamport قبل المعاملة لا يساوي الرصيد بعدها |
ProgramCacheHitMaxLimit | تحميل الحساب | ذاكرة التخزين المؤقت للبرنامج وصلت للحد الأقصى |
CommitCancelled | الالتزام | تم إلغاء الالتزام داخلياً |
Is this page helpful?