Состояние аккаунта: словарь вместо переменных
Зачем это нужно
В Ethereum данные контракта живут в его собственных полях. В Fabric — в общем хранилище «ключ — значение», доступном через объект контекста. В Waves сделано третье: у каждого аккаунта есть свой словарь, и писать в него можно вообще без всякого кода, обычной транзакцией.
Это неожиданно удобно. Прежде чем писать смарт-контракт, можно уже вести журнал: записывать оценки, статусы поставок, показания счётчиков. И это же хранилище потом читает и меняет dApp — то есть данные и код разведены.
Эта глава — про этот словарь: что в нём можно хранить, сколько это стоит и где границы. Все числа ниже сняты на локальном узле Waves 1.6.4.
Словарь аккаунта
Пишем три значения:
const { data } = require('@waves/waves-transactions');
const tx = data({ data: [
{ key: 'gruppa', type: 'string', value: 'IS-21' },
{ key: 'ball', type: 'integer', value: 5 },
{ key: 'dopusk', type: 'boolean', value: true },
], chainId: 'R' }, seed);
тип: 12 | комиссия: 100000
в блоке: 28
Читаем:
curl http://127.0.0.1:6869/addresses/data/3M5HPFG1r9q79uxff6rm1Z3UFZcXXXHX32p
[{"key":"ball","type":"integer","value":5},
{"key":"dopusk","type":"boolean","value":true},
{"key":"gruppa","type":"string","value":"IS-21"}]
Порядок — алфавитный по ключу, а не тот, в котором записывали.
В транзакции данных нет получателя. Запись всегда идёт в словарь того, кто подписал. Это главное правило: чужой словарь не изменит никто, включая владельца узла. А вот читать может кто угодно — состояние публично внутри сети.
Четыре типа значений и удаление
| Тип | Пример | Что это |
|---|---|---|
integer | 5 | целое со знаком |
boolean | true | да или нет |
string | "IS-21" | текст в UTF-8 |
binary | base64:AbCd… | произвольные байты |
Дробных чисел нет. Цену в рублях и копейках хранят целым числом копеек — ровно так же, как суммы в самой сети.
Удаление — запись со значением null и без типа:
const tx = data({ data: [{ key: 'ball', value: null }], chainId: 'R' }, seed);
состояние до: [{"key":"ball",...},{"key":"dopusk",...},{"key":"gruppa",...}]
состояние после: [{"key":"dopusk","type":"boolean","value":true},{"key":"gruppa","type":"string","value":"IS-21"}]
Удаление убирает ключ из текущего состояния, но не из истории: транзакция, которая его записывала, никуда из цепочки не делась. Блокчейн остаётся блокчейном — стереть прошлое нельзя, можно только перестать считать его текущим.
Сколько это стоит и где границы
Цена растёт по килобайтам:
значение 10 байт → комиссия 100000
значение 1000 байт → комиссия 100000
значение 1100 байт → комиссия 200000
значение 2100 байт → комиссия 300000
значение 3100 байт → комиссия 400000
То есть 0,001 WAVES за каждый начатый килобайт. Первый килобайт стоит столько же, сколько десять байт, а вот 1100 байт — уже вдвое дороже.
Границы (проверено перебором):
ключ 400 знаков → код 200
ключ 401 знак → код 400 | Too big sequence requested
значение 32 767 байт → код 200
значение 32 768 байт → код 400 | Too big sequence requested
Сообщение Too big sequence requested одно на оба случая — и на длинный ключ, и на большое значение. Что именно велико, узел не уточняет, так что проверять придётся самому.
Одной транзакцией можно записать много ключей сразу — сто штук прошли без вопросов. Комиссию при этом надо ставить по размеру:
100 ключей с комиссией 100000 → State check failed. Reason: Fee for DataTransaction
(100000 in WAVES) does not exceed minimal value of 200000 WAVES.
100 ключей с комиссией 300000 → код 200
Как читать состояние
Весь словарь целиком:
curl .../addresses/data/<адрес>
Один ключ:
curl .../addresses/data/<адрес>/n7
{"key":"n7","type":"integer","value":7}
Несуществующий ключ:
код: 404 | {"error":304,"message":"no data for this key"}
Ключи по образцу — то, что заменяет вложенные структуры:
curl '.../addresses/data/<адрес>?matches=n1%5B0-9%5D'
найдено: 10
[{"key":"n10","type":"integer","value":10},{"key":"n11",…},{"key":"n12",…}]
Вложенных структур в состоянии нет. Вместо студенты[z-1].оценки пишут составной ключ: z_z-1_ball. А потом достают все ключи студента одним запросом по образцу. Этот приём встречается в каждом втором dApp.
Типичные ошибки
Ошибка 1. Искать поле получателя.
Его нет. Записать в чужой словарь нельзя никаким способом — это не ограничение библиотеки, а правило сети.
Ошибка 2. Ставить комиссию по умолчанию на большую запись.
State check failed. Reason: Fee for DataTransaction (100000 in WAVES)
does not exceed minimal value of 200000 WAVES.
Библиотека считает комиссию по размеру сама, если её не задавать вручную. Задали — считайте сами.
Ошибка 3. Хранить дробное число.
Тип integer не примет 1.5. Хранить надо копейки или тысячные доли, а запятую ставить при показе.
Ошибка 4. Забыть закодировать образец в адресе.
?matches=n1[0-9] без кодирования доедет до узла искажённым, и ответ будет пустым — без всякой ошибки.
Ошибка 5. Складывать в состояние файлы.
Тип binary есть, но 32 767 байт — это потолок, и каждый килобайт платный. Файлы кладут в обычное хранилище, а в состояние — только их хеш.
Шпаргалка по главе
| Что нужно | Как |
|---|---|
| Записать | data({ data: [{ key, type, value }], chainId }, seed) |
| Удалить ключ | { key: 'ball', value: null } — без типа |
| Весь словарь | GET /addresses/data/<адрес> |
| Один ключ | GET /addresses/data/<адрес>/<ключ> (404 = нет такого) |
| Ключи по образцу | GET /addresses/data/<адрес>?matches=<регулярка в URL> |
| Типы значений | integer, boolean, string, binary |
| Границы | ключ ≤ 400 знаков, значение ≤ 32 767 байт |
| Цена | 0,001 WAVES за каждый начатый килобайт |
Словарь главы
| Термин | Что это |
|---|---|
| Состояние аккаунта | Словарь «ключ — значение», свой у каждого адреса |
| Транзакция данных | Тип 12; пишет только в словарь подписавшего |
| Составной ключ | Приём вместо вложенности: z_z-1_ball |
matches | Фильтр ключей по регулярному выражению |
binary | Тип для произвольных байтов; хранят хеши, а не файлы |
| Удаление ключа | Запись value: null без типа |
Too big sequence requested | Превышена граница ключа или значения |
| Начатый килобайт | Единица, по которой считают комиссию |
Цель: скорость ≥ 95 зн/мин, точность ≥ 88%
const { data } = require('@waves/waves-transactions');
Что должен уметь после главы
- записать несколько значений в состояние своего аккаунта и прочитать их;
- назвать четыре типа значений и объяснить, почему нет дробных чисел;
- удалить ключ и объяснить, что при этом происходит с историей;
- посчитать комиссию по размеру записи;
- назвать границы ключа и значения и узнать соответствующее сообщение;
- заменить вложенную структуру составным ключом и выборкой по образцу.
Проверка по главе
Сводная проверка по всей главе: 10 вопросов, 5:00 на всё, без подсказок по ходу. Вопросы показываются по одному, ответ изменить нельзя. Если время выйдет — экзамен завершится с тем, что успел ответить. Пересдавать можно сколько угодно раз.
Куда дальше: проверенные ресурсы
- Состояние аккаунта — правила хранилища и границы.
- Транзакция данных — поля и расчёт комиссии.
- Waves Explorer — смотреть состояние чужих аккаунтов глазами.
- REST: данные аккаунта — все варианты запроса, включая
matches.
Комментарии
Обсуждение живёт в GitHub Discussions и грузится с чужого домена — поэтому только по нажатию.