通过Haskell将FunC变成FunCional:Serokell如何赢得电报区块链竞赛

您可能已经听说Telegram 将启动Ton区块链平台 。 但是您可能会错过不久前Telegram 宣布要为该平台实施一个或多个智能合约的竞争的消息。


拥有丰富的开发大型区块链项目经验的Serokell团队不会离开。 我们委派了五名员工参加比赛,两周后,他们以(联合国)谦虚的随机昵称Sexy Chameleon参加了比赛。 在本文中,我将讨论它们是如何成功的。 我们希望在接下来的十分钟内,您至少会读到一个有趣的故事,并且最大程度地,您会发现其中有用的东西,可以在您的工作中应用。


但是,让我们从深入了解上下文开始。


竞争及其条件


因此,参与者的主要任务是实施一项或多项提议的智能合约,以及提出改善TON生态系统的提议。 比赛于9月24日至10月15日举行,结果仅在11月15日公布。 考虑到这段时间,Telegram设法进行并宣布了有关在C ++中设计和开发应用程序以测试和评估Telegram中VoIP呼叫质量的竞赛的结果,这是相当长的时间。


我们从组织者提议的清单中选择了两个智能合约。 对于其中一个,我们使用了与TON一起分发的工具,第二个我们使用了由我们的工程师专门为TON开发并内置到Haskell中的新语言来实现。


功能编程语言的选择并非偶然。 在我们的企业博客中,我们经常谈论为什么我们认为功能语言的复杂性过于夸张,以及为什么我们通常更喜欢它们是面向对象的。 顺便说一下,它也包含本文原文


我们为什么决定参加


简而言之,因为我们的专业化是非标准且复杂的项目,需要特殊技能,并且通常对IT社区具有科学价值。 我们热烈支持开放源代码开发,并致力于其普及化,并与俄罗斯领先的大学在计算机科学和数学领域进行合作。


我们非常喜欢的竞赛和参与Telegram项目的有趣任务本身就是一种极好的动力,但是奖金却成为了额外的动力。 :)


TON区块链研究


我们密切关注区块链,人工智能和机器学习方面的新发展,并努力在我们工作的每个领域中都不要错过任何重要的发布。 因此,在比赛开始时,我们的团队已经熟悉TON白皮书中的想法。 但是,在开始使用TON之前,我们没有分析平台的技术文档和实际源代码,因此第一步非常明显-仔细研究网站项目存储库中的官方文档。


在比赛开始之前,该代码已经发布,因此,为了节省时间,我们决定寻找用户编写的指南或压缩文件。 不幸的是,这没有得到结果-除了在Ubuntu上构建平台的说明之外,我们没有找到其他材料。


该文档本身经过了全面开发,但在某些时候很难阅读。 很多时候,我们不得不回到某些方面,从抽象概念的高级描述切换到低层的实现细节。


如果规范中根本没有详细的实现说明,那会更容易。 有关虚拟机如何呈现其堆栈的信息,对开发人员而言,为TON平台创建智能合约的注意力比帮助他们的更多。


尼克斯:建设一个项目


在Serokell,我们是Nix的忠实拥护者。 我们为他们收集项目并使用NixOps部署它们,并且NixOS已安装在我们所有的服务器上。 因此,我们所有的构建都是可复制的,并且可以在可以安装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家伙还创建了三种新的编程语言:


Fift是一种通用的堆栈编程语言,让人联想到Forth 。 他的超能力是与TVM互动的能力。


FunC是一种智能合约编程语言,与C类似,并且已编译为另一种语言-Fift Assembler。


Fift汇编程序 -Fift库,用于为TVM生成二进制可执行代码。 Fift汇编程序缺少编译器。 它是一种嵌入式领域特定语言(eDSL)


我们的竞争作品


最后,是时候看看我们努力的结果了。


异步支付渠道


支付渠道-一种智能合约,允许两个用户在区块链之外发送付款。 结果,不仅节省了钱(没有佣金),而且节省了时间(您不必等到处理下一个程序段)。 付款金额可以任意少,并且可以根据需要多次发生。 同时,双方无需互相信任,因为最终结算的公平性是通过智能合约来保证的。


我们找到了解决该问题的简单方法。 两方可以交换签名的消息,每个消息包含两个数字-每个参与者支付的总金额。 这两个数字的工作方式类似于传统分布式系统中的矢量时钟 ,并设置事务的“先发生”顺序。 使用此数据,合同将能够解决任何可能的冲突。


实际上,要实现这个想法,一个数字就足够了,但是我们都留下了,因为我们能够创建一个更加方便的用户界面。 此外,我们决定在每封邮件中包含付款金额。 没有它,如果消息由于某种原因丢失,那么尽管所有金额和最终计算都是正确的,但用户可能不会注意到丢失。


为了检验我们的想法,我们寻找了使用这种简单明了的支付渠道协议的示例。 令人惊讶的是,我们发现只有两个:


  1. 仅在单向信道的情况下类似方法的描述
  2. 本教程描述了与我们的想法相同的想法,但是没有说明许多重要的细节,例如总体正确性和解决冲突的过程。

很明显,详细描述我们的协议是有意义的,尤其要注意其正确性。 经过几次迭代,该规范已经准备就绪,现在您也可以查看


我们执行了FunC的合同,并按照组织者的建议在Fift中编写了用于与合同交互的命令行实用程序。 我们可以为CLI选择其他任何语言,但是尝试使用Fift看看它如何在操作中表现出来很有趣。


坦率地说,与Fift合作后,我们没有充分的理由选择这种语言,而不是使用具有开发工具和库的流行和积极使用的语言。 使用堆栈语言进行编程非常不愉快,因为您必须时刻牢记堆栈中的内容,而编译器却无济于事。


因此,我们认为,存在Fift的唯一理由是其作为Fift汇编程序的宿主语言的作用。 但是,将TVM汇编器嵌入到现有的某种语言中,而不是为此目的想出一个新的语言,不是更好吗?


TVM Haskell eDSL


现在该讨论我们的第二个智能合约了。 我们决定开发一个多签名钱包,但是在FunC上写另一个智能合约太无聊了。 我们想添加一些热情,它成为我们自己的TVM汇编语言。


像Fift汇编程序一样,我们的新语言是可嵌入的,但是我们选择了Haskell作为主机,而不是Fift,这使我们可以充分使用其高级类型系统。 在使用智能合约的情况下,即使是很小的错误也会付出很高的代价,我们认为静态类型化是一个很大的优势。


为了演示Haskell内置的TVM汇编程序的外观,我们在其上实现了一个标准钱包。 这里有一些注意事项:


  • 该合同包含一个功能,但是您可以随意使用。 当您使用主机语言(即Haskell)定义新功能时,我们的eDSL允许您选择是将其转换为TVM中的单独子程序,还是将其内置于调用位置。
  • 像Haskell一样,函数具有在编译时检查的类型。 在我们的eDSL中,函数输入类型是函数期望的堆栈类型,结果类型是调用后将获得的堆栈类型。
  • 该代码具有stacktype注释, stacktype注释描述了拨号对等方的预期堆栈类型。 在原始的钱包合同中,这些只是注释,但在我们的eDSL中,它们实际上是代码的一部分,并在编译时进行检查。 它们可以用作文档或语句,如果堆栈类型随代码的变化而变化,则可以帮助开发人员发现问题。 当然,此类注释不会影响运行时性能,因为不会为其生成TVM代码。
  • 这仍然是两周内编写的原型,因此在该项目上还有很多工作要做。 例如,您在下面的代码中看到的所有类实例都应自动生成。

这是我们的eDSL上的multisig wallet实现的样子:


 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的完整源代码和多签名钱包合同可以在此存储库中找到详细地说,我们的同事George Agapov 谈到了内置语言。


关于比赛和TON的结论


我们的工作总共花费了380个小时(以及对文档,会议和开发本身的了解)。 五名开发人员参加了比赛:STO,团队负责人,区块链平台专家和Haskell软件开发人员。


由于黑客马拉松的精神,紧密的团队合作,对新技术各方面的快速投入的需求一直令人兴奋,因此我们发现可以轻松地参加比赛的资源。 宝贵的经验和出色的记忆力弥补了在资源有限的情况下为达到最大效果而​​度过的几个不眠之夜。 此外,从事此类任务的工作始终是对公司流程的良好考验,因为如果不进行出色的内部交互,要获得真正体面的结果非常困难。


除了歌词:TON团队所做的大量工作给我们留下了深刻的印象。 他们设法建​​立了一个复杂,美观,最重要的工作系统。 TON被证明是具有巨大潜力的平台。 然而,要发展这个生态系统,就其在区块链项目中的使用以及改进开发工具而言,还需要做更多的工作。 我们很荣幸能成为这一过程的一部分。


如果在阅读本文之后,您仍然对如何使用TON解决问题有任何疑问或想法,请写信给我们 -我们将很高兴分享我们的经验。

Source: https://habr.com/ru/post/zh-CN476756/


All Articles