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

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

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

ولكن لنبدأ مع القليل من السياق.

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

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

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

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

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

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

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

أبحاث TON blockchain

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

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

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

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

نيكس: تجميع المشروع معًا

في Serokell نحن معجبين كبار لا شىء. نجمع مشاريعنا معها وننشرها باستخدام NixOps، وتثبيتها على جميع خوادمنا نيكسوس. بفضل هذا، جميع إصداراتنا قابلة للتكرار وتعمل على أي نظام تشغيل يمكن تثبيت 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 على جهاز TON الظاهري (TVM). يعد TVM أكثر تعقيدًا من معظم الأجهزة الافتراضية الأخرى، وله وظائف مثيرة جدًا للاهتمام، على سبيل المثال، يمكنه العمل معها استمرار и روابط للبيانات.

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

فيفت هي لغة برمجة مكدسة عالمية تشبه عليها. قدرته الفائقة هي القدرة على التفاعل مع TVM.

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

المجمع الخامس - المكتبة الخامسة لتوليد كود ثنائي قابل للتنفيذ لـ TVM. لا يحتوي المجمع الخامس على مترجم. هذا لغة المجال المحددة المضمنة (eDSL).

منافستنا تعمل

وأخيرا، حان الوقت للنظر في نتائج جهودنا.

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

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

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

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

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

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

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

قمنا بتنفيذ العقد في FunC، وقمنا بكتابة أداة سطر الأوامر للتفاعل مع عقدنا بالكامل في Fift، كما أوصى المنظمون. كان بإمكاننا اختيار أي لغة أخرى لواجهة سطر الأوامر (CLI)، لكننا كنا مهتمين بتجربة تطبيق Fit لمعرفة مدى أدائه عمليًا.

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

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

TVM هاسكل eDSL

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

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

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

  • يتكون هذا العقد من وظيفة واحدة، ولكن يمكنك استخدام أي عدد تريده. عندما تحدد وظيفة جديدة في اللغة المضيفة (أي هاسكل)، يتيح لك eDSL الخاص بنا اختيار ما إذا كنت تريد أن تصبح روتينًا منفصلاً في TVM أو ببساطة مضمنة عند نقطة الاتصال.
  • مثل هاسكل، تحتوي الوظائف على أنواع يتم فحصها في وقت الترجمة. في 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 ساعة (بما في ذلك التعرف على الوثائق والاجتماعات والتطوير الفعلي). شارك خمسة مطورين في مشروع المسابقة: CTO، قائد الفريق، متخصصو منصة blockchain ومطورو برامج Haskell.

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

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

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

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

إضافة تعليق