使用 Haskell 將 FunC 轉變為 FunCtional:Serokell 如何贏得 Telegram 區塊鏈競賽

你可能聽過 Telegram 即將上線Ton區塊鏈平台。 但你可能錯過了不久前 Telegram 的消息 宣布了一項競賽 用於該平台實施一個或多個智能合約。

Serokell團隊在開發大型區塊鏈專案方面擁有豐富的經驗,他們也不能袖手旁觀。 我們派了五名員工參加比賽,兩週後,他們以「性感變色龍」的綽號獲得了第一名。 在這篇文章中,我將討論他們是如何做到的。 我們希望在接下來的十分鐘裡,你至少會讀到一個有趣的故事,最多你會發現一些有用的東西,可以應用在你的工作上。

但讓我們從一些背景開始。

競爭及其條件

因此,參與者的主要任務是實施一項或多項擬議的智慧合約,以及提出改善 TON 生態系統的建議。 比賽從24月15日持續到15月XNUMX日,結果直到XNUMX月XNUMX日才公佈。 相當長的一段時間了,考慮到在此期間 Telegram 成功舉辦並公佈了 C++ 應用程式設計和開發競賽的結果,以測試和評估 Telegram 中 VoIP 通話的品質。

我們從主辦單位提出的名單中選擇了兩個智能合約。 對於其中一個,我們使用了 TON 分發的工具,第二個是用我們的工程師專門為 TON 開發並內建於 Haskell 中的新語言來實現的。

選擇函數式程式語言並非偶然。 在我們的 企業部落格 我們經常談論為什麼我們認為函數式語言的複雜性被誇大了,以及為什麼我們通常更喜歡它們而不是物件導向的語言。 順便說一句,它還包含 本文原創.

為什麼我們決定參加?

簡而言之,因為我們的專業是非標準且複雜的項目,需要特殊技能,並且通常對 IT 社群具有科學價值。 我們大力支持開源開發並致力於其普及,並與俄羅斯領先大學在電腦科學和數學領域合作。

競賽的有趣任務和參與我們心愛的 Telegram 專案本身就是一個很好的動力,但獎金也成為了額外的激勵。 🙂

TON 區塊鏈研究

我們密切關注區塊鏈、人工智慧和機器學習的新發展,並儘量不錯過我們工作的每個領域的任何一個重要版本。 因此,當比賽開始時,我們的團隊已經熟悉了來自 TON 白皮書。 然而,在開始使用 TON 之前,我們並沒有分析該平台的技術文檔和實際源代碼,因此第一步非常明顯 - 徹底研究 TON 的官方文檔 在線項目庫.

比賽開始的時候,程式碼已經發布了,所以為了節省時間,我們決定尋找作者寫的指南或總結 由使用者。 不幸的是,這並沒有給出任何結果——除了在 Ubuntu 上組裝平台的說明之外,我們沒有找到任何其他材料。

文件本身經過了深入研究,但在某些方面很難閱讀。 很多時候,我們必須回到某些點,從抽像想法的高階描述切換到低階實作細節。

如果規範根本不包含實現的詳細描述,那就更容易了。 有關虛擬機器如何表示其堆疊的資訊更有可能分散為 TON 平台創建智慧合約的開發人員的注意力,而不是為他們提供幫助。

Nix:將專案整合在一起

在 Serokell,我們是忠實粉絲 尼克斯。 我們用它收集我們的專案並使用它部署它們 尼克斯行動,並安裝在我們所有的伺服器上 操作系統。 因此,我們所有的建置都是可重現的,並且可以在任何可以安裝 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

請注意,您不需要安裝任何依賴項。 無論您使用的是 NixOS、Ubuntu 還是 macOS,Nix 都會神奇地為您做一切。

TON 編程

TON 網路中的智慧合約程式碼在 TON 虛擬機器(TVM)上運作。 TVM 比大多數其他虛擬機器更複雜,並且具有非常有趣的功能,例如,它可以與 延續 и 數據連結.

此外,TON 的人創建了三種新的程式語言:

菲夫特 是一種通用堆疊程式語言,類似於 第四。 他的超能力是與TVM互動的能力。

樂趣C 是一種智能合約程式語言,類似 C 並被編譯成另一種語言——Fift Assembler。

第五裝配工 — 用於為 TVM 產生二進位可執行程式碼的 Fivet 函式庫。 Fifth Assembler 沒有編譯器。 這 嵌入式領域特定語言 (eDSL).

我們的比賽有效

最後,是時候看看我們努力的成果了。

非同步支付通道

支付通道是一種智慧合約,允許兩個用戶在區塊鏈之外發送付款。 因此,您不僅節省了金錢(沒有佣金),還節省了時間(您不必等待下一個區塊被處理)。 付款金額可以根據需要小額支付,也可以根據需要頻繁支付。 在這種情況下,各方不必相互信任,因為最終結算的公平性是由智能合約保證的。

我們找到了一個相當簡單的解決方案。 兩方可以交換簽名訊息,每個訊息包含兩個數字-各方支付的全額金額。 這兩個數字的工作方式類似於 向量時鐘 在傳統的分散式系統中,並在事務上設定「之前發生」的順序。 使用這些數據,合約將能夠解決任何可能的衝突。

事實上,一個數字就足以實現這個想法,但我們留下了兩個,因為這樣我們可以製作一個更方便的使用者介面。 此外,我們決定在每個訊息中包含付款金額。 如果沒有它,如果訊息由於某種原因丟失,那麼,儘管所有金額和最終計算都是正確的,但用戶可能不會注意到丟失。

為了測試我們的想法,我們尋找使用這種簡單而簡潔的支付通道協定的範例。 令人驚訝的是,我們只找到了兩個:

  1. 描述 類似的方法,僅適用於單向通道的情況。
  2. 教學,它描述了與我們相同的想法,但沒有解釋許多重要的細節,例如一般正確性和衝突解決方案。

很明顯,詳細描述我們的協議是有意義的,特別注意它的正確性。 經過幾次迭代,規範已經準備就緒,現在您也可以了。 看著她.

我們在 FunC 中實現了合約,並按照組織者的建議,完全在 Fift 中編寫了用於與合約互動的命令列實用程式。 我們可以為 CLI 選擇任何其他語言,但我們有興趣嘗試 Fit 來看看它在實踐中的表現如何。

老實說,在與 Fift 合作之後,我們沒有看到任何令人信服的理由來選擇這種語言,而不是帶有開發工具和庫的流行且積極使用的語言。 使用基於堆疊的語言進行程式設計是非常不愉快的,因為您必須不斷記住堆疊上的內容,而編譯器對此無能為力。

因此,我們認為,Fift 存在的唯一理由是它作為 Fift Assembler 的宿主語言的作用。 但是,將 TVM 彙編器嵌入到某種現有語言中,而不是為了這個本質上唯一的目的而發明一種新語言,不是更好嗎?

TVM Haskell eDSL

現在是時候談談我們的第二個智能合約了。 我們決定開發一個多重簽名錢包,但在 FunC 中編寫另一個智能合約太無聊了。 我們想添加一些風味,那就是我們自己的 TVM 組合語言。

與 Fift Assembler 一樣,我們的新語言是嵌入式的,但我們選擇 Haskell 作為主機而不是 Fift,使我們能夠充分利用其先進的類型系統。 在使用智能合約時,即使是一個小錯誤的成本也可能非常高,我們認為靜態類型是一個很大的優勢。

為了演示 TVM 彙編器嵌入 Haskell 的樣子,我們在其上實作了一個標準錢包。 以下是一些需要注意的事項:

  • 合約由一個函數組成,但您可以使用任意多個函數。 當您在宿主語言(即 Haskell)中定義新函數時,我們的 eDSL 允許您選擇是否希望它成為 TVM 中的單獨例程,或只是在呼叫時內聯。
  • 與 Haskell 一樣,函數具有在編譯時檢查的類型。 在我們的 eDSL 中,函數的輸入類型是函數期望的堆疊類型,結果類型是呼叫後將產生的堆疊類型。
  • 程式碼有註釋 stacktype,描述呼叫點處的預期堆疊類型。 在原始錢包合約中,這些只是註釋,但在我們的 eDSL 中,它們實際上是程式碼的一部分,並在編譯時進行檢查。 它們可以作為文件或語句,幫助開發人員在程式碼變更和堆疊類型變更時發現問題。 當然,此類註解不會影響執行時間效能,因為不會為它們產生 TVM 程式碼。
  • 這仍然是兩週內編寫的原型,因此該專案還有很多工作要做。 例如,您在下面的程式碼中看到的類別的所有實例都應該自動產生。

這就是我們的 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。

關於比賽和 TON 的結論

總共,我們的工作花了 380 個小時(包括熟悉文件、會議和實際開發)。 五名開發人員參與了競賽項目:CTO、團隊負責人、區塊鏈平台專家和 Haskell 軟體開發人員。

我們毫不費力地找到了參加比賽的資源,因為黑客馬拉鬆的精神、緊密的團隊合作以及快速沉浸在新技術方面的需要總是令人興奮的。 在資源有限的情況下,為了取得最大成果而度過的幾個不眠之夜,得到了寶貴的經驗和美好的記憶的補償。 此外,執行此類任務始終是對公司流程的良好考驗,因為如果沒有良好的內部互動,就很難取得真正令人滿意的結果。

撇開歌詞不談:TON 團隊投入的大量工作給我們留下了深刻的印象。 他們成功地建構了一個複雜、美觀且最重要的是可行的系統。 TON 已證明自己是一個具有巨大潛力的平台。 然而,為了發展這個生態系統,無論是在區塊鏈專案中的使用或是在改進開發工具方面,還需要做更多的工作。 我們很自豪現在能夠成為這一進程的一部分。

如果您在閱讀本文後仍有任何疑問或對如何使用 TON 解決您的問題有想法, 寫信給我們 — 我們很樂意分享我們的經驗。

來源: www.habr.com

添加評論