Biến FunC thành FunCtional với Haskell: Serokell đã giành chiến thắng trong Cuộc thi Blockchain Telegram như thế nào

Bạn có thể đã nghe nói rằng Telegram sắp ra mắt nền tảng blockchain Ton. Nhưng có thể bạn đã bỏ lỡ tin tức cách đây không lâu Telegram công bố một cuộc thi để thực hiện một hoặc nhiều hợp đồng thông minh cho nền tảng này.

Nhóm Serokell, với nhiều kinh nghiệm trong việc phát triển các dự án blockchain lớn, không thể đứng ngoài cuộc. Chúng tôi đã cử năm nhân viên tham gia cuộc thi và hai tuần sau, họ đã giành vị trí đầu tiên trong cuộc thi với biệt danh ngẫu nhiên (khiêm tốn) Tắc kè hoa gợi cảm. Trong bài viết này tôi sẽ nói về cách họ đã làm điều đó. Chúng tôi hy vọng rằng trong mười phút tới ít nhất bạn sẽ đọc được một câu chuyện thú vị và nhiều nhất là bạn sẽ tìm thấy điều gì đó hữu ích trong đó mà bạn có thể áp dụng vào công việc của mình.

Nhưng hãy bắt đầu với một bối cảnh nhỏ.

Cạnh tranh và điều kiện của nó

Vì vậy, nhiệm vụ chính của những người tham gia là thực hiện một hoặc nhiều hợp đồng thông minh được đề xuất, cũng như đưa ra các đề xuất để cải thiện hệ sinh thái TON. Cuộc thi diễn ra từ ngày 24/15 đến ngày 15/XNUMX và kết quả chỉ được công bố vào ngày XNUMX/XNUMX. Khá lâu, vì trong thời gian này Telegram đã tổ chức và công bố kết quả các cuộc thi về thiết kế và phát triển ứng dụng trong C++ để thử nghiệm và đánh giá chất lượng cuộc gọi VoIP trong Telegram.

Chúng tôi đã chọn hai hợp đồng thông minh từ danh sách do ban tổ chức đề xuất. Đối với một trong số đó, chúng tôi đã sử dụng các công cụ được phân phối bằng TON và công cụ thứ hai được triển khai bằng ngôn ngữ mới do các kỹ sư của chúng tôi phát triển dành riêng cho TON và được tích hợp vào Haskell.

Việc lựa chọn ngôn ngữ lập trình chức năng không phải là ngẫu nhiên. Trong của chúng tôi blog công ty Chúng tôi thường nói về lý do tại sao chúng tôi cho rằng sự phức tạp của các ngôn ngữ chức năng là một sự cường điệu quá mức và tại sao chúng tôi thường thích chúng hơn các ngôn ngữ hướng đối tượng. Nhân tiện, nó cũng chứa bản gốc của bài viết này.

Tại sao chúng tôi lại quyết định tham gia?

Tóm lại, vì chuyên môn của chúng tôi là các dự án phức tạp và không chuẩn, đòi hỏi những kỹ năng đặc biệt và thường có giá trị khoa học đối với cộng đồng CNTT. Chúng tôi ủng hộ mạnh mẽ sự phát triển nguồn mở và tham gia vào việc phổ biến nó, đồng thời hợp tác với các trường đại học hàng đầu của Nga trong lĩnh vực khoa học máy tính và toán học.

Bản thân các nhiệm vụ thú vị của cuộc thi và việc tham gia vào dự án Telegram yêu quý của chúng tôi đã là một động lực tuyệt vời, nhưng quỹ giải thưởng đã trở thành một động lực bổ sung. 🙂

Nghiên cứu chuỗi khối TON

Chúng tôi theo dõi chặt chẽ những phát triển mới trong blockchain, trí tuệ nhân tạo và học máy và cố gắng không bỏ lỡ một bản phát hành quan trọng nào trong từng lĩnh vực mà chúng tôi làm việc. Vì vậy, khi cuộc thi bắt đầu, đội chúng tôi đã làm quen với các ý tưởng từ giấy trắng TON. Tuy nhiên, trước khi bắt đầu làm việc với TON, chúng tôi đã không phân tích tài liệu kỹ thuật và mã nguồn thực tế của nền tảng, vì vậy bước đầu tiên khá rõ ràng - nghiên cứu kỹ lưỡng tài liệu chính thức về websitekho dự án.

Vào thời điểm cuộc thi bắt đầu, mã đã được xuất bản nên để tiết kiệm thời gian, chúng tôi quyết định tìm kiếm hướng dẫn hoặc tóm tắt được viết bởi người dùng. Thật không may, điều này không mang lại bất kỳ kết quả nào - ngoài hướng dẫn lắp ráp nền tảng trên Ubuntu, chúng tôi không tìm thấy bất kỳ tài liệu nào khác.

Bản thân tài liệu đã được nghiên cứu kỹ lưỡng nhưng rất khó đọc ở một số khu vực. Chúng tôi thường phải quay lại một số điểm nhất định và chuyển từ mô tả cấp cao về các ý tưởng trừu tượng sang chi tiết triển khai ở cấp độ thấp.

Sẽ dễ dàng hơn nếu đặc tả không bao gồm mô tả chi tiết về việc triển khai. Thông tin về cách một máy ảo thể hiện ngăn xếp của nó có nhiều khả năng khiến các nhà phát triển mất tập trung khi tạo hợp đồng thông minh cho nền tảng TON hơn là giúp đỡ họ.

Nix: kết hợp dự án lại với nhau

Tại Serokell, chúng tôi là những người hâm mộ cuồng nhiệt Làm không công. Chúng tôi thu thập các dự án của mình bằng nó và triển khai chúng bằng cách sử dụng NixOpsvà được cài đặt trên tất cả các máy chủ của chúng tôi Hệ điều hành Nix. Nhờ đó, tất cả các bản dựng của chúng tôi đều có thể tái tạo và hoạt động trên mọi hệ điều hành mà Nix có thể cài đặt trên đó.

Vì vậy chúng tôi bắt đầu bằng cách tạo Lớp phủ Nix với biểu thức để lắp ráp TON. Với sự trợ giúp của nó, việc biên dịch TON trở nên đơn giản nhất có thể:

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

Lưu ý rằng bạn không cần cài đặt bất kỳ phụ thuộc nào. Nix sẽ làm mọi thứ cho bạn một cách kỳ diệu, cho dù bạn đang sử dụng NixOS, Ubuntu hay macOS.

Lập trình cho TON

Mã hợp đồng thông minh trong Mạng TON chạy trên Máy ảo TON (TVM). TVM phức tạp hơn hầu hết các máy ảo khác và có chức năng rất thú vị, chẳng hạn như nó có thể hoạt động với phần tiếp theo и liên kết đến dữ liệu.

Hơn nữa, những người đến từ TON đã tạo ra ba ngôn ngữ lập trình mới:

năm là một ngôn ngữ lập trình ngăn xếp phổ quát giống như thứ tư. Siêu năng lực của anh chính là khả năng tương tác với TVM.

FunC là ngôn ngữ lập trình hợp đồng thông minh tương tự như C và được biên dịch sang ngôn ngữ khác - Fift Assembler.

Nhà lắp ráp thứ năm — Thư viện Fift để tạo mã thực thi nhị phân cho TVM. Trình biên dịch thứ năm không có trình biên dịch. Cái này Ngôn ngữ cụ thể của miền nhúng (eDSL).

Sự cạnh tranh của chúng tôi hoạt động

Cuối cùng, đã đến lúc nhìn lại kết quả nỗ lực của chúng ta.

Kênh thanh toán không đồng bộ

Kênh thanh toán là một hợp đồng thông minh cho phép hai người dùng gửi thanh toán bên ngoài blockchain. Kết quả là, bạn không chỉ tiết kiệm được tiền (không có hoa hồng) mà còn cả thời gian (bạn không phải đợi khối tiếp theo được xử lý). Các khoản thanh toán có thể nhỏ như mong muốn và thường xuyên theo yêu cầu. Trong trường hợp này, các bên không cần phải tin tưởng lẫn nhau vì tính công bằng của quyết định cuối cùng được đảm bảo bởi hợp đồng thông minh.

Chúng tôi đã tìm ra một giải pháp khá đơn giản cho vấn đề này. Hai bên có thể trao đổi tin nhắn đã ký, mỗi tin nhắn chứa hai số—toàn bộ số tiền mà mỗi bên thanh toán. Hai số này hoạt động như thế nào đồng hồ vector trong các hệ thống phân tán truyền thống và đặt thứ tự "xảy ra trước" cho các giao dịch. Sử dụng dữ liệu này, hợp đồng sẽ có thể giải quyết mọi xung đột có thể xảy ra.

Trên thực tế, một con số là đủ để thực hiện ý tưởng này, nhưng chúng tôi đã bỏ cả hai vì bằng cách này, chúng tôi có thể tạo ra giao diện người dùng thuận tiện hơn. Ngoài ra, chúng tôi đã quyết định đưa số tiền thanh toán vào mỗi tin nhắn. Nếu không có nó, nếu tin nhắn bị mất vì lý do nào đó, thì mặc dù tất cả số tiền và phép tính cuối cùng sẽ chính xác nhưng người dùng có thể không nhận thấy sự mất mát.

Để kiểm tra ý tưởng của mình, chúng tôi đã tìm kiếm các ví dụ về cách sử dụng giao thức kênh thanh toán đơn giản và ngắn gọn như vậy. Đáng ngạc nhiên là chúng tôi chỉ tìm thấy hai:

  1. Описание một cách tiếp cận tương tự, chỉ dành cho trường hợp kênh một chiều.
  2. Hướng dẫn, mô tả ý tưởng tương tự như ý tưởng của chúng tôi, nhưng không giải thích nhiều chi tiết quan trọng, chẳng hạn như tính đúng đắn chung và quy trình giải quyết xung đột.

Rõ ràng là việc mô tả chi tiết giao thức của chúng tôi là hợp lý, đặc biệt chú ý đến tính chính xác của nó. Sau nhiều lần lặp lại, thông số kỹ thuật đã sẵn sàng và bây giờ bạn cũng có thể làm được. nhìn cô ấy kìa.

Chúng tôi đã triển khai hợp đồng trong FunC và viết tiện ích dòng lệnh để tương tác với hợp đồng của mình hoàn toàn trong Fift, theo khuyến nghị của ban tổ chức. Chúng tôi có thể chọn bất kỳ ngôn ngữ nào khác cho CLI của mình nhưng chúng tôi muốn dùng thử Fit để xem nó hoạt động như thế nào trong thực tế.

Thành thật mà nói, sau khi làm việc với Fift, chúng tôi không thấy bất kỳ lý do thuyết phục nào để thích ngôn ngữ này hơn các ngôn ngữ phổ biến và được sử dụng tích cực với các công cụ và thư viện đã phát triển. Lập trình bằng ngôn ngữ dựa trên ngăn xếp khá khó chịu, vì bạn phải liên tục ghi nhớ những gì có trong ngăn xếp và trình biên dịch không giúp ích gì cho việc này.

Do đó, theo quan điểm của chúng tôi, lý do duy nhất cho sự tồn tại của Fift là vai trò của nó như một ngôn ngữ chủ cho Fift Assembler. Nhưng sẽ tốt hơn nếu nhúng trình biên dịch TVM vào một số ngôn ngữ hiện có, thay vì phát minh ra một ngôn ngữ mới cho mục đích cơ bản duy nhất này?

TVM Haskell eDSL

Bây giờ là lúc nói về hợp đồng thông minh thứ hai của chúng ta. Chúng tôi quyết định phát triển ví đa chữ ký, nhưng việc viết một hợp đồng thông minh khác trong FunC sẽ quá nhàm chán. Chúng tôi muốn thêm một số hương vị và đó là ngôn ngữ hợp ngữ của riêng chúng tôi dành cho TVM.

Giống như Fift Assembler, ngôn ngữ mới của chúng tôi được nhúng nhưng chúng tôi đã chọn Haskell làm máy chủ thay vì Fift, cho phép chúng tôi tận dụng tối đa hệ thống loại nâng cao của nó. Theo chúng tôi, khi làm việc với các hợp đồng thông minh, chi phí cho một lỗi nhỏ cũng có thể rất cao, theo quan điểm của chúng tôi, việc gõ tĩnh là một lợi thế lớn.

Để chứng minh trình biên dịch mã TVM trông như thế nào được nhúng trong Haskell, chúng tôi đã triển khai một ví tiêu chuẩn trên đó. Dưới đây là một số điều cần chú ý:

  • Hợp đồng này bao gồm một chức năng, nhưng bạn có thể sử dụng bao nhiêu tùy thích. Khi bạn xác định một chức năng mới bằng ngôn ngữ máy chủ (tức là Haskell), eDSL của chúng tôi cho phép bạn chọn xem bạn muốn chức năng đó trở thành một quy trình riêng biệt trong TVM hay chỉ đơn giản là nội tuyến tại thời điểm gọi.
  • Giống như Haskell, hàm có các kiểu được kiểm tra tại thời điểm biên dịch. Trong eDSL của chúng tôi, loại đầu vào của hàm là loại ngăn xếp mà hàm mong đợi và loại kết quả là loại ngăn xếp sẽ được tạo sau lệnh gọi.
  • Mã có chú thích stacktype, mô tả loại ngăn xếp dự kiến ​​tại điểm gọi. Trong hợp đồng ví ban đầu, đây chỉ là những nhận xét nhưng trong eDSL của chúng tôi, chúng thực sự là một phần của mã và được kiểm tra tại thời điểm biên dịch. Chúng có thể đóng vai trò là tài liệu hoặc câu lệnh giúp nhà phát triển tìm ra vấn đề nếu mã thay đổi và loại ngăn xếp thay đổi. Tất nhiên, những chú thích như vậy không ảnh hưởng đến hiệu suất thời gian chạy vì không có mã TVM nào được tạo cho chúng.
  • Đây vẫn là một nguyên mẫu được viết trong hai tuần, vì vậy dự án vẫn còn rất nhiều việc phải làm. Ví dụ: tất cả phiên bản của các lớp bạn thấy trong mã bên dưới sẽ được tạo tự động.

Đây là quá trình triển khai ví multisig trên eDSL của chúng tôi:

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

Bạn có thể tìm thấy mã nguồn đầy đủ của hợp đồng eDSL và ví đa chữ ký của chúng tôi tại kho lưu trữ này. Và nhiều hơn nữa đã kể chi tiết về các ngôn ngữ tích hợp, đồng nghiệp của chúng tôi Georgy Agapov.

Kết luận về cuộc thi và TON

Tổng cộng, công việc của chúng tôi mất 380 giờ (bao gồm cả việc làm quen với tài liệu, các cuộc họp và phát triển thực tế). Năm nhà phát triển đã tham gia dự án cạnh tranh: CTO, trưởng nhóm, chuyên gia nền tảng blockchain và nhà phát triển phần mềm Haskell.

Chúng tôi đã tìm thấy các nguồn lực để tham gia cuộc thi mà không gặp khó khăn gì, vì tinh thần của hackathon, tinh thần đồng đội chặt chẽ và nhu cầu nhanh chóng hòa mình vào các khía cạnh của công nghệ mới luôn rất thú vị. Một số đêm mất ngủ để đạt được kết quả tối đa trong điều kiện nguồn lực hạn chế được bù đắp bằng kinh nghiệm vô giá và những kỷ niệm tuyệt vời. Ngoài ra, thực hiện những nhiệm vụ như vậy luôn là một phép thử tốt cho các quy trình của công ty, vì cực kỳ khó đạt được kết quả thực sự tốt nếu không có sự tương tác nội bộ hoạt động tốt.

Bỏ lời bài hát sang một bên: chúng tôi rất ấn tượng với khối lượng công việc mà nhóm TON đã thực hiện. Họ đã cố gắng xây dựng một hệ thống làm việc phức tạp, đẹp mắt và quan trọng nhất. TON đã chứng tỏ mình là một nền tảng có tiềm năng lớn. Tuy nhiên, để hệ sinh thái này phát triển, còn rất nhiều việc phải làm, cả về việc sử dụng nó trong các dự án blockchain và về mặt cải tiến các công cụ phát triển. Bây giờ chúng tôi tự hào là một phần của quá trình này.

Nếu sau khi đọc bài viết này mà bạn vẫn còn thắc mắc hoặc có ý tưởng về cách sử dụng TON để giải quyết vấn đề của mình, Viết thư cho chúng tôi - chúng tôi sẽ sẵn lòng chia sẻ kinh nghiệm của mình.

Nguồn: www.habr.com

Thêm một lời nhận xét