使用 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 解决您的问题有想法, 写信给我们 — 我们很乐意分享我们的经验。

来源: habr.com

添加评论