Как стать автором
Обновить

Что нам стоит блокчейн построить?

Время на прочтение 29 мин
Количество просмотров 33K
Вся история человечества — это непрерывное избавление от цепей и создание новых, еще более крепких. (Анонимный автор)

Анализируя многочисленные blockchain проекты (Bitshares, Hyperledger, Exonum, Ethereum, Bitcoin и др.), я понимаю, что с технической точки зрения все они построены по одним принципам. Блокчейны напоминают дома, у которых при всем разнообразии конструкций, декора и назначений имеются фундамент, стены, крыша, окна, двери, которые связаны друг с другом определенными способами. И если понять основные принципы проектирования зданий, знать свойства применяемых материалов, то можно определить целевое назначение конкретного дома. В настоящее время с блокчейном возникла ситуация, что все про него слышали, но мало кто понимает архитектуру и принципы работы. Поэтому возникает непонимание для чего и как имеет смысл использовать технологии блокчейна.

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

Итак, давайте вспомним какие проблемы изначально решил блокчейн.

Уверен, что многие скажут про распределенную, децентрализованную, публичную и неизменяемую базу данных. Но зачем это все было нужно?

Я предпочитаю начинать изучение любой технологии с чтения стандартов, так как именно на них основываются все статьи и книги по исследуемой теме. Но стандарты блокчейна в настоящее время отсутствуют, в ISO созданы только комитеты для их разработки. На текущий момент в каждом публичном блокчейн проекте имеется свой документ White paper, который по сути является техническим заданием. Первый общеизвестный блокчейн проект — это сеть Bitcoin. Идем на официальный сайт сети и смотрим с чего все начиналось.

Задача блокчейна


Итак, задача, которую решил блокчейн в сети пионере Bitcoin — это совершение доверительной передачи собственности на цифровые активы (assets) в недоверительной среде без посредников. Например, в сети Bitcoin цифровой актив — это цифровые монеты bitcoin. И все технические решения Bitcoin и других блокчейнов сводятся к решению этой задачи.

Проблемы, которые решает блокчейн


Предположим некая финансовая организация говорит, что построила сеть по всему миру, с помощью которой можно переводить деньги любому человеку. Поверите ли вы ей? Если эта организация Visa или MasterCard, скорее всего, поверите, а если, условно говоря, AnonymousWorldMoney, наверное, нет. Почему же? А потому, что мы прекрасно знаем, как делаются распределенные системы частными компаниями, с какими целями, и к чему это может привести. Рассмотрим подробнее проблемы таких систем, и как они могут быть решены с применением технологий блокчейна.

Допустим, в условной AnonymousWorldMoney стоят сервера с базами данных, и хорошо, если их будет несколько в разных дата-центрах. Когда отправитель переводит деньги, регистрируется транзакция, которая реплицируется на все сервера, и деньги доходят до получателя.

image

В идеальном мире такая схема прекрасно работает, в нашем же возникают следующие проблемы:

  1. Проблема идентификации участников с одной стороны и необходимость анонимности транзакций с другой. Т.е. надо перевести деньги конкретному получателю и так, чтобы об этой транзакции никто не знал, кроме участников сделки. У банков есть номера счетов и банковских карт, привязанных к конкретному физическому или юридическому лицу, а банковская тайна защищает информацию о транзакциях. А кто гарантирует, что условная AnonymousWorldMoney не использует персональные данные и информацию о транзакциях в своих целях?
  2. Как убедиться, что получатель получил именно ту сумму, которую ему перевели? Условно говоря, отправитель перевел $100, а получатель получил $10. Приходит отправитель в офис AnonymousWorldMoney со своей квитанцией, а клерк показывает свою версию, где записано, что отправитель перевел только $10.
  3. Проблема недоверительной среды, например, мошенничество, называемое double-spending. Недобросовестный участник может потратить свой баланс несколько раз, пока платеж не реплицировался на все сервера. CAP теорему, конечно, никто не отменял, и согласованность в конечном итоге будет достигнута, но кто-то не получит деньги за оказанные услуги или товары. Поэтому, если нет полного доверия к платежной организации или участникам сделок, то надо строить сеть, основанную не на доверии, а на криптографии.
  4. Условная AnonymousWorldMoney имеет конечное число серверов, которые могут стать недоступными непредумышленно или по злому умыслу.
  5. AnonymousWorldMoney возьмет свою ощутимую комиссию.
  6. Возможность управления. В процессе эксплуатации Bitcoin выяснилось, что люди хотят не только переводить монеты друг другу, но и проверять различные условия прохождения транзакции, программировать сценарии работы, автоматически выполнять действия в зависимости от условий и т.д.

Как блокчейн решает эти проблемы


  1. Идентификация участников осуществляется с помощью пары ключей: приватного и открытого, а алгоритм цифровой подписи однозначно идентифицирует отправителя и получателя, оставляя их личности анонимными.
  2. Транзакции собираются в блоки, вычисляется хеш блока, который записывается в следующий блок. Такая последовательность записи хешей в блоках и дала название технологии blockchain, и она же делает невозможным незаметное изменение / удаление блоков или отдельных транзакций из блоков. Таким образом, если транзакция попала в блокчейн, то можно быть уверенным, что ее данные останутся неизменными.
  3. Мошенничество double-spending предотвращается путем достижения консенсуса в сети, какие данные считать верными, а какие отбрасывать. В сети Bitcoin консенсус достигается доказательством выполнения работы PoW (Proof-of-Work).
  4. Надежность функционирования сети достигается тем, что блокчейн является публичным, где каждый участник может запустить свою ноду, получить полную копию блокчейна и, более того, самостоятельно начать проверять транзакции на правильность. Надо отметить, что современные блокчейны позволяют строить не только публичные (открытые), но и частные (закрытые) блокчейны, а также использовать комбинированные схемы.
  5. Полностью от комиссии в блокчейне не избавится, т.к. надо платить людям поддерживающим сеть, но в блокчейне необходимость комиссии доказывается так убедительно, что не остается сомнений в ее необходимости.
  6. Современные блокчейны имеют возможность реализовывать бизнес логику, которая в блокчейне называется Smart Contracts. Логика смарт контрактов реализуются на различных языках высокого уровня.

Далее рассмотрим эти решения подробнее.

Архитектура блокчейна


Составные части блокчейна


Каждый участник может запустить свою ноду с полной копией блокчейна (full node). Полные ноды, которые могут записывать транзакции в блокчейн, называются узлами консенсуса (witness) или майнерами (miner). Полные ноды, которые только проверяют правильность транзакций называются узлами аудита (audit). Легкие клиенты (light clients) не хранят полных копий блокчейна, а взаимодействуют с сетью, используя полные ноды.
Большинство пользователей для совершения транзакций используют именно легких клиентов или web кошельки. Все ноды связаны друг с другом. При таком наборе элементов архитектура сети становится более устойчивой:

image

Жизненный цикл транзакции


Посмотрим на жизненный цикл транзакции и разберем его по частям:

image

Технологии блокчейна


Остановимся подробнее на технических решениях и их связях друг с другом.

Идентификация


Каждая блокчейн транзакция должна быть подписана цифровой подписью. Поэтому для совершения транзакции каждый участник должен иметь пару ключей: private / public. Иногда пару ключей называют кошелек (wallet), т.к. ключи однозначно связаны с уникальным цифровым адресом и балансом участника. В реальности ключи и адреса — это просто строки цифр в разных системах счисления. Примеры ключей и адреса кошелька:

Private key: 0a78194a8a893b8baac7c09b6a4a4b4b161b2f80a126cbb79bde231a4567420f
Public key: 0579b478952214d7cddac32ac9dc522c821a4489bc10aac3a81b9d1cd7a92e57ba
Address: 0x3814JnJpGnt5tB2GD1qfKP709W3KbRdfb27V

Для создания цифровой подписи в блокчейнах используется алгоритм, основанный на эллиптических кривых: Elliptic Curve Digital Signature Algorithm (ECDSA). Для его работы приватный ключ (256 битное число), обычно, берется случайно. Число вариантов ключей составляет 2 в степени 256, поэтому можно говорить о практической невозможности совпадения значений приватных ключей.

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

Есть масса статей с подробностями по криптографии, используемой в блокчейне, например: Bitcoin in a nutshell — Cryptography

Приватный ключ должен быть строго секретен и храниться в безопасности. Публичный ключ известен всем. При утере приватного ключа доступ к активу (монетам) восстановить невозможно и деньги будут утеряны навсегда. Поэтому задача надежного хранения приватных ключей крайне актуальна, т.к. это не банк, куда всегда можно прийти с паспортом и восстановить счет. Существует целая индустрия по производству, так называемых, холодных криптокошельков, похожих на флешки:

image

или можно использовать более надежные способы, как пример, выбивать значение приватного ключа на жетонах:

image

Транзакции


Подробнее про структуру транзакции можно посмотреть в статье Bitcoin in a nutshell — Transaction. Нам же важно понимать, что каждая транзакция имеет по крайней мере следующие данные:

From: 0x48C89c341C5960Ca2Bf3732D6D8a0F4f89Cc4368 - цифровой адрес отправителя
To: 0x367adb7894334678b90аfe7882a5b06f7fbc783a - цифровой адрес получателя
Value: 0.0001 - сумма транзакции
Transaction Hash: 0x617ede331e8a99f46a363b32b239542bb4006e4fa9a2727a6636ffe3eb095cef - хэш транзакции

Далее транзакция подписывается приватным ключом и рассылается (см. подробности по работе протокола Bitcoin in a nutshell-Protocol) всем нодам в блокчейне, которые проверяют транзакции на валидность. Алгоритм проверки транзакции нетривиален и включает два десятка шагов.

Блоки транзакций


Проверив валидность транзакций, ноды формируют из них блоки. Помимо транзакций в блок записывается хеш предыдущего блока, число (счетчик Nonce), и происходит вычисление хеша текущего блока по алгоритму SHA-256. Хеш должен обладать установленным условиям сложности. Например, в сети Bitcoin сложность хеша автоматически меняется раз в 2 недели в зависимости от мощности сети так, чтобы блок генерировался примерно раз в 10 минут. Сложность определятся следующим условием: найденный хеш должен быть меньше заранее заданного числа. Если данное условие не выполняется, то к Nonce прибавляется 1, и работа по вычислению хеша повторяется. Для подбора хеша используется поле Nonce, т.к. это единственные данные в блоке, которые можно изменить, остальные должны оставаться неизменными. Правильный хеш должен иметь определенное число нулей в начале, например, один из реальных хешей:

000000000000000000000bf03212e7dd1176f52f816fa395fc9b93c44bc11f91

Успешное нахождение хеша и является доказательством проделанной работы (Proof-of-Work, PoW) для сетей Bitcoin или Ethereum. Процесс нахождения хешей называется майнингом (mining), по аналогии с добычей золота. Название достаточно точно определяет суть процесса, т.к. происходит простой перебор вариантов, и если кто-то нашел подходящий хеш, то это действительно удача. Это как найти реальный самородок золота в тоннах пустой породы. Вознаграждение за блок сейчас составляет 12.5 BTC и если умножить на актуальный курс биткоина $3900, то получается больше килограмма чистого золота. Есть за что побороться!

После успешного нахождения хеша блок и сам найденный хеш записываются в блокчейн следующим блоком. Подробнее по структуру блоков можно посмотреть в статье Bitcoin in a nutshell-Blockchain, а ниже приведу упрощенную схему:

image

Блокчейн начинается с блока, у которого еще нет хеша предыдущего блока. Такой блок в блокчейне один и имеет собственное название Genesis block. У остальных блоков одинаковая структура и отличаются они только числом транзакций. Реальные транзакции и блоки создающиеся в настоящее время в Bitcoin или Ethereum можно смотреть в Block Explorer.

Размер блоков в Bitcoin ограничен 1Мб и при минимальном объеме информации в транзакции около 200 байт, максимально в блоке может быть около 6000 транзакций. Отсюда, кстати, и следует производительность Bitcoin, над которой все смеются: блок генерируется примерно раз в 10 мин * 60 сек = 600 сек, что и дает формальную производительность около 10 TPS. Хотя на самом деле — это не производительность, а сознательно реализованный алгоритм работы. В Ethereum для конкуренции просто сделали время генерации блока 15 сек. и производительность формально взлетела. Поэтому в блокчейнах, использующих PoW в качестве консенсуса вообще бессмысленно сравнивать производительность, т.к. она напрямую зависит от сложности вычисления кеша, которую можно назначить любую.

Форки


А что происходит, если, например, несколько узлов нашли хеши удовлетворяющие условиям сложности, но разные по значению (иными словами, пришли к разным консенсусам) и записали блоки в блокчейн? Давайте посмотрим, как блокчейн защищается от данной ситуации. В этом случае происходит, так называемый, форк ('вилка'), и блокчейн имеет две версии цепочки:

image

Что происходит далее? Далее часть сети начинает работать над блоком N+2 от одной цепочки, а часть от другой:

image

Какой-то из этих блоков будет найден раньше и отправлен в блокчейн и тогда по правилам блокчейн должен будет переключиться на более длинную цепочку и отменить все транзакции из альтернативного блока:

image

При этом, может сложиться ситуация, когда транзакция участника находилась только в одном из блоков форка, который и был отменен. Поэтому, чтобы быть уверенным, что нужная транзакция записалась в блокчейн, есть общая рекомендация — прежде чем доверять транзакции надо подождать пока следующие несколько блоков не будут добавлены в блокчейн. Рекомендации, сколько блоков ждать для разных блокчейнов различаются. Например, для сети Bitcoin минимум — это 2 блока, максимум 6.

Такая же картина с форком блоков будет наблюдаться и при, так называемой, атаке 51% — это когда группа майнеров будет пытаться вырастить альтернативную цепочку блоков, добиваясь отмены цепочки cо своими мошенническими транзакциями. Хотя в настоящее время, вместо мошенничества, выгоднее тратить свои мощности на честный майнинг.

Консенсус


Для записи блока в блокчейн сеть должна прийти к консенсусу. Давайте вспомним, задачу достижения консенсуса в компьютерных сетях связи. Проблема формулируется, как задача византийских генералов BFT (Byzantine fault tolerance). Опуская живописное описание проблем византийской армии, задачу можно сформулировать так: как узлам сети прийти к общему результату, если часть узлов сети могут сознательно их искажать. Существующие алгоритмы решения задачи BFT показывают, что сеть может функционировать правильно, если мошенников меньше 1/3. Почему в сети Bitcoin не был применен консенсус BFT? Зачем нужно было использовать PoW? Есть несколько причин:

  • BFT хорошо работает с небольшим фиксированным набором нод, а в публичном блокчейне число нод непредсказуемо и, более того, ноды могут произвольно включаться-выключаться.
  • Необходимо мотивировать людей запускать блокчейн ноды. Для этого люди должны получать вознаграждение. В BFT формально не за что получать вознаграждение, а вот за что идет вознаграждение в PoW всем понятно на интуитивном уровне: за электричество, потребленное процессором в процессе нахождения хеша блока.

Помимо PoW существует еще несколько консенсусов, которые используются в современных блокчейнах, например:

  • PoS (Proof-of-Stake) — в блокчейне Hyperledger
  • DPoS (Delegated Proof-of-Stake) — в блокчейне BitShares
  • Модификации BFT: SBFT ( Simplified BFT ) и PBFT ( Practical BFT ), например, в блокчейне Exonum

Немного остановимся на консенсусе PoS, т.к. именно PoS и его разновидности получили наибольшее распространение в приватных блокчейнах. Почему именно в приватных? С одной стороны, характеристики PoS лучше по сравнению с PoW, т.к. для достижения консенсуса нужны меньшие вычислительные ресурсы, а значит увеличивается скорость записи данных в блокчейн. Но с другой стороны в PoS больше возможностей для мошенничества, поэтому для нейтрализации этого все участники блокчейна должны быть известны.

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

  • Консенсус Delegated PoS (DPoS) разделяет участников на «голосующие» и «валидирующие». Держатели монет (голосующие участники) делегируют свое право проверять и записывать транзакции в блокчейн другим участникам. Таким образом, валидаторы выполняют всю вычислительную работу и получают за это вознаграждение, а наличие голосующих участников гарантирует честность валидаторов, т.к. их можно сменить в любой момент.
  • Консенсус LPoS (Leased Proof-of-Stake) позволяет отдать свои средства в аренду другим узлам, чтобы те имели больше шансов для проверки блоков. Т.о. можно получать комиссию за транзакции, при этом не участвуя в самой проверке транзакций и майнинге блоков.

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

  • PoET ( Proof-of-Elapsed Time )
  • PoC ( Proof-of-Capacity )
  • PoB ( Proof-of-Burn )
  • PoWeight ( Proof-of-Weight )
  • PoA ( Proof-of-Activity ) — PoW + PoS
  • PoI ( Proof-of- Importans )

Надежность и модели развертывания блокчейнов


Публичный блокчейн

Устойчивость Public или другое название Permissionless blockchain достигается тем, что каждый может подключиться и просмотреть информацию или даже подключить свой узел, а доверие строится на консенсусе PoW.

Приватный блокчейн

Private или Private Permissioned blockchain. В этих блокчейнах только определенная группа участников (организаций или людей) имеет доступ к информации. Такие блокчейны строят организации с целью увеличения общей выгоды или эффективности. Их надежность обеспечивается общими целями участников и алгоритмами консенсуса PoS и BFT.

Блокчейн-консорциум

Существуют Consortium или Public Permissioned blockchain. Это такие блокчейны, к которым каждый может подключиться для просмотра, но добавлять информацию или подключить свой узел участник может только с разрешения других участников. Такие блокчейны строят организации с целью повышения доверия со стороны заказчиков или потребителей продукции или общества в целом. Здесь надежность также достигается присутствием доверия между участниками и теми же алгоритмами консенсуса PoS и BFT.

Smart Contracts


В блокчейны, реализованные после Bitcoin, в той или степени добавлена возможность выполнения смарт-контрактов. По сути смарт-контракт — это транзакция, в которой помещен программный код для выполнения. Смарт-контракты в сети Ethereum выполняются в EVM (Ethereum Virtual Machine). Для начала выполнения смарт-контракта его надо явно запустить другой транзакцией, или должно выполниться предусловия для выполнения. Результаты выполнения смарт-контракта также запишутся в блокчейн. Получение данных извне блокчейна возможно, но крайне ограничено.

Какую бизнес логику можно реализовать с помощью смарт-контракта? На самом деле не так уж много, например проверка условий по данным из блокчейна, изменение собственников цифровых активов в зависимости от этих условий, запись данных в постоянное хранилище внутри блокчейна. Реализуется логика на специальном языке высокого уровня Solidity.

Классическим примером функциональности, которую реализуют с использованием смарт-контрактов — это выпуск токенов для проведения ICO. Например, мной был реализован смарт-контракт на выпуск скромных 500 000 000 AlexToken. По ссылке в Etherscan находится

исходный код смарт-контракта на языке Solidity
pragma solidity ^0.4.23;

library SafeMath {
    /**
     * @dev Multiplies two numbers, throws on overflow.
     **/
    function mul(uint256 a, uint256 b) internal pure returns (uint256 c) {
        if (a == 0) {
            return 0;
        }
        c = a * b;
        assert(c / a == b);
        return c;
    }
    
    /**
     * @dev Integer division of two numbers, truncating the quotient.
     **/
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        // assert(b > 0); // Solidity automatically throws when dividing by 0
/**
 * @title SafeMath
 * @dev Math operations with safety checks that throw on error
 */
        // uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold
        return a / b;
    }
    
    /**
     * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
     **/
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        assert(b <= a);
        return a - b;
    }
    
    /**
     * @dev Adds two numbers, throws on overflow.
     **/
    function add(uint256 a, uint256 b) internal pure returns (uint256 c) {
        c = a + b;
        assert(c >= a);
        return c;
    }
}

/**
 * @title Ownable
 * @dev The Ownable contract has an owner address, and provides basic authorization control
 * functions, this simplifies the implementation of "user permissions".
 **/
 
contract Ownable {
    address public owner;
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev The Ownable constructor sets the original `owner` of the contract to the sender account.
     **/
   constructor() public {
      owner = msg.sender;
    }
    
    /**
     * @dev Throws if called by any account other than the owner.
     **/
    modifier onlyOwner() {
      require(msg.sender == owner);
      _;
    }
    
    /**
     * @dev Allows the current owner to transfer control of the contract to a newOwner.
     * @param newOwner The address to transfer ownership to.
     **/
    function transferOwnership(address newOwner) public onlyOwner {
      require(newOwner != address(0));
      emit OwnershipTransferred(owner, newOwner);
      owner = newOwner;
    }
}

/**
 * @title ERC20Basic interface
 * @dev Basic ERC20 interface
 **/
contract ERC20Basic {
    function totalSupply() public view returns (uint256);
    function balanceOf(address who) public view returns (uint256);
    function transfer(address to, uint256 value) public returns (bool);
    event Transfer(address indexed from, address indexed to, uint256 value);
}

/**
 * @title ERC20 interface
 * @dev see https://github.com/ethereum/EIPs/issues/20
 **/
contract ERC20 is ERC20Basic {
    function allowance(address owner, address spender) public view returns (uint256);
    function transferFrom(address from, address to, uint256 value) public returns (bool);
    function approve(address spender, uint256 value) public returns (bool);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

/**
 * @title Basic token
 * @dev Basic version of StandardToken, with no allowances.
 **/
contract BasicToken is ERC20Basic {
    using SafeMath for uint256;
    mapping(address => uint256) balances;
    uint256 totalSupply_;
    
    /**
     * @dev total number of tokens in existence
     **/
    function totalSupply() public view returns (uint256) {
        return totalSupply_;
    }
    
    /**
     * @dev transfer token for a specified address
     * @param _to The address to transfer to.
     * @param _value The amount to be transferred.
     **/
    function transfer(address _to, uint256 _value) public returns (bool) {
        require(_to != address(0));
        require(_value <= balances[msg.sender]);
        
        balances[msg.sender] = balances[msg.sender].sub(_value);
        balances[_to] = balances[_to].add(_value);
        emit Transfer(msg.sender, _to, _value);
        return true;
    }
    
    /**
     * @dev Gets the balance of the specified address.
     * @param _owner The address to query the the balance of.
     * @return An uint256 representing the amount owned by the passed address.
     **/
    function balanceOf(address _owner) public view returns (uint256) {
        return balances[_owner];
    }
}

contract StandardToken is ERC20, BasicToken {
    mapping (address => mapping (address => uint256)) internal allowed;
    /**
     * @dev Transfer tokens from one address to another
     * @param _from address The address which you want to send tokens from
     * @param _to address The address which you want to transfer to
     * @param _value uint256 the amount of tokens to be transferred
     **/
    function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
        require(_to != address(0));
        require(_value <= balances[_from]);
        require(_value <= allowed[_from][msg.sender]);
    
        balances[_from] = balances[_from].sub(_value);
        balances[_to] = balances[_to].add(_value);
        allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
        
        emit Transfer(_from, _to, _value);
        return true;
    }
    
    /**
     * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
     *
     * Beware that changing an allowance with this method brings the risk that someone may use both the old
     * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this
     * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     * @param _spender The address which will spend the funds.
     * @param _value The amount of tokens to be spent.
     **/
    function approve(address _spender, uint256 _value) public returns (bool) {
        allowed[msg.sender][_spender] = _value;
        emit Approval(msg.sender, _spender, _value);
        return true;
    }
    
    /**
     * @dev Function to check the amount of tokens that an owner allowed to a spender.
     * @param _owner address The address which owns the funds.
     * @param _spender address The address which will spend the funds.
     * @return A uint256 specifying the amount of tokens still available for the spender.
     **/
    function allowance(address _owner, address _spender) public view returns (uint256) {
        return allowed[_owner][_spender];
    }
    
    /**
     * @dev Increase the amount of tokens that an owner allowed to a spender.
     *
     * approve should be called when allowed[_spender] == 0. To increment
     * allowed value is better to use this function to avoid 2 calls (and wait until
     * the first transaction is mined)
     * From MonolithDAO Token.sol
     * @param _spender The address which will spend the funds.
     * @param _addedValue The amount of tokens to increase the allowance by.
     **/
    function increaseApproval(address _spender, uint _addedValue) public returns (bool) {
        allowed[msg.sender][_spender] = allowed[msg.sender][_spender].add(_addedValue);
        emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
        return true;
    }
    
    /**
     * @dev Decrease the amount of tokens that an owner allowed to a spender.
     *
     * approve should be called when allowed[_spender] == 0. To decrement
     * allowed value is better to use this function to avoid 2 calls (and wait until
     * the first transaction is mined)
     * From MonolithDAO Token.sol
     * @param _spender The address which will spend the funds.
     * @param _subtractedValue The amount of tokens to decrease the allowance by.
     **/
    function decreaseApproval(address _spender, uint _subtractedValue) public returns (bool) {
        uint oldValue = allowed[msg.sender][_spender];
        if (_subtractedValue > oldValue) {
            allowed[msg.sender][_spender] = 0;
        } else {
            allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue);
        }
        emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
        return true;
    }
}

/**
 * @title Configurable
 * @dev Configurable varriables of the contract
 **/
contract Configurable {
    uint256 public constant cap = 1000000000*10**18;
    uint256 public constant basePrice = 100*10**18; // tokens per 1 ether
    uint256 public tokensSold = 0;
    
    uint256 public constant tokenReserve = 500000000*10**18;
    uint256 public remainingTokens = 0;
}

/**
 * @title CrowdsaleToken 
 * @dev Contract to preform crowd sale with token
 **/
contract CrowdsaleToken is StandardToken, Configurable, Ownable {
    /**
     * @dev enum of current crowd sale state
     **/
     enum Stages {
        none,
        icoStart, 
        icoEnd
    }
    
    Stages currentStage;
    /**
     * @dev constructor of CrowdsaleToken
     **/
    constructor() public {
        currentStage = Stages.none;
        balances[owner] = balances[owner].add(tokenReserve);
        totalSupply_ = totalSupply_.add(tokenReserve);
        remainingTokens = cap;
        emit Transfer(address(this), owner, tokenReserve);
    }
    
    /**
     * @dev fallback function to send ether to for Crowd sale
     **/
    function () public payable {
        require(currentStage == Stages.icoStart);
        require(msg.value > 0);
        require(remainingTokens > 0);
        
        uint256 weiAmount = msg.value; // Calculate tokens to sell
        uint256 tokens = weiAmount.mul(basePrice).div(1 ether);
        uint256 returnWei = 0;
        
        if(tokensSold.add(tokens) > cap){
            uint256 newTokens = cap.sub(tokensSold);
            uint256 newWei = newTokens.div(basePrice).mul(1 ether);
            returnWei = weiAmount.sub(newWei);
            weiAmount = newWei;
            tokens = newTokens;
        }
        
        tokensSold = tokensSold.add(tokens); // Increment raised amount
        remainingTokens = cap.sub(tokensSold);
        if(returnWei > 0){
            msg.sender.transfer(returnWei);
            emit Transfer(address(this), msg.sender, returnWei);
        }
        
        balances[msg.sender] = balances[msg.sender].add(tokens);
        emit Transfer(address(this), msg.sender, tokens);
        totalSupply_ = totalSupply_.add(tokens);
        owner.transfer(weiAmount);// Send money to owner
    }
    

    /**
     * @dev startIco starts the public ICO
     **/
    function startIco() public onlyOwner {
        require(currentStage != Stages.icoEnd);
        currentStage = Stages.icoStart;
    }
    

    /**
     * @dev endIco closes down the ICO 
     **/
    function endIco() internal {
        currentStage = Stages.icoEnd;
        // Transfer any remaining tokens
        if(remainingTokens > 0)
            balances[owner] = balances[owner].add(remainingTokens);
        // transfer any remaining ETH balance in the contract to the owner
        owner.transfer(address(this).balance); 
    }

    /**
     * @dev finalizeIco closes down the ICO and sets needed varriables
     **/
    function finalizeIco() public onlyOwner {
        require(currentStage != Stages.icoEnd);
        endIco();
    }
    
}
 
/**
 * @title LavevelToken 
 * @dev Contract to create the Lavevel Token
 **/
contract AlexToken is CrowdsaleToken {
    string public constant name = "AlexToken";
    string public constant symbol = "ALT";
    uint32 public constant decimals = 18;
}

и бинарное представление, как его видит сеть
60806040526000600355600060045533600560006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600560146101000a81548160ff021916908360028111156200006f57fe5b0217905550620001036b019d971e4fe8401e74000000600080600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546200024a6401000000000262000b1d179091906401000000009004565b600080600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550620001986b019d971e4fe8401e740000006001546200024a6401000000000262000b1d179091906401000000009004565b6001819055506b033b2e3c9fd0803ce8000000600481905550600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef6b019d971e4fe8401e740000006040518082815260200191505060405180910390a362000267565b600081830190508281101515156200025e57fe5b80905092915050565b611cb880620002776000396000f300608060405260043610610112576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146104c7578063095ea7b31461055757806318160ddd146105bc57806323b872dd146105e7578063313ce5671461066c578063355274ea146106a3578063518ab2a8146106ce57806366188463146106f957806370a082311461075e57806389311e6f146107b55780638da5cb5b146107cc578063903a3ef61461082357806395d89b411461083a578063a9059cbb146108ca578063bf5839031461092f578063c7876ea41461095a578063cbcb317114610985578063d73dd623146109b0578063dd62ed3e14610a15578063f2fde38b14610a8c575b60008060008060006001600281111561012757fe5b600560149054906101000a900460ff16600281111561014257fe5b14151561014e57600080fd5b60003411151561015d57600080fd5b600060045411151561016e57600080fd5b3494506101a7670de0b6b3a764000061019968056bc75e2d6310000088610acf90919063ffffffff16565b610b0790919063ffffffff16565b9350600092506b033b2e3c9fd0803ce80000006101cf85600354610b1d90919063ffffffff16565b111561024c576101f66003546b033b2e3c9fd0803ce8000000610b3990919063ffffffff16565b915061022e670de0b6b3a764000061022068056bc75e2d6310000085610b0790919063ffffffff16565b610acf90919063ffffffff16565b90506102438186610b3990919063ffffffff16565b92508094508193505b61026184600354610b1d90919063ffffffff16565b6003819055506102886003546b033b2e3c9fd0803ce8000000610b3990919063ffffffff16565b6004819055506000831115610344573373ffffffffffffffffffffffffffffffffffffffff166108fc849081150290604051600060405180830381858888f193505050501580156102dd573d6000803e3d6000fd5b503373ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef856040518082815260200191505060405180910390a35b610395846000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610b1d90919063ffffffff16565b6000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055503373ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef866040518082815260200191505060405180910390a361045184600154610b1d90919063ffffffff16565b600181905550600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc869081150290604051600060405180830381858888f193505050501580156104bf573d6000803e3d6000fd5b505050505050005b3480156104d357600080fd5b506104dc610b52565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561051c578082015181840152602081019050610501565b50505050905090810190601f1680156105495780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561056357600080fd5b506105a2600480360381019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610b8b565b604051808215151515815260200191505060405180910390f35b3480156105c857600080fd5b506105d1610c7d565b6040518082815260200191505060405180910390f35b3480156105f357600080fd5b50610652600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610c87565b604051808215151515815260200191505060405180910390f35b34801561067857600080fd5b50610681611041565b604051808263ffffffff1663ffffffff16815260200191505060405180910390f35b3480156106af57600080fd5b506106b8611046565b6040518082815260200191505060405180910390f35b3480156106da57600080fd5b506106e3611056565b6040518082815260200191505060405180910390f35b34801561070557600080fd5b50610744600480360381019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061105c565b604051808215151515815260200191505060405180910390f35b34801561076a57600080fd5b5061079f600480360381019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506112ed565b6040518082815260200191505060405180910390f35b3480156107c157600080fd5b506107ca611335565b005b3480156107d857600080fd5b506107e16113eb565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34801561082f57600080fd5b50610838611411565b005b34801561084657600080fd5b5061084f6114ab565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561088f578082015181840152602081019050610874565b50505050905090810190601f1680156108bc5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156108d657600080fd5b50610915600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506114e4565b604051808215151515815260200191505060405180910390f35b34801561093b57600080fd5b50610944611703565b6040518082815260200191505060405180910390f35b34801561096657600080fd5b5061096f611709565b6040518082815260200191505060405180910390f35b34801561099157600080fd5b5061099a611716565b6040518082815260200191505060405180910390f35b3480156109bc57600080fd5b506109fb600480360381019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050611726565b604051808215151515815260200191505060405180910390f35b348015610a2157600080fd5b50610a76600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050611922565b6040518082815260200191505060405180910390f35b348015610a9857600080fd5b50610acd600480360381019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506119a9565b005b600080831415610ae25760009050610b01565b8183029050818382811515610af357fe5b04141515610afd57fe5b8090505b92915050565b60008183811515610b1457fe5b04905092915050565b60008183019050828110151515610b3057fe5b80905092915050565b6000828211151515610b4757fe5b818303905092915050565b6040805190810160405280600981526020017f416c6578546f6b656e000000000000000000000000000000000000000000000081525081565b600081600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b6000600154905090565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614151515610cc457600080fd5b6000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020548211151515610d1157600080fd5b600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020548211151515610d9c57600080fd5b610ded826000808773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610b3990919063ffffffff16565b6000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610e80826000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610b1d90919063ffffffff16565b6000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610f5182600260008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610b3990919063ffffffff16565b600260008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3600190509392505050565b601281565b6b033b2e3c9fd0803ce800000081565b60035481565b600080600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490508083111561116d576000600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550611201565b6111808382610b3990919063ffffffff16565b600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505b8373ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546040518082815260200191505060405180910390a3600191505092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561139157600080fd5b60028081111561139d57fe5b600560149054906101000a900460ff1660028111156113b857fe5b141515156113c557600080fd5b6001600560146101000a81548160ff021916908360028111156113e457fe5b0217905550565b600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561146d57600080fd5b60028081111561147957fe5b600560149054906101000a900460ff16600281111561149457fe5b141515156114a157600080fd5b6114a9611b01565b565b6040805190810160405280600381526020017f414c54000000000000000000000000000000000000000000000000000000000081525081565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415151561152157600080fd5b6000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054821115151561156e57600080fd5b6115bf826000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610b3990919063ffffffff16565b6000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550611652826000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610b1d90919063ffffffff16565b6000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a36001905092915050565b60045481565b68056bc75e2d6310000081565b6b019d971e4fe8401e7400000081565b60006117b782600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610b1d90919063ffffffff16565b600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546040518082815260200191505060405180910390a36001905092915050565b6000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141515611a0557600080fd5b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614151515611a4157600080fd5b8073ffffffffffffffffffffffffffffffffffffffff16600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a380600560006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b6002600560146101000a81548160ff02191690836002811115611b2057fe5b021790555060006004541115611c0a57611ba5600454600080600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610b1d90919063ffffffff16565b600080600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505b600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc3073ffffffffffffffffffffffffffffffffffffffff16319081150290604051600060405180830381858888f19350505050158015611c89573d6000803e3d6000fd5b505600a165627a7a723058205bbef016cc7699572f944871cb6f05e69915ada3a92a1d9f03a3fb434aac0c2b0029


Больше подробностей про смарт-контракты можно узнать в статье: Что такое смарт-контракты в Ethereum.

Заключение


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

  • Транзакции совершаются в доверительной среде;
  • Наличие комиссии посредников не ухудшает жизнь участников;
  • У участников нет собственности, которую можно представить в виде цифровых активов;
  • Нет распределенности в цифровых активах, т.е. ценностью владеет или поставляет только один участник.

Какое будущее ждет блокчейн? Сейчас можно только предполагать возможные пути развития блокчейн технологий:

  • Блокчейн станет такой же обычной технологией баз данных, как, например, SQL или NoSQL для решения своего определенного круга задач;
  • Блокчейн станет широко распространенным протоколом, как HTTP для Интернета;
  • Блокчейн станет основой для новой финансовой и политической системы планеты!

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

It's only the beginning!

Все три статьи:


Что нам стоит блокчейн построить?
Блокчейн: что нам стоит кейс построить?
Блокчейн: что нам стоит PoC построить?
Теги:
Хабы:
+4
Комментарии 3
Комментарии Комментарии 3

Публикации

Истории

Работа

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн