Создание веб-приложения со списком дел на основе блокчейна

Опубликовано: 5 Января, 2022

Здесь мы собираемся создать приложение со списком дел, которое будет сохранять данные в блокчейне. Блокчейн-часть этого приложения также может пониматься как база данных. Сначала мы создадим смарт-контракт, а затем и само веб-приложение. Мы будем использовать Bloc в качестве имени приложения, но сначала давайте посмотрим на компоненты.

Компоненты в блочном приложении

  • Ганаш - локальный блокчейн Ethereum.
  • Web3 JS - для того, чтобы приложение могло взаимодействовать с блокчейном.
  • Bootstrap - для интерфейса приложения.
  • Solidity - для составления смарт-контракта.
  • JQuery - для манипуляций с DOM.

Что такое смарт-контракт?

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

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

Примечание. Убедитесь, что вы используете сайт Http вместо Https. Сайт Http позволит нам развернуть наш смарт-контракт в нашей локальной цепочке блоков.

Щелкните значок плюса, чтобы создать файл bloc.sol.

Создать блок Sol

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

 прагма солидность ^ 0.5.1;

Чтобы сообщить компилятору о нашем смарт-контракте, мы определим блок контракта. Контракт, подобный классу в ООП, который содержит все поля и методы:

 прагма солидность ^ 0.5.1;
контракт Bloc {
}

Для сохранения задачи нам необходимо сделать следующее:

1. Создайте структуру для своей задачи: Struct позволяет создавать определяемый пользователем тип данных. У него будет строка как задача и логическое значение, указывающее, выполнена ли задача или нет.

 прагма солидность ^ 0.5.1;
контракт Bloc {
 struct Task {
    строковое задание;
       bool isDone;
   } 
}

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

 прагма солидность ^ 0.5.1;
 контракт Bloc { 
   struct Task {
    строковое задание;
       bool isDone;
   }
   отображение (адрес => Задача []) частных пользователей;
}

Способы манипулирования нашей задачей в контракте:

1. Создать задачу: этот метод создает задачу.

  • Метод addTask принимает строку в качестве аргумента.
  • calldata устанавливает расположение данных для строкового аргумента.
  • external делает метод доступным при вызове через web3js.
  • msg.sender дает нам адрес пользователя, вызывающего метод.
  • Метод push для добавления задачи в сопоставление.
 прагма солидность ^ 0.5.1;
 
контракт Bloc { 
   struct Task {
    строковое задание;
       bool isDone;
   }
   
   отображение (адрес => Задача []) частных пользователей;
 
  функция addTask (строка calldata _task) external {
      Пользователи [msg.sender] .push (Задача ({
          задача: _task,
          isDone: false
      }));
   } 
}

2. Чтение задачи: этот метод помогает прочитать значение в задаче.

  • Чтобы вернуть структуру Task из метода getTask, нам нужно добавить строку 2.
  • Метод getTask принимает индекс задачи и выдает задачу.
  • memory - это место данных для возвращаемой Задачи.
  • view сообщает, что функция не модифицирует состояние цепочки блоков.
 прагма солидность ^ 0.5.1;
 
контракт Bloc { 
   struct Task {
    строковое задание;
       bool isDone;
   }
   
   отображение (адрес => Задача []) частных пользователей;
 
  функция addTask (строка calldata _task) external {
      Пользователи [msg.sender] .push (Задача ({
          задача: _task,
          isDone: false
      }));
   }
   
   функция getTask (uint _taskIndex) возвращает внешнее представление (память задач) {
       Задача хранения задач = Пользователи [msg.sender] [_ taskIndex];
       вернуть задачу;
   } 
}

3. Задача обновления: этот метод обновит значения в задаче.

  • Этот метод устанавливает или снимает отметку с задачи.
  • Метод updateStatus принимает индекс задачи и статус для обновления.
  • С помощью taskIndex мы сможем получить доступ к структуре задачи, поэтому мы установим isDone на переданный статус.
 pragma solidity ^0.5.1;
 
contract Bloc{ 
   struct Task{
    string task;
       bool isDone;
   }
   
   mapping (address => Task[]) private Users;
 
  function addTask(string calldata _task) external{
      Users[msg.sender].push(Task({
          task:_task,
          isDone:false
      }));
   }
   
   function getTask(uint _taskIndex) external view returns (Task memory){
       Task storage task = Users[msg.sender][_taskIndex];
       return task;
   }
 
  function updateStatus(uint256 _taskIndex,bool _status) external{
        Users[msg.sender][_taskIndex].isDone = _status;
   } 
}

4. Удалить задачу: метод deleteTask принимает индекс задачи и затем удаляет элемент из массива, как в C.

 прагма солидность ^ 0.5.1;
 
контракт Bloc { 
   struct Task {
    строковое задание;
       bool isDone;
   }
   
   отображение (адрес => Задача []) частных пользователей;
 
  функция addTask (строка calldata _task) external {
      Пользователи [msg.sender] .push (Задача ({
          задача: _task,
          isDone: false
      }));
   }
   
   функция getTask (uint _taskIndex) возвращает внешнее представление (память задач) {
       Задача хранения задач = Пользователи [msg.sender] [_ taskIndex];
       вернуть задачу;
   }
 
  function updateStatus (uint256 _taskIndex, bool _status) external {
        Пользователи [msg.sender] [_ taskIndex] .isDone = _status;
   }
 
    function deleteTask (uint256 _taskIndex) external {
       удалить пользователей [msg.sender] [_ taskIndex];
   } 
}

5. Получить количество задач: количество задач можно получить как длину массива задач.

А полная программа солидности после добавления всех вышеперечисленных методов решения задачи выглядит так:

Твердость

pragma solidity ^0.5.1;
// Creating a contract
contract Bloc{
// Defining a structure to
// store a task
struct Task
{
string task;
bool isDone;
}
mapping (address => Task[]) private Users;
// Defining function to add a task
function addTask(string calldata _task) external
{
Users[msg.sender].push(Task({
task:_task,
isDone: false
}));
}
// Defining a function to get details of a task
function getTask(uint _taskIndex) external view returns (Task memory)
{
Task storage task = Users[msg.sender][_taskIndex];
return task;
}
// Defining a function to update status of a task
function updateStatus(uint256 _taskIndex, bool _status) external
{
Users[msg.sender][_taskIndex].isDone = _status;
}
// Defining a function to delete a task
function deleteTask(uint256 _taskIndex) external
{
delete Users[msg.sender][_taskIndex];
}
// Defining a function to get task count.
function getTaskCount() external view returns (uint256)
{
return Users[msg.sender].length;
}
}

Нажмите кнопку Compile под опцией левой панели навигации Solidity.

Скомпилируйте bloc.sol

Нажмите « Развернуть и запустить транзакцию» в их развертывании, и в развернутом контракте вы найдете все методы.

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

Прежде чем мы начнем создавать веб-приложение, нам понадобится блокчейн Ethereum, который будет содержать смарт-контракт и данные нашего приложения. Мы собираемся использовать Ganache, чтобы загрузить и установить его.

После установки откройте Ganache и щелкните по быстрому запуску Ethereum, после чего обратите внимание на адрес, помеченный в разделе RPC Server (должен быть примерно так http://127.0.0.1:7545).

Теперь создаем веб-приложение

HTML

<!doctype html>
< html lang = "en" >
< head >
<!-- Required meta tags -->
< meta charset = "utf-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1, shrink-to-fit=no" >
<!-- Bootstrap CSS -->
integrity = "sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin = "anonymous" >
<!-- App CSS-->
< style >
.card-container {
width: 300px;
height: 500px;
background: #2699FB;
border: 1px solid #2699FB;
border-radius: 10px;
opacity: 1;
margin-right: auto;
margin-left: auto;
border-left: 0;
}
.icon-circle {
background: white;
border-radius: 200px;
height: 70px;
font-weight: bold;
width: 70px;
display: table;
margin-top: 12px;
margin-left: 31px;
}
.bloc {
margin-top: 12px;
margin-left: 28px;
}
.task-count {
margin-top: 7px;
margin-left: 31px;
}
.task-card {
width: 300px;
height: 325px;
border: 1px solid #FFFFFF;
background: #FFFFFF;
border-radius: 25px 25px 10px 10px;
opacity: 1;
position: relative;
}
.fab {
background: #2699FB;
border-radius: 200px;
height: 40px;
width: 40px;
font-weight: bold;
border: 0;
position: absolute;
right: 0;
bottom: 0;
margin-right: 27px;
margin-bottom: 31px;
}
.add-task-container {
top: 150px;
width: 300px;
height: 187px;
background: #FFFFFF;
border-radius: 25px 25px 0px 0px;
opacity: 1;
position: absolute;
}
.task-done {
color: gray;
text-decoration: line-through;
}
</ style >
< title >Bloc</ title >
</ head >
< body style = "background: #BCE0FD" >
< div class = "card-container my-5 border-left-0" >
< div class = "icon-circle" >
alt = "icon" >
</ div >
< h2 class = "bloc text-white" >< strong >BLOC</ strong ></ h1 >
< p id = "taskCount" class = "task-count text-white" >0 Task</ p >
< div class = "task-card" >
<!-- Floating Action Button -->
< button id = "fab" class = "fab float-right" data-toggle = "modal" data-target = "#add-task-container" >
alt = "" height = "16" width = "16" >
</ button >
<!-- #Floating Action Button -->
<!-- Task List-->
< ul id = "list" class = "list-group mt-3" >
</ ul >
<!-- #Task List-->
<!-- Add Task Modal -->
< div id = "add-task-container" class = "modal add-task-container" data-backdrop = "static" >
< div class = "container" >
< div class = "col mx-2" >
< h5 class = "text-primary text-center mt-4" >New Task</ h5 >
< input id = "new-task" class = "mt-3" type = "text" >
< button type = "button" data-dismiss = "modal" class = "btn btn-primary btn-block mt-3"
onclick = "addTask(document.getElementById('new-task').value);" >Add
Task</ button >
</ div >
</ div >
</ div >
<!-- #Add Task Modal -->
</ div >
</ div >
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
integrity = "sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
crossorigin = "anonymous" ></ script >
integrity = "sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
crossorigin = "anonymous" ></ script >
integrity = "sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV"
crossorigin = "anonymous" ></ script >
<!-- App and related files JS-->
< script src = "js/config.js" ></ script >
< script src = "js/getAccount.js" ></ script >
< script src = "js/app.js" ></ script >
</ body >
</ html >

Веб-страница будет выглядеть так:

блок

getAccount.js

Javascript

// Connect to Ganache Make sure you enter the address you noted earlier here //
web3 = new Web3( new Web3.providers.HttpProvider( ' http://localhost:7545 ' ));
// getAccount() will get the first account from ganache and will set it as defaultAccount for our contract operations ////
async function getAccount() {
let accounts = await web3.eth.getAccounts();
web3.eth.defaultAccount = accounts[0];
console.log(web3.eth.defaultAccount + ' account detected' );
return web3.eth.defaultAccount;
}

config.js

  • Перейдите в Remix IDE и убедитесь, что у вас есть bloc.sol из предыдущего руководства (убедитесь, что сайт HTTP, а не https).
  • Перейдите к компилятору solidity, расположенному на левой панели, и щелкните по compile bloc.sol. Внизу вы найдете кнопку со значком копирования и текстом, когда ABI нажимает на нее. Вставьте его в строку 1 js / config.js.
 пусть contractABI = КОПИРОВАННЫЙ ТЕКСТ;
Скопированный текст будет заключен в [].
  • Перейдите к развертыванию и запуску транзакций в среде, выберите Web3 Provider.
  • Введите адрес, который вы скопировали из Ganache, и вставьте его, нажмите OK.
  • Теперь будет видна кнопка развертывания, щелкнув по ней.
  • Внизу вы найдете ярлык «Развернутые контракты», теперь на нем будет щелкнуть кнопка со значком «Копировать».
  • И вставьте в js / config.js строку 2.
 let contractAddress = 'КОПИРОВАННЫЙ текст';
Скопированный текст может выглядеть так: 0xF3017acEDd45526aC6153FBBCfcA8096173D245a.
КонтрактABI помогает web3js с нашим смарт-контрактом
ContractAddress сообщает web3js о том, где на блокчейне находится наш смарт-контракт.

app.js

Javascript

$(document).ready(createTaskList());
// Auto focus on input of add task modal //
$( '#add-task-container' ).on( 'shown.bs.modal' , function () {
$( '#new-task' ).trigger( 'focus' );
});
/**
* createTaskList() set the contract object and gets the number
* of tasks of the user and then calls addTaskToList() to add
* them to HTML one after the other after all task are added to
* HTML then calls updateTaskCount()
* @author Gupta Shrinath < https://github.com/gupta-shrinath >
*/
async function createTaskList() {
// Get account from the Ganache EVM //
try {
await getAccount();
// Set contract and set gas //
contract = new web3.eth.Contract(contractABI, contractAddress);
try {
numberOfTask = await contract.methods.getTaskCount().call({ from: web3.eth.defaultAccount });
/* The actual number of task may differ because
when an task is removed the task element is
removed and the index value now has nothing.
*/
console.log( 'Number of Tasks are ' + numberOfTask);
// If there are task present //
if (numberOfTask != 0) {
// Fetch one task after the other until no task remain //
console.log( 'Start fetching task ...' );
let taskIterator = 0;
while (taskIterator < numberOfTask) {
try {
let task = await contract.methods.getTask(taskIterator).call({ from: web3.eth.defaultAccount });
if (task[0] != '' ) {
// addTaskToList add this task as children to the ul tag //
addTaskToList(taskIterator, task[0], task[1]);
}
else {
console.log( 'The index ' + taskIterator + ' is empty' );
}
} catch {
console.log( 'Failed to get Task ' + taskIterator);
}
taskIterator++;
}
// Update the task count in HTML //
updateTasksCount();
}
} catch {
console.log( 'Failed to get task count from blockchain' );
}
} catch {
console.log( 'Failed to get the acount' );
}
}
/**
* addTaskToList() takes the task attributes and adds them to
* the HTML
* @author Gupta Shrinath < https://github.com/gupta-shrinath >
* @param {number} id
* @param {string} name
* @param {boolean} status
*/
function addTaskToList(id, name, status) {
console.log( 'addTaskToList(): Add Task ' + (id) + ' ' + [name, status]);
/* Get the id of ul element so to be able to
add children to it
*/
let list = document.getElementById( 'list' );
/* Create a li element and add the class
required to make look good and
set the id of it
*/
let item = document.createElement( 'li' );
item.classList.add( 'list-group-item' , 'border-0' , 'd-flex' , 'justify-content-between' , 'align-items-center' );
item.id = 'item-' + id;
// Create a text to add it to the li element//
let task = document.createTextNode(name);
/* Create a checkbox and set its id and checked
value to add it to the li element
*/
var checkbox = document.createElement( "INPUT" );
checkbox.setAttribute( "type" , "checkbox" );