ProHoster > Блог > адміністрування > Тестовий клієнт TON (Telegram Open Network) та нова мова Fift для смарт-контрактів
Тестовий клієнт TON (Telegram Open Network) та нова мова Fift для смарт-контрактів
Понад рік тому стало відомо про плани месенджера Telegram випустити власну децентралізовану мережу Telegram Open Network. Тоді став доступний об'ємний технічний документ, який, ймовірно, був написаний Миколою Дуровим та описував структуру майбутньої мережі. Для тих, хто пропустив, рекомендую ознайомитися з моїм переказом цього документа (частина 1, частина 2; третя частина, на жаль, все ще припадає пилом в чернетках).
З того часу жодних значущих новин про статус розробки TON не було, поки пару днів тому (в одному з неофіційних каналів) не з'явилося посилання на сторінку https://test.ton.org/download.html, де розміщені:
◦ ton-test-liteclient-full.tar.xz - Вихідники легкого клієнта для тестової мережі TON;
◦ ton-lite-client-test1.config.json - Конфігураційний файл для підключення до тестової мережі;
◦ README — інформація про складання та запуск клієнта;
◦ HOWTO - Покрокова інструкція про створення смарт-контракту за допомогою клієнта;
◦ ton.pdf — оновлений документ (від 2 березня 2019 р.) із технічним оглядом мережі TON;
◦ tvm.pdf - Технічний опис TVM (TON Virtual Machine, віртуальної машини TON);
◦ tblkch.pdf - Технічний опис блокчейна TON;
◦ fiftbase.pdf - Опис нової мови Fift, призначеного для створення смарт-контрактів в TON.
Повторюся, офіційних підтверджень сторінки та всіх цих документів з боку Телеграма не було, але обсяг цих матеріалів робить їх досить правдоподібними. Запуск опублікованого клієнта здійснюйте на свій страх і ризик.
Складання тестового клієнта
Для початку спробуємо зібрати та запустити тестовий клієнт – благо, README Докладно описує цей нескладний процес. Я робитиму це на прикладі macOS 10.14.5, за успішність складання на інших системах ручатися не можу.
Завантажуємо та розпаковуємо архів із вихідниками. Важливо завантажувати останню версію, оскільки зворотна сумісність на цьому етапі не гарантується.
Переконуємося, що в системі встановлені останні версії make, cmake (версії 3.0.2 або вище), OpenSSL (включно із заголовковими файлами C), g++ або clang. Мені нічого достановлювати не довелося, все зібралося одразу.
Припустимо, вихідники розпаковані в папку ~/lite-client. Окремо від неї створюємо порожню папку для зібраного проекту (наприклад, ~/liteclient-build), і з неї (cd ~/liteclient-build) Викликаємо команди:
Якщо все зроблено правильно, ви повинні побачити щось таке:
Доступних команд, як бачимо, небагато:
◦ help - Вивести цей список команд;
◦ quit - Вийти;
◦ time - показати поточний час на сервері;
◦ status - показати стан підключення та локальної БД;
◦ last - Оновити стан блокчейна (завантажити останній блок). Цю команду важливо виконувати перед будь-якими запитами, щоб бути впевненим, що ви бачите актуальний стан мережі.
◦ sendfile<filename> - Завантажити локальний файл в мережу TON. Так відбувається взаємодія з мережею — у тому числі, наприклад, створення нових смарт-контрактів та запити на переказ коштів між обліковими записами;
◦ getaccount<address> - показати поточне (на момент виконання команди last) стан акаунту із зазначеною адресою;
◦ privkey<filename> — завантажити приватний ключ із локального файлу.
Якщо під час запуску клієнта передати йому папку за допомогою опції -D, то він складатиме в неї останній блок майстерні:
Тепер можемо перейти до цікавіших речей — вивчити мову Fift, спробувати скомпілювати смарт-контракт (наприклад, створити тестовий гаманець), завантажити його в мережу та спробувати переказ коштів між обліковими записами.
Мова Fift
З документа fiftbase.pdf можна дізнатися, що для створення смарт-контрактів команда Telegram створила нову стікову мову Fift (мабуть, від чисельного п'ятий, За аналогією з Forth - мовою, з якою у Fift багато спільного).
Документ досить об'ємний, на 87 сторінок, і я не докладно переказуватиму його зміст у рамках цієї статті (як мінімум, тому що сам не закінчив його читання :). Зупинюся на основних моментах і наведу кілька прикладів коду цією мовою.
На базовому рівні, синтаксис Фіфт досить простий: його код складається з слів, як правило, розділених пробілами або перекладами рядків (частковий випадок: деякі слова не вимагають роздільника після себе). Будь-яке слово - це регістро-залежна послідовність символів, якій відповідає деяке визначення (Грубо кажучи, те, що інтерпретатор повинен зробити, коли зустрічає це слово). Якщо визначення слова немає, інтерпретатор намагається розібрати його як число і покласти на стек. До речі, числа тут - раптово - 257-бітові цілі, а дробових немає зовсім - точніше, вони відразу перетворюються на пару цілих, що утворюють чисельник і знаменник раціонального дробу.
Слова зазвичай взаємодіють зі значеннями, що лежать на верхівці стека. Окремий тип слів префіксний - Використовує не стек, а наступні за ними символи з вихідного файлу. Наприклад, так реалізовані рядкові літерали – символ «лапка» (") є префіксним словом, яке шукає наступне (закриває) лапку, і поміщає рядок між ними на стек. Подібним чином поводяться однорядкові (//) та багаторядкові (/*) коментарі.
На цьому майже весь внутрішній устрій мови закінчується. Решта (включаючи керуючі конструкції) визначено як слова (або внутрішні, такі як арифметичні операції та визначення нових слів, або визначені в «стандартній бібліотеці») Fift.fif, яка лежить у папці crypto/fift у вихідниках).
Простий приклад програми на Fift:
{ 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» виводить інтерпретатор, коли закінчує обробляти поточний рядок в інтерактивному режимі введення)
Цей страшно виглядає файл призначений для створення смарт-контракту - він буде поміщений у файл new-wallet-query.boc після виконання. Зверніть увагу, що тут використовується ще одна, асемблерна мова для TON Virtual Machine (нею я не зупинятимуся докладно), інструкції якого і будуть поміщені в блокчейн.
Таким чином, асемблер для TVM написаний на Fift – вихідники цього асемблера знаходяться у файлі crypto/fift/Asm.fif і підключаються на початку наведеного вище шматка коду.
Що я можу сказати, певне, Микола Дуров просто любить створювати нові мови програмування 🙂
Створення смарт-контракту та взаємодія з TON
Отже, припустимо, ми зібрали клієнт TON та інтерпретатор Fift, як описано вище, та познайомилися з мовою. Як створити смарт-контракт? Про це розповідається у файлиці HOWTO, доданому до вихідників.
Аккаунти в TON
Як я описував у огляді TON, Ця мережа містить більше одного блокчейна - є один загальний, т.зв. "майстерчейн", а також довільна кількість додаткових "воркчейнів", що ідентифікуються 32-бітним числом. Майстерчейн має ідентифікатор -1, крім нього може використовуватися «базовий» воркчейн з ідентифікатором 0. У кожного воркчейна може бути своя конфігурація. Внутрішньо кожен воркчейн дробиться на шардчейни, але це вже деталь реалізації, яку необов'язково пам'ятати.
В межах одного воркчейна зберігається безліч акаунтів, які мають свої ідентифікатори account_id. Для майстерня та нульового воркчейна вони мають довжину 256 біт. Таким чином, ідентифікатор облікового запису записується, наприклад, так:
Це «сирий» формат: спочатку ідентифікатор воркчейна, потім двокрапка, і ідентифікатор облікового запису в шістнадцятковому записі.
Крім того, є укорочений формат — номер воркчейна та адреса облікового запису кодуються в бінарному вигляді, до них дописується контрольна сума і все це кодується в Base64:
Ef+BVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODSkb
Знаючи цей формат запису, ми можемо запросити поточний стан якогось облікового запису через тестовий клієнт за допомогою команди
[ 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, осередки – є деревоподібним, у кожного осередку можуть бути як свої дані, так і дочірні осередки. Це показано у вигляді відступів в останніх рядках.
Складання смарт-контракту
Тепер давайте створимо самі таку структуру (вона називається BOC - bag of cells) за допомогою мови Fift. На щастя, самостійно писати смарт-контракт не доведеться — у папці crypto/block з архіву з вихідними файлами new-wallet.fif, Який допоможе створити нам новий гаманець. Скопіюємо його в папку із зібраним клієнтом (~/liteclient-buildякщо ви діяли за інструкцією вище). Його ж вміст я наводив вище як приклад коду на Fift.
Тут <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)
Це означає, що гаманець з ідентифікатором -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. Перше число тут - це поточний sequence number, який зберігається в обліковому записі, що видає грами. Звідки його взяти? Як говорилося вище, запустіть клієнт і виконайте:
last
getaccount -1:8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d
Число 0000000D (у вас воно буде більше) і є sequence number, який треба підставити в 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, а потім знову запитати статус облікового запису, у якого ми попросили грами, то ми повинні побачити, що його sequence number збільшився на одиночку - це означає, що він відправив гроші нашому обліковцю.
Залишився останній крок – завантажуємо код нашого гаманця (баланс його вже поповнений, але без коду смарт-контракту ми не зможемо ним керувати). Виконуємо 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) і перевіряємо, що баланс і seqno нашого гаманця змінилися (getaccount <account_id>).
Ось і все тепер ми вміємо створювати смарт-контракти в TON і відправляти до них запити. Як бачимо, нинішньої функціональності вже достатньо, щоб, наприклад, зробити більш доброзичливий гаманець з графічним інтерфейсом (втім, очікується, що він і так стане доступним як частина месенджера).
Тільки зареєстровані користувачі можуть брати участь в опитуванні. Увійдіть, будь ласка.
Чи зацікавлені ви у продовженні статей з розбором TON, TVM, Fift?
Так, чекаю завершення циклу статей із загальним оглядом TON
Так, цікаво почитати докладніше про мову Fift
Так, хочу дізнатися більше про TON Virtual Machine та асемблері для нього