Возможно, многие из вас задавались вопросом: как изменится поведение смарт-контракта, если его данные будут весить сотни мегабайт и хранить сотни тысяч или миллионы записей? Будут ли дорожать транзакции? Как это повлияет на сеть в целом? Будут ли одни типы переменных в solidity справляться с подобной задачей лучше, чем другие? Мы решили лично узнать ответы на эти вопросы и провести эксперимент в нашей приватной сети Ethereum, смоделировав описанные ситуации. Что из этого получилось читайте дальше в статье.
Описание теста
Мы хотели выяснить, как повлияет загрузка на один смарт-контракт миллиона транзакций и большого объёма данных.
Замерялись такие параметры:
- стоимость одной транзакции в kgas;
- длительность создания одного блока;
- размер одного блока;
Параметры блокчейна
В нашем распоряжении имелся приватный PoA-блокчейн с двумя авторизованными пишущими узлами и одним «пассивным», с которого и отправлялись транзакции.
Все три узла были запущены на идентичных серверах:
- Процессор: 2 Intel Xeon E5-2670 2,60 ГГц.
- ОЗУ: 8 Гб.
- ОС: Windows Server 2012 R2 Datacenter (64-битная).
Описание смарт-контрактов
Было создано два смарт-контракта. Один имел в качестве поля mapping bytes32 => bytes32, другой — одномерный массив bytes32. Каждый из контрактов содержал функцию, принимающую в качестве параметра значение bytes32 и сохраняющую это значение как элемент соответствующего mapping'a или массива (по сути, значение сохранялось в хранилище блокчейна).
Краткое описание контракта с mapping'ом
contract TesterMapping {
mapping (bytes32 => bytes32) StoragedData;
function Storing(bytes32 data) {
if(StoragedData[data]!= "Y")
StoragedData[data] = "Y";
}
}
Краткое описание контракта с массивом
Con tract TesterArray {
bytes32[] StoragedArray;
function Storing(bytes32 data) {
for(uint256 i = 0; i < StoragedArray.length; i++) {
if(StoragedArray[i] == data)
return;
}
StoragedArray.push(data);
}
}
Ход и результаты тестирования
Сперва тестировался смарт-контракт с полем-mapping'ом. Раз в секунду на него посылалась транзакция со случайным 32-байтовым значением. Из-за технических проблем тестирование заняло несколько больше времени, чем планировалось, и миллионная транзакция была отправлена спустя три недели после отправки первой. Затем был протестирован смарт-контракт с массивом.
Mapping—контракт
В течение всего эксперимента ни стоимость транзакции, ни размер блока не колебались на сколько-нибудь значимые величины. Каждая транзакция обрабатывалась практически мгновенно, как первая, так и миллионная. Длительность создания блока периодически менялась от 1 до 9 секунд, но всегда симметрично заданному в генезис-блоке значению в 5 сек (если между созданием блока n и n+1 проходила одна секунда, то блок n+2 появлялся через 9 сек), то есть средняя длительность создания блока оставалось равной 5 секундам. Однако какой-то закономерности в возникновении этих флуктуаций замечено не было и, возможно, это было связано с работой нашей сети или вспомогательным программным обеспечением серверов (антивирусы и прочее).
Контракт с массивом
В данном варианте из-за наличия цикла перебора массива (для поиска совпадающих значений) стоимость одной транзакции выросла с 40 тысяч KGas до более чем 2 миллионов KGas уже за первые две тысячи транзакций. При этом продолжительность обработки одной транзакции уже за первые несколько сотен транзакций стала больше длительности создания одного блока. Из-за этого размер одного блока за приблизительно 500 транзакций упал до минимального и больше не увеличивался, так как на один блок стала приходиться в лучшем случае одна транзакция. Длительность обработки очень быстро стала настолько большой, что после отправки всего трёх тысяч транзакций «разгребание» получившейся очереди заняло около четырёх часов, при этом работа сети была бы парализована (конечно, если у отправителя в «боевой» сети хватило бы средств оплачивать такое количество столь дорогих транзакций).
Выводы
- Размер mapping'а не влияет на быстродействие работы с ним или на стоимость транзакции (как минимум, до 1 миллиона элементов).
- Виртуальная машина Solidity крайне неэффективна при работе с итерационными циклами.
- Для работы с большим количеством записей лучше использовать mapping.
К сожалению, мы не нашли способа определить, какой объем данных в хранилище занимают данные конкретного смарт-контракта при использовании mapping-массива. Возможно, кто-нибудь из читателей сможет подсказать свой вариант.
Автор: ayyylmao