الاختبار العام: حل خصوصية إيثريوم وقابلية التوسع

Blokcheyn هي تقنية مبتكرة تعد بتحسين العديد من مجالات حياة الإنسان. فهو ينقل العمليات والمنتجات الحقيقية إلى الفضاء الرقمي، ويضمن سرعة وموثوقية المعاملات المالية، ويقلل من تكلفتها، كما يسمح لك بإنشاء تطبيقات DAPP حديثة باستخدام العقود الذكية في الشبكات اللامركزية.

نظرًا للفوائد العديدة والتطبيقات المتنوعة لـ blockchain، قد يبدو من المفاجئ أن هذه التكنولوجيا الواعدة لم تشق طريقها بعد إلى كل صناعة. المشكلة هي أن سلاسل الكتل اللامركزية الحديثة تفتقر إلى قابلية التوسع. تقوم الإيثريوم بمعالجة حوالي 20 معاملة في الثانية، وهو ما لا يكفي لتلبية احتياجات الأعمال الديناميكية اليوم. وفي الوقت نفسه، تتردد الشركات التي تستخدم تقنية البلوكشين في التخلي عن الإيثيريوم بسبب درجة الحماية العالية التي توفرها من القرصنة وفشل الشبكة.

لضمان اللامركزية والأمن وقابلية التوسع في blockchain، وبالتالي حل مشكلة قابلية التوسع، قام فريق التطوير أوبورتي أنشأت Plasma Cash، وهي سلسلة فرعية تتكون من عقد ذكي وشبكة خاصة تعتمد على Node.js، والتي تنقل حالتها بشكل دوري إلى السلسلة الجذرية (Ethereum).

الاختبار العام: حل خصوصية إيثريوم وقابلية التوسع

العمليات الرئيسية في البلازما النقدية

1. يطلق المستخدم على وظيفة العقد الذكي اسم "الإيداع"، ويمرر فيها مبلغ ETH الذي يريد إيداعه في رمز Plasma Cash. تقوم وظيفة العقد الذكي بإنشاء رمز مميز وإنشاء حدث حوله.

2. تتلقى عقد Plasma Cash المشتركة في أحداث العقود الذكية حدثًا حول إنشاء إيداع وإضافة معاملة حول إنشاء رمز مميز إلى المجمع.

3. بشكل دوري، تأخذ عقد Plasma Cash الخاصة جميع المعاملات من المجمع (ما يصل إلى مليون) وتشكل كتلة منها، وتحسب شجرة Merkle، وبالتالي التجزئة. يتم إرسال هذه الكتلة إلى العقد الأخرى للتحقق منها. تتحقق العقد مما إذا كان تجزئة Merkle صالحة وما إذا كانت المعاملات صالحة (على سبيل المثال، ما إذا كان مرسل الرمز المميز هو مالكه). بعد التحقق من الكتلة، تستدعي العقدة وظيفة "submitBlock" للعقد الذكي، والتي تحفظ رقم الكتلة وتجزئة Merkle في سلسلة الحافة. يُنشئ العقد الذكي حدثًا يشير إلى الإضافة الناجحة للكتلة. تتم إزالة المعاملات من المجمع.

4. تبدأ العقد التي تتلقى حدث إرسال الكتلة في تطبيق المعاملات التي تمت إضافتها إلى الكتلة.

5. في مرحلة ما، يريد مالك (أو غير مالك) الرمز المميز سحبه من Plasma Cash. للقيام بذلك، يقوم باستدعاء وظيفة "startExit"، ويمرر فيها معلومات حول آخر معاملتين على الرمز المميز، والتي تؤكد أنه مالك الرمز المميز. يتحقق العقد الذكي، باستخدام تجزئة Merkle، من وجود المعاملات في الكتل ويرسل الرمز المميز للسحب، والذي سيحدث في غضون أسبوعين.

6. إذا حدثت عملية سحب الرمز المميز مع وجود انتهاكات (تم إنفاق الرمز المميز بعد بدء إجراء السحب أو كان الرمز المميز بالفعل ملكًا لشخص آخر قبل السحب)، فيمكن لمالك الرمز المميز دحض السحب في غضون أسبوعين.

الاختبار العام: حل خصوصية إيثريوم وقابلية التوسع

يتم تحقيق الخصوصية بطريقتين

1. لا تعرف السلسلة الجذرية شيئًا عن المعاملات التي يتم إنشاؤها وإعادة توجيهها داخل السلسلة الفرعية. تظل المعلومات حول من قام بإيداع وسحب ETH من Plasma Cash علنية.

2. تسمح السلسلة الفرعية بالمعاملات المجهولة باستخدام zk-SNARKs.

كومة التكنولوجيا

  • NodeJS
  • رديس
  • Etherium
  • Soild

تجريب

أثناء تطوير Plasma Cash قمنا باختبار سرعة النظام وحصلنا على النتائج التالية:

  • تتم إضافة ما يصل إلى 35 معاملة في الثانية إلى المجمع؛
  • يمكن تخزين ما يصل إلى 1 معاملة في الكتلة.

تم إجراء الاختبارات على الخوادم الثلاثة التالية:

1. معالج Intel Core i7-6700 رباعي النواة Skylake متضمن. NVMe SSD – 512 جيجابايت، 64 جيجابايت ذاكرة الوصول العشوائي DDR4
تم رفع 3 عقد التحقق من صحة البلازما النقدية.

2. AMD Ryzen 7 1700X ثماني النواة "Summit Ridge" (Zen)، وSATA SSD - 500 جيجابايت، وذاكرة الوصول العشوائي DDR64 سعة 4 جيجابايت
تم رفع عقدة Ropsten testnet ETH.
تم رفع 3 عقد التحقق من صحة البلازما النقدية.

3. معالج Intel Core i9-9900K ثماني النواة متضمن. NVMe SSD – 1 تيرابايت، 64 جيجابايت رام DDR4
تم رفع عقدة إرسال Plasma Cash واحدة.
تم رفع 3 عقد التحقق من صحة البلازما النقدية.
تم إطلاق اختبار لإضافة المعاملات إلى شبكة Plasma Cash.

المجموع: 10 عقد بلازما كاش في شبكة خاصة.

اختبار 1

هناك حد قدره مليون معاملة لكل كتلة. لذلك، يتم تقسيم مليون معاملة إلى كتلتين (نظرًا لأن النظام قادر على المشاركة في المعاملات وإرسالها أثناء إرسالها).


الحالة الأولية: الكتلة الأخيرة رقم 7؛ يتم تخزين مليون معاملة ورمز في قاعدة البيانات.

00:00 — بداية البرنامج النصي لإنشاء المعاملة
01:37 - تم إنشاء مليون معاملة وبدأ الإرسال إلى العقدة
01:46 - حصلت عقدة الإرسال على 240 ألف معاملة من المجموعة وكتلة النماذج رقم 8. نرى أيضًا أنه تمت إضافة 320 ألف معاملة إلى المجموعة خلال 10 ثوانٍ
01:58 - تم التوقيع على الكتلة رقم 8 وإرسالها للتحقق من صحتها
02:03 - تم التحقق من صحة الكتلة رقم 8 واستدعاء وظيفة "إرسال الكتلة" الخاصة بالعقد الذكي باستخدام رقم تجزئة وكتلة Merkle
02:10 - انتهى البرنامج النصي التجريبي من العمل، مما أدى إلى إرسال مليون معاملة في 1 ثانية
02:33 - بدأت العقد في تلقي المعلومات التي تمت إضافة الكتلة رقم 8 إلى السلسلة الجذرية، وبدأت في تنفيذ 240 ألف معاملة
02:40 - تمت إزالة 240 ألف معاملة من المجمع، وهي موجودة بالفعل في الكتلة رقم 8
02:56 - أخذت عقدة الإرسال المعاملات المتبقية البالغة 760 ألفًا من المجمع وبدأت في حساب تجزئة Merkle وكتلة التوقيع رقم 9
03:20 - تحتوي جميع العقد على مليون و1 ألف معاملة ورموز مميزة
03:35 - تم التوقيع على الكتلة رقم 9 وإرسالها للتحقق من صحتها إلى العقد الأخرى
03:41 - حدث خطأ في الشبكة
04:40 - انتهت مهلة انتظار التحقق من صحة الكتلة رقم 9
04:54 - أخذت عقدة الإرسال المعاملات المتبقية البالغة 760 ألفًا من المجمع وبدأت في حساب تجزئة Merkle وكتلة التوقيع رقم 9
05:32 - تم التوقيع على الكتلة رقم 9 وإرسالها للتحقق من صحتها إلى العقد الأخرى
05:53 - تم التحقق من صحة الكتلة رقم 9 وإرسالها إلى السلسلة الجذرية
06:17 - بدأت العقد في تلقي المعلومات التي تمت إضافة الكتلة رقم 9 إلى السلسلة الجذرية وبدأت في تنفيذ 760 ألف معاملة
06:47 — تمت تصفية المجمع من المعاملات الموجودة في الكتلة رقم 9
09:06 - تحتوي جميع العقد على 2 مليون معاملة ورمز مميز

اختبار 2

هناك حد 350 ألف لكل كتلة. ونتيجة لذلك، لدينا 3 كتل.


الحالة الأولية: الكتلة الأخيرة رقم 9؛ يتم تخزين 2 مليون معاملة ورمز مميز في قاعدة البيانات

00:00 — تم بالفعل إطلاق البرنامج النصي لإنشاء المعاملات
00:44 - تم إنشاء مليون معاملة وبدأ الإرسال إلى العقدة
00:56 - حصلت عقدة الإرسال على 320 ألف معاملة من المجموعة وكتلة النماذج رقم 10. نرى أيضًا أنه تمت إضافة 320 ألف معاملة إلى المجموعة خلال 10 ثوانٍ
01:12 - تم التوقيع على الكتلة رقم 10 وإرسالها إلى العقد الأخرى للتحقق من صحتها
01:18 - انتهى البرنامج النصي التجريبي من العمل، مما أدى إلى إرسال مليون معاملة في 1 ثانية
01:20 - تم التحقق من صحة الكتلة رقم 10 وإرسالها إلى السلسلة الجذرية
01:51 - تلقت جميع العقد معلومات من السلسلة الجذرية التي تمت إضافة الكتلة رقم 10 والبدء في تطبيق 320 ألف معاملة
02:01 - تمت تصفية المجمع لـ 320 ألف معاملة تمت إضافتها إلى الكتلة رقم 10
02:15 - حصلت عقدة الإرسال على 350 ألف معاملة من المجموعة وكتلة النماذج رقم 11
02:34 - تم توقيع الكتلة رقم 11 وإرسالها إلى العقد الأخرى للتحقق من صحتها
02:51 — تم التحقق من صحة الكتلة رقم 11 وإرسالها إلى السلسلة الجذرية
02:55 — العقدة الأخيرة التي أكملت المعاملات من الكتلة رقم 10
10:59 — استغرقت المعاملة مع تقديم الكتلة رقم 9 وقتًا طويلاً جدًا في السلسلة الجذرية، لكنها اكتملت وتلقت جميع العقد معلومات عنها وبدأت في تنفيذ 350 ألف معاملة
11:05 - تمت تصفية المجمع لـ 320 ألف معاملة تمت إضافتها إلى الكتلة رقم 11
12:10 - تحتوي جميع العقد على مليون و1 ألف معاملة ورموز مميزة
12:17 - حصلت عقدة الإرسال على 330 ألف معاملة من المجموعة وكتلة النماذج رقم 12
12:32 - تم توقيع الكتلة رقم 12 وإرسالها إلى العقد الأخرى للتحقق من صحتها
12:39 - تم التحقق من صحة الكتلة رقم 12 وإرسالها إلى السلسلة الجذرية
13:44 - تلقت جميع العقد معلومات من السلسلة الجذرية التي تمت إضافة الكتلة رقم 12 والبدء في تطبيق 330 ألف معاملة
14:50 - تحتوي جميع العقد على 2 مليون معاملة ورمز مميز

اختبار 3

في الخادمين الأول والثاني، تم استبدال عقدة التحقق بعقدة الإرسال.


الحالة الأولية: الكتلة الأخيرة رقم 84؛ 0 المعاملات والرموز المحفوظة في قاعدة البيانات

00:00 — تم إطلاق 3 نصوص برمجية تنشئ وترسل مليون معاملة لكل منها
01:38 - تم إنشاء مليون معاملة وبدأ الإرسال لإرسال العقدة رقم 1
01:50 — عقدة الإرسال رقم 3 أخذت 330 ألف معاملة من المجموعة وكتلة النماذج رقم 85 (f21). نرى أيضًا أنه تمت إضافة 350 ألف معاملة إلى المجموعة خلال 10 ثوانٍ
01:53 - تم إنشاء مليون معاملة وبدأ الإرسال لإرسال العقدة رقم 1
01:50 — عقدة الإرسال رقم 3 أخذت 330 ألف معاملة من المجموعة وكتلة النماذج رقم 85 (f21). نرى أيضًا أنه تمت إضافة 350 ألف معاملة إلى المجموعة خلال 10 ثوانٍ
02:01 - عقدة الإرسال رقم 1 أخذت 250 ألف معاملة من المجموعة وكتلة النماذج رقم 85 (65e)
02:06 - تم توقيع الكتلة رقم 85 (f21) وإرسالها إلى العقد الأخرى للتحقق من صحتها
02:08 - انتهى البرنامج النصي التجريبي للخادم رقم 3، الذي أرسل مليون معاملة في 1 ثانية، من العمل
02:14 — تم التحقق من صحة الكتلة رقم 85 (f21) وإرسالها إلى السلسلة الجذرية
02:19 - تم توقيع الكتلة رقم 85 (65e) وإرسالها إلى العقد الأخرى للتحقق من صحتها
02:22 - تم إنشاء مليون معاملة وبدأ الإرسال لإرسال العقدة رقم 1
02:27 - تم التحقق من صحة الكتلة رقم 85 (65e) وإرسالها إلى السلسلة الجذرية
02:29 - عقدة الإرسال رقم 2 أخذت 111855 معاملة من المجموعة وتشكل الكتلة رقم 85 (256).
02:36 - تم التوقيع على الكتلة رقم 85 (256) وإرسالها إلى العقد الأخرى للتحقق من صحتها
02:36 - انتهى البرنامج النصي التجريبي للخادم رقم 1، الذي أرسل مليون معاملة في 1 ثانية، من العمل
02:38 - تم التحقق من صحة الكتلة رقم 85 (256) وإرسالها إلى السلسلة الجذرية
03:08 - انتهى البرنامج النصي للخادم رقم 2 من العمل، والذي أرسل مليون معاملة في 1 ثانية
03:38 - تلقت جميع العقد معلومات من السلسلة الجذرية التي تمت إضافة الكتل #85 (f21)، #86(65e)، #87(256) وبدأت في تطبيق 330 ألفًا، و250 ألفًا، و111855 معاملة
03:49 - تمت تصفية المجمع عند 330 ألف، 250 ألف، 111855 معاملة تمت إضافتها إلى الكتل رقم 85 (f21)، #86(65e)، #87(256)
03:59 - عقدة الإرسال رقم 1 أخذت 888145 معاملة من المجمع وكتلة النماذج رقم 88 (214)، عقدة الإرسال رقم 2 أخذت 750 ألف معاملة من المجمع وكتلة النماذج رقم 88 (50a)، عقدة الإرسال رقم 3 أخذت 670 ألف معاملة من المجموعة والنماذج رقم 88 (d3b)
04:44 - تم التوقيع على الكتلة رقم 88 (d3b) وإرسالها إلى العقد الأخرى للتحقق من صحتها
04:58 - تم التوقيع على الكتلة رقم 88 (214) وإرسالها إلى العقد الأخرى للتحقق من صحتها
05:11 - تم التوقيع على الكتلة رقم 88 (50a) وإرسالها إلى العقد الأخرى للتحقق من صحتها
05:11 — تم التحقق من صحة الكتلة رقم 85 (d3b) وإرسالها إلى السلسلة الجذرية
05:36 - تم التحقق من صحة الكتلة رقم 85 (214) وإرسالها إلى السلسلة الجذرية
05:43 - تمت إضافة جميع العقد التي تلقت معلومات من السلسلة الجذرية التي تمنع #88 (d3b) و#89(214) وبدأت في تطبيق 670 ألفًا و750 ألف معاملة
06:50 — بسبب فشل الاتصال، لم يتم التحقق من صحة الكتلة رقم 85 (50أ).
06:55 - عقدة الإرسال رقم 2 أخذت 888145 معاملة من المجموعة وكتلة النماذج رقم 90 (50 أ)
08:14 - تم التوقيع على الكتلة رقم 90 (50a) وإرسالها إلى العقد الأخرى للتحقق من صحتها
09:04 - تم التحقق من صحة الكتلة رقم 90 (50a) وإرسالها إلى السلسلة الجذرية
11:23 - تلقت جميع العقد معلومات من السلسلة الجذرية التي تمت إضافة الكتلة رقم 90 (50a)، والبدء في تطبيق 888145 معاملة. في الوقت نفسه، قام الخادم رقم 3 بالفعل بتطبيق المعاملات من الكتل رقم 88 (d3b)، #89(214)
12:11 - جميع حمامات السباحة فارغة
13:41 — تحتوي جميع عقد الخادم رقم 3 على 3 ملايين معاملة ورمز مميز
14:35 — تحتوي جميع عقد الخادم رقم 1 على 3 ملايين معاملة ورمز مميز
19:24 — تحتوي جميع عقد الخادم رقم 2 على 3 ملايين معاملة ورمز مميز

عوائق

أثناء تطوير Plasma Cash، واجهنا المشكلات التالية، والتي قمنا بحلها تدريجيًا ونعمل على حلها:

1. الصراع في تفاعل وظائف النظام المختلفة. على سبيل المثال، أدت وظيفة إضافة المعاملات إلى المجمع إلى منع عمل إرسال الكتل والتحقق من صحتها، والعكس صحيح، مما أدى إلى انخفاض السرعة.

2. ولم يكن من الواضح على الفور كيفية إرسال عدد كبير من المعاملات مع تقليل تكاليف نقل البيانات.

3. ولم يكن من الواضح كيف وأين يتم تخزين البيانات من أجل تحقيق نتائج عالية.

4. لم يكن من الواضح كيفية تنظيم الشبكة بين العقد، حيث أن حجم الكتلة التي تحتوي على مليون معاملة يستغرق حوالي 1 ميجابايت.

5. يؤدي العمل في الوضع أحادي الترابط إلى قطع الاتصال بين العقد عند إجراء عمليات حسابية طويلة (على سبيل المثال، إنشاء شجرة Merkle وحساب التجزئة الخاصة بها).

كيف تعاملنا مع كل هذا؟

كان الإصدار الأول من عقدة Plasma Cash بمثابة نوع من الجمع الذي يمكنه القيام بكل شيء في نفس الوقت: قبول المعاملات، وإرسال الكتل والتحقق من صحتها، وتوفير واجهة برمجة التطبيقات للوصول إلى البيانات. نظرًا لأن NodeJS هو في الأصل خيط واحد، فقد منعت وظيفة حساب شجرة Merkle الثقيلة وظيفة معاملة الإضافة. لقد رأينا خيارين لحل هذه المشكلة:

1. قم بتشغيل العديد من عمليات NodeJS، تؤدي كل منها وظائف محددة.

2. استخدمworker_threads وانقل تنفيذ جزء من التعليمات البرمجية إلى سلاسل الرسائل.

ونتيجة لذلك، استخدمنا كلا الخيارين في نفس الوقت: قمنا بتقسيم عقدة واحدة منطقيًا إلى 3 أجزاء يمكن أن تعمل بشكل منفصل، ولكن في نفس الوقت بشكل متزامن

1. عقدة التقديم، التي تقبل المعاملات في المجمع وتقوم بإنشاء الكتل.

2. عقدة التحقق التي تتحقق من صحة العقد.

3. عقدة API - توفر واجهة برمجة التطبيقات للوصول إلى البيانات.

في هذه الحالة، يمكنك الاتصال بكل عقدة عبر مقبس يونكس باستخدام cli.

قمنا بنقل العمليات الثقيلة، مثل حساب شجرة Merkle، إلى موضوع منفصل.

وبذلك نكون قد حققنا التشغيل الطبيعي لجميع وظائف Plasma Cash في وقت واحد ودون أعطال.

بمجرد تشغيل النظام، بدأنا في اختبار السرعة، ولسوء الحظ، حصلنا على نتائج غير مرضية: 5 معاملة في الثانية وما يصل إلى 000 معاملة لكل كتلة. كان علي أن أعرف ما الذي تم تنفيذه بشكل غير صحيح.

في البداية بدأنا باختبار آلية الاتصال مع Plasma Cash لمعرفة القدرة القصوى للنظام. لقد كتبنا سابقًا أن عقدة Plasma Cash توفر واجهة مقبس Unix. في البداية كان يعتمد على النص. تم إرسال كائنات json باستخدام `JSON.parse()` و`JSON.stringify()`.

```json
{
  "action": "sendTransaction",
  "payload":{
    "prevHash": "0x8a88cc4217745fd0b4eb161f6923235da10593be66b841d47da86b9cd95d93e0",
    "prevBlock": 41,
    "tokenId": "57570139642005649136210751546585740989890521125187435281313126554130572876445",
    "newOwner": "0x200eabe5b26e547446ae5821622892291632d4f4",
    "type": "pay",
    "data": "",
    "signature": "0xd1107d0c6df15e01e168e631a386363c72206cb75b233f8f3cf883134854967e1cd9b3306cc5c0ce58f0a7397ae9b2487501b56695fe3a3c90ec0f61c7ea4a721c"
  }
}
```

قمنا بقياس سرعة نقل هذه الكائنات ووجدنا ~ 130 كيلو في الثانية. لقد حاولنا استبدال الوظائف القياسية للعمل مع json، لكن الأداء لم يتحسن. يجب تحسين محرك V8 بشكل جيد لهذه العمليات.

لقد عملنا مع المعاملات والرموز والكتل من خلال الفئات. عند إنشاء مثل هذه الفئات، انخفض الأداء بمقدار 2 مرات، مما يشير إلى أن OOP غير مناسب لنا. اضطررت إلى إعادة كتابة كل شيء لنهج وظيفي بحت.

التسجيل في قاعدة البيانات

في البداية، تم اختيار Redis لتخزين البيانات باعتباره أحد الحلول الأكثر إنتاجية التي تلبي متطلباتنا: تخزين القيمة الرئيسية، والعمل مع جداول التجزئة، والمجموعات. أطلقنا redis-benchmark وحصلنا على ما يقرب من 80 ألف عملية في الثانية في وضع خط أنابيب واحد.

للحصول على أداء عالي، قمنا بضبط Redis بشكل أكثر دقة:

  • تم إنشاء اتصال بمأخذ توصيل Unix.
  • لقد قمنا بتعطيل حفظ الحالة على القرص (للحصول على الموثوقية، يمكنك إعداد نسخة متماثلة وحفظها على القرص في Redis منفصل).

في Redis، المجمع عبارة عن جدول تجزئة لأننا نحتاج إلى أن نكون قادرين على استرداد جميع المعاملات في استعلام واحد وحذف المعاملات واحدة تلو الأخرى. لقد حاولنا استخدام قائمة عادية، ولكنها تكون أبطأ عند تفريغ القائمة بأكملها.

عند استخدام NodeJS القياسي، حققت مكتبات Redis أداءً يصل إلى 18 ألف معاملة في الثانية. انخفضت السرعة 9 مرات.

وبما أن المعيار أظهر لنا أن الاحتمالات كانت أكبر بخمس مرات بشكل واضح، فقد بدأنا في التحسين. قمنا بتغيير المكتبة إلى ioredis وحصلنا على أداء 5 كيلو في الثانية. أضفنا المعاملات واحدة تلو الأخرى باستخدام الأمر `hset`. لذلك قمنا بإنشاء الكثير من الاستعلامات في Redis. نشأت الفكرة لدمج المعاملات في دفعات وإرسالها بأمر واحد "hmset". والنتيجة هي 25 كيلو في الثانية.

لعدة أسباب سنشرحها أدناه، فإننا نتعامل مع البيانات باستخدام "Buffer"، وكما اتضح، إذا قمت بتحويلها إلى نص ("buffer.toString('hex')`) قبل الكتابة، فيمكنك الحصول على المزيد أداء. وهكذا زادت السرعة إلى 35 كيلو في الثانية. في هذه اللحظة، قررنا تعليق المزيد من التحسين.

كان علينا التبديل إلى بروتوكول ثنائي للأسباب التالية:

1. غالبًا ما يقوم النظام بحساب التجزئة والتوقيعات وما إلى ذلك، ولهذا يحتاج إلى بيانات في المخزن المؤقت.

2. عند إرسالها بين الخدمات، يكون وزن البيانات الثنائية أقل من وزن النص. على سبيل المثال، عند إرسال كتلة تحتوي على مليون معاملة، يمكن أن تشغل البيانات الموجودة في النص أكثر من 1 ميغابايت.

3. يؤثر تحويل البيانات باستمرار على الأداء.

لذلك، اتخذنا بروتوكولنا الثنائي الخاص لتخزين البيانات ونقلها كأساس، والذي تم تطويره على أساس مكتبة "البيانات الثنائية" الرائعة.

ونتيجة لذلك، حصلنا على هياكل البيانات التالية:

-عملية

  ```json
  {
    prevHash: BD.types.buffer(20),
    prevBlock: BD.types.uint24le,
    tokenId: BD.types.string(null),
    type: BD.types.uint8,
    newOwner: BD.types.buffer(20),
    dataLength: BD.types.uint24le,
    data: BD.types.buffer(({current}) => current.dataLength),
    signature: BD.types.buffer(65),
    hash: BD.types.buffer(32),
    blockNumber: BD.types.uint24le,
    timestamp: BD.types.uint48le,
  }
  ```

- رمز

  ```json
  {
    id: BD.types.string(null),
    owner: BD.types.buffer(20),
    block: BD.types.uint24le,
    amount: BD.types.string(null),
  }
  ```

-حاجز

  ```json
  {
    number: BD.types.uint24le,
    merkleRootHash: BD.types.buffer(32),
    signature: BD.types.buffer(65),
    countTx: BD.types.uint24le,
    transactions: BD.types.array(Transaction.Protocol, ({current}) => current.countTx),
    timestamp: BD.types.uint48le,
  }
  ```

باستخدام الأوامر المعتادة `BD.encode(block, Protocol).slice();` و`BD.decode(buffer, Protocol)` نقوم بتحويل البيانات إلى `Buffer` لحفظها في Redis أو إعادة توجيهها إلى عقدة أخرى واسترداد البيانات استعادة البيانات.

لدينا أيضًا بروتوكولان ثنائيان لنقل البيانات بين الخدمات:

- بروتوكول للتفاعل مع عقدة البلازما عبر مقبس يونكس

  ```json
  {
    type: BD.types.uint8,
    messageId: BD.types.uint24le,
    error: BD.types.uint8,
    length: BD.types.uint24le,
    payload: BD.types.buffer(({node}) => node.length)
  }
  ```

حيث:

  • `type` - الإجراء المطلوب تنفيذه، على سبيل المثال، 1 - sendTransaction، 2 - getTransaction؛
  • "الحمولة". — البيانات التي يجب تمريرها إلى الوظيفة المناسبة؛
  • "معرف الرسالة". - معرف الرسالة حتى يمكن التعرف على الرد.

— بروتوكول التفاعل بين العقد

  ```json
  {
    code: BD.types.uint8,
    versionProtocol: BD.types.uint24le,
    seq: BD.types.uint8,
    countChunk: BD.types.uint24le,
    chunkNumber: BD.types.uint24le,
    length: BD.types.uint24le,
    payload: BD.types.buffer(({node}) => node.length)
  }
  ```

حيث:

  • "كود" — رمز الرسالة، على سبيل المثال 6 — PREPARE_NEW_BLOCK، 7 — BLOCK_VALID، 8 — BLOCK_COMMIT؛
  • "بروتوكول الإصدار". - إصدار البروتوكول، حيث يمكن رفع العقد ذات الإصدارات المختلفة على الشبكة ويمكن أن تعمل بشكل مختلف؛
  • "التتابع". - معرف الرسالة؛
  • "العد قطعة". и "رقم القطعة". ضروري لتقسيم الرسائل الكبيرة.
  • "الطول". и "الحمولة". الطول والبيانات نفسها.

نظرًا لأننا قمنا بكتابة البيانات مسبقًا، فإن النظام النهائي أسرع بكثير من مكتبة `rlp` الخاصة بـ Ethereum. ولسوء الحظ، لم نتمكن بعد من رفضه، لأنه من الضروري الانتهاء من العقد الذكي، وهو ما نخطط للقيام به في المستقبل.

إذا تمكنا من الوصول إلى السرعة 35 000 المعاملات في الثانية، نحتاج أيضًا إلى معالجتها في الوقت الأمثل. نظرًا لأن الوقت التقريبي لتكوين الكتلة يستغرق 30 ثانية، فنحن بحاجة إلى تضمينه في الكتلة 1 000 000 المعاملات، وهو ما يعني إرسال المزيد 100 ميغابايت من البيانات.

في البداية، استخدمنا مكتبة "ethereumjs-devp2p" للتواصل بين العقد، لكنها لم تتمكن من التعامل مع الكثير من البيانات. ونتيجة لذلك، استخدمنا مكتبة `ws` وقمنا بتكوين إرسال البيانات الثنائية عبر websocket. بالطبع، واجهنا أيضًا مشكلات عند إرسال حزم بيانات كبيرة، لكننا قسمناها إلى أجزاء والآن اختفت هذه المشكلات.

وكذلك تكوين شجرة ميركل وحساب التجزئة 1 000 000 المعاملات تتطلب حوالي 10 ثواني من الحساب المستمر. خلال هذا الوقت، يتمكن الاتصال بجميع العقد من الانفصال. تقرر نقل هذا الحساب إلى موضوع منفصل.

الاستنتاجات:

في الواقع، النتائج التي توصلنا إليها ليست جديدة، ولكن لسبب ما ينسىها العديد من الخبراء أثناء التطوير.

  • يؤدي استخدام البرمجة الوظيفية بدلاً من البرمجة الشيئية إلى تحسين الإنتاجية.
  • المونوليث أسوأ من بنية الخدمة لنظام NodeJS الإنتاجي.
  • يؤدي استخدام "worker_threads" للحسابات الثقيلة إلى تحسين استجابة النظام، خاصة عند التعامل مع عمليات الإدخال/الإخراج.
  • يعتبر مقبس Unix أكثر استقرارًا وأسرع من طلبات http.
  • إذا كنت بحاجة إلى نقل البيانات الكبيرة بسرعة عبر الشبكة، فمن الأفضل استخدام مآخذ الويب وإرسال البيانات الثنائية، مقسمة إلى أجزاء، والتي يمكن إعادة توجيهها إذا لم تصل، ثم دمجها في رسالة واحدة.

نحن ندعوك للزيارة GitHub جيثب: مشروع: https://github.com/opporty-com/Plasma-Cash/tree/new-version

تمت مشاركة المقالة بواسطة ألكسندر ناشيفان، مطور خبير شركة الحلول الذكية.

المصدر: www.habr.com

إضافة تعليق