FunC-ni Haskell ilə FunCtional-a çevirmək: Serokell Telegram Blockchain Müsabiqəsində necə qalib gəldi

Yəqin ki, o Telegramı eşitmisiniz Ton blokçeyn platformasını işə salmaq üzrədir. Ancaq çox keçməmiş Telegram xəbərini qaçırmış ola bilərsiniz müsabiqə elan etdi bu platforma üçün bir və ya daha çox ağıllı müqavilənin həyata keçirilməsi üçün.

Böyük blokçeyn layihələrinin hazırlanmasında böyük təcrübəyə malik olan Serokell komandası kənarda dayana bilməzdi. Müsabiqəyə beş işçi həvalə etdik və iki həftə sonra onlar Sexy Chameleon (in) təvazökar təsadüfi ləqəbi ilə birinci yeri tutdular. Bu yazıda bunu necə etdikləri barədə danışacağam. Ümid edirik ki, növbəti on dəqiqə ərzində siz heç olmasa maraqlı bir hekayə oxuyacaqsınız və ən çox orada işinizdə tətbiq edə biləcəyiniz faydalı bir şey tapacaqsınız.

Ancaq bir az kontekstlə başlayaq.

Rəqabət və onun şərtləri

Belə ki, iştirakçıların əsas vəzifələri təklif olunan smart müqavilələrdən birinin və ya bir neçəsinin həyata keçirilməsi, həmçinin TON ekosisteminin təkmilləşdirilməsi üçün təkliflərin verilməsi olub. Müsabiqə sentyabrın 24-dən oktyabrın 15-dək davam edib və nəticələr yalnız noyabrın 15-də açıqlanıb. Uzun müddətdir nəzərə alsaq ki, bu müddət ərzində Telegram Telegram-da VoIP zənglərinin keyfiyyətinin yoxlanılması və qiymətləndirilməsi üçün C++-da tətbiqlərin dizaynı və inkişafı üzrə müsabiqələr keçirib nəticələrini elan edib.

Təşkilatçıların təklif etdiyi siyahıdan iki ağıllı müqavilə seçdik. Onlardan biri üçün biz TON ilə paylanmış alətlərdən istifadə etdik, ikincisi isə mühəndislərimiz tərəfindən xüsusi olaraq TON üçün hazırlanmış və Haskell-də qurulmuş yeni dildə həyata keçirildi.

Funksional proqramlaşdırma dilinin seçilməsi təsadüfi deyil. Bizim korporativ blog Biz tez-tez funksional dillərin mürəkkəbliyinin nə üçün böyük bir şişirtmə olduğunu düşündüyümüzdən və niyə ümumiyyətlə onları obyekt yönümlü dillərdən üstün tutduğumuzdan danışırıq. Yeri gəlmişkən, o da ehtiva edir bu məqalənin əsli.

Niyə biz iştirak etmək qərarına gəldik?

Bir sözlə, ona görə ki, bizim ixtisasımız xüsusi bacarıq tələb edən və İT ictimaiyyəti üçün çox vaxt elmi dəyər kəsb edən qeyri-standart və mürəkkəb layihələrdir. Biz açıq mənbəli inkişafı güclü dəstəkləyirik və onun populyarlaşdırılması ilə məşğul oluruq, həmçinin kompüter elmləri və riyaziyyat sahəsində Rusiyanın aparıcı universitetləri ilə əməkdaşlıq edirik.

Müsabiqənin maraqlı tapşırıqları və sevimli Telegram layihəmizə cəlb olunması özlüyündə əla motivasiya olsa da, mükafat fondu əlavə stimul oldu. 🙂

TON blockchain tədqiqatı

Biz blokçeyn, süni intellekt və maşın öyrənmə sahəsində yeni inkişafları yaxından izləyirik və işlədiyimiz hər bir sahədə əhəmiyyətli buraxılışı qaçırmamağa çalışırıq. Buna görə də, müsabiqə başlayanda komandamız artıq ideyalarla tanış idi TON ağ kağız. Bununla belə, TON ilə işə başlamazdan əvvəl biz texniki sənədləri və platformanın faktiki mənbə kodunu təhlil etmədik, buna görə də ilk addım olduqca aydın oldu - bu barədə rəsmi sənədlərin hərtərəfli öyrənilməsi. Onlinelayihə depoları.

Müsabiqə başlayanda kod artıq dərc olunmuşdu, ona görə də vaxta qənaət etmək üçün biz tərəfindən yazılmış bələdçi və ya xülasə axtarmağa qərar verdik. istifadəçilər tərəfindən. Təəssüf ki, bu, heç bir nəticə vermədi - platformanın Ubuntu-da yığılması ilə bağlı təlimatlardan başqa, biz heç bir başqa material tapmadıq.

Sənədlərin özü yaxşı araşdırılmışdı, lakin bəzi sahələrdə oxumaq çətin idi. Çox vaxt biz müəyyən məqamlara qayıtmalı və mücərrəd ideyaların yüksək səviyyəli təsvirindən aşağı səviyyəli icra detallarına keçməli olurduq.

Spesifikasiyada icranın təfərrüatlı təsviri ümumiyyətlə olmasaydı, daha asan olardı. Virtual maşının öz yığınını necə təmsil etdiyi barədə məlumat, TON platforması üçün ağıllı müqavilələr yaradan tərtibatçıların diqqətini onlara kömək etməkdən daha çox yayındırır.

Nix: layihəni bir araya gətirmək

Serokelldə biz böyük azarkeşiyik Nix. Onunla layihələrimizi toplayırıq və istifadə edərək tətbiq edirik NixOps, və bütün serverlərimizdə quraşdırılmışdır Nix OS. Bunun sayəsində bütün quruluşlarımız təkrarlana bilir və Nix-in quraşdırıla biləcəyi istənilən əməliyyat sistemində işləyir.

Beləliklə, biz yaratmağa başladıq TON montajı üçün ifadə ilə Nix örtüyü. Onun köməyi ilə TON tərtib etmək mümkün qədər sadədir:

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

Nəzərə alın ki, heç bir asılılıq quraşdırmanıza ehtiyac yoxdur. Nix, NixOS, Ubuntu və ya macOS-dan istifadə etməyinizdən asılı olmayaraq, sehrli şəkildə sizin üçün hər şeyi edəcək.

TON üçün proqramlaşdırma

TON Şəbəkəsindəki ağıllı müqavilə kodu TON Virtual Maşınında (TVM) işləyir. TVM digər virtual maşınların əksəriyyətindən daha mürəkkəbdir və çox maraqlı funksionallığa malikdir, məsələn, onunla işləyə bilər davamları и məlumatlara keçidlər.

Üstəlik, TON-dan olan uşaqlar üç yeni proqramlaşdırma dili yaratdılar:

beş bənzəyən universal stek proqramlaşdırma dilidir Dördüncü. Onun super qabiliyyəti TVM ilə qarşılıqlı əlaqə qurmaq bacarığıdır.

FunC oxşar olan ağıllı müqavilə proqramlaşdırma dilidir C və başqa bir dildə - Fift Assemblerdə tərtib edilir.

Beşinci Assembler — TVM üçün ikili icra olunan kod yaratmaq üçün beş kitabxana. Beşinci Assemblerdə kompilyator yoxdur. Bu Daxili Domen Xüsusi Dili (eDSL).

Müsabiqəmiz işləyir

Nəhayət, səylərimizin nəticələrinə baxmaq vaxtıdır.

Asinxron ödəniş kanalı

Ödəniş kanalı iki istifadəçiyə blokçeyn xaricində ödənişlər göndərməyə imkan verən ağıllı müqavilədir. Nəticədə, siz nəinki pula (komissiya yoxdur), həm də vaxta qənaət edirsiniz (növbəti blokun işlənməsini gözləmək lazım deyil). Ödənişlər istədiyiniz qədər kiçik və tələb olunan qədər tez ola bilər. Bu halda, tərəflər bir-birinə etibar etmək məcburiyyətində deyillər, çünki son hesablaşmanın ədalətliliyi ağıllı müqavilə ilə təmin edilir.

Problemin kifayət qədər sadə həllini tapdıq. İki tərəf imzalanmış mesajları mübadilə edə bilər, hər biri iki nömrədən ibarətdir - hər bir tərəfin ödədiyi tam məbləğ. Bu iki rəqəm kimi işləyir vektor saatı ənənəvi paylanmış sistemlərdə və əməliyyatlar üzrə "əvvəl baş vermiş" əmrini təyin edin. Bu məlumatlardan istifadə edərək, müqavilə hər hansı mümkün münaqişəni həll edə biləcək.

Əslində bu ideyanı həyata keçirmək üçün bir rəqəm kifayətdir, lakin biz hər ikisini tərk etdik, çünki bu yolla daha rahat istifadəçi interfeysi yarada bildik. Bundan əlavə, hər bir mesajda ödəniş məbləğini qeyd etmək qərarına gəldik. Onsuz, əgər hansısa səbəbdən mesaj itirilirsə, o zaman bütün məbləğlər və yekun hesablama düzgün olsa da, istifadəçi itkini hiss etməyə bilər.

Fikrimizi yoxlamaq üçün belə sadə və qısa ödəniş kanalı protokolundan istifadə nümunələri axtardıq. Təəccüblüdür ki, biz yalnız ikisini tapdıq:

  1. Təsvir oxşar yanaşma, yalnız bir istiqamətli kanal üçün.
  2. Dərslik, bizimki ilə eyni fikri təsvir edən, lakin ümumi düzgünlük və münaqişələrin həlli prosedurları kimi bir çox vacib detalları izah etmədən.

Aydın oldu ki, protokolumuzun düzgünlüyünə xüsusi diqqət yetirməklə onu ətraflı təsvir etmək məntiqlidir. Bir neçə təkrarlamadan sonra spesifikasiya hazır idi və indi siz də edə bilərsiniz. ona bax.

Müqaviləni FunC-də həyata keçirdik və təşkilatçıların tövsiyə etdiyi kimi, müqaviləmizlə tamamilə Fift-də qarşılıqlı əlaqə yaratmaq üçün komanda xətti yardım proqramını yazdıq. Biz CLI üçün hər hansı başqa dil seçə bilərdik, lakin onun praktikada necə işlədiyini görmək üçün Fit-i sınamaqda maraqlıydıq.

Düzünü desəm, Fift ilə işlədikdən sonra biz bu dili inkişaf etmiş alətlər və kitabxanaları olan populyar və fəal şəkildə istifadə olunan dillərə üstünlük vermək üçün heç bir tutarlı səbəb görmədik. Stack-əsaslı bir dildə proqramlaşdırma olduqca xoşagəlməzdir, çünki stackdə olanları daim başınızda saxlamalısınız və kompilyator buna kömək etmir.

Buna görə də, fikrimizcə, Fift-in mövcudluğunun yeganə əsaslandırması onun Fift Assembler üçün ana dil rolu olmasıdır. Ancaq bu mahiyyətcə yeganə məqsəd üçün yenisini icad etməkdənsə, TVM assemblerini bəzi mövcud dillərə yerləşdirmək daha yaxşı olmazdımı?

TVM Haskell eDSL

İndi ikinci ağıllı müqaviləmiz haqqında danışmaq vaxtıdır. Çox imzalı pul kisəsi hazırlamağa qərar verdik, lakin FunC-də başqa bir ağıllı müqavilə yazmaq çox darıxdırıcı olardı. Biz bir az dad əlavə etmək istədik və bu, TVM üçün öz montaj dilimiz idi.

Fift Assembler kimi, bizim yeni dilimiz quraşdırılıb, lakin biz onun qabaqcıl tip sistemindən tam istifadə etməyə imkan verən Fift əvəzinə ev sahibi kimi Haskell-i seçdik. Kiçik bir səhvin belə qiymətinin çox yüksək ola biləcəyi ağıllı müqavilələrlə işləyərkən, statik yazı, fikrimizcə, böyük üstünlükdür.

Haskell-də quraşdırılmış TVM assemblerinin necə göründüyünü nümayiş etdirmək üçün biz onun üzərində standart pul kisəsi tətbiq etdik. Burada diqqət yetirməli olduğunuz bir neçə şey var:

  • Bu müqavilə bir funksiyadan ibarətdir, lakin siz istədiyiniz qədər istifadə edə bilərsiniz. Siz host dilində (yəni Haskell) yeni funksiya təyin etdikdə, bizim eDSL sizə onun TVM-də ayrıca rutin və ya sadəcə zəng nöqtəsində daxil olmasını istəməyinizi seçmək imkanı verir.
  • Haskell kimi, funksiyaların tərtib zamanı yoxlanılan növləri var. Bizim eDSL-də funksiyanın giriş növü funksiyanın gözlədiyi yığın növüdür, nəticə növü isə zəngdən sonra istehsal olunacaq yığın növüdür.
  • Kodun annotasiyaları var stacktype, zəng nöqtəsində gözlənilən yığın növünü təsvir edir. Orijinal pul kisəsi müqaviləsində bunlar sadəcə şərhlər idi, lakin eDSL-də onlar əslində kodun bir hissəsidir və tərtib zamanı yoxlanılır. Kod dəyişdikdə və yığın növü dəyişdikdə, tərtibatçıya problemi tapmağa kömək edən sənədlər və ya ifadələr kimi xidmət edə bilərlər. Əlbəttə ki, bu cür annotasiyalar icra müddətinə təsir göstərmir, çünki onlar üçün TVM kodu yaradılmır.
  • Bu hələ iki həftə ərzində yazılmış prototipdir, ona görə də layihə üzərində hələ çox iş görülməlidir. Məsələn, aşağıdakı kodda gördüyünüz siniflərin bütün nümunələri avtomatik olaraq yaradılmalıdır.

Multisig pul kisəsinin tətbiqi eDSL-də belə görünür:

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 və çox imzalı pul kisəsi müqaviləmizin tam mənbə kodunu burada tapa bilərsiniz bu depo. Və daha çox ətraflı izah etdi daxili dillər haqqında, həmkarımız Georgi Agapov.

Rəqabət və TON haqqında nəticələr

Ümumilikdə işimiz 380 saat çəkdi (sənədlərlə tanışlıq, görüşlər və faktiki inkişaf daxil olmaqla). Müsabiqə layihəsində beş tərtibatçı iştirak edib: texniki direktor, komanda rəhbəri, blokçeyn platformasının mütəxəssisləri və Haskell proqram tərtibatçıları.

Biz yarışmada çətinlik çəkmədən iştirak etmək üçün resurslar tapdıq, çünki hakaton ruhu, sıx komanda işi və özümüzü yeni texnologiyaların aspektlərinə sürətlə qərq etmək ehtiyacı həmişə həyəcan vericidir. Məhdud resurslar şəraitində maksimum nəticə əldə etmək üçün bir neçə yuxusuz gecələr əvəzolunmaz təcrübə və əla xatirələrlə kompensasiya olunur. Bundan əlavə, bu cür tapşırıqlar üzərində işləmək həmişə şirkətin prosesləri üçün yaxşı bir sınaqdır, çünki yaxşı işləyən daxili qarşılıqlı əlaqə olmadan həqiqətən layiqli nəticələr əldə etmək olduqca çətindir.

Mahnı sözləri bir kənara: TON komandasının gördüyü iş bizi heyran etdi. Mürəkkəb, gözəl və ən əsası işləyən sistem qurmağı bacardılar. TON böyük potensiala malik bir platforma olduğunu sübut etdi. Bununla belə, bu ekosistemin inkişafı üçün həm blokçeyn layihələrində istifadəsi, həm də inkişaf alətlərinin təkmilləşdirilməsi baxımından daha çox iş görülməlidir. Biz indi bu prosesin bir hissəsi olmaqdan qürur duyuruq.

Bu məqaləni oxuduqdan sonra hələ də hər hansı bir sualınız varsa və ya problemlərinizi həll etmək üçün TON-dan necə istifadə etmək barədə fikirləriniz varsa, bizə yazın - təcrübəmizi bölüşməkdən məmnun olarıq.

Mənbə: www.habr.com

Добавить комментарий