تحويل FunC إلى FunCtional مع Haskell: كيف فاز Serokell Telegram Blockchain المنافسة

ربما سمعت أن Telegram ستطلق منصة Ton blockchain . لكن كان من الممكن أن تفوتك الأخبار التي أعلنت Telegram منذ فترة ليست ببعيدة عن منافسة لتنفيذ عقد أو أكثر من العقود الذكية لهذا النظام الأساسي.


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


ولكن لنبدأ مع الغوص قليلاً في السياق.


المنافسة وشروطها


لذلك ، كانت المهام الرئيسية للمشاركين هي تنفيذ واحد أو أكثر من العقود الذكية المقترحة ، وكذلك تقديم مقترحات لتحسين النظام البيئي TON. عقدت المسابقة في الفترة من 24 سبتمبر إلى 15 أكتوبر ، ولم يتم الإعلان عن النتائج إلا في 15 نوفمبر. لفترة طويلة ، بالنظر إلى أنه خلال هذا الوقت تمكنت Telegram من إجراء وإعلان نتائج المسابقات على التصميم وتطوير التطبيقات في C ++ لاختبار وتقييم جودة مكالمات VoIP في Telegram.


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


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


لماذا قررنا المشاركة


باختصار ، لأن تخصصنا هو مشاريع غير قياسية ومعقدة تتطلب مهارات خاصة وغالبًا ما تكون ذات قيمة علمية لمجتمع تكنولوجيا المعلومات. نحن نؤيد بحرارة التطوير مفتوح المصدر ونشارك في الترويج له ، وكذلك نتعاون مع الجامعات الرائدة في روسيا في مجال علوم الكمبيوتر والرياضيات.


كانت المهام المهمة للمسابقة والمشاركة في مشروع Telegram ، التي أحببناها غالياً ، في حد ذاتها دافعًا ممتازًا ، لكن صندوق الجائزة أصبح حافزًا إضافيًا. :)


تون بلوكشين البحوث


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


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


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


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


لا شىء: بناء المشروع


في Serokell ، نحن من عشاق Nix . نقوم بتجميع مشاريعنا لهم ونشرها باستخدام NixOps ، ويتم تثبيت NixOS على جميع خوادمنا. بفضل هذا ، جميع أعمالنا قابلة للتكرار وتعمل تحت أي نظام تشغيل يمكن تثبيت Nix عليه.


لذلك بدأنا بإنشاء تراكب Nix بتعبير لإنشاء TON . استخدامه لتجميع TON بسيط قدر الإمكان:


$ cd ~/.config/nixpkgs/overlays && git clone https://github.com/serokell/ton.nix $ cd /path/to/ton/repo && nix-shell [nix-shell]$ cmakeConfigurePhase && make 

لاحظ أنك لا تحتاج إلى تثبيت أي تبعيات. ستقوم Nix بعمل كل شيء بطريقة سحرية لك ، سواء كنت تستخدم NixOS أو Ubuntu أو macOS.


البرمجة للطن


يتم تنفيذ رمز العقد الذكي TON Network على TON Virtual Machine (TVM). TVM أكثر تعقيدًا من معظم الأجهزة الافتراضية الأخرى ، ولديه وظيفة مثيرة جدًا للاهتمام ، على سبيل المثال ، يمكنه العمل مع الاستمرارات والارتباطات بالبيانات .


علاوة على ذلك ، أنشأ شباب TON ثلاث لغات برمجة جديدة:


Fift هي لغة برمجة مكدس عالمية تذكرنا بـ Forth . قدرته الفائقة هي القدرة على التفاعل مع TVM.


FunC هي لغة برمجة ذكية للعقود تشبه لغة C وتم تجميعها بلغة أخرى - Fift Assembler.


Fift Assembler - مكتبة Fift لتوليد رمز قابل للتنفيذ ثنائي ل TVM. يفتقر المجمع إلى مترجم. إنها لغة مضمنة خاصة بالمجال (eDSL) .


أعمالنا التنافسية


أخيرًا ، حان الوقت لإلقاء نظرة على نتائج جهودنا.


قناة الدفع غير المتزامنة


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


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


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


لاختبار فكرتنا ، بحثنا عن أمثلة لاستخدام بروتوكول قناة دفع بسيط وموجز. من المستغرب ، وجدنا اثنين فقط:


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

أصبح من الواضح أنه من المنطقي وصف بروتوكولنا بالتفصيل ، مع إيلاء اهتمام خاص لصحته. بعد عدة تكرارات ، أصبحت المواصفات جاهزة ، والآن يمكنك أيضًا الاطلاع عليها .


قمنا بتنفيذ عقد لصالح FunC ، وكتبنا أداة سطر الأوامر للتفاعل مع عقدنا في Fift ، كما أوصى المنظمون. يمكننا اختيار أي لغة أخرى لـ CLI الخاصة بنا ، ولكن كان من الممتع بالنسبة لنا تجربة Fift لنرى كيف تظهر نفسها في العمل.


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


لذلك ، المبرر الوحيد لوجود Fift هو دورها كلغة مضيفة لـ Fift Assembler. لكن ألا يكون من الأفضل تضمين أداة تجميع TVM في بعض اللغات الحالية ، وعدم طرح لغة جديدة لهذا الغرض ، وبالأخص الغرض الوحيد؟


TVM هاسكل eDSL


حان الوقت الآن للحديث عن عقدنا الذكي الثاني. قررنا تطوير محفظة متعددة التوقيعات ، ولكن كتابة عقد ذكي آخر على FunC سيكون مملًا جدًا. أردنا إضافة بعض الحماس ، وأصبحت لغة التجميع الخاصة بنا لـ TVM.


مثل Fift Assembler ، لغتنا الجديدة قابلة للتضمين ، ولكن بدلاً من Fift ، اخترنا Haskell كمضيف ، مما سمح لنا باستخدام نظام الكتابة المتقدم بالكامل. عند العمل بعقود ذكية ، حيث يمكن أن يكون سعر الخطأ البسيط مرتفعًا للغاية ، تعتبر الكتابة الثابتة ، في رأينا ، ميزة كبيرة.


لإظهار الشكل الذي يبدو عليه مجمّع TVM في Haskell ، قمنا بتطبيق محفظة قياسية عليه. فيما يلي بعض الأشياء التي يجب الانتباه إليها:


  • يتكون هذا العقد من وظيفة واحدة ، ولكن يمكنك استخدام ما تريد. عندما تحدد وظيفة جديدة باللغة المضيفة (أي ، في Haskell) ، تتيح لك eDSL الخاصة بنا اختيار ما إذا كنت تريد أن تتحول إلى برنامج فرعي منفصل في TVM أو فقط يتم بناؤها في مكان المكالمة.
  • مثل Haskell ، الدالات لها أنواع يتم فحصها في وقت الترجمة. في eDSL ، نوع إدخال الوظيفة هو نوع المكدس الذي تتوقعه الوظيفة ، ونوع النتيجة هو نوع المكدس الذي سيتم الحصول عليه بعد المكالمة.
  • تحتوي التعليمة البرمجية على التعليقات التوضيحية stacktype التي تصف النوع المتوقع من المكدس في نظير الطلب. في عقد المحفظة الأصلي ، كانت هذه مجرد تعليقات ، ولكن في eDSL لدينا هي في الواقع جزء من الشفرة ويتم فحصها في وقت الترجمة. يمكن أن تكون بمثابة وثائق أو عبارات تساعد المطور في العثور على المشكلة إذا تغير نوع المكدس مع تغير الرمز. بالطبع ، لا تؤثر هذه التعليقات التوضيحية على أداء وقت التشغيل ، حيث لا يتم إنشاء رمز TVM لهم.
  • لا يزال هذا نموذجًا أوليًا كتب في غضون أسبوعين ، ولا يزال هناك الكثير من العمل الذي يتعين القيام به في المشروع. على سبيل المثال ، يجب إنشاء جميع مثيلات الفصل التي تراها في الرمز أدناه تلقائيًا.

إليك ما يبدو عليه تطبيق محفظة multisig على eDSL:


 main :: IO () main = putText $ pretty $ declProgram procedures methods where procedures = [ ("recv_external", decl recvExternal) , ("recv_internal", decl recvInternal) ] methods = [ ("seqno", declMethod getSeqno) ] data Storage = Storage { sCnt :: Word32 , sPubKey :: PublicKey } instance DecodeSlice Storage where type DecodeSliceFields Storage = [PublicKey, Word32] decodeFromSliceImpl = do decodeFromSliceImpl @Word32 decodeFromSliceImpl @PublicKey instance EncodeBuilder Storage where encodeToBuilder = do encodeToBuilder @Word32 encodeToBuilder @PublicKey data WalletError = SeqNoMismatch | SignatureMismatch deriving (Eq, Ord, Show, Generic) instance Exception WalletError instance Enum WalletError where toEnum 33 = SeqNoMismatch toEnum 34 = SignatureMismatch toEnum _ = error "Uknown MultiSigError id" fromEnum SeqNoMismatch = 33 fromEnum SignatureMismatch = 34 recvInternal :: '[Slice] :-> '[] recvInternal = drop recvExternal :: '[Slice] :-> '[] recvExternal = do decodeFromSlice @Signature dup preloadFromSlice @Word32 stacktype @[Word32, Slice, Signature] -- cnt cs sign pushRoot decodeFromCell @Storage stacktype @[PublicKey, Word32, Word32, Slice, Signature] -- pk cnt' cnt cs sign xcpu @1 @2 stacktype @[Word32, Word32, PublicKey, Word32, Slice, Signature] -- cnt cnt' pk cnt cs sign equalInt >> throwIfNot SeqNoMismatch push @2 sliceHash stacktype @[Hash Slice, PublicKey, Word32, Slice, Signature] -- hash pk cnt cs sign xc2pu @0 @4 @4 stacktype @[PublicKey, Signature, Hash Slice, Word32, Slice, PublicKey] -- pubk sign hash cnt cs pubk chkSignU stacktype @[Bool, Word32, Slice, PublicKey] -- ? cnt cs pubk throwIfNot SignatureMismatch accept swap decodeFromSlice @Word32 nip dup srefs @Word8 pushInt 0 if IsEq then ignore else do decodeFromSlice @Word8 decodeFromSlice @(Cell MessageObject) stacktype @[Slice, Cell MessageObject, Word8, Word32, PublicKey] xchg @2 sendRawMsg stacktype @[Slice, Word32, PublicKey] endS inc encodeToCell @Storage popRoot getSeqno :: '[] :-> '[Word32] getSeqno = do pushRoot cToS preloadFromSlice @Word32 

يمكن العثور على الكود المصدري الكامل الخاص بـ eDSL وعقد المحفظة المتعددة التوقيعات في هذا المستودع. وبمزيد من التفصيل تحدث زميلنا جورج أجابوف عن اللغات المدمجة.


استنتاجات حول المنافسة و TON


في المجموع ، استغرق عملنا 380 ساعة (جنبا إلى جنب مع الوثائق واللقاءات والتنمية نفسها). شارك خمسة مطورين في المسابقة: STO ، قائد الفريق ، متخصصون في منصات blockchain ، ومطورو برامج Haskell.


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


بصرف النظر عن كلمات الأغاني: أعجبنا بحجم العمل الذي أنجزه فريق TON. تمكنوا من بناء نظام عمل معقد ، جميل ، والأهم من ذلك. أثبت TON أن يكون منصة مع إمكانات كبيرة. ومع ذلك ، من أجل تطوير هذا النظام الإيكولوجي ، هناك الكثير مما يجب عمله ، سواء من حيث استخدامه في مشاريع blockchain أو من حيث تحسين أدوات التطوير. نحن فخورون بأن نكون جزءًا من هذه العملية الآن.


إذا كنت لا تزال لديك أي أسئلة أو أفكار بعد قراءة هذه المقالة حول كيفية تطبيق TON لحل مشاكلك ، فاكتب لنا - سنشارك بكل سرور تجربتنا.

Source: https://habr.com/ru/post/ar476756/


All Articles