Mengubah FunC menjadi FunCtional dengan Haskell: Bagaimana Serokell memenangkan Kompetisi Telegram Blockchain

Anda mungkin pernah mendengar Telegram itu akan meluncurkan platform blockchain Ton. Namun Anda mungkin sudah ketinggalan berita Telegram yang belum lama ini mengumumkan kompetisi untuk implementasi satu atau lebih kontrak pintar untuk platform ini.

Tim Serokell, dengan pengalaman luas dalam mengembangkan proyek-proyek blockchain besar, tidak bisa tinggal diam. Kami mendelegasikan lima karyawan ke kompetisi tersebut, dan dua minggu kemudian mereka menempati posisi pertama dengan julukan acak (dalam) sederhana, Bunglon Seksi. Pada artikel ini saya akan berbicara tentang bagaimana mereka melakukannya. Kami berharap dalam sepuluh menit ke depan Anda setidaknya akan membaca sebuah cerita yang menarik, dan paling banyak Anda akan menemukan sesuatu yang berguna di dalamnya yang dapat Anda terapkan dalam karya Anda.

Tapi mari kita mulai dengan sedikit konteks.

Persaingan dan kondisinya

Jadi, tugas utama para peserta adalah implementasi satu atau lebih kontrak pintar yang diusulkan, serta membuat proposal untuk meningkatkan ekosistem TON. Kompetisi berlangsung dari 24 September hingga 15 Oktober, dan hasilnya baru diumumkan pada 15 November. Cukup lama mengingat selama ini Telegram berhasil mengadakan dan mengumumkan hasil kontes desain dan pengembangan aplikasi C++ untuk pengujian dan penilaian kualitas panggilan VoIP di Telegram.

Kami memilih dua kontrak pintar dari daftar yang diusulkan oleh penyelenggara. Untuk salah satunya, kami menggunakan alat yang didistribusikan dengan TON, dan yang kedua diimplementasikan dalam bahasa baru yang dikembangkan oleh teknisi kami khusus untuk TON dan dibangun di Haskell.

Pemilihan bahasa pemrograman fungsional bukanlah suatu kebetulan. Di kami blog perusahaan Kita sering berbicara tentang mengapa menurut kita kompleksitas bahasa fungsional terlalu dilebih-lebihkan dan mengapa kita umumnya lebih memilih bahasa tersebut daripada bahasa berorientasi objek. Ngomong-ngomong, itu juga berisi asli artikel ini.

Mengapa kami memutuskan untuk berpartisipasi?

Singkatnya, karena spesialisasi kami adalah proyek non-standar dan kompleks yang memerlukan keahlian khusus dan seringkali memiliki nilai ilmiah bagi komunitas TI. Kami sangat mendukung pengembangan sumber terbuka dan terlibat dalam mempopulerkannya, dan juga bekerja sama dengan universitas terkemuka Rusia di bidang ilmu komputer dan matematika.

Tugas menarik dari kompetisi dan keterlibatan dalam proyek Telegram tercinta merupakan motivasi yang sangat baik, namun dana hadiah menjadi insentif tambahan. πŸ™‚

Penelitian blockchain TON

Kami memantau dengan cermat perkembangan baru dalam blockchain, kecerdasan buatan, dan pembelajaran mesin dan berusaha untuk tidak melewatkan satu pun rilis signifikan di setiap area tempat kami bekerja. Oleh karena itu, pada saat kompetisi dimulai, tim kami sudah mengetahui ide-ide darinya kertas putih TON. Namun, sebelum mulai bekerja dengan TON, kami tidak menganalisis dokumentasi teknis dan kode sumber platform yang sebenarnya, jadi langkah pertama cukup jelas - mempelajari dokumentasi resmi secara menyeluruh. Online ΠΈ Π² repositori proyek.

Pada saat kompetisi dimulai, kodenya sudah dipublikasikan, jadi untuk menghemat waktu, kami memutuskan untuk mencari panduan atau ringkasan yang ditulis oleh pengguna. Sayangnya, ini tidak membuahkan hasil apa pun - selain instruksi untuk merakit platform di Ubuntu, kami tidak menemukan materi lain.

Dokumentasinya sendiri telah diteliti dengan baik, namun sulit dibaca di beberapa area. Seringkali kita harus kembali ke poin tertentu dan beralih dari deskripsi ide abstrak tingkat tinggi ke detail implementasi tingkat rendah.

Akan lebih mudah jika spesifikasinya tidak memuat penjelasan rinci tentang pelaksanaannya sama sekali. Informasi tentang bagaimana mesin virtual merepresentasikan tumpukannya lebih cenderung mengalihkan perhatian pengembang yang membuat kontrak pintar untuk platform TON daripada membantu mereka.

Nix: menyusun proyek

Di Serokell kami adalah penggemar beratnya Nol. Kami mengumpulkan proyek-proyek kami dengannya dan menyebarkannya menggunakan NixOps, dan diinstal di semua server kami Nix OS. Berkat ini, semua build kami dapat direproduksi dan berfungsi pada sistem operasi apa pun tempat Nix dapat diinstal.

Jadi kami mulai dengan menciptakan Nix overlay dengan ekspresi untuk perakitan TON. Dengan bantuannya, mengkompilasi TON sesederhana mungkin:

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

Perhatikan bahwa Anda tidak perlu menginstal dependensi apa pun. Nix secara ajaib akan melakukan segalanya untuk Anda, baik Anda menggunakan NixOS, Ubuntu, atau macOS.

Pemrograman untuk TON

Kode kontrak pintar di Jaringan TON berjalan di TON Virtual Machine (TVM). TVM lebih kompleks daripada kebanyakan mesin virtual lainnya, dan memiliki fungsionalitas yang sangat menarik, misalnya, dapat digunakan lanjutan ΠΈ tautan ke data.

Selain itu, orang-orang dari TON menciptakan tiga bahasa pemrograman baru:

Kelima adalah bahasa pemrograman tumpukan universal yang menyerupai Keempat. Kemampuan supernya adalah kemampuan berinteraksi dengan TVM.

KesenanganC adalah bahasa pemrograman kontrak pintar yang mirip dengan C dan dikompilasi ke dalam bahasa lain - Fift Assembler.

Perakit Kelima β€” Pustaka Fift untuk menghasilkan kode biner yang dapat dieksekusi untuk TVM. Fifth Assembler tidak memiliki kompiler. Ini Bahasa Khusus Domain Tertanam (eDSL).

Kompetisi kami berhasil

Akhirnya, inilah waktunya untuk melihat hasil usaha kita.

Saluran pembayaran asinkron

Saluran pembayaran adalah kontrak pintar yang memungkinkan dua pengguna mengirim pembayaran di luar blockchain. Hasilnya, Anda tidak hanya menghemat uang (tidak ada komisi), tetapi juga waktu (Anda tidak perlu menunggu blok berikutnya diproses). Pembayaran bisa sekecil yang diinginkan dan sesering yang diperlukan. Dalam hal ini, para pihak tidak harus saling percaya, karena keadilan penyelesaian akhir dijamin oleh kontrak pintar.

Kami menemukan solusi yang cukup sederhana untuk masalah ini. Dua pihak dapat bertukar pesan yang ditandatangani, masing-masing berisi dua nomorβ€”jumlah penuh yang dibayarkan oleh masing-masing pihak. Kedua angka ini berfungsi seperti jam vektor dalam sistem terdistribusi tradisional dan mengatur urutan transaksi "terjadi sebelumnya". Dengan menggunakan data ini, kontrak akan dapat menyelesaikan segala kemungkinan konflik.

Faktanya, satu nomor sudah cukup untuk mengimplementasikan ide ini, tetapi kami meninggalkan keduanya karena dengan cara ini kami dapat membuat antarmuka pengguna yang lebih nyaman. Selain itu, kami memutuskan untuk menyertakan jumlah pembayaran di setiap pesan. Tanpanya, jika pesan hilang karena alasan tertentu, meskipun semua jumlah dan perhitungan akhir sudah benar, pengguna mungkin tidak menyadari kehilangannya.

Untuk menguji ide kami, kami mencari contoh penggunaan protokol saluran pembayaran yang sederhana dan ringkas. Anehnya, kami hanya menemukan dua:

  1. ОписаниС pendekatan serupa, hanya untuk kasus saluran searah.
  2. tutorial, yang menggambarkan gagasan yang sama dengan gagasan kami, tetapi tanpa menjelaskan banyak rincian penting, seperti kebenaran umum dan prosedur penyelesaian konflik.

Menjadi jelas bahwa masuk akal untuk menjelaskan protokol kami secara rinci, memberikan perhatian khusus pada kebenarannya. Setelah beberapa kali iterasi, spesifikasinya sudah siap, dan sekarang Anda juga bisa. Lihat wanita itu.

Kami menerapkan kontrak di FunC, dan kami menulis utilitas baris perintah untuk berinteraksi dengan kontrak kami sepenuhnya di Fift, seperti yang direkomendasikan oleh penyelenggara. Kami dapat memilih bahasa lain untuk CLI kami, namun kami tertarik mencoba Fit untuk melihat kinerjanya dalam praktik.

Sejujurnya, setelah bekerja dengan Fift, kami tidak melihat alasan kuat untuk memilih bahasa ini daripada bahasa yang populer dan digunakan secara aktif dengan alat dan perpustakaan yang dikembangkan. Pemrograman dalam bahasa berbasis tumpukan cukup tidak menyenangkan, karena Anda harus selalu mengingat apa yang ada di tumpukan, dan kompiler tidak membantu dalam hal ini.

Oleh karena itu, menurut kami, satu-satunya pembenaran keberadaan Fift adalah perannya sebagai bahasa host untuk Fift Assembler. Namun bukankah lebih baik menyematkan assembler TVM ke dalam bahasa yang sudah ada, daripada menciptakan bahasa baru hanya untuk tujuan ini?

TVM Haskell eDSL

Sekarang saatnya membicarakan kontrak pintar kedua kita. Kami memutuskan untuk mengembangkan dompet multi-tanda tangan, tetapi menulis kontrak pintar lainnya di FunC akan terlalu membosankan. Kami ingin menambahkan sedikit rasa, dan itu adalah bahasa assembly kami sendiri untuk TVM.

Seperti Fift Assembler, bahasa baru kami tertanam, tetapi kami memilih Haskell sebagai host, bukan Fift, sehingga memungkinkan kami memanfaatkan sepenuhnya sistem tipe canggihnya. Saat bekerja dengan kontrak pintar, di mana kerugian akibat kesalahan kecil sekalipun bisa sangat tinggi, pengetikan statis, menurut kami, merupakan keuntungan besar.

Untuk mendemonstrasikan tampilan assembler TVM yang tertanam di Haskell, kami menerapkan dompet standar di dalamnya. Berikut beberapa hal yang perlu diperhatikan:

  • Kontrak ini terdiri dari satu fungsi, tetapi Anda dapat menggunakan sebanyak yang Anda suka. Saat Anda mendefinisikan fungsi baru dalam bahasa host (yaitu Haskell), eDSL kami memungkinkan Anda memilih apakah Anda ingin fungsi tersebut menjadi rutinitas terpisah di TVM atau sekadar disisipkan pada titik panggilan.
  • Seperti Haskell, fungsi memiliki tipe yang diperiksa pada waktu kompilasi. Di eDSL kami, tipe input suatu fungsi adalah tipe tumpukan yang diharapkan oleh fungsi tersebut, dan tipe hasil adalah tipe tumpukan yang akan dihasilkan setelah panggilan.
  • Kode ini memiliki anotasi stacktype, menjelaskan jenis tumpukan yang diharapkan pada titik panggilan. Dalam kontrak dompet asli, ini hanyalah komentar, tetapi di eDSL kami, komentar tersebut sebenarnya adalah bagian dari kode dan diperiksa pada waktu kompilasi. Mereka dapat berfungsi sebagai dokumentasi atau pernyataan yang membantu pengembang menemukan masalah jika kode berubah dan jenis tumpukan berubah. Tentu saja, anotasi tersebut tidak memengaruhi kinerja waktu proses, karena tidak ada kode TVM yang dibuat untuk anotasi tersebut.
  • Ini masih berupa prototipe yang ditulis dalam dua minggu, jadi masih banyak pekerjaan yang harus diselesaikan pada proyek tersebut. Misalnya, semua instance kelas yang Anda lihat pada kode di bawah ini harus dibuat secara otomatis.

Seperti inilah penerapan dompet multisig di eDSL kami:

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

Kode sumber lengkap kontrak eDSL dan dompet multi-tanda tangan kami dapat ditemukan di repositori ini. Dan banyak lagi diceritakan secara rinci tentang bahasa bawaan, rekan kami Georgy Agapov.

Kesimpulan tentang kompetisi dan TON

Secara total, pekerjaan kami memakan waktu 380 jam (termasuk sosialisasi dengan dokumentasi, pertemuan, dan pengembangan aktual). Lima pengembang mengambil bagian dalam proyek kompetisi: CTO, pemimpin tim, spesialis platform blockchain, dan pengembang perangkat lunak Haskell.

Kami menemukan sumber daya untuk berpartisipasi dalam kontes tanpa kesulitan, karena semangat hackathon, kerja sama tim yang erat, dan kebutuhan untuk segera membenamkan diri dalam aspek teknologi baru selalu menarik. Beberapa malam tanpa tidur untuk mencapai hasil maksimal dalam kondisi sumber daya yang terbatas diimbangi dengan pengalaman yang sangat berharga dan kenangan yang luar biasa. Selain itu, mengerjakan tugas-tugas seperti itu selalu merupakan ujian yang baik bagi proses perusahaan, karena sangat sulit mencapai hasil yang benar-benar layak tanpa interaksi internal yang berfungsi dengan baik.

Selain liriknya: kami terkesan dengan kerja keras yang dilakukan oleh tim TON. Mereka berhasil membangun sistem kerja yang kompleks, indah, dan yang terpenting. TON telah membuktikan dirinya sebagai platform dengan potensi besar. Namun, agar ekosistem ini dapat berkembang, masih banyak yang perlu dilakukan, baik dalam hal penggunaannya dalam proyek-proyek blockchain maupun dalam hal meningkatkan alat pengembangan. Kami bangga sekarang menjadi bagian dari proses ini.

Jika setelah membaca artikel ini Anda masih memiliki pertanyaan atau ide tentang cara menggunakan TON untuk menyelesaikan masalah Anda, menulis kepada kami β€” kami akan dengan senang hati berbagi pengalaman kami.

Sumber: www.habr.com

Tambah komentar