Хабрахабр

Реестр пакетов на Ethereum

В качестве примера возьмем реестр пакетов наподобие npm только использующий цифровую подпись, децентрализованное хранилище Swarm и смарт-контракты на основе Ethereum. Сегодня только ленивый не запускает очередной бесполезный проект на блокчейне, в этом уроке я расскажу как сделать что-то имеющее практическое применение.

Внимание Это ознакомительная версия контракта, требующая аудита.

Мотивация

Реестр кода на блокчейне имеет ряд приемуществ перед обычным, это:

  • подтверждение цифровой подписью.
  • глобальный доступ.
  • токенизация

Рассмотрим каждый из пунктов по отдельности:

Подтверждение цифровой подписью

Код должен быть подписан разработчиком, которому вы доверяете. Это важный момент в построении доверенной среды. Сегодня при желании любой крупный репозиторий может быть атакован, а код в нем подменен, узнать об этом вы вряд ли сможете. В случае с блокчейном это происходит автоматически. Блокчейн репозиторий защищает нас от подмены путем хранения подтвержденной хэш-суммы.

Глобальный доступ

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

Токенизация

Сегодня все учетные записи и данные в большинстве случаев регистрируются на сотрудников, как следствие при увольнении передача прав на такие ресурсы может стать проблемой. Управлять и владеть пакетом может один или несколько участников или даже организация.

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

Технологии

Для тех кто не знает Swarm – это децентрализованное файловое хранилище наподобие ipfs, только от разработчиков Ethereum. Для внедрения такого решения будем использовать смарт-контракт на блокчейне Ethereum и распределенную файловую систему Swarm. Сразу замечу, что запуск Swarm запланирован на 4 квартал этого года и код может немного измениться, но общая концепция останется прежней.

Каждая директория содержит манифест – список всех файлов в директории. Файлы в swarm хранятся в виде директорий. Каждой директории присваивается bzz-адрес, который является вершиной дерева Меркеля для файлов перечисленных в манифесте. Манифест это по сути JSON с массивом путей к файлам и их хешами. Для идентификации файлов используется хеш-сумма защищенная от атаки удинением сообщения, для этого перед взятием хэш-суммы в начало данных добавляется значение длинны в виде 64-битного числа с порядком байт от младшего к старшему.

Алгоритм

  1. Пакет регистрируется (по имени) смарт-контрактом, получаем хеш-имя.
  2. Код загружается в swarm, получаем bzz-адрес.
  3. Регистрируется новая версия пакета в смарт-контракте с полученным bzz-адресом.

Реализация

Модель данных

не поддерживаются). Хранение версии ведется по принципам semver без именованных веток (alpha, rc0 и т.п. Выглядит это так: Все пакеты хранятся в списке, где ключ — имя пакета, а значение – дерево версий.

// Пакет
struct Package { address owner; // Последний мажорная версия uint8 latestMajor; // Список всех мажорных версия mapping(uint8 => Major) majors;
} // Мажорная версия пакета, содержит все минорные.
struct Major { // Последняя минорная версия uint8 latestMinor; // Список минорных версий mapping(uint8 => Minor) minors;
} // Минорная версия пакета, содержит все билды.
struct Minor { // Последний минорный билд uint16 latestBuild; // Список билдов. mapping(uint16 => Build) builds;
} // Собственно номер билда и указание bzz-адреса.
struct Build { // bzz-адрес bytes32 bzz; // Флаг bool isPublished;
}

Память контракта

Для работы контракта используются три списка:

// Список пакетов.
mapping(bytes32 => Package) packages; // Сопоставление имен и хешей для внешних инструментов.
mapping(bytes32 => bytes) names; // Адреса получателей трансферов.
mapping(bytes32 => address) transfers;

Регистрация пакета

Хеширование имени нужно для того, чтобы работа с пакетом, имеющим название любой длинны всегда занимала одинаковое время/потребляла одинаковое количество газа: Регистрация пакета нужна для того, чтобы связать название пакета и владельца, а так же конвертировать имя в хеш.

function register(bytes _name) public returns(bytes32)
{ // Убеждаемся, что имя имеет ненулевую длинну require(_name.length > 0); // Конвертируем имя в хеш bytes32 name = resolve(_name); // Проверяем, что пакет не имеет владельца require(packages[name].owner == address(0)); // Регистрируем пакет packages[name] = Package(msg.sender, 0); // Заносим имя в список для обратного разрешения имен names[name] = _name; return name;
}

Вызов из JS:

const Web3 = require('web3');
const = require('./contract.js'); const web3 = new Web3(new Web3('https://rinkeby.infura.io/')); // Initialize contract instance
const reg = new web3.eth.Contract(abi, '0x57147069B117fD911Da6c43F3fBdC54a7A7D8C1d'); reg.methods.register('hello_world').send()
.then((hash) => console.log(hash));

Загрузка в Swarm

Данные в Sqarm загружаются по протоколу HTTP, при этом для загрузки директории можно поместить файлы в tar-архив:

tar -c * | curl -H "Content-Type: application/x-tar" --data-binary @- http://localhost:8500/bzz:/

В результате получим 32-битную вершину дерева Меркеля (bzz-адрес), например:

1e0e21894d731271e50ea2cecf60801fdc8d0b23ae33b9e808e5789346e3355e

Для получения файлов из swarm необходимо получить список файлов:

curl -s http://localhost:8500/bzz-list:/ccef599d1a13bed9989e424011aed2c023fce25917864cd7de38a761567410b8/ | jq .
> { "common_prefixes": [ "dir1/", "dir2/", "dir3/" ], "entries" : [ { "path": "file.txt", "contentType": "text/plain", "size": 9, "mod_time": "2017-03-12T15:19:55.112597383Z", "hash": "94f78a45c7897957809544aa6d68aa7ad35df695713895953b885aca274bd955" } ] }

Для экспериментов можете использовать сайт https://swarm-gateways.org/.

Регистрация новой версии

Для регистрации новой версии достаточно вызвать метод register с указанием хеша имени, номеров версии (мажорный, минорный, билд) и bzz-адреса.

function publish(bytes32 _package, uint8 _major, uint8 _minor, uint16 _build, bytes32 _bzz)
public
{ Package storage package = packages[_package]; // Проверка парва владения. require(package.owner == msg.sender); // Проверка версии на уникальность require(hasBuild(_package, _major, _minor, _build) == false); // Объявляем необходимые структуры package.majors[_major].minors[_minor].builds[_build] = Build(_bzz, true); Major memory major = package.majors[_major]; Minor memory minor = package.majors[_major].minors[_minor]; // Обновление значения последней мажорной версии if (package.latestMajor < _major) { package.latestMajor = _major; } // Обновление значения последней минорной версии для мажроной версии. if (major.latestMinor < _minor) { package.majors[_major].latestMinor = _minor; } // Обновление значения последннего билда минорной версии. if (minor.latestBuild < _build) { package.majors[_major].minors[_minor].latestBuild = _build; } emit Published(_package, _major, _minor, _build);
}

reg.methods.publish(hash, 0, 1, 0, bzz).send();

Передача прав

Метод transfer назначает нового владельца, после чего новый владелец может получить пакет вызвав метод receive. Как уже говорилось выше право владения пакетом можно передавать, методы реализующие этот функционал: transfer и receive.

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

Методы контракта

  • register — регистрирует контракт.
  • resolve — конвертирует имя в хеш.
  • publish — регистрация новой версии. Генерирует событие Published.
  • unpublish — отзыв версии. Генерирует событие Unpublished.
  • getOwner — возвращает текущего владельца пакета.
  • hasBuild — возвращает статус последнего опубликованного билда.
  • isPublished — возвращает статус версии.
  • getLatestMajorVersion — получение номера последнего мажроной версии пакета.
  • getLatestMinorVersion — получение номера последнего минорной версии пакета для указанной мажорной версии.
  • getLatestBuildVersion — получение номера последнего билда пакета для указанной минорной версии.
  • transfer — передача пакета новому владельцу.
  • receive — получение пакета новым владельцем.

Ограничения

Отозвать возможно только определенный билд. Данная схема исключает возможность отзыва мажорных или минорных версии. Для этого предварительно необходимо опубликовать новый, а затем отозвать предыдущий билд. Так же нельзя отзывать последний билд минорной версии. Это исключает необходимость перебирать дерево версий для переназначения новой latest версии и таким образом сокращает количество потребляемого газа.

Заключение

А за счет применения блокчейна и распределенной структуры, изменения в вашем коде гарантированно достигнут пользователей. В таком виде все версии пакетов становятся глобально идентифицируемы по хешу, что позволяет, для примера, привязывать номера CVE к определенным версиям кода.

Если есть идеи, как улучшить данный код, оставляйте issues или PR, следите за изменениями, ну и ставьте звезды, буду признателен.

Ссылки

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»