Дерево Меркла: Ржавое И Быстрое



Дерево Меркла: ржавое и быстрое

Всем привет! Недавно я открыл для себя язык Rust. Своими первыми впечатлениями он поделился в предыдущая статья .

Теперь я решил копнуть немного глубже, для этого мне нужно что-то посерьезнее списка.

Мой выбор пал на дерево Меркла.

В этой статье я хочу:

  • расскажите нам об этой структуре данных
  • посмотрите, что уже есть в Rust
  • предложите свою реализацию
  • сравнить производительность
Дерево Меркла Это относительно простая структура данных, и информации о ней уже много в Интернете, но я думаю, что моя статья была бы неполной без описания дерева.



В чем проблема

Дерево Меркла было изобретено еще в 1979 году, но свою популярность оно приобрело благодаря блокчейну.

Цепочка блоков в сети очень велика (у биткоина она составляет более 200 ГБ), и не все узлы могут ее загрузить.

Например, телефоны или кассовые аппараты.

Однако им необходимо знать о том, что конкретная транзакция включена в блок.

Для этого был придуман протокол SPV – упрощенная проверка платежей .



Как работает дерево?

Это бинарное дерево, листьями которого являются хеши любых объектов.

Для построения следующего уровня хеши соседних листьев берутся попарно, конкатенируются и вычисляется хеш результата конкатенации, что проиллюстрировано на картинке в заголовке.

Таким образом, корень дерева представляет собой хеш всех листьев.

Если вы удалите или добавите элемент, корень изменится.



Как работает дерево

Имея дерево Меркла, можно построить доказательство включения транзакции в блок как путь от хеша транзакции до корня.

Например, нас интересует транзакция Tx2, доказательством для нее будет (Hash3, Hash01).

Этот механизм используется в SPV. Клиент загружает только заголовок блока с его хешем.

Имея интересующую транзакцию, он запрашивает подтверждение у узла, содержащего всю цепочку.

Затем он делает проверку, для Tx2 это будет:

  
  
  
   

hash(Hash01, hash(Hash2, Hash3)) = Root Hash

Полученный результат сравнивается с корнем заголовка блока.

Такой подход делает невозможным фальсификацию доказательства, поскольку в этом случае результат проверки не будет соответствовать содержимому заголовка.

Какие реализации уже существуют? Rust — молодой язык, но на нем уже написано множество реализаций дерева Меркла.

Судя по Github, на данный момент их 56, что больше, чем в C и C++ вместе взятых.

Хотя Go их выдерживает с помощью 80 реализаций.



SpinResearch/merkle.rs

Для сравнения я выбрал эту реализацию, исходя из количества звезд в репозитории.

Это дерево строится самым очевидным образом, т. е.

представляет собой граф объектов.

Как я уже отмечал, этот подход не совсем дружелюбен к Rust. Например, невозможно сделать двустороннюю связь от детей к родителям.

Построение доказательства происходит посредством поиска в глубину.

Если лист с нужным хешем найден, то результатом будет путь к нему.



Что можно улучшить

Мне не хотелось делать простую (n+1)-ю реализацию, поэтому я задумался об оптимизации.

Мой код векторное дерево Меркла расположен Гитхаб .



Хранилище данных

Первое, что приходит на ум — перенести дерево в массив.

Это решение будет намного лучше с точки зрения локальности данных: больше попаданий в кэш и лучшая предварительная загрузка.

Обход объектов, разбросанных по памяти, занимает больше времени.

Удобен тот факт, что все хеши имеют одинаковую длину, потому что.

рассчитываются по одному алгоритму.

Дерево Меркла в массиве будет выглядеть так:

Дерево Меркла: ржавое и быстрое



Доказательство

При инициализации дерева вы можете создать HashMap с индексами всех листьев.

Таким образом, когда листа нет, не нужно обходить все дерево, а если есть, то можно сразу подойти к нему и подняться до корня, строя доказательство.

В своей реализации я сделал HashMap необязательным.



Конкатенация и хеширование

Казалось бы, что здесь можно улучшить? Все уже понятно — берем два хеша, склеиваем их и вычисляем новый хеш.

Но дело в том, что эта функция некоммутативна, т.е.

hash(H0, H1) ≠ hash(H1, H0).

Из-за этого при построении доказательства нужно помнить, с какой стороны находится соседний узел.

Это усложняет реализацию алгоритма и добавляет необходимость хранить дополнительные данные.

Это очень легко исправить, просто отсортируйте два узла перед хешированием.

Для примера я дал Тх2, с учетом коммутируемости проверка будет выглядеть так:

hash(hash(Hash2, Hash3), Hash01) = Root Hash

Когда вам не нужно беспокоиться о порядке, алгоритм проверки выглядит как простое свертывание массива:

pub fn validate(&self, proof: &Vec<&[u8]>) -> bool { proof[2.].

iter() .

fold( get_pair_hash(proof[0], proof[1], self.algo), |a, b| get_pair_hash(a.as_ref(), b, self.algo) ).

as_ref() == self.get_root() }

Нулевой элемент — это хеш искомого объекта, первый — его сосед. Пойдем! История была бы неполной без сравнения производительности, которое заняло у меня в несколько раз больше времени, чем реализация самого дерева.

Для этих целей я использовал фреймворк критерий .

Исходники самих тестов находятся здесь .

Все тестирование происходит через интерфейс ДеревоОбертка , который был реализован для обоих испытуемых:

pub trait TreeWrapper<V> { fn create(&self, c: &mut Criterion, counts: Vec<usize>, data: Vec<Vec<V>>, title: String); fn find(&self, c: &mut Criterion, counts: Vec<usize>, data: Vec<Vec<V>>, title: String); fn validate(&self, c: &mut Criterion, counts: Vec<usize>, data: Vec<Vec<V>>, title: String); }

Оба дерева работают с одной и той же криптографией.

кольцо .

Я не собираюсь здесь сравнивать разные библиотеки.

Я взял самый обычный.

В качестве объектов хеширования используются случайно сгенерированные строки.

Деревья сравниваются на массивах разной длины: (500, 1000, 1500, 2000, 2500, 3000).

2500–3000 — примерное количество транзакций в блоке Биткойн на данный момент. На всех графиках ось X — количество элементов массива (или количество транзакций в блоке), а ось Y — среднее время выполнения операции в микросекундах.

То есть чем больше, тем хуже.



Создание дерева

Хранение всех данных дерева в одном массиве значительно превосходит граф объектов.

Для массива из 500 элементов это в 1,5 раза, а для 3000 элементов уже в 3,6 раза.

Отчетливо виден нелинейный характер зависимости сложности от количества входных данных в стандартной реализации.

Я также добавил для сравнения два варианта векторного дерева: с Хэшмап и без этого.

Заполнение дополнительной структуры данных добавляет около 20%, но позволяет гораздо быстрее искать объекты при построении доказательства.



Дерево Меркла: ржавое и быстрое



Построение доказательства

Это показывает очевидную неэффективность поиска в глубину.

С увеличением входных данных становится только хуже.

Важно понимать, что искомые объекты представляют собой листы, поэтому сложность журнал (п) Не может быть.

Если данные предварительно хешируются, то время операции практически не зависит от количества элементов.

Без хеширования сложность растет линейно и представляет собой перебор методом перебора.



Дерево Меркла: ржавое и быстрое



Проверка доказательств

Это последняя операция.

Оно не зависит от структуры дерева, т.к.

работает с результатом построения доказательства.

Я считаю, что основная трудность здесь — вычисление хеша.



Дерево Меркла: ржавое и быстрое

Нижняя граница

  • Способ хранения данных оказывает большое влияние на производительность.

    Когда все находится в одном массиве, это происходит намного быстрее.

    И сериализовать такую структуру будет очень легко.

    Общий объем кода также уменьшается.

  • Объединение узлов с сортировкой значительно упрощает код, но не делает его быстрее.

Немного о Русте
  • мне понравилась основа критерий .

    Предоставляет четкие результаты со средними значениями и отклонениями, дополненными графиками.

    Возможность сравнивать разные реализации одного и того же кода.

  • Отсутствие наследства не сильно мешает жить.

  • Макросы — мощный механизм.

    я использовал их в тесты вашего дерева для параметризации.

    Я думаю, если ими плохо пользоваться, можно сделать что-то, что не понравится.

  • В некоторых местах компилятор утомлял проверками памяти.

    Мое первоначальное предположение, что начать писать на Rust будет не так-то просто, оказалось верным.



    Дерево Меркла: ржавое и быстрое

Ссылки Теги: #Алгоритмы #программирование #Высокая производительность #Rust #блокчейн #структуры данных #структуры данных #оптимизация производительности #инженерия программного обеспечения #дерево Меркла
Вместе с данным постом часто просматривают: