Μετατρέποντας το FunC σε FunCtional με τη Haskell: Πώς ο Serokell κέρδισε τον Διαγωνισμό Blockchain Telegram

Πιθανότατα έχετε ακούσει αυτό το Telegram πρόκειται να κυκλοφορήσει την πλατφόρμα blockchain Ton. Αλλά μπορεί να χάσατε τα νέα ότι πριν από λίγο καιρό το Telegram προκήρυξε διαγωνισμό για την υλοποίηση ενός ή περισσότερων έξυπνων συμβολαίων για αυτήν την πλατφόρμα.

Η ομάδα Serokell, με μεγάλη εμπειρία στην ανάπτυξη μεγάλων έργων blockchain, δεν μπορούσε να σταθεί στην άκρη. Αναθέσαμε πέντε υπαλλήλους στον διαγωνισμό και δύο εβδομάδες αργότερα κατέλαβαν την πρώτη θέση σε αυτόν με το (μη) μέτριο τυχαίο ψευδώνυμο Sexy Chameleon. Σε αυτό το άρθρο θα μιλήσω για το πώς το έκαναν. Ελπίζουμε στα επόμενα δέκα λεπτά να διαβάσετε τουλάχιστον μια ενδιαφέρουσα ιστορία και το πολύ να βρείτε κάτι χρήσιμο σε αυτήν που μπορείτε να εφαρμόσετε στη δουλειά σας.

Ας ξεκινήσουμε όμως με ένα μικρό πλαίσιο.

Ο ανταγωνισμός και οι προϋποθέσεις του

Έτσι, τα κύρια καθήκοντα των συμμετεχόντων ήταν η υλοποίηση ενός ή περισσοτέρων από τα προτεινόμενα έξυπνα συμβόλαια, καθώς και η υποβολή προτάσεων για τη βελτίωση του οικοσυστήματος TON. Ο διαγωνισμός διήρκεσε από τις 24 Σεπτεμβρίου έως τις 15 Οκτωβρίου και τα αποτελέσματα ανακοινώθηκαν μόλις στις 15 Νοεμβρίου. Αρκετά καιρό, λαμβάνοντας υπόψη ότι σε αυτό το διάστημα η Telegram κατάφερε να πραγματοποιήσει και να ανακοινώσει τα αποτελέσματα διαγωνισμών για το σχεδιασμό και την ανάπτυξη εφαρμογών σε C++ για δοκιμή και αξιολόγηση της ποιότητας των κλήσεων VoIP στο Telegram.

Επιλέξαμε δύο έξυπνα συμβόλαια από τη λίστα που πρότειναν οι διοργανωτές. Για ένα από αυτά, χρησιμοποιήσαμε εργαλεία που διανεμήθηκαν με TON και το δεύτερο εφαρμόστηκε σε μια νέα γλώσσα που αναπτύχθηκε από τους μηχανικούς μας ειδικά για το TON και ενσωματώθηκε στο Haskell.

Η επιλογή μιας λειτουργικής γλώσσας προγραμματισμού δεν είναι τυχαία. Στο δικό μας εταιρικό ιστολόγιο Συχνά μιλάμε για το γιατί πιστεύουμε ότι η πολυπλοκότητα των λειτουργικών γλωσσών είναι τεράστια υπερβολή και γιατί γενικά τις προτιμούμε από τις αντικειμενοστρεφείς. Παρεμπιπτόντως, περιέχει επίσης πρωτότυπο αυτού του άρθρου.

Γιατί αποφασίσαμε να συμμετάσχουμε;

Εν ολίγοις, επειδή η εξειδίκευσή μας είναι μη τυποποιημένα και πολύπλοκα έργα που απαιτούν ειδικές δεξιότητες και συχνά έχουν επιστημονική αξία για την κοινότητα της πληροφορικής. Υποστηρίζουμε σθεναρά την ανάπτυξη ανοιχτού κώδικα και ασχολούμαστε με τη διάδοσή του, και επίσης συνεργαζόμαστε με κορυφαία ρωσικά πανεπιστήμια στον τομέα της επιστήμης των υπολογιστών και των μαθηματικών.

Τα ενδιαφέροντα καθήκοντα του διαγωνισμού και η συμμετοχή στο αγαπημένο μας έργο Telegram ήταν από μόνα τους ένα εξαιρετικό κίνητρο, αλλά το χρηματικό έπαθλο έγινε ένα επιπλέον κίνητρο. 🙂

Έρευνα blockchain TON

Παρακολουθούμε στενά τις νέες εξελίξεις στο blockchain, την τεχνητή νοημοσύνη και τη μηχανική μάθηση και προσπαθούμε να μην χάσουμε ούτε μία σημαντική κυκλοφορία σε κάθε έναν από τους τομείς στους οποίους εργαζόμαστε. Ως εκ τούτου, από τη στιγμή που ξεκίνησε ο διαγωνισμός, η ομάδα μας ήταν ήδη εξοικειωμένη με τις ιδέες από TON λευκό χαρτί. Ωστόσο, πριν ξεκινήσουμε τη δουλειά με την TON, δεν αναλύσαμε την τεχνική τεκμηρίωση και τον πραγματικό πηγαίο κώδικα της πλατφόρμας, επομένως το πρώτο βήμα ήταν αρκετά προφανές - μια διεξοδική μελέτη της επίσημης τεκμηρίωσης για Σε απευθείας σύνδεση και αποθετήρια έργων.

Μέχρι να ξεκινήσει ο διαγωνισμός, ο κώδικας είχε ήδη δημοσιευτεί, οπότε για να εξοικονομήσουμε χρόνο, αποφασίσαμε να αναζητήσουμε έναν οδηγό ή περίληψη γραμμένη από από τους χρήστες. Δυστυχώς, αυτό δεν έδωσε κανένα αποτέλεσμα - εκτός από οδηγίες για τη συναρμολόγηση της πλατφόρμας στο Ubuntu, δεν βρήκαμε άλλα υλικά.

Η ίδια η τεκμηρίωση ήταν καλά ερευνημένη, αλλά ήταν δύσκολο να διαβαστεί σε ορισμένες περιοχές. Αρκετά συχνά έπρεπε να επιστρέψουμε σε ορισμένα σημεία και να μεταβούμε από υψηλού επιπέδου περιγραφές αφηρημένων ιδεών σε λεπτομέρειες υλοποίησης χαμηλού επιπέδου.

Θα ήταν ευκολότερο εάν η προδιαγραφή δεν περιελάμβανε καθόλου λεπτομερή περιγραφή της υλοποίησης. Οι πληροφορίες σχετικά με το πώς μια εικονική μηχανή αντιπροσωπεύει τη στοίβα της είναι πιο πιθανό να αποσπάσουν την προσοχή των προγραμματιστών που δημιουργούν έξυπνα συμβόλαια για την πλατφόρμα TON παρά να τους βοηθήσουν.

Nix: συναρμολόγηση του έργου

Στο Serokell είμαστε μεγάλοι θαυμαστές Νεράιδα. Συλλέγουμε τα έργα μας με αυτό και τα αναπτύσσουμε χρησιμοποιώντας NixOps, και εγκατεστημένο σε όλους τους διακομιστές μας Nix OS. Χάρη σε αυτό, όλες οι εκδόσεις μας είναι αναπαραγώγιμες και λειτουργούν σε οποιοδήποτε λειτουργικό σύστημα στο οποίο μπορεί να εγκατασταθεί το 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 εκτελείται στην εικονική μηχανή TON (TVM). Το TVM είναι πιο περίπλοκο από τις περισσότερες άλλες εικονικές μηχανές και έχει πολύ ενδιαφέρουσα λειτουργικότητα, για παράδειγμα, μπορεί να λειτουργήσει συνέχειες и συνδέσμους προς δεδομένα.

Επιπλέον, τα παιδιά από το TON δημιούργησαν τρεις νέες γλώσσες προγραμματισμού:

Πέντε είναι μια καθολική γλώσσα προγραμματισμού στοίβας που μοιάζει με Εμπρός. Η σούπερ ικανότητά του είναι η ικανότητα αλληλεπίδρασης με το TVM.

FunC είναι μια γλώσσα προγραμματισμού έξυπνης σύμβασης που είναι παρόμοια με C και μεταγλωττίζεται σε άλλη γλώσσα - Fift Assembler.

Πέμπτος Συναρμολογητής — Πέντε βιβλιοθήκη για τη δημιουργία δυαδικού εκτελέσιμου κώδικα για TVM. Το Fifth Assembler δεν έχει μεταγλωττιστή. Αυτό Γλώσσα συγκεκριμένης ενσωματωμένης περιοχής (eDSL).

Ο διαγωνισμός μας λειτουργεί

Επιτέλους, ήρθε η ώρα να δούμε τα αποτελέσματα των προσπαθειών μας.

Ασύγχρονο κανάλι πληρωμής

Το κανάλι πληρωμής είναι ένα έξυπνο συμβόλαιο που επιτρέπει σε δύο χρήστες να στέλνουν πληρωμές εκτός του blockchain. Ως αποτέλεσμα, εξοικονομείτε όχι μόνο χρήματα (δεν υπάρχει προμήθεια), αλλά και χρόνο (δεν χρειάζεται να περιμένετε για την επεξεργασία του επόμενου μπλοκ). Οι πληρωμές μπορεί να είναι τόσο μικρές όσο επιθυμείτε και όσο συχνά απαιτείται. Σε αυτή την περίπτωση, τα μέρη δεν χρειάζεται να εμπιστεύονται το ένα το άλλο, αφού η δικαιοσύνη της τελικής διευθέτησης διασφαλίζεται από το έξυπνο συμβόλαιο.

Βρήκαμε μια αρκετά απλή λύση στο πρόβλημα. Δύο μέρη μπορούν να ανταλλάξουν υπογεγραμμένα μηνύματα, το καθένα από τα οποία περιέχει δύο αριθμούς—το πλήρες ποσό που καταβάλλεται από κάθε μέρος. Αυτοί οι δύο αριθμοί λειτουργούν όπως διανυσματικό ρολόι στα παραδοσιακά κατανεμημένα συστήματα και να ορίσετε την εντολή "συνέβη πριν" στις συναλλαγές. Χρησιμοποιώντας αυτά τα δεδομένα, η σύμβαση θα είναι σε θέση να επιλύσει οποιαδήποτε πιθανή σύγκρουση.

Στην πραγματικότητα, ένας αριθμός αρκεί για να υλοποιηθεί αυτή η ιδέα, αλλά αφήσαμε και τα δύο γιατί έτσι θα μπορούσαμε να φτιάξουμε ένα πιο βολικό περιβάλλον χρήστη. Επιπλέον, αποφασίσαμε να συμπεριλάβουμε το ποσό πληρωμής σε κάθε μήνυμα. Χωρίς αυτό, εάν το μήνυμα χαθεί για κάποιο λόγο, τότε, αν και όλα τα ποσά και ο τελικός υπολογισμός θα είναι σωστά, ο χρήστης μπορεί να μην παρατηρήσει την απώλεια.

Για να δοκιμάσουμε την ιδέα μας, αναζητήσαμε παραδείγματα χρήσης ενός τόσο απλού και συνοπτικού πρωτοκόλλου καναλιού πληρωμής. Παραδόξως, βρήκαμε μόνο δύο:

  1. Περιγραφή παρόμοια προσέγγιση, μόνο για την περίπτωση ενός μονοκατευθυντικού καναλιού.
  2. Φροντιστήριο, που περιγράφει την ίδια ιδέα με τη δική μας, χωρίς όμως να εξηγεί πολλές σημαντικές λεπτομέρειες, όπως τη γενική ορθότητα και τις διαδικασίες επίλυσης συγκρούσεων.

Έγινε σαφές ότι έχει νόημα να περιγράψουμε λεπτομερώς το πρωτόκολλό μας, δίνοντας ιδιαίτερη προσοχή στην ορθότητά του. Μετά από αρκετές επαναλήψεις, η προδιαγραφή ήταν έτοιμη και τώρα μπορείτε και εσείς. κοίτα την.

Υλοποιήσαμε τη σύμβαση στο FunC και γράψαμε το βοηθητικό πρόγραμμα γραμμής εντολών για αλληλεπίδραση με το συμβόλαιό μας εξ ολοκλήρου στο Fift, όπως προτείνεται από τους διοργανωτές. Θα μπορούσαμε να έχουμε επιλέξει οποιαδήποτε άλλη γλώσσα για το CLI μας, αλλά μας ενδιέφερε να δοκιμάσουμε το Fit για να δούμε την απόδοση του στην πράξη.

Για να είμαστε ειλικρινείς, μετά τη συνεργασία με το Fift, δεν είδαμε κανέναν επιτακτικό λόγο για να προτιμήσουμε αυτήν τη γλώσσα από δημοφιλείς και ενεργά χρησιμοποιούμενες γλώσσες με ανεπτυγμένα εργαλεία και βιβλιοθήκες. Ο προγραμματισμός σε μια γλώσσα που βασίζεται σε στοίβα είναι αρκετά δυσάρεστος, αφού πρέπει να κρατάτε συνεχώς στο μυαλό σας ό,τι υπάρχει στη στοίβα και ο μεταγλωττιστής δεν βοηθά σε αυτό.

Επομένως, κατά τη γνώμη μας, η μόνη δικαιολογία για την ύπαρξη του Fift είναι ο ρόλος του ως γλώσσα υποδοχής για τον Fift Assembler. Αλλά δεν θα ήταν καλύτερο να ενσωματώσετε το assembler TVM σε κάποια υπάρχουσα γλώσσα, αντί να εφεύρετε μια νέα για αυτόν τον ουσιαστικά μοναδικό σκοπό;

TVM Haskell eDSL

Τώρα ήρθε η ώρα να μιλήσουμε για το δεύτερο έξυπνο συμβόλαιό μας. Αποφασίσαμε να αναπτύξουμε ένα πορτοφόλι πολλαπλών υπογραφών, αλλά η σύνταξη ενός άλλου έξυπνου συμβολαίου στο FunC θα ήταν πολύ βαρετή. Θέλαμε να προσθέσουμε λίγη γεύση και αυτή ήταν η δική μας γλώσσα συναρμολόγησης για το TVM.

Όπως το Fift Assembler, η νέα μας γλώσσα είναι ενσωματωμένη, αλλά επιλέξαμε το Haskell ως κεντρικό υπολογιστή αντί για το Fift, επιτρέποντάς μας να εκμεταλλευτούμε πλήρως το σύστημα προηγμένου τύπου του. Όταν εργάζεστε με έξυπνα συμβόλαια, όπου το κόστος ακόμη και ενός μικρού σφάλματος μπορεί να είναι πολύ υψηλό, η στατική πληκτρολόγηση, κατά τη γνώμη μας, είναι ένα μεγάλο πλεονέκτημα.

Για να δείξουμε πώς μοιάζει ο συναρμολογητής 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 και πολλαπλών υπογραφών στη διεύθυνση αυτό το αποθετήριο. Κι αλλα είπε λεπτομερώς σχετικά με τις ενσωματωμένες γλώσσες, ο συνάδελφός μας Georgy Agapov.

Συμπεράσματα για τον διαγωνισμό και τον ΤΟΝ

Συνολικά, η εργασία μας διήρκεσε 380 ώρες (συμπεριλαμβανομένης της εξοικείωσης με την τεκμηρίωση, τις συναντήσεις και την πραγματική ανάπτυξη). Πέντε προγραμματιστές συμμετείχαν στο διαγωνισμό: CTO, επικεφαλής ομάδας, ειδικοί πλατφόρμας blockchain και προγραμματιστές λογισμικού Haskell.

Βρήκαμε πόρους για να συμμετάσχουμε στον διαγωνισμό χωρίς δυσκολία, καθώς το πνεύμα ενός hackathon, η στενή ομαδική εργασία και η ανάγκη να βυθιστούμε γρήγορα σε πτυχές των νέων τεχνολογιών είναι πάντα συναρπαστικά. Αρκετές άγρυπνες νύχτες για την επίτευξη των μέγιστων αποτελεσμάτων σε συνθήκες περιορισμένων πόρων αντισταθμίζονται από ανεκτίμητη εμπειρία και εξαιρετικές αναμνήσεις. Επιπλέον, η εργασία σε τέτοιες εργασίες είναι πάντα μια καλή δοκιμή των διαδικασιών της εταιρείας, καθώς είναι εξαιρετικά δύσκολο να επιτευχθούν πραγματικά αξιοπρεπή αποτελέσματα χωρίς εσωτερική αλληλεπίδραση που λειτουργεί σωστά.

Εκτός από τους στίχους: εντυπωσιαστήκαμε από τον όγκο της δουλειάς που έκανε η ομάδα TON. Κατάφεραν να φτιάξουν ένα σύνθετο, όμορφο και το πιο σημαντικό, λειτουργικό σύστημα. Η TON έχει αποδείξει ότι είναι μια πλατφόρμα με μεγάλες δυνατότητες. Ωστόσο, για να αναπτυχθεί αυτό το οικοσύστημα, πρέπει να γίνουν πολύ περισσότερα, τόσο ως προς τη χρήση του σε έργα blockchain όσο και ως προς τη βελτίωση των εργαλείων ανάπτυξης. Είμαστε περήφανοι που τώρα είμαστε μέρος αυτής της διαδικασίας.

Εάν μετά την ανάγνωση αυτού του άρθρου εξακολουθείτε να έχετε ερωτήσεις ή έχετε ιδέες για το πώς να χρησιμοποιήσετε το TON για να λύσετε τα προβλήματά σας, γράψε μας — Θα χαρούμε να μοιραστούμε την εμπειρία μας.

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο