Создание децентрализованного приложения блокчейн (Dapp) с Solidity на Ethereum + Javascript с тестами.

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

Итак, что мы хотим построить. Мы хотим создать приложение для голосования. Причем очень простой. Ethereum позволяет нам создавать децентрализованное приложение, а надежность - это язык, который мы используем для использования в стороннем приложении javascript в качестве интерфейса. Для настройки нашего путешествия нам потребуются следующие приложения, установленные в качестве зависимостей.
1. Диспетчер пакетов узлов (NPM):
2. Трюфель
3. Ганаш
4. Метамаск

Диспетчер пакетов узла
Это позволяет нам управлять пакетами из node.js. и использовать их. Вы можете подтвердить его наличие в своей системе, набрав его в командной строке
$ npm -v

Truffle
Этот пакет npm позволяет нам создавать децентрализованные приложения на блокчейне Ethereum. Это позволяет нам тестировать наши смарт-контракты на нашей локальной копии блокчейна и развертывать контракты в основной сети блокчейна. Вы можете установить версию трюфеля для использования с проектом, используя команду ниже
$ npm install -g [email protected]

Ganache
Это приложение позволяет вам иметь 10 различных фейковых аккаунтов и фейковый эфир. Вы можете скачать его отсюда https://truffleframework.com/ganache

Metamask
Это расширение в Chrome, которое мы можем использовать для взаимодействия с локальным блокчейном, который мы запускаем, или с основным блокчейном Ethereum. Мы будем использовать его в этом приложении, поэтому вы захотите выполнить поиск в Google по запросу metamask extension, а затем установить его в своем браузере Chrome.

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

В этом руководстве я буду использовать vscode в качестве плагина IDE и Solidity от Хуана Бланко.

Шаг первый:

Сначала откройте приложение ganache, и вы должны увидеть что-то вроде этого

То, что вы видите выше, - это 10 разных учетных записей, созданных для вас с помощью ganache, и хотя в моей собственной копии некоторые учетные записи имеют менее 100 eth, у вас должно быть 100 eth во всех учетных записях. В правой части каждой учетной записи есть символ в виде ключа. При нажатии на этот значок отображается ваш закрытый ключ для текущей учетной записи, и его можно использовать для импорта учетной записи в разные сети. Это будет показано позже.

Теперь приступим. Выберите место в вашей системе, и давайте создадим папку, подобную той, что у нас ниже.

$ mkdir election
$ cd election

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

$ truffle unbox pet-shop

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

$ git clone https://github.com/truffle-box/pet-shop-box.git .

В этом руководстве я использовал VSCode, и вы можете открыть текущую папку с помощью этой команды

code .

После вышеизложенного у вас должно быть следующее представление в любой IDE, которую вы используете.

Давайте пройдемся по тому, что у нас есть здесь:

  • Каталог контрактов: здесь мы будем хранить все наши смарт-контракты. Вы уже можете видеть, что у нас есть контракт на миграцию внутри, который обрабатывает наши миграции в блокчейн.
  • Каталог миграции: здесь находятся все файлы миграции. Если вы работали в других фреймворках, в которых есть ORM, вы заметите, что это что-то знакомое. Всякий раз, когда мы развертываем смарт-контракты в блокчейне, мы обновляем состояние блокчейна и, следовательно, нуждаемся в миграции.
  • каталог node_modules: это дом для всех наших зависимостей Node.
  • src directory: здесь мы будем разрабатывать наше клиентское приложение.
  • каталог тестов: здесь мы будем писать тесты для наших смарт-контрактов.
  • файл truffle-config.js: это основной файл конфигурации для нашего проекта Truffle.
  • файл truffle-box.json: этот файл содержит некоторые команды, которые можно использовать в проекте.

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

Затем мы приступаем к написанию наших смарт-контрактов. Для пользователей типа unix вы можете использовать команду ниже:

$ touch contracts/Election.sol

и для пользователей Windows вы можете это сделать.

Следующее, что нужно сделать, это вставить в файл Election.sol.

Позвольте мне объяснить приведенный выше код

Первая строка объявляет версию надежности, в которой вы хотите написать свой код. Это делается в первую очередь во всех смарт-контрактах надежности. Объявление смарт-контракта начинается с ключевого слова contract, как и в ООП, вы запускаете класс с ключевого слова class. Затем мы объявили строку-кандидат и сделали ее общедоступной. В других серверных языках, таких как C # или java, ключевое слово public будет стоять перед строкой. Также в solidity объявление переменной candidatepublic будет генерировать бесплатную функцию получения из solidity.

Далее идет функция конструктора, эта функция вызывается всякий раз, когда смарт-контракт развертывается в блокчейне. Если вы новичок в программировании ООП, функция-конструктор обычно находится там, где вы инициализируете переменные и объекты внутри функции.

Затем мы хотим развернуть наш контракт в блокчейне, но сначала нам нужно создать для него файл миграции. В папке миграции вы заметите один файл, который начинается с номера 1. Мы пронумеруем наши файлы миграции, чтобы определить порядок развертывания миграции. Создайте новый файл миграции с именем «2_deploy_contracts.js» через среду IDE или из командной строки, например $ touch migration/2_deploy_contracts.js

Скопируйте приведенный ниже код в файл

Затем мы запускаем с терминала или консоли следующие команды

$ truffle migrate

После завершения миграции мы хотим взаимодействовать с нашим развернутым смарт-контрактом. На типе терминала

$ truffle console

Вы должны увидеть консоль с такой подсказкой $ truffle<development>:

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

$ Election.deployed().then(function(instance) {app=instance})

Выбор - это имя контракта, который мы создали ранее, и мы получили развернутый экземпляр контракта с функцией deployed() и присвоили его переменной app внутри функции обратного вызова обещания.

После ввода вышеуказанного кода вы должны увидеть $ undefined, это не должно вас беспокоить, поскольку это означает, что процесс завершен. Однако теперь у нас есть переменная app, которую можно использовать для подобного вызова кандидата.

$ app.candidate()

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

Шаг второй:

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

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

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

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

Мы также отслеживаем всех кандидатов в сопоставлении, поскольку структура сопоставления в твердом состоянии не позволяет объявлять размер сопоставления. Вместо этого структура отображения возвращает значения на основе переданного ей ключа. Например, если у нас есть сопоставление всего с 5 кандидатами, и мы пытаемся извлечь из сопоставления кандидата с целым числом без знака 50, мы получим пустую структуру кандидата. Если ключ не найден, возвращается пустой результат. Подробнее о картографии здесь.

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

Мы объявили функцию addCandidate, которая принимает один аргумент строкового типа, представляющий имя кандидата. Внутри функции мы увеличиваем кеш счетчика кандидатов, чтобы обозначить, что был добавлен новый кандидат. Затем мы обновляем сопоставление новой структурой Candidate, используя текущее количество кандидатов в качестве ключа. Эта структура кандидата инициализируется идентификатором кандидата из текущего числа кандидатов, именем из аргумента функции и начальным счетчиком голосов до 0. Обратите внимание, что видимость этой функции является частной, потому что мы хотим вызывать ее только внутри контракта.

Если вы работаете с C # или java-фоном, вы заметите, что ключевые слова public и private используются для объявления функции или свойства, но помещаются после аргумента в функциях и после типа в объявлении переменной.

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

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

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

$ truffle migrate --reset

И теперь у нас есть работающий смарт-контракт. Затем, чтобы подтвердить то, что мы сделали до сих пор, войдите в консоль трюфелей, как мы делали ранее, и введите ту же команду, что и раньше.

Election.deployed().then(function(instance) {app=instance})

На этот раз, чтобы найти кандидата, мы должны ввести приведенный ниже код.

app.candidates(1) 

Здесь мы вводим значение индекса кандидатов. мы можем заменить 1 на 2 и посмотреть, что мы получим обратно.

Написание тестов

Затем создайте тестовый файл с именем «selection.js» в папке tests. Фреймворк Truffle поставляется с фреймворком для тестирования мокко и библиотекой chai для запуска наших тестов. Давайте вставим приведенный ниже код в наш файл.

Давайте пройдемся по этому файлу. Мы импортировали наш контракт на выборы в этот тестовый файл и создали экземпляр тестового контракта, вводя наши учетные записи, которые будут использоваться для тестирования. На самом деле мы написали два основных теста, и эти тесты проверяют
1. Число инициализированных кандидатов.
2. Значения объекта-кандидата инициализированы соответствующими значениями.

Чтобы убедиться, что наш тест в порядке, мы можем выполнить следующую команду.

$ truffle test

Уф !!! какой долгий путь мы прошли, но если вы застряли на этом пути, вы можете получить репозиторий из Интернета.

Наше клиентское приложение

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

Сначала мы начнем с файла app.js

Теперь замените index.html приведенным ниже кодом:

Отметим несколько вещей, которые выполняет код App.js:

  1. Настроить web3: web3.js - это библиотека javascript, которая позволяет нашему клиентскому приложению взаимодействовать с цепочкой блоков. Мы настраиваем web3 внутри функции initWeb3.
  2. Я инициализируем контракты: мы извлекаем развернутый экземпляр смарт-контракта внутри этой функции и назначаем некоторые значения, которые позволят нам взаимодействовать с ним.
  3. Функция рендеринга: функция рендеринга размещает весь контент на странице с данными из смарт-контракта. На данный момент мы перечисляем только кандидатов, которые были созданы в смарт-контракте, и отображаем их в таблице. Мы также получаем текущую учетную запись, которая подключена к блокчейну внутри этой функции, и отображаем ее на странице.

Для html это простая веб-страница html. Ничего сложного.

Теперь давайте посмотрим на клиентское приложение в браузере. Во-первых, убедитесь, что вы перенесли свои контракты следующим образом:

$ truffle migrate --reset

Затем запустите сервер разработки из командной строки следующим образом:

$ npm run dev

Это должно автоматически открыть новое окно браузера с вашим клиентским приложением.

Убедитесь, что ваш локальный блокчейн работает в ганаше. Если ваш не работает, убедитесь, что номер порта в ganache совпадает с номером порта в файле truffle-config.js.

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

Импорт аккаунта в метамаску:

Шаг 1. В вашем запущенном приложении ganache выберите одну из учетных записей, которые вы хотите импортировать, и нажмите кнопку в правой части экрана.
Шаг 2: Откройте метамаску в браузере Chrome. Войдите в систему, если вы этого не сделали.
Шаг 3: Выберите сеть localhost из различных доступных вам сетей (localhost: 8545)
Шаг 4: Щелкните значок в правом верхнем углу и выберите учетную запись для импорта.
Шаг 5: Вставьте туда закрытый ключ, и ваша учетная запись была успешно импортирована.

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

Шаг 3: голосование

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

// Store accounts that have voted     
mapping(address => bool) public voters;

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

Основная функциональность этой функции заключается в увеличении подсчета голосов кандидата путем считывания структуры Candidate из сопоставления «кандидаты» и увеличения «voteCount» на 1 с помощью оператора приращения (++). Давайте посмотрим на то, что он делает:

  1. Принимает один аргумент. Это целое число без знака с идентификатором кандидата.
  2. Его видимость является общедоступной, потому что мы хотим, чтобы это вызывала внешняя учетная запись.
  3. Он добавляет учетную запись, которая проголосовала, в только что созданное сопоставление избирателей. Это позволит нам отслеживать, проголосовал ли избиратель на выборах. Мы получаем доступ к учетной записи, которая вызывает эту функцию, с помощью глобальной переменной msg.sender, предоставленной Solidity.
  4. Он реализует операторы require, которые прекращают выполнение, если условия не выполняются. Сначала потребуйте, чтобы избиратель еще не голосовал. Мы делаем это, считывая адрес учетной записи с помощью «msg.sender» из сопоставления. Если он там есть, значит, аккаунт уже проголосовал. Затем требуется, чтобы идентификатор кандидата был действительным. Идентификатор кандидата должен быть больше нуля и меньше или равен общему количеству кандидатов.

Полный файл Election.sol должен выглядеть так:

Тестирование функции голосования

Теперь давайте добавим тест в наш тестовый файл «selection.js»:

it("allows a voter to cast a vote", function() {
    return Election.deployed().then(function(instance) {
      electionInstance = instance;
      candidateId = 1;
      return electionInstance.vote(candidateId, { from: accounts[0] });
    }).then(function(receipt) {
      return electionInstance.voters(accounts[0]);
    }).then(function(voted) {
      assert(voted, "the voter was marked as voted");
      return electionInstance.candidates(candidateId);
    }).then(function(candidate) {
      var voteCount = candidate[2];
      assert.equal(voteCount, 1, "increments the candidate's vote count");
    })
  });

Здесь мы хотим протестировать две вещи:

  1. Убедитесь, что функция увеличивает счетчик голосов за кандидата.
  2. Убедитесь, что избиратель добавляется к отображению всякий раз, когда он голосует.

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

it("throws an exception for invalid candidates", function() {
    return Election.deployed().then(function(instance) {
      electionInstance = instance;
      return electionInstance.vote(99, { from: accounts[1] })
    }).then(assert.fail).catch(function(error) {
      assert(error.message.indexOf('revert') >= 0, "error message must contain revert");
      return electionInstance.candidates(1);
    }).then(function(candidate1) {
      var voteCount = candidate1[2];
      assert.equal(voteCount, 1, "candidate 1 did not receive any votes");
      return electionInstance.candidates(2);
    }).then(function(candidate2) {
      var voteCount = candidate2[2];
      assert.equal(voteCount, 0, "candidate 2 did not receive any votes");
    });
  });

Мы можем утверждать, что транзакция не удалась и было возвращено сообщение об ошибке. Мы можем покопаться в этом сообщении об ошибке, чтобы убедиться, что сообщение об ошибке содержит подстроку «вернуться». Затем мы можем гарантировать, что состояние нашего контракта не изменилось, убедившись, что кандидаты не получили никаких голосов.

Теперь давайте напишем тест, чтобы предотвратить двойное голосование:

it("throws an exception for double voting", function() {
    return Election.deployed().then(function(instance) {
      electionInstance = instance;
      candidateId = 2;
      electionInstance.vote(candidateId, { from: accounts[1] });
      return electionInstance.candidates(candidateId);
    }).then(function(candidate) {
      var voteCount = candidate[2];
      assert.equal(voteCount, 1, "accepts first vote");
      // Try to vote again
      return electionInstance.vote(candidateId, { from: accounts[1] });
    }).then(assert.fail).catch(function(error) {
      assert(error.message.indexOf('revert') >= 0, "error message must contain revert");
      return electionInstance.candidates(1);
    }).then(function(candidate1) {
      var voteCount = candidate1[2];
      assert.equal(voteCount, 1, "candidate 1 did not receive any votes");
      return electionInstance.candidates(2);
    }).then(function(candidate2) {
      var voteCount = candidate2[2];
      assert.equal(voteCount, 1, "candidate 2 did not receive any votes");
    });
  });

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

Теперь давайте запустим наши тесты:

$ truffle test

Ура, они проходят! 🎉

Фактическое голосование

Давайте добавим форму, позволяющую пользователям голосовать, под таблицей в файле index.html:

<form onSubmit="App.castVote(); return false;">
  <div class="form-group">
    <label for="candidatesSelect">Select Candidate</label>
    <select class="form-control" id="candidatesSelect">
    </select>
  </div>
  <button type="submit" class="btn btn-primary">Vote</button>
  <hr />
</form>

Давайте разберемся с этой формой:

  1. Мы создаем форму с пустым элементом выбора. Мы заполним варианты выбора кандидатами, предоставленными нашим смарт-контрактом, в файле «app.js».
  2. В форме есть обработчик onSubmit, который будет вызывать функцию castVote. Мы определим это в нашем файле «app.js».

Затем нам нужно обновить наш файл app.js, чтобы иметь возможность обрабатывать оба процесса, описанные выше. Нам нужен раскрывающийся список, чтобы можно было перечислить и выбрать кандидатов. Помните, что вы также можете использовать для этого переключатель. Затем мы скроем форму, когда учетная запись проголосует. Помните, что мы дадим возможность избирателю проголосовать только один раз.

render: function() {
  var electionInstance;
  var loader = $("#loader");
  var content = $("#content");
  loader.show();
  content.hide();
  // Load account data
  web3.eth.getCoinbase(function(err, account) {
    if (err === null) {
      App.account = account;
      $("#accountAddress").html("Your Account: " + account);
    }
  });
  // Load contract data
  App.contracts.Election.deployed().then(function(instance) {
    electionInstance = instance;
    return electionInstance.candidatesCount();
  }).then(function(candidatesCount) {
    var candidatesResults = $("#candidatesResults");
    candidatesResults.empty();
    var candidatesSelect = $('#candidatesSelect');
    candidatesSelect.empty();
    for (var i = 1; i <= candidatesCount; i++) {
      electionInstance.candidates(i).then(function(candidate) {
        var id = candidate[0];
        var name = candidate[1];
        var voteCount = candidate[2];
        // Render candidate Result
        var candidateTemplate = "<tr><th>" + id + "</th><td>" + name + "</td><td>" + voteCount + "</td></tr>"
        candidatesResults.append(candidateTemplate);
        // Render candidate ballot option
        var candidateOption = "<option value='" + id + "' >" + name + "</ option>"
        candidatesSelect.append(candidateOption);
      });
    }
    return electionInstance.voters(App.account);
  }).then(function(hasVoted) {
    // Do not allow a user to vote
    if(hasVoted) {
      $('form').hide();
    }
    loader.hide();
    content.show();
  }).catch(function(error) {
    console.warn(error);
  });
}

Затем нам нужно написать функцию, которая вызывается при нажатии кнопки отправки, то есть мы голосуем. См. ниже:

castVote: function() {
    var candidateId = $('#candidatesSelect').val();
    App.contracts.Election.deployed().then(function(instance) {
      return instance.vote(candidateId, { from: App.account });
    }).then(function(result) {
      // Wait for votes to update
      $("#content").hide();
      $("#loader").show();
    }).catch(function(err) {
      console.error(err);
    });
  }

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

Теперь ваше интерфейсное приложение должно выглядеть так:

Идите вперед и попробуйте функцию голосования. Как только вы это сделаете, вы должны увидеть всплывающее окно с подтверждением Metamask, подобное этому:

В зависимости от вашей системы он может открывать новую вкладку вместо нового всплывающего окна.

После того, как вы нажмете "Отправить", вы успешно проголосовали! Вы по-прежнему будете видеть экран загрузки. На данный момент вам нужно обновить страницу, чтобы увидеть записанные голоса. В следующем разделе мы реализуем функцию обновления загрузчика автоматически.

Шаг 4. Наблюдайте за событиями

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

contract Election {
    // ...
    event votedEvent (
        uint indexed _candidateId
    );
    // ...
}

Теперь мы можем вызвать это событие votedEvent внутри нашей функции «голосование» следующим образом:

function vote (uint _candidateId) public {
    // require that they haven't voted before
    require(!voters[msg.sender]);
    // require a valid candidate
    require(_candidateId > 0 && _candidateId <= candidatesCount);
    // record that voter has voted
    voters[msg.sender] = true;
    // update candidate vote Count
    candidates[_candidateId].voteCount ++;
    // trigger voted event
    votedEvent(_candidateId);
}

Теперь, когда мы обновили наш контракт, мы должны запустить наши миграции:

$ truffle migrate --reset

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

it("allows a voter to cast a vote", function() {
  return Election.deployed().then(function(instance) {
    electionInstance = instance;
    candidateId = 1;
    return electionInstance.vote(candidateId, { from: accounts[0] });
  }).then(function(receipt) {
    assert.equal(receipt.logs.length, 1, "an event was triggered");
    assert.equal(receipt.logs[0].event, "votedEvent", "the event type is correct");
    assert.equal(receipt.logs[0].args._candidateId.toNumber(), candidateId, "the candidate id is correct");
    return electionInstance.voters(accounts[0]);
  }).then(function(voted) {
    assert(voted, "the voter was marked as voted");
    return electionInstance.candidates(candidateId);
  }).then(function(candidate) {
    var voteCount = candidate[2];
    assert.equal(voteCount, 1, "increments the candidate's vote count");
  })
});

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

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

listenForEvents: function() {
  App.contracts.Election.deployed().then(function(instance) {
    instance.votedEvent({}, {
      fromBlock: 0,
      toBlock: 'latest'
    }).watch(function(error, event) {
      console.log("event triggered", event)
      // Reload when a new vote is recorded
      App.render();
    });
  });
}

Эта функция делает несколько вещей. Во-первых, мы подписываемся на проголосованное событие, вызывая функцию votedEvent. Мы передаем некоторые метаданные, которые говорят нам прослушивать все события в цепочке блоков. Затем мы «наблюдаем» за этим событием. Здесь мы подключаемся к консоли каждый раз, когда запускается votedEvent. Мы также повторно визуализируем весь контент на странице. Это позволит избавиться от загрузчика после того, как голосование будет записано, и отобразит обновленное количество голосов в таблице.

Наконец, мы можем вызывать эту функцию всякий раз, когда инициализируем контракт:

initContract: function() {
  $.getJSON("Election.json", function(election) {
    // Instantiate a new truffle contract from the artifact
    App.contracts.Election = TruffleContract(election);
    // Connect provider to interact with contract
    App.contracts.Election.setProvider(App.web3Provider);
    App.listenForEvents();
    return App.render();
  });
}

Теперь вы можете голосовать в своем клиентском приложении и наблюдать за голосами, записанными в режиме реального времени! Наберитесь терпения, событие может произойти через несколько секунд. Если вы не видите событие, попробуйте перезапустить Chrome. Есть известная проблема с Metamask, связанная с событиями. Для меня это всегда исправляет перезапуск Chrome.

Поздравляю! 🎉 Фух, вот и все. Вы успешно создали полноценное децентрализованное приложение на блокчейне Ethereum! Уф !!!