测试客户端 TON(Telegram Open Network)和新的智能合约 Fift 语言

一年多前,人们得知 Telegram Messenger 计划发布自己的去中心化网络 电报开放网络。 随后出现了一份大量的技术文件,据称是尼古拉·杜罗夫撰写的,描述了未来网络的结构。 对于那些错过的人,我建议您阅读我对这份文件的重述(部分1, 部分2; 唉,第三部分仍然在草稿中积满灰尘)。

从那时起,就没有关于 TON 开发状况的重大新闻,直到几天前(在其中一篇文章中) 非官方渠道) 页面链接未出现 https://test.ton.org/download.html,位于哪里:
ton-test-liteclient-full.tar.xz — TON 测试网络的轻客户端来源;
ton-lite-client-test1.config.json — 连接测试网络的配置文件;
读我 — 有关构建和启动客户端的信息;
如何 — 使用客户端创建智能合约的分步说明;
吨.pdf — 更新了文档(日期为 2 年 2019 月 XNUMX 日),其中包含 TON 网络的技术概述;
电视节目.pdf ——TVM(TON Virtual Machine,TON虚拟机)的技术描述;
tblkch.pdf — TON 区块链的技术描述;
第五基地.pdf — 新 Fift 语言的描述,旨在在 TON 中创建智能合约。

我再说一遍,该页面和来自 Telegram 的所有这些文件都没有得到官方确认,但这些材料的数量使它们非常可信。 启动已发布的客户端 风险自担.

构建测试客户端

首先,让我们尝试构建并运行一个测试客户端 - 幸运的是, 读我 详细描述了这个简单的过程。 我将使用 macOS 10.14.5 作为示例来执行此操作;我不能保证在其他系统上构建成功。

  1. 下载并解压 源档案。 下载最新版本非常重要,因为现阶段无法保证向后兼容性。

  2. 确保系统上安装了最新版本的 make、cmake(版本 3.0.2 或更高版本)、OpenSSL(包括 C 头文件)、g++ 或 clang。 我不需要安装任何东西,一切都立即组合在一起。

  3. 我们假设源代码被解压到一个文件夹中 ~/lite-client。 除此之外,为组装的项目创建一个空文件夹(例如, ~/liteclient-build),并从中得出(cd ~/liteclient-build)调用命令:

    cmake ~/lite-client
    cmake --build . --target test-lite-client

    测试客户端 TON(Telegram Open Network)和新的智能合约 Fift 语言

    为了构建智能合约的 Fiveft 语言解释器(更多相关信息见下文),我们还调用

    cmake --build . --target fift

  4. 下载当前版本 配置文件 连接到测试网络并将其与组装好的客户端放在文件夹中。

  5. 完成,您可以启动客户端:

    ./test-lite-client -C ton-lite-client-test1.config.json

如果一切都正确完成,您应该看到如下内容:

测试客户端 TON(Telegram Open Network)和新的智能合约 Fift 语言

正如我们所看到的,可用的命令很少:
help — 显示命令列表;
quit - 出去;
time — 显示服务器上的当前时间;
status — 显示连接和本地数据库状态;
last — 更新区块链的状态(下载最后一个区块)。 在发出任何请求之前运行此命令非常重要,以确保您看到网络的当前状态。
sendfile <filename> — 将本地文件上传到 TON 网络。 这就是与网络交互的发生方式 - 例如,包括创建新的智能合约和请求在账户之间转移资金;
getaccount <address> — 显示当前(执行命令时) last) 指定地址的账户状态;
privkey <filename> — 从本地文件加载私钥。

如果在启动客户端时,您使用选项将文件夹传输到客户端 -D,然后他会将主链的最后一个区块添加到其中:

./test-lite-client -C ton-lite-client-test1.config.json -D ~/ton-db-dir

现在我们可以继续做更有趣的事情 - 学习 Fift 语言,尝试编译智能合约(例如,创建测试钱包),将其上传到网络并尝试在帐户之间转移资金。

语言五

来自文件 第五基地.pdf 你可以发现Telegram团队创建了一种新的堆栈语言来创建智能合约 菲夫特 (显然从数字 第五,与 Forth 类似,Fifth 与 Forth 语言有很多共同点)。

该文档相当庞大,有87页,我不会在本文的框架内详细复述其内容(至少因为我自己还没有读完:)。 我将重点关注要点并给出一些这种语言的代码示例。

从根本上来说,Fift 的语法非常简单:它的代码包括 ,通常用空格或换行符分隔(特殊情况:某些单词后面不需要分隔符)。 任何 слово 是一个区分大小写的字符序列,对应于某个特定的 决心 (粗略地说,解释器遇到这个词时应该做什么)。 如果没有单词的定义,解释器会尝试将其解析为数字并将其放入堆栈中。 顺便说一句,这里的数字突然变成了 257 位整数,而且根本没有分数,更准确地说,它们立即变成一对整数,形成有理分数的分子和分母。

单词往往与堆栈顶部的值交互。 一个单独的词类型 - 字首 — 不使用堆栈,而是使用源文件中的后续字符。 例如,这就是字符串文字的实现方式 - 引号字符 (") 是一个前缀词,用于查找下一个(结束)引号,并将它们之间的字符串压入堆栈。 单行代码的行为方式相同(//) 和多行 (/*) 评论。

这几乎是该语言的整个内部结构的终点。 其他一切(包括控制结构)都被定义为单词(或者是内部的,例如算术运算和新单词的定义;或者是在“标准库”中定义的) Fift.fif,位于文件夹中 crypto/fift 在来源中)。

Five 中的一个简单示例程序:

{ dup =: x dup * =: y } : setxy
3 setxy x . y . x y + .
7 setxy x . y . x y + .

第一行定义一个新词 setxy (注意前缀 {,它在结束块之前创建一个块 } 和前缀 :,它实际上定义了这个词)。 setxy 从堆栈顶部获取一个数字,将其定义(或重新定义)为全局的 持续的 x,并将该数字的平方作为常数 y (鉴于常量的值可以重新定义,我宁愿将它们称为变量,但我遵循语言中的命名约定)。

接下来的两行将一个数字放入堆栈并调用 setxy,然后显示常量的值 x, y (该词用于输出 .),两个常量都被放入堆栈中,求和,并打印结果。 结果我们会看到:

3 9 12 ok
7 49 56 ok

(解释器在交互输入模式下处理完当前行后会打印“ok”行)

好吧,完整的代码示例:

"Asm.fif" include

-1 constant wc  // create a wallet in workchain -1 (masterchain)

// Create new simple wallet
<{  SETCP0 DUP IFNOTRET INC 32 THROWIF  // return if recv_internal, fail unless recv_external
    512 INT LDSLICEX DUP 32 PLDU   // sign cs cnt
    c4 PUSHCTR CTOS 32 LDU 256 LDU ENDS  // sign cs cnt cnt' pubk
    s1 s2 XCPU            // sign cs cnt pubk cnt' cnt
    EQUAL 33 THROWIFNOT   // ( seqno mismatch? )
    s2 PUSH HASHSU        // sign cs cnt pubk hash
    s0 s4 s4 XC2PU        // pubk cs cnt hash sign pubk
    CHKSIGNU              // pubk cs cnt ?
    34 THROWIFNOT         // signature mismatch
    ACCEPT
    SWAP 32 LDU NIP 
    DUP SREFS IF:<{
      8 LDU LDREF         // pubk cnt mode msg cs
      s0 s2 XCHG SENDRAWMSG  // pubk cnt cs ; ( message sent )
    }>
    ENDS
    INC NEWC 32 STU 256 STU ENDC c4 POPCTR
}>c
// code
<b 0 32 u, 
   newkeypair swap dup constant wallet_pk 
   "new-wallet.pk" B>file
   B, 
b> // data
// no libraries
<b b{00110} s, rot ref, swap ref, b>  // create StateInit
dup ."StateInit: " <s csr. cr
dup hash dup constant wallet_addr
."new wallet address = " wc . .": " dup x. cr
wc over 7 smca>$ type cr
256 u>B "new-wallet.addr" B>file
<b 0 32 u, b>
dup ."signing message: " <s csr. cr
dup hash wallet_pk ed25519_sign_uint rot
<b b{1000100} s, wc 8 i, wallet_addr 256 u, b{000010} s, swap <s s, b{0} s, swap B, swap <s s, b>
dup ."External message for initialization is " <s csr. cr
2 boc+>B dup Bx. cr
"new-wallet-query.boc" tuck B>file
."(Saved to file " type .")" cr

这个看起来吓人的文件用于创建智能合约 - 它将被放置在一个文件中 new-wallet-query.boc 执行后。 请注意,TON 虚拟机这里使用的是另一种汇编语言(我不会详细介绍它),其指令将放在区块链上。

因此,TVM 的汇编程序是用 Fift 编写的 - 该汇编程序的源代码位于文件中 crypto/fift/Asm.fif 和 连接在上述代码的开头。

我能说什么,显然 Nikolai Durov 只是喜欢创建新的编程语言​​:)

创建智能合约并与 TON 交互

因此,假设我们已经如上所述组装了 TON 客户端和 Fift 解释器,并熟悉了该语言。 现在如何创建智能合约? 文件中对此进行了描述 如何,附在来源中。

TON 账户

正如我在 TON 评论,这个网络包含多个区块链——有一个共同的区块链,即所谓的区块链。 “主链”,以及任意数量的附加“工作链”,由 32 位数字标识。 主链的标识符为-1;除此之外,还可以使用标识符为0的“基础”工作链,每个工作链可以有自己的配置。 在内部,每个工作链都分为分片链,但这是一个不需要记住的实现细节。

在一个工作链中,存储了许多具有自己的 account_id 标识符的帐户。 对于主链和零工作链,它们的长度是256位。 因此,帐户标识符的写法例如如下:

-1:8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d

这是“原始”格式:首先是工作链 ID,然后是冒号,以及十六进制表示法的账户 ID。

此外,还有一种缩短的格式——工作链号和账户地址以二进制形式编码,并添加校验和,所有这些都以 Base64 编码:

Ef+BVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODSkb

知道了这种记录格式,我们可以使用以下命令通过测试客户端请求帐户的当前状态

getaccount -1:8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d

我们会得到这样的结果:

[ 3][t 2][1558746708.815218925][test-lite-client.cpp:631][!testnode]    requesting account state for -1:8156775B79325E5D62E742D9B96C30B6515A5CD2F1F64C5DA4B193C03F070E0D
[ 3][t 2][1558746708.858564138][test-lite-client.cpp:652][!testnode]    got account state for -1:8156775B79325E5D62E742D9B96C30B6515A5CD2F1F64C5DA4B193C03F070E0D with respect to blocks (-1,8000000000000000,72355):F566005749C1B97F18EDE013EBA7A054B9014961BC1AD91F475B9082919A2296:1BD5DE54333164025EE39D389ECE2E93DA2871DA616D488253953E52B50DC03F and (-1,8000000000000000,72355):F566005749C1B97F18EDE013EBA7A054B9014961BC1AD91F475B9082919A2296:1BD5DE54333164025EE39D389ECE2E93DA2871DA616D488253953E52B50DC03F
account state is (account
  addr:(addr_std
    anycast:nothing workchain_id:-1 address:x8156775B79325E5D62E742D9B96C30B6515A5CD2F1F64C5DA4B193C03F070E0D)
  storage_stat:(storage_info
    used:(storage_used
      cells:(var_uint len:1 value:3)
      bits:(var_uint len:2 value:539)
      public_cells:(var_uint len:0 value:0)) last_paid:0
    due_payment:nothing)
  storage:(account_storage last_trans_lt:74208000003
    balance:(currencies
      grams:(nanograms
        amount:(var_uint len:7 value:999928362430000))
      other:(extra_currencies
        dict:hme_empty))
    state:(account_active
      (
        split_depth:nothing
        special:nothing
        code:(just
          value:(raw@^Cell 
            x{}
             x{FF0020DDA4F260D31F01ED44D0D31FD166BAF2A1F80001D307D4D1821804A817C80073FB0201FB00A4C8CB1FC9ED54}
            ))
        data:(just
          value:(raw@^Cell 
            x{}
             x{0000000D}
            ))
        library:hme_empty))))
x{CFF8156775B79325E5D62E742D9B96C30B6515A5CD2F1F64C5DA4B193C03F070E0D2068086C000000000000000451C90E00DC0E35B7DB5FB8C134_}
 x{FF0020DDA4F260D31F01ED44D0D31FD166BAF2A1F80001D307D4D1821804A817C80073FB0201FB00A4C8CB1FC9ED54}
 x{0000000D}

我们看到存储在指定工作链的 DHT 中的结构。 例如,在该领域 storage.balance 是经常账户余额,单位为 storage.state.code - 智能合约代码,以及 storage.state.data - 其当前数据。 请注意,TON 数据存储 - Cell、cells - 是树状的,每个单元格都可以有自己的数据和子单元格。 这在最后几行显示为缩进。

构建智能合约

现在让我们自己创建一个这样的结构(它被称为 BOC - 细胞袋)使用第五语言。 幸运的是,您不必自己编写智能合约 - 在文件夹中 crypto/block 源存档中有一个文件 new-wallet.fif,这将帮助我们创建一个新的钱包。 让我们将其复制到包含已组装客户端的文件夹中(~/liteclient-build,如果您按照上面的说明进行操作)。 我在上面引用了它的内容作为 Fift 上的代码示例。

按如下方式执行该文件:

./crypto/fift -I"<source-directory>/crypto/fift" new-wallet.fif

这是 <source-directory> 必须替换为解压源的路径(遗憾的是,此处不能使用“~”符号,需要完整路径)。 而不是使用钥匙 -I 你可以定义一个环境变量 FIFTPATH 并将这条路径放入其中。

由于我们使用文件名启动 Fift new-wallet.fif,它将执行并退出。 如果省略文件名,则可以与解释器交互播放。

执行后,控制台中应该显示如下内容:

StateInit: x{34_}
 x{FF0020DDA4F260810200D71820D70B1FED44D0D31FD3FFD15112BAF2A122F901541044F910F2A2F80001D31F3120D74A96D307D402FB00DED1A4C8CB1FCBFFC9ED54}
 x{0000000055375F730EDC2292E8CB15C42E8036EE9C25AA958EE002D2DE48A205E3A3426B}

new wallet address = -1 : 4fcd520b8fcca096b567d734be3528edc6bed005f6930a9ec9ac1aa714f211f2 
0f9PzVILj8yglrVn1zS-NSjtxr7QBfaTCp7JrBqnFPIR8nhZ
signing message: x{00000000}

External message for initialization is x{89FEE120E20C7E953E31546F64C23CD654002C1AA919ADD24DB12DDF85C6F3B58AE41198A28AD8DAF3B9588E7A629252BA3DB88F030D00BC1016110B2073359EAC3C13823C53245B65D056F2C070B940CDA09789585935C7ABA4D2AD4BED139281CFA1200000001_}
 x{FF0020DDA4F260810200D71820D70B1FED44D0D31FD3FFD15112BAF2A122F901541044F910F2A2F80001D31F3120D74A96D307D402FB00DED1A4C8CB1FCBFFC9ED54}
 x{0000000055375F730EDC2292E8CB15C42E8036EE9C25AA958EE002D2DE48A205E3A3426B}

B5EE9C724104030100000000D60002CF89FEE120E20C7E953E31546F64C23CD654002C1AA919ADD24DB12DDF85C6F3B58AE41198A28AD8DAF3B9588E7A629252BA3DB88F030D00BC1016110B2073359EAC3C13823C53245B65D056F2C070B940CDA09789585935C7ABA4D2AD4BED139281CFA1200000001001020084FF0020DDA4F260810200D71820D70B1FED44D0D31FD3FFD15112BAF2A122F901541044F910F2A2F80001D31F3120D74A96D307D402FB00DED1A4C8CB1FCBFFC9ED5400480000000055375F730EDC2292E8CB15C42E8036EE9C25AA958EE002D2DE48A205E3A3426B6290698B
(Saved to file new-wallet-query.boc)

这意味着带有ID的钱包 -1:4fcd520b8fcca096b567d734be3528edc6bed005f6930a9ec9ac1aa714f211f2 (或者,什么是相同的, 0f9PzVILj8yglrVn1zS-NSjtxr7QBfaTCp7JrBqnFPIR8nhZ)创建成功。 相应的代码将在文件中 new-wallet-query.boc,他的地址是在 new-wallet.addr,私钥位于 new-wallet.pk (小心 - 再次运行脚本将覆盖这些文件)。

当然,TON 网络还不知道这个钱包;它仅以这些文件的形式存储。 现在需要将其上传到网络。 然而问题是,创建智能合约需要支付佣金,而你的账户余额仍然为零。

在工作模式下,这个问题将通过在交易所购买克(或从另一个钱包转移)来解决。 嗯,在当前的测试模式下,已经创建了一个特殊的智能合约,您可以像这样索取最多 20 克的智能合约。

生成对其他人智能合约的请求

我们向智能合约发出请求,该合约像这样左右分配克。 在同一个文件夹中 crypto/block 找到文件 testgiver.fif:

// "testgiver.addr" file>B 256 B>u@ 
0x8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d
dup constant wallet_addr ."Test giver address = " x. cr

0x4fcd520b8fcca096b567d734be3528edc6bed005f6930a9ec9ac1aa714f211f2
constant dest_addr

-1 constant wc
0x00000011 constant seqno

1000000000 constant Gram
{ Gram swap */ } : Gram*/

6.666 Gram*/ constant amount

// b x --> b'  ( serializes a Gram amount )
{ -1 { 1+ 2dup 8 * ufits } until
  rot over 4 u, -rot 8 * u, } : Gram, 

// create a message (NB: 01b00.., b = bounce)
<b b{010000100} s, wc 8 i, dest_addr 256 u, amount Gram, 0 9 64 32 + + 1+ 1+ u, "GIFT" $, b>
<b seqno 32 u, 1 8 u, swap ref, b>
dup ."enveloping message: " <s csr. cr
<b b{1000100} s, wc 8 i, wallet_addr 256 u, 0 Gram, b{00} s,
   swap <s s, b>
dup ."resulting external message: " <s csr. cr
2 boc+>B dup Bx. cr
"wallet-query.boc" B>file

我们还将其保存在已组装客户端的文件夹中,但我们将更正第五行 - 在“行”之前constant dest_addr”。 让我们将其替换为您之前创建的钱包地址(完整,非缩写)。 开头不需要写“-1:”,而是在开头写“0x”。

您也可以更改线路 6.666 Gram*/ constant amount — 这是您要求的克数(不超过 20)。 即使指定整数,也要保留小数点。

最后,您需要更正该行 0x00000011 constant seqno。 这里的第一个数字是当前的序列号,存储在发行克的账户中。 我可以从哪里得到它? 如上所述,启动客户端并运行:

last
getaccount -1:8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d

最后,智能合约数据将包含

...
x{FF0020DDA4F260D31F01ED44D0D31FD166BAF2A1F80001D307D4D1821804A817C80073FB0201FB00A4C8CB1FC9ED54}
 x{0000000D}

数字 0000000D(你的会更大)是必须替换为的序列号 testgiver.fif.

就这样,保存文件并运行(./crypto/fift testgiver.fif)。 输出将是一个文件 wallet-query.boc。 这就是形成的样子 сообщение 到其他人的智能合约 - 请求“将这么多克转移到某个帐户”。

使用客户端,我们将其上传到网络:

> sendfile wallet-query.boc
[ 1][t 1][1558747399.456575155][test-lite-client.cpp:577][!testnode]    sending query from file wallet-query.boc
[ 3][t 2][1558747399.500236034][test-lite-client.cpp:587][!query]   external message status is 1

如果你现在打电话 last,然后再次请求我们请求克的帐户的状态,然后我们应该看到它的序列号增加了 XNUMX - 这意味着它向我们的帐户发送了钱。

最后一步仍然是 - 下载我们钱包的代码(其余额已被补充,但如果没有智能合约代码,我们将无法管理它)。 我们开展 sendfile new-wallet-query.boc - 就是这样,您在 TON 网络中拥有了自己的钱包(即使目前只是测试钱包)。

创建传出交易

要从创建的帐户余额中转账,有一个文件 crypto/block/wallet.fif,也需要和组装好的客户端放在文件夹中。

与前面的步骤类似,您需要调整您要转账的金额、接收方地址(dest_addr)和钱包的 seqno(初始化钱包后等于 1,每次发出交易后增加 1 - 您可以通过请求您的帐户状态来查看)。 对于测试,您可以使用我的钱包 - 0x4fcd520b8fcca096b567d734be3528edc6bed005f6930a9ec9ac1aa714f211f2.

启动时(./crypto/fift wallet.fif)该脚本将从文件中获取您的钱包地址(从您转账的位置)及其私钥 new-wallet.addr и new-wallet.pk,收到的消息将被写入 new-wallet-query.boc.

和以前一样,要直接执行交易,请调用 sendfile new-wallet-query.boc 在客户端中。 之后,不要忘记更新区块链的状态(last)并检查我们钱包的余额和序列号是否已更改(getaccount <account_id>).

测试客户端 TON(Telegram Open Network)和新的智能合约 Fift 语言

就这样,现在我们可以在 TON 中创建智能合约并向它们发送请求。 正如您所看到的,当前的功能已经足够,例如,制作一个带有图形界面的更友好的钱包(但是,预计它将已经作为信使的一部分提供)。

只有注册用户才能参与调查。 登录拜托

您有兴趣继续分析 TON、TVM、Fift 的文章吗?

  • 是的,我正在等待 TON 概述系列文章的完成

  • 是的,阅读有关第五语言的更多内容很有趣

  • 是的,我想了解有关 TON 虚拟机及其汇编器的更多信息

  • 不,这些都不有趣

39 位用户投票。 12 名用户弃权。

您对 Telegram 推出 TON 的计划有何看法?

  • 我对这个项目寄予厚望

  • 我只是饶有兴趣地关注着它的发展。

  • 我对此表示怀疑并怀疑它的成功。

  • 我倾向于认为这一举措是失败的,对于广大群众来说是不必要的

47 位用户投票。 12 名用户弃权。

来源: habr.com

添加评论