你可能听说过 Telegram
Serokell团队在开发大型区块链项目方面拥有丰富的经验,他们也不能袖手旁观。 我们派了五名员工参加比赛,两周后,他们以“性感变色龙”的绰号获得了第一名。 在这篇文章中,我将讨论他们是如何做到的。 我们希望在接下来的十分钟里,你至少会读到一个有趣的故事,最多你会发现一些有用的东西,可以应用到你的工作中。
但让我们从一些背景开始。
竞争及其条件
因此,参与者的主要任务是实施一项或多项拟议的智能合约,以及提出改善 TON 生态系统的建议。 比赛从24月15日持续到15月XNUMX日,结果直到XNUMX月XNUMX日才公布。 相当长的一段时间了,考虑到在此期间 Telegram 成功举办并公布了 C++ 应用程序设计和开发竞赛的结果,以测试和评估 Telegram 中 VoIP 通话的质量。
我们从组织者提出的名单中选择了两个智能合约。 对于其中一个,我们使用了 TON 分发的工具,第二个是用我们的工程师专门为 TON 开发并内置于 Haskell 中的新语言来实现的。
选择函数式编程语言并非偶然。 在我们的
为什么我们决定参加?
简而言之,因为我们的专业是非标准且复杂的项目,需要特殊技能,并且通常对 IT 社区具有科学价值。 我们大力支持开源开发并致力于其普及,并与俄罗斯领先大学在计算机科学和数学领域进行合作。
竞赛的有趣任务和参与我们心爱的 Telegram 项目本身就是一个很好的动力,但奖金也成为了额外的激励。 🙂
TON 区块链研究
我们密切关注区块链、人工智能和机器学习的新发展,并尽量不错过我们工作的每个领域的任何一个重要版本。 因此,当比赛开始时,我们的团队已经熟悉了来自
比赛开始的时候,代码已经发布了,所以为了节省时间,我们决定寻找一下作者写的指南或总结 由用户。 不幸的是,这并没有给出任何结果——除了在 Ubuntu 上组装平台的说明之外,我们没有找到任何其他材料。
文档本身经过了深入研究,但在某些方面很难阅读。 很多时候,我们不得不返回到某些点,从抽象思想的高级描述切换到低级实现细节。
如果规范根本不包含实现的详细描述,那就更容易了。 有关虚拟机如何表示其堆栈的信息更有可能分散为 TON 平台创建智能合约的开发人员的注意力,而不是为他们提供帮助。
Nix:将项目整合在一起
在 Serokell,我们是忠实粉丝
所以我们首先创建
$ 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 的人创建了三种新的编程语言:
菲夫特 是一种通用堆栈编程语言,类似于
乐趣C 是一种智能合约编程语言,类似于
第五装配工 — 用于为 TVM 生成二进制可执行代码的 Fivet 库。 Fifth Assembler 没有编译器。 这
我们的比赛有效
最后,是时候看看我们努力的结果了。
异步支付通道
支付通道是一种智能合约,允许两个用户在区块链之外发送付款。 因此,您不仅节省了金钱(没有佣金),还节省了时间(您不必等待下一个块被处理)。 付款金额可以根据需要小额支付,也可以根据需要频繁支付。 在这种情况下,各方不必相互信任,因为最终结算的公平性是由智能合约保证的。
我们找到了一个相当简单的解决方案。 两方可以交换签名消息,每条消息包含两个数字——各方支付的全额金额。 这两个数字的工作方式类似于
事实上,一个数字就足以实现这个想法,但我们留下了两个,因为这样我们可以制作一个更方便的用户界面。 此外,我们决定在每条消息中包含付款金额。 如果没有它,如果消息由于某种原因丢失,那么,尽管所有金额和最终计算都是正确的,但用户可能不会注意到丢失。
为了测试我们的想法,我们寻找使用这种简单而简洁的支付通道协议的示例。 令人惊讶的是,我们只找到了两个:
很明显,详细描述我们的协议是有意义的,特别注意它的正确性。 经过几次迭代,规范已经准备就绪,现在您也可以了。
我们在 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 和多重签名钱包合约的完整源代码可以在以下位置找到:
关于比赛和 TON 的结论
总共,我们的工作花费了 380 个小时(包括熟悉文档、会议和实际开发)。 五名开发人员参与了竞赛项目:CTO、团队负责人、区块链平台专家和 Haskell 软件开发人员。
我们毫不费力地找到了参加比赛的资源,因为黑客马拉松的精神、紧密的团队合作以及快速沉浸在新技术方面的需要总是令人兴奋的。 在资源有限的情况下,为了取得最大成果而度过的几个不眠之夜,得到了宝贵的经验和美好的记忆的补偿。 此外,执行此类任务始终是对公司流程的良好考验,因为如果没有良好的内部互动,就很难取得真正令人满意的结果。
撇开歌词不谈:TON 团队投入的大量工作给我们留下了深刻的印象。 他们成功地构建了一个复杂、美观且最重要的是可行的系统。 TON 已证明自己是一个具有巨大潜力的平台。 然而,为了发展这个生态系统,无论是在区块链项目中的使用还是在改进开发工具方面,还需要做更多的工作。 我们很自豪现在能够成为这一进程的一部分。
如果阅读本文后您仍然有任何疑问或对如何使用 TON 解决您的问题有想法,
来源: habr.com