הפיכת FunC ל- FunCtional עם Haskell: איך זכה סרוקל בתחרות הבלוקצ'יין של טלגרם

בטח שמעתם את הטלגרם הזה עומדת להשיק את פלטפורמת הבלוקצ'יין Ton. אבל אולי פספסת את החדשות שלא מזמן טלגרם הכריזה על תחרות ליישום חוזה חכם אחד או יותר עבור פלטפורמה זו.

צוות Serokel, בעל ניסיון רב בפיתוח פרויקטי בלוקצ'יין גדולים, לא יכול היה לעמוד מנגד. האצלנו חמישה עובדים לתחרות, וכעבור שבועיים הם זכו במקום הראשון בה בכינוי האקראי (הלא) הצנוע (Sexy Chameleon). במאמר זה אדבר על איך הם עשו את זה. אנו מקווים שבעשר הדקות הקרובות לפחות תקראו סיפור מעניין, ולכל היותר תמצאו בו משהו שימושי שתוכלו ליישם בעבודתכם.

אבל נתחיל עם קצת הקשר.

תחרות ותנאיה

אז, המשימות העיקריות של המשתתפים היו יישום של אחד או יותר מהחוזים החכמים המוצעים, כמו גם הצעת הצעות לשיפור המערכת האקולוגית של TON. התחרות התקיימה בין ה-24 בספטמבר ל-15 באוקטובר, והתוצאות פורסמו רק ב-15 בנובמבר. די הרבה זמן, בהתחשב בכך שבזמן זה הצליחה טלגרם לקיים ולהכריז על תוצאות תחרויות על עיצוב ופיתוח אפליקציות ב-C++ לבדיקה והערכת איכות שיחות ה-VoIP בטלגרם.

בחרנו שני חוזים חכמים מהרשימה שהציעו המארגנים. עבור אחד מהם, השתמשנו בכלים המופצים עם TON, והשני יושם בשפה חדשה שפותחה על ידי המהנדסים שלנו במיוחד עבור TON ומובנית בתוך Haskell.

הבחירה בשפת תכנות פונקציונלית אינה מקרית. בשלנו בלוג תאגידי לעתים קרובות אנו מדברים על מדוע אנו חושבים שהמורכבות של שפות פונקציונליות היא הגזמה עצומה ומדוע אנו מעדיפים אותן בדרך כלל על פני שפות מונחה עצמים. אגב, זה גם מכיל המקורי של מאמר זה.

למה בכלל החלטנו להשתתף?

בקיצור, כי ההתמחות שלנו היא פרויקטים לא סטנדרטיים ומורכבים שדורשים כישורים מיוחדים ולרוב הם בעלי ערך מדעי לקהילת ה-IT. אנו תומכים מאוד בפיתוח קוד פתוח ועוסקים בפופולריות שלו, וגם משתפים פעולה עם אוניברסיטאות רוסיות מובילות בתחום מדעי המחשב והמתמטיקה.

המשימות המעניינות של התחרות והמעורבות בפרויקט הטלגרם האהוב שלנו היו כשלעצמן מוטיבציה מצוינת, אבל קרן הפרסים הפכה לתמריץ נוסף. 🙂

מחקר בלוקצ'יין של TON

אנו עוקבים מקרוב אחר התפתחויות חדשות בבלוקצ'יין, בינה מלאכותית ולמידת מכונה ומשתדלים לא לפספס אף מהדורה משמעותית בכל אחד מהתחומים בהם אנו עובדים. לכן, כשהתחרות התחילה, הצוות שלנו כבר הכיר רעיונות מ נייר לבן טון. עם זאת, לפני התחלת העבודה עם TON, לא ניתחנו את התיעוד הטכני ואת קוד המקור בפועל של הפלטפורמה, כך שהשלב הראשון היה די ברור - מחקר יסודי של התיעוד הרשמי על מקוון ו - מאגרי פרויקטים.

עד תחילת התחרות, הקוד כבר פורסם, אז כדי לחסוך זמן, החלטנו לחפש מדריך או תקציר שנכתב על ידי משתמשים. למרבה הצער, זה לא נתן תוצאות - מלבד הנחיות להרכבת הפלטפורמה באובונטו, לא מצאנו חומרים נוספים.

התיעוד עצמו נחקר היטב, אך היה קשה לקריאה באזורים מסוימים. לעתים קרובות היינו צריכים לחזור לנקודות מסוימות ולעבור מתיאורים ברמה גבוהה של רעיונות מופשטים לפרטי יישום ברמה נמוכה.

יהיה קל יותר אם המפרט לא יכלול תיאור מפורט של היישום כלל. מידע על האופן שבו מכונה וירטואלית מייצגת את הערימה שלה סביר יותר להסיח את דעתם של מפתחים שיוצרים חוזים חכמים עבור פלטפורמת TON מאשר לעזור להם.

ניקס: להרכיב את הפרויקט

בסרוקל אנחנו מעריצים גדולים ניקס. אנחנו אוספים את הפרויקטים שלנו עם זה ופורסים אותם באמצעות NixOps, ומותקן בכל השרתים שלנו מערכת ההפעלה Nix. הודות לכך, כל ה-builds שלנו ניתנים לשחזור ועובדים על כל מערכת הפעלה עליה ניתן להתקין את 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, אובונטו או macOS.

תכנות עבור TON

קוד החוזה החכם ברשת TON פועל על ה-TON Virtual Machine (TVM). TVM מורכבת יותר מרוב המכונות הווירטואליות האחרות, ויש לה פונקציונליות מאוד מעניינת, למשל, היא יכולה לעבוד איתה המשך и קישורים לנתונים.

יתר על כן, החבר'ה מ-TON יצרו שלוש שפות תכנות חדשות:

חמש היא שפת תכנות ערימה אוניברסלית הדומה ל רביעית. יכולת העל שלו היא היכולת ליצור אינטראקציה עם TVM.

FunC היא שפת תכנות חוזים חכמה הדומה ל C והוא מורכב לשפה אחרת - Fift Assembler.

אסמבלר חמישי - ספריית Fift להפקת קוד הפעלה בינארי עבור TVM. ל-Fifth Assembler אין מהדר. זֶה שפה ספציפית לתחום משובץ (eDSL).

התחרות שלנו עובדת

לבסוף, הגיע הזמן להסתכל על תוצאות המאמצים שלנו.

ערוץ תשלום אסינכרוני

ערוץ תשלום הוא חוזה חכם המאפשר לשני משתמשים לשלוח תשלומים מחוץ לבלוקצ'יין. כתוצאה מכך, אתה חוסך לא רק כסף (אין עמלה), אלא גם זמן (אינך צריך לחכות לעיבוד הבלוק הבא). התשלומים יכולים להיות קטנים ככל הרצוי ובתדירות הנדרשת. במקרה זה, הצדדים אינם צריכים לסמוך זה על זה, שכן הגינות הפשרה הסופי מובטחת בחוזה החכם.

מצאנו פתרון פשוט למדי לבעיה. שני צדדים יכולים להחליף הודעות חתומות, שכל אחת מהן מכילה שני מספרים - הסכום המלא ששולם על ידי כל צד. שני המספרים האלה עובדים כמו שעון וקטור במערכות מבוזרות מסורתיות ולהגדיר את סדר ה"קרה לפני" בעסקאות. באמצעות נתונים אלה, החוזה יוכל לפתור כל עימות אפשרי.

למעשה, מספר אחד מספיק כדי ליישם את הרעיון הזה, אבל עזבנו את שניהם כי כך נוכל ליצור ממשק משתמש נוח יותר. בנוסף, החלטנו לכלול את סכום התשלום בכל הודעה. בלעדיו, אם ההודעה אבדה מסיבה כלשהי, אזי למרות שכל הסכומים והחישוב הסופי יהיו נכונים, ייתכן שהמשתמש לא יבחין באובדן.

כדי לבדוק את הרעיון שלנו, חיפשנו דוגמאות לשימוש בפרוטוקול ערוץ תשלום כה פשוט ותמציתי. באופן מפתיע, מצאנו רק שניים:

  1. תיאור גישה דומה, רק במקרה של ערוץ חד-כיווני.
  2. הדרכה, המתאר את אותו רעיון כמו שלנו, אך מבלי להסביר פרטים רבים וחשובים, כגון תקינות כללית ונהלים ליישוב סכסוכים.

התברר כי הגיוני לתאר את הפרוטוקול שלנו בפירוט, תוך שימת לב מיוחדת לנכונותו. לאחר מספר איטרציות, המפרט היה מוכן, ועכשיו גם אתה יכול. תסתכל עליה.

הטמענו את החוזה ב-FunC, וכתבנו את כלי השירות של שורת הפקודה לאינטראקציה עם החוזה שלנו לחלוטין ב-Fift, כפי שהומלץ על ידי המארגנים. יכולנו לבחור כל שפה אחרת עבור ה-CLI שלנו, אבל היינו מעוניינים לנסות את Fit כדי לראות איך היא מתפקדת בפועל.

למען האמת, לאחר שעבדנו עם Fift, לא ראינו סיבות משכנעות להעדיף את השפה הזו על פני שפות פופולריות ובשימוש פעיל עם כלים וספריות מפותחות. תכנות בשפה מבוססת מחסנית הוא די לא נעים, מכיוון שאתה צריך לשמור כל הזמן בראש את מה שיש בערימה, והקומפיילר לא עוזר בזה.

לפיכך, לדעתנו, ההצדקה היחידה לקיומה של פיפט היא תפקידה כשפה מארחת ל-Fift Assembler. אבל האם לא עדיף להטמיע את ה-TVM assembler בשפה קיימת כלשהי, במקום להמציא שפה חדשה למטרה הבלעדית הזו?

TVM Haskell eDSL

עכשיו הגיע הזמן לדבר על החוזה החכם השני שלנו. החלטנו לפתח ארנק מרובה חתימות, אבל כתיבת חוזה חכם נוסף ב-FunC תהיה משעממת מדי. רצינו להוסיף קצת טעם, וזו הייתה שפת ההרכבה שלנו עבור TVM.

בדומה ל-Fift Assembler, השפה החדשה שלנו מוטמעת, אך בחרנו ב-Haskell כמארח במקום ב-Fift, מה שמאפשר לנו לנצל את מלוא היתרונות של מערכת הסוג המתקדמת שלה. כשעובדים עם חוזים חכמים, שבהם העלות של אפילו טעות קטנה יכולה להיות גבוהה מאוד, הקלדה סטטית, לדעתנו, היא יתרון גדול.

כדי להדגים איך נראה TVM assembler מוטבע ב- 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 שעות (כולל היכרות עם תיעוד, פגישות ופיתוח בפועל). חמישה מפתחים לקחו חלק בפרויקט התחרות: CTO, ראש צוות, מומחי פלטפורמת בלוקצ'יין ומפתחי תוכנה Haskell.

מצאנו משאבים להשתתף בתחרות ללא קושי, שכן הרוח של האקתון, עבודת צוות צמודה והצורך לשקוע במהירות בהיבטים של טכנולוגיות חדשות תמיד מרגש. מספר לילות ללא שינה כדי להשיג תוצאות מקסימליות בתנאים של משאבים מוגבלים מתוגמלים על ידי ניסיון שלא יסולא בפז וזיכרונות מצוינים. בנוסף, עבודה על משימות כאלה היא תמיד מבחן טוב של תהליכי החברה, שכן קשה מאוד להשיג תוצאות הגונות באמת ללא אינטראקציה פנימית מתפקדת היטב.

מילים בצד: התרשמנו מכמות העבודה שהשקיע צוות TON. הם הצליחו לבנות מערכת מורכבת, יפה, והכי חשוב, עובדת. TON הוכיחה את עצמה כפלטפורמה עם פוטנציאל רב. עם זאת, כדי שהמערכת האקולוגית הזו תתפתח, צריך לעשות הרבה יותר, הן מבחינת השימוש בה בפרויקטים של בלוקצ'יין והן מבחינת שיפור כלי הפיתוח. אנו גאים להיות חלק מהתהליך הזה.

אם לאחר קריאת מאמר זה עדיין יש לך שאלות או יש לך רעיונות כיצד להשתמש ב-TON כדי לפתור את הבעיות שלך, כתוב לנו - נשמח לחלוק את הניסיון שלנו.

מקור: www.habr.com

הוספת תגובה