Создание веб-приложения со списком дел на основе блокчейна
Здесь мы собираемся создать приложение со списком дел, которое будет сохранять данные в блокчейне. Блокчейн-часть этого приложения также может пониматься как база данных. Сначала мы создадим смарт-контракт, а затем и само веб-приложение. Мы будем использовать 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 --> < link rel = "stylesheet" href = " https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.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" > < img class = "px-2 py-2" src = " https://github.com/gupta-shrinath/Bloc/raw/gupta-shrinath/images/add-list.png " 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" > < img clas = "m-auto" src = " https://github.com/gupta-shrinath/Bloc/raw/gupta-shrinath/images/plus.png " 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 // // 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" ); checkbox.setAttribute( "id" , "item-" + id + РЕКОМЕНДУЕМЫЕ СТАТЬИ |