Как создать Node.js API для блокчейна Ethereum
Опубликовано: 2022-03-10За последние десять лет технология блокчейна набирает обороты и воплотила в жизнь большое количество продуктов и платформ, таких как Chainalysis (финансовые технологии), Burstiq (медицинские технологии), Filament (IoT), Opus (потоковая передача музыки). и Ocular (кибербезопасность).
Из этих примеров мы видим, что блокчейн используется во многих продуктах и вариантах использования, что делает его очень важным и полезным. В финтех (финансовых технологиях) он используется в качестве децентрализованных реестров для обеспечения безопасности и прозрачности в таких местах, как Chain, Chainalysis, а также полезен в медицинских технологиях для защиты конфиденциальных данных о здоровье в Burstiq и Robomed — не говоря уже о медиатехнологиях, таких как Opus. и Audius, которые также используют блокчейн для прозрачности роялти и, таким образом, получают полные роялти.
Ocular использует безопасность, поставляемую с блокчейном, для управления идентификацией биометрических систем, в то время как Filament использует регистры блокчейна для зашифрованной связи в реальном времени. Это показывает, насколько важным стал для нас блокчейн, сделав нашу жизнь лучше. Но что такое блокчейн?
Блокчейн — это база данных , которая используется в сети компьютеров. Как только запись была добавлена в цепочку, ее довольно сложно изменить. Чтобы убедиться, что все копии базы данных одинаковы, сеть проводит постоянные проверки.
Так зачем нам блокчейн? Блокчейн — это безопасный способ записывать действия и обновлять данные, сохраняя при этом запись своей истории по сравнению с традиционными записями или базами данных, где очень возможны взломы, ошибки и простои. Данные не могут быть повреждены кем-либо или случайно удалены, и вы получаете как исторический след данных, так и мгновенно обновляемую запись, которая не может быть стерта или стать недоступной из-за простоя сервера.
Поскольку вся цепочка блоков дублируется на многих компьютерах, любой пользователь может просмотреть всю цепочку блоков. Транзакции или записи обрабатываются не одним центральным администратором, а сетью пользователей, которые работают над проверкой данных и достижением консенсуса.
Приложения, использующие блокчейн, называются dApps (децентрализованные приложения). Оглядываясь вокруг сегодня, мы в основном находим децентрализованные приложения в финтехе, но блокчейн выходит за рамки децентрализованных финансов. У нас есть медицинские платформы, платформы для потоковой передачи/обмена музыкой, платформы для электронной коммерции, платформы для кибербезопасности и IoT, которые переходят к децентрализованным приложениям (dApps), как указано выше.
Итак, когда имеет смысл рассмотреть возможность использования блокчейна для наших приложений, а не стандартной базы данных или записи?
Общие применения блокчейна
- Управление и защита цифровых отношений
В любое время, когда вы хотите вести долгосрочный и прозрачный учет активов (например, для регистрации прав на недвижимость или квартиру), блокчейн может стать идеальным решением. В частности, «умные контракты» Ethereum отлично подходят для облегчения цифровых отношений. С помощью смарт-контракта автоматические платежи могут быть запущены, когда стороны в транзакции соглашаются, что их условия были выполнены. - Устранение посредников/привратников
Например, в настоящее время большинству провайдеров приходится взаимодействовать с гостями через централизованную платформу-агрегатор, такую как Airbnb или Uber (которая, в свою очередь, берет комиссию за каждую транзакцию). Блокчейн может все изменить.
Например, TUI настолько убеждена в силе блокчейна, что предлагает новаторские способы прямого соединения владельцев отелей и клиентов. Таким образом, они могут совершать транзакции через блокчейн простым, безопасным и последовательным способом, а не через центральную платформу бронирования. - Записывайте безопасные транзакции между партнерами, чтобы обеспечить доверие
Традиционная база данных может быть хороша для записи простых транзакций между двумя сторонами, но когда все становится сложнее, блокчейн может помочь устранить узкие места и упростить отношения. Более того, дополнительная безопасность децентрализованной системы делает блокчейн идеальным для транзакций в целом.
Примером может служить Мельбурнский университет, который начал хранить свои записи в блокчейне. Наиболее многообещающим вариантом использования блокчейна в высшем образовании является преобразование «учета» степеней, сертификатов и дипломов. Это экономит много средств от выделенных серверов для хранения или записей. - Хранение записей о прошлых действиях для приложений, в которых данные находятся в постоянном потоке
Блокчейн — это лучший и более безопасный способ записи активности и сохранения актуальности данных при сохранении записи их истории. Данные не могут быть повреждены кем-либо или случайно удалены, и вы получаете как исторический след данных, так и мгновенно обновляемую запись. Примером хорошего варианта использования является блокчейн в электронной коммерции, где и блокчейн, и электронная коммерция включают транзакции.
Блокчейн делает эти транзакции более безопасными и быстрыми, в то время как деятельность электронной коммерции зависит от них. Технология Blockchain позволяет пользователям обмениваться цифровыми активами и безопасно хранить их как автоматически, так и вручную. Эта технология способна обрабатывать действия пользователей, такие как обработка платежей, поиск продуктов, покупка продуктов и обслуживание клиентов. Это также снижает расходы на управление запасами и обработку платежей. - Децентрализация позволяет использовать его где угодно
В отличие от того, что было раньше, когда нам приходилось ограничиваться определенным регионом по разным причинам, таким как политика обмена валюты, ограничения платежных шлюзов затрудняют доступ к финансовым ресурсам многих стран, не входящих в ваш регион или континент. С ростом и мощью децентрализации блокчейна или одноранговой системы работать с другими странами становится проще.
Например, интернет-магазин в Европе может иметь покупателей в Африке и не требовать посредника для обработки своих платежных запросов. Кроме того, эти технологии открывают двери интернет-магазинам для использования потребительских рынков в далеких странах с биткойнами, то есть криптовалютой. - Блокчейн нейтрален в отношении технологий
Блокчейн работает со всеми и любым стеком технологий, используемым разработчиком. Вам не нужно изучать Node в качестве разработчика Python, чтобы использовать блокчейн или изучать Golang. Это делает блокчейн очень простым в использовании.
На самом деле мы можем использовать его напрямую с нашими интерфейсными приложениями в Vue/React, при этом блокчейн выступает в качестве нашей единственной базы данных для простых несложных задач и вариантов использования, таких как загрузка данных или получение хэшей для отображения записей для наших пользователей или создание интерфейсных игр, таких как казино. игры и игры на ставки (в которых требуется большое доверие). Кроме того, благодаря возможностям web3 мы можем напрямую хранить данные в цепочке.
Итак, мы увидели целый ряд преимуществ использования блокчейна, но когда нам вообще не стоит беспокоиться об использовании блокчейна?
Недостатки блокчейна
- Уменьшенная скорость для цифровой транзакции
Блокчейны требуют огромных вычислительных мощностей, что, как правило, снижает скорость цифровых транзакций, хотя есть обходные пути: рекомендуется использовать централизованные базы данных, когда требуются высокоскоростные транзакции в миллисекундах. - Неизменяемость данных
Неизменяемость данных всегда была одним из самых больших недостатков блокчейна. Понятно, что от этого выигрывают несколько систем, включая цепочку поставок, финансовые системы и так далее. Однако его недостаток заключается в том, что после записи данные нельзя удалить. Каждый человек на земле имеет право на неприкосновенность частной жизни. Однако, если тот же человек использует цифровую платформу, работающую на технологии блокчейна, то он не сможет удалить ее след из системы, когда он этого не хочет. Проще говоря, он никак не может удалить свой след, разорвав права на неприкосновенность частной жизни. - Требуются экспертные знания
Внедрение и управление блокчейн-проектом сложно. Чтобы пройти весь процесс, необходимы глубокие знания. Вот почему трудно встретить специалистов или экспертов по блокчейну, потому что обучение эксперта по блокчейну требует много времени и усилий. Следовательно, эта статья — хорошее место для начала и хорошее руководство, если вы уже начали. - Совместимость
Многочисленные блокчейн-сети, усердно работающие над решением проблемы распределенного реестра, однозначно затрудняют их связывание или интеграцию друг с другом. Это затрудняет связь между различными цепями. - Интеграция с устаревшими приложениями
Многие предприятия и приложения по-прежнему используют устаревшие системы и архитектуру; внедрение технологии блокчейн требует полной перестройки этих систем, что, я должен сказать, для многих из них неосуществимо.
Блокчейн все еще развивается и совершенствуется все время, поэтому не удивляйтесь, если эти минусы, упомянутые сегодня, позже превратятся в плюсы. Биткойн, который является криптовалютой, является одним из популярных примеров блокчейна, популярным блокчейном, который находится на подъеме помимо криптовалюты биткойн, является блокчейн Ethereum. Биткойн фокусируется на криптовалютах, в то время как Ethereum больше фокусируется на смарт-контрактах, которые были основной движущей силой для новых технологических платформ.
Рекомендуемая литература : Биткойн против Эфириума: в чем разница?
Приступим к созданию нашего API
Имея четкое представление о блокчейне, теперь давайте посмотрим, как создать блокчейн Ethereum и интегрировать его в стандартный API в Node.js. Конечная цель — получить хорошее представление о том, как создаются платформы dApps и Blockchain.
Большинство децентрализованных приложений имеют схожую архитектуру и структуру. По сути, у нас есть пользователь, который взаимодействует с внешним интерфейсом dApp — веб-сайтом или мобильным — который затем взаимодействует с внутренними API. Затем серверная часть по запросу взаимодействует со смарт-контрактом (контрактами) или блокчейном через общедоступные узлы; они либо запускают приложения Node.js, либо серверная часть использует блокчейн, напрямую запуская программное обеспечение Node.js. Между этими процессами все еще так много промежуточных моментов: от выбора полностью децентрализованного или полудецентрализованного приложения до выбора того, что должно быть децентрализовано и как безопасно хранить закрытые ключи.
Рекомендуемая литература : Архитектура децентрализованных приложений: внутренняя часть, безопасность и шаблоны проектирования
Вещи, которые мы должны знать в первую очередь
В этом уроке мы попытаемся создать серверную часть децентрализованного приложения для музыкального магазина, которое использует возможности блокчейна Ethereum для хранения музыки и обмена ею для загрузки или потоковой передачи.
Базовая структура приложения, которое мы пытаемся создать, состоит из трех частей:
- Аутентификация , которая осуществляется по электронной почте; конечно, нам нужно добавить зашифрованный пароль в приложение.
- Хранение данных с музыкальными данными сначала сохраняется в ipfs, а адрес хранения сохраняется в блокчейне для поиска.
- Retrieval , когда любой аутентифицированный пользователь может получить доступ к сохраненным данным на нашей платформе и использовать их.
Мы будем создавать это с помощью Node.js, но вы также можете создавать с помощью Python или любого другого языка программирования. Мы также увидим, как хранить медиаданные в IPFS, получать адрес и писать функции для хранения этого адреса, а также извлекать этот адрес из цепочки блоков с помощью языка программирования Solidity.
Вот некоторые инструменты, которые мы должны иметь в своем распоряжении для создания или работы с Ethereum и Node.js.
- Node.js
Первое требование — приложение Node. Мы пытаемся создать приложение Node.js, поэтому нам нужен компилятор. Убедитесь, что у вас установлен Node.js, и загрузите последний двоичный файл с долгосрочной поддержкой ( LTS ). - Люкс «Трюфель»
Truffle — это среда разработки и тестирования контрактов, а также конвейер активов для блокчейна Ethereum. Он предоставляет среду для компиляции, конвейерной обработки и запуска сценариев. Когда вы говорите о разработке блокчейна, Truffle становится популярной остановкой. Узнайте о Truffle Suite на странице Truffle Suite: Sweet Tools для смарт-контрактов. - Ганаш CLI
Еще один инструмент, который хорошо работает с Truffle, — это Ganache-CLI. Он создан и поддерживается командой Truffle Suite. После сборки и компиляции вам понадобится эмулятор для разработки и запуска блокчейн-приложений, а затем развертывания смарт-контрактов для использования. Ganache упрощает развертывание контракта в эмуляторе без использования реальных денег для транзакционных издержек, перерабатываемых счетов и многого другого. Узнайте больше о Ganache CLI в Ganache CLI и Ganache. - Ремикс
Remix похож на альтернативу Ganache, но также поставляется с графическим интерфейсом, который помогает ориентироваться в развертывании и тестировании смарт-контрактов Ethereum. Вы можете узнать больше об этом на Remix — Ethereum IDE и сообщество. Все, что вам нужно сделать, это посетить https://remix.ethereum.org и использовать графический интерфейс для написания и развертывания смарт-контрактов. - Веб3
Web3 — это набор библиотек, который позволяет вам взаимодействовать с узлом Ethereum. Это могут быть локальные или удаленные узлы контракта через HTTP, IPC или веб-сокеты. Введение в Web3.js · Ускоренный курс Ethereum Blockchain Developer — хорошее место, чтобы немного узнать о Web3. - ИПФС
Основной протокол, который используется при создании dApps. Межпланетная файловая система (IPFS) — это протокол и одноранговая сеть для хранения и обмена данными в распределенной файловой системе. IPFS Powers the Distributed Web объясняет больше об IPFS и о том, как она обычно используется.
Создание серверного API с нуля
Итак, сначала нам нужно создать серверную часть, которую мы будем использовать, и мы используем Node.js. Когда мы хотим создать новый API Node.js, первое, что мы собираемся сделать, — это инициализировать пакет npm. Как вы, наверное, знаете, npm расшифровывается как Node Package Manager и поставляется вместе с бинарным файлом Node.js. Поэтому мы создаем новую папку и называем ее «блокчейн-музыка» . Мы открываем терминал в этой папке, а затем запускаем следующую команду:
$ npm init -y && touch server.js routes.js
Это запускает проект с файлом package.json и дает положительный ответ на все запросы. Затем мы также создаем файл server.js и файл route.js для написания функций routes
в API.
После всего этого вам нужно будет установить пакеты, которые нам нужны, чтобы сделать нашу сборку простой и понятной. Этот процесс является непрерывным, т.е. вы можете установить пакет в любой момент разработки вашего проекта.
Давайте установим самые важные из них, которые нам нужны прямо сейчас:
- Express.js
- @трюфель/контракт
- трюфель.js
- web3.js
- дотенв
-
short-id
- MongoDB
- нодмон
Вам также придется установить Truffle.js глобально , чтобы вы могли использовать его везде в вашей локальной среде. Если вы хотите установить их все сразу, запустите следующий код в терминале:
$ npm install nodemon truffle-contract dotenv mongodb shortid express web3 --save && npm install truffle -g
Флаг --save
предназначен для сохранения имени пакета в файле package.json . Флаг -g
предназначен для глобального хранения этого конкретного пакета, чтобы мы могли использовать его в любом проекте, над которым будем работать.
Затем мы создаем файл .env , в котором мы можем хранить секретный URI нашей базы данных MongoDB для использования. Мы делаем это, запустив touch.env в Терминале. Если у вас еще нет учетной записи базы данных в MongoDB, сначала начните со страницы MongoDB.
Пакет dotenv экспортирует нашу сохраненную переменную в среду процесса Node.js. Пожалуйста, убедитесь, что вы не отправляете файл .env при отправке в общедоступные репозитории, чтобы избежать утечки ваших паролей и личных данных.
Затем мы должны добавить сценарии для этапов сборки и разработки нашего проекта в наш файл package.json . В настоящее время наш package.json выглядит так:
{ "name": "test", "version": "1.0.0", "description": "", "main": "server.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "express": "^4.17.1", "socket.io": "^2.3.0", "truffle-contract": "^4.0.31", "web3": "^1.3.0" } }
Затем мы собираемся добавить сценарий запуска в файл package.json для использования сервера nodemon, чтобы всякий раз, когда мы вносим изменения, он перезапускал сам сервер, и сценарий сборки, который напрямую использует сервер узла, он может выглядеть так:
{ "name": "test", "version": "1.0.0", "description": "", "main": "server.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "nodemon server.js", "build": "node server.js" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "express": "^4.17.1", "socket.io": "^2.3.0", "truffle-contract": "^4.0.31", "web3": "^1.3.0" } }
Затем мы должны инициализировать Truffle для использования в нашем смарт-контракте с помощью пакета Truffle, который мы установили глобально ранее. В той же папке наших проектов мы запускаем следующую команду ниже в нашем терминале:
$ truffle init
Затем мы можем начать писать наш код в нашем файле server.js . Опять же, мы пытаемся создать простое децентрализованное приложение для музыкального магазина, где клиенты могут загружать музыку, чтобы каждый другой пользователь мог получить к ней доступ и слушать ее.
Наш server.js должен быть чистым для простого соединения и разделения компонентов, поэтому маршруты и другие функции будут помещены в другие файлы, такие как route.js . Наш пример server.js может быть:
require('dotenv').config(); const express= require('express') const app =express() const routes = require('./routes') const Web3 = require('web3'); const mongodb = require('mongodb').MongoClient const contract = require('truffle-contract'); app.use(express.json()) mongodb.connect(process.env.DB,{ useUnifiedTopology: true },(err,client)=>{ const db =client.db('Cluster0') //home routes(app,db) app.listen(process.env.PORT || 8082, () => { console.log('listening on port 8082'); }) })
По сути, выше мы импортируем нужные нам библиотеки с помощью require
, затем добавляем промежуточное ПО, которое позволяет использовать JSON в нашем API с помощью app.use
, затем подключаемся к нашей базе данных MongoDB и получаем доступ к базе данных, а затем указываем, какой кластер базы данных мы пытаемся получить доступ (для этого урока это «Cluster0» ). После этого мы вызываем функцию и импортируем ее из файла маршрутов . Наконец, мы прослушиваем любые попытки подключения к порту 8082
.
Этот файл server.js — это всего лишь основа для запуска приложения. Обратите внимание, что мы импортировали route.js . Этот файл будет содержать конечные точки маршрута для нашего API. Мы также импортировали пакеты, которые нам нужно было использовать, в файл server.js и инициализировали их.
Мы собираемся создать пять конечных точек для потребления пользователями:
- Конечная точка регистрации для регистрации пользователей только по электронной почте. В идеале мы бы сделали это с адресом электронной почты и паролем, но поскольку мы просто хотим идентифицировать каждого пользователя, мы не собираемся углубляться в безопасность паролей и хэширование ради краткости этого руководства.
POST /register Requirements: email
- Конечная точка входа для пользователей по электронной почте.
POST /login Requirements: email
- Конечная точка загрузки для пользователей — API, который получает данные музыкального файла. Внешний интерфейс преобразует файлы MP3/WAV в аудиобуфер и отправляет этот буфер в API.
POST /upload Requirements: name, title of music, music file buffer or URL stored
- Конечная точка доступа, которая будет предоставлять данные музыкального буфера любому зарегистрированному пользователю, который их запрашивает, и записывает, кто к ним обращался.
GET /access/{email}/{id} Requirements: email, id
- Мы также хотим предоставить доступ ко всей музыкальной библиотеке и вернуть результаты зарегистрированному пользователю.
GET /access/{email} Requirements: email
Затем мы пишем наши функции маршрута в файле route.js . Мы используем функции хранения и извлечения базы данных, а затем обязательно экспортируем функцию маршрута в конец файла, чтобы ее можно было импортировать в другой файл или папку.
const shortid = require('short-id') function routes(app, db){ app.post('/register', (req,res)=>{ let email = req.body.email let idd = shortid.generate() if(email){ db.findOne({email}, (err, doc)=>{ if(doc){ res.status(400).json({"status":"Failed", "reason":"Already registered"}) }else{ db.insertOne({email}) res.json({"status":"success","id":idd}) } }) }else{ res.status(400).json({"status":"Failed", "reason":"wrong input"}) } }) app.post('/login', (req,res)=>{ let email = req.body.email if(email){ db.findOne({email}, (err, doc)=>{ if(doc){ res.json({"status":"success","id":doc.id}) }else{ res.status(400).json({"status":"Failed", "reason":"Not recognised"}) } }) }else{ res.status(400).json({"status":"Failed", "reason":"wrong input"}) } }) app.post('/upload', (req,res)=>{ let buffer = req.body.buffer let name = req.body.name let title = req.body.title if(buffer && title){ }else{ res.status(400).json({"status":"Failed", "reason":"wrong input"}) } }) app.get('/access/:email/:id', (req,res)=>{ if(req.params.id && req.params.email){ }else{ res.status(400).json({"status":"Failed", "reason":"wrong input"}) } }) } module.exports = routes
Внутри этой функции route
у нас есть много других функций, вызываемых как в параметрах app
так и в параметрах db
. Это функции конечной точки API, которые позволяют пользователям указывать конечную точку в URL-адресе. В конечном итоге мы выбираем одну из этих функций для выполнения и предоставляем результаты в ответ на входящие запросы.
У нас есть четыре основные функции конечной точки:
-
get
: для чтения операций записи -
post
: для создания операций записи -
put
: для обновления операций записи -
delete
: для удаления операций записи
В этой функции routes
мы использовали операции get
и post
. Мы используем post
для регистрации, входа в систему и операций загрузки, а get
— для доступа к операциям с данными. Чтобы получить дополнительные разъяснения по этому поводу, вы можете ознакомиться со статьей Джейми Коркхилла «Как начать работу с узлом: введение в API, HTTP и ES6+ JavaScript».
В приведенном выше коде мы также можем видеть некоторые операции с базой данных, например, в маршруте регистрации . Мы сохранили электронную почту нового пользователя с помощью db.createa
и проверили электронную почту в функции входа в систему с помощью db.findOne
. Теперь, прежде чем мы сможем сделать все это, нам нужно назвать коллекцию или таблицу с помощью метода db.collection
. Это именно то, что мы рассмотрим далее.
Примечание . Чтобы узнать больше об операциях с базой данных в MongoDB, ознакомьтесь с документацией по методам оболочки mongo.
Создание простого смарт-контракта на блокчейне с помощью Solidity
Теперь мы собираемся написать контракт Blockchain на Solidity (это язык, на котором написаны смарт-контракты), чтобы просто хранить наши данные и извлекать их, когда они нам нужны. Данные, которые мы хотим сохранить, — это данные музыкального файла, а это означает, что мы должны загрузить музыку в IPFS, а затем сохранить адрес буфера в блокчейне.
Сначала мы создаем новый файл в папке контракта и называем его Inbox.sol . Чтобы написать смарт-контракт, полезно иметь хорошее представление о Solidity, но это не сложно, так как похоже на JavaScript.
Примечание . Если вам интересно узнать больше о Solidity, я добавил несколько ресурсов внизу статьи, чтобы вы могли начать работу.
pragma solidity ^0.5.0; contract Inbox{ //Structure mapping (string=>string) public ipfsInbox; //Events event ipfsSent(string _ipfsHash, string _address); event inboxResponse(string response); //Modifiers modifier notFull (string memory _string) { bytes memory stringTest = bytes(_string); require(stringTest.length==0); _; } // An empty constructor that creates an instance of the conteact constructor() public{} //takes in receiver's address and IPFS hash. Places the IPFSadress in the receiver's inbox function sendIPFS(string memory _address, string memory _ipfsHash) notFull(ipfsInbox[_address]) public{ ipfsInbox[_address] = _ipfsHash; emit ipfsSent(_ipfsHash, _address); } //retrieves hash function getHash(string memory _address) public view returns(string memory) { string memory ipfs_hash=ipfsInbox[_address]; //emit inboxResponse(ipfs_hash); return ipfs_hash; } }
В нашем контракте у нас есть две основные функции: функции sendIPFS
и getHash
. Прежде чем мы поговорим о функциях, мы видим, что сначала нам нужно было определить контракт с именем Inbox
. Внутри этого класса у нас есть структуры, используемые в объекте ipfsInbox
(сначала события, потом модификаторы).
После определения структур и событий мы должны инициализировать контракт, вызвав функцию- constructor
. Затем мы определили три функции. (Функция checkInbox
использовалась в тесте для проверки результатов.)
В sendIPFS
пользователь вводит идентификатор и хеш-адрес, после чего он сохраняется в блокчейне. Функция getHash
извлекает хэш-адрес, когда ей присвоен идентификатор. Опять же, логика этого заключается в том, что мы в конечном итоге хотим хранить музыку в IPFS. Чтобы проверить, как это работает, вы можете зайти в Remix IDE, скопировать, вставить и протестировать свой контракт, а также отладить любые ошибки и запустить снова (надеюсь, это не понадобится!).
После проверки правильности работы нашего кода в ремиксе, давайте перейдем к его локальной компиляции с помощью пакета Truffle. Но сначала нам нужно внести некоторые изменения в наши файлы и настроить наш эмулятор с помощью ganache-cli
:
Во-первых, давайте установим ganache-cli
. В том же каталоге выполните следующую команду в своем терминале:
$ npm install ganache-cli -g
Затем давайте откроем другой Терминал и запустим другую команду в той же папке:
$ ganache-cli
Это запускает эмулятор для нашего контракта с блокчейном для подключения и работы. Сверните терминал и продолжите работу с другим терминалом, который вы использовали.
Теперь перейдите к файлу truffle.js , если вы используете ОС Linux/Mac, или truffle-config.js в Windows, и измените этот файл, чтобы он выглядел следующим образом:
const path = require("path"); module.exports = { // to customize your Truffle configuration! contracts_build_directory: path.join(__dirname, "/build"), networks: { development: { host: "127.0.0.1", port: 8545, network_id: "*" //Match any network id } } };
По сути, мы добавили путь к папке сборки, в которой смарт-контракт конвертируется в файлы JSON. Затем мы также указали сеть, которую Truffle должен использовать для миграции.
Затем также в папке миграции создайте новый файл с именем 2_migrate_inbox.js и добавьте в файлы следующий код:
var IPFSInbox = artifacts.require("./Inbox.sol"); module.exports = function(deployer) { deployer.deploy(IPFSInbox); };
Мы сделали это, чтобы получить файл контракта и автоматически развернуть его в JSON, используя функцию deployer
во время миграции Truffle.
После вышеуказанных изменений запускаем:
$ truffle compile
В конце мы должны увидеть несколько сообщений, указывающих на успешную компиляцию, например:
> Compiled successfully using: - solc: 0.5.16+commit.9c3226ce.Emscripten.clang
Затем мы переносим наш контракт, запустив:
$ truffle migrate
После того, как мы успешно перенесли наши контракты, в конце у нас должно получиться что-то вроде этого:
Summary ======= > Total deployments: 1 > Final cost: 0.00973432 ETH
И мы почти закончили! Мы создали наш API с помощью Node.js, а также настроили и создали наш смарт-контракт.
Мы также должны написать тесты для нашего контракта, чтобы проверить поведение нашего контракта и убедиться, что это желаемое поведение. Тесты обычно пишутся и помещаются в test
папку. Пример теста, написанного в файле с именем InboxTest.js, созданном в тестовой папке:
const IPFSInbox = artifacts.require("./Inbox.sol") contract("IPFSInbox", accounts =>{ it("emit event when you send a ipfs address", async()=>{ //ait for the contract const ipfsInbox = await IPFSInbox.deployed() //set a variable to false and get event listener eventEmitted = false //var event = () await ipfsInbox.ipfsSent((err,res)=>{ eventEmitted=true }) //call the contract function which sends the ipfs address await ipfsInbox.sendIPFS(accounts[1], "sampleAddress", {from: accounts[0]}) assert.equal(eventEmitted, true, "sending an IPFS request does not emit an event") }) })
Итак, мы запускаем наш тест, выполнив следующее:
$ truffle test
Он проверяет наш контракт с файлами в тестовой папке и показывает количество пройденных и не пройденных тестов. Для этого урока мы должны получить:
$ truffle test Using network 'development'. Compiling your contracts... =========================== > Compiling .\contracts\Inbox.sol > Artifacts written to C:\Users\Ademola\AppData\Local\Temp\test--2508-n0vZ513BXz4N > Compiled successfully using: — solc: 0.5.16+commit.9c3226ce.Emscripten.clang Contract: IPFSInbox √ emit event when you send an ipfs address (373ms) 1 passing (612ms)
Интеграция смарт-контракта в серверный API с использованием Web3
В большинстве случаев, когда вы видите учебные пособия, вы видите децентрализованные приложения, созданные для интеграции внешнего интерфейса непосредственно в блокчейн. Но бывают случаи, когда необходима также интеграция с серверной частью, например, при использовании сторонних API и сервисов серверной части или при использовании блокчейна для создания CMS.
Использование Web3 очень важно для этой цели, поскольку оно помогает нам получить доступ к удаленным или локальным узлам Ethereum и использовать их в наших приложениях. Прежде чем мы продолжим, мы обсудим локальные и удаленные узлы Ethereum. Локальные узлы — это узлы, развернутые в нашей системе с помощью эмуляторов, таких как ganache-cli
, но удаленный узел — это узел, развернутый на онлайн-сборщиках/платформах, таких как ropsten или rinkeby . Чтобы погрузиться глубже, вы можете следовать учебному пособию о том, как развернуть на ropsten 5-минутное руководство по развертыванию смарт-контрактов с Truffle и Ropsten, или вы можете использовать поставщика кошелька truffle и выполнить развертывание с помощью более простого способа развертывания ваших смарт-контрактов.
В этом руководстве мы используем ganache-cli
cli, но если бы мы выполняли развертывание на ropsten, мы должны были скопировать или сохранить адрес нашего контракта где-нибудь, например, в нашем файле .env, а затем перейти к обновлению файла server.js , импортировать web3, импортировать перенесенный контракт и настроить экземпляр Web3.
require('dotenv').config(); const express= require('express') const app =express() const routes = require('./routes') const Web3 = require('web3'); const mongodb = require('mongodb').MongoClient const contract = require('truffle-contract'); const artifacts = require('./build/Inbox.json'); app.use(express.json()) if (typeof web3 !== 'undefined') { var web3 = new Web3(web3.currentProvider) } else { var web3 = new Web3(new Web3.providers.HttpProvider('https://localhost:8545')) } const LMS = contract(artifacts) LMS.setProvider(web3.currentProvider) mongodb.connect(process.env.DB,{ useUnifiedTopology: true }, async(err,client)=>{ const db =client.db('Cluster0') const accounts = await web3.eth.getAccounts(); const lms = await LMS.deployed(); //const lms = LMS.at(contract_address) for remote nodes deployed on ropsten or rinkeby routes(app,db, lms, accounts) app.listen(process.env.PORT || 8082, () => { console.log('listening on port '+ (process.env.PORT || 8082)); }) })
В файле server.js мы проверяем, инициализирован ли уже экземпляр web3. Если нет, мы инициализируем его на сетевом порту, который мы определили ранее ( 8545
). Затем мы создаем контракт на основе перенесенного файла JSON и пакета truffle-contract
и устанавливаем поставщика контракта на поставщика экземпляра Web3, который уже должен быть инициализирован.
Затем мы получаем учетные записи через web3.eth.getAccounts
. На этапе разработки мы вызываем развернутую функцию в нашем классе контракта, которая запрашивает у ganache-cli
, который все еще работает, предоставить нам адрес контракта для использования. Но если мы уже развернули наш контракт на удаленном узле, мы вызываем функцию, вводя адрес в качестве аргумента. Пример функции прокомментирован ниже определенной переменной lms
в нашем коде выше. Затем мы вызываем функцию routes
, вводя экземпляр приложения, экземпляр базы данных, экземпляр контракта ( lms
) и данные учетных записей в качестве аргументов. Наконец, мы слушаем запросы на порту 8082
.
Кроме того, к настоящему времени мы должны были установить пакет MongoDB, потому что мы используем его в нашем API в качестве нашей базы данных. Получив это, мы переходим на страницу маршрутов, где используем методы, определенные в контракте, для выполнения таких задач, как сохранение и извлечение музыкальных данных.
В итоге наш route.js должен выглядеть так:
const shortid = require('short-id') const IPFS =require('ipfs-api'); const ipfs = IPFS({ host: 'ipfs.infura.io', port: 5001,protocol: 'https' }); function routes(app, dbe, lms, accounts){ let db= dbe.collection('music-users') let music = dbe.collection('music-store') app.post('/register', (req,res)=>{ let email = req.body.email let idd = shortid.generate() if(email){ db.findOne({email}, (err, doc)=>{ if(doc){ res.status(400).json({"status":"Failed", "reason":"Already registered"}) }else{ db.insertOne({email}) res.json({"status":"success","id":idd}) } }) }else{ res.status(400).json({"status":"Failed", "reason":"wrong input"}) } }) app.post('/login', (req,res)=>{ let email = req.body.email if(email){ db.findOne({email}, (err, doc)=>{ if(doc){ res.json({"status":"success","id":doc.id}) }else{ res.status(400).json({"status":"Failed", "reason":"Not recognised"}) } }) }else{ res.status(400).json({"status":"Failed", "reason":"wrong input"}) } }) app.post('/upload', async (req,res)=>{ let buffer = req.body.buffer let name = req.body.name let title = req.body.title let id = shortid.generate() + shortid.generate() if(buffer && title){ let ipfsHash = await ipfs.add(buffer) let hash = ipfsHash[0].hash lms.sendIPFS(id, hash, {from: accounts[0]}) .then((_hash, _address)=>{ music.insertOne({id,hash, title,name}) res.json({"status":"success", id}) }) .catch(err=>{ res.status(500).json({"status":"Failed", "reason":"Upload error occured"}) }) }else{ res.status(400).json({"status":"Failed", "reason":"wrong input"}) } }) app.get('/access/:email', (req,res)=>{ if(req.params.email){ db.findOne({email: req.body.email}, (err,doc)=>{ if(doc){ let data = music.find().toArray() res.json({"status":"success", data}) } }) }else{ res.status(400).json({"status":"Failed", "reason":"wrong input"}) } }) app.get('/access/:email/:id', (req,res)=>{ let id = req.params.id if(req.params.id && req.params.email){ db.findOne({email:req.body.email},(err,doc)=>{ if(doc){ lms.getHash(id, {from: accounts[0]}) .then(async(hash)=>{ let data = await ipfs.files.get(hash) res.json({"status":"success", data: data.content}) }) }else{ res.status(400).json({"status":"Failed", "reason":"wrong input"}) } }) }else{ res.status(400).json({"status":"Failed", "reason":"wrong input"}) } }) } module.exports = routes
At the beginning of the routes file, we imported the short-id
package and ipfs-http-client
and then initialized IPFS with the HTTP client using the backend URL ipfs.infura.io
and port 5001
. This allowed us to use the IPFS methods to upload and retrieve data from IPFS (check out more here).
In the upload route, we save the audio buffer to IPFS which is better compared to just storing it on the blockchain for anyone registered or unregistered to use. Then we saved the address of the buffer in the blockchain by generating an ID and using it as an identifier in the sendIFPS
function. Finally, then we save all the other data associated with the music file to our database. We should not forget to update our argument in the routes function since we changed it in the server.js file.
In the access route using id
, we then retrieve our data by getting the id
from the request, using the id
to access the IPFS hash address, and then access the audio buffer using the address. But this requires authentication of a user by email which is done before anything else.
Phew, we're done ! Right now we have an API that can receive requests from users, access a database, and communicate to a node that has the software running on them. We shouldn't forget that we have to export our function with module.exports
though!
As we have noticed, our app is a decentralized app . However, it's not fully decentralized as we only stored our address data on the blockchain and every other piece of data was stored securely in a centralized database which is the basis for semi-dApps . So the consumption of data can be done directly via request or using a frontend application in JavaScript to send fetch requests.
Our music store backend app can now safely store music data and provide access to anyone who needs to access it, provided it is a registered user. Using blockchain for music sharing makes it cheaper to store music data while focusing on connecting artists directly with users, and perhaps it could help them generate revenue that way. This wouldn't require a middleman that uses royalty; instead, all of the revenue would go to the artist as users request their music to either download or stream. A good example of a music streaming application that uses blockchain just like this is Opus OPUS: Decentralized music sharing platform. However, there are also a few others like Musicoin, Audius, and Resonate.
Что дальше?
The final thing after coding is to start our server by running npm run start
or npm run build
and test our backend endpoints on either the browser or with Postman. After running and testing our API we could add more features to our backend and blockchain smart contract. If you'd like to get more guidance on that, please check the further reading section for more articles.
It's worth mentioning that it is critical to write unit and integration tests for our API to ensure correct and desirable behaviors. Once we have all of that done, we can deploy our application on the cloud for public use. This can be done on its own with or without adding a frontend (microservices) on Heroku, GCP, or AWS for public use. Happy coding!
Note : You can always check my repo for reference. Also, please note that the .env file containing the MongoDB database URI is included for security reasons.
Further Reading And Related Resources
- “How to Build Ethereum Dapp with React.js: Complete Step-By-Step Guide,” Gregory McCubbin
- “Ethereum + IPFS + React DApp Tutorial Pt. 1,” Alexander Ma
- “Ethereum Development with Go,” Miguel Mota
- “Create your first Ethereum dAPP with Web3 and Vue.JS (Part 1),” Nico Vergauwen
- “Deploy a Smart Contract on Ethereum with Python, Truffle and web3py,” Gabriel Saldanha
- “Why Use Blockchain Technology?,” Bernard Marr
- “How To Build Your Own Blockchain Using Node.js,” DevTeam.Space
- “How To Build A Blockchain App With Ethereum, Web3.js & Solidity Smart Contracts,” Gregory McCubbin
- “How To Build A Simple Cryptocurrency Blockchain In Node.js,” Alfrick Opidi
- “How Blockchain Technology Is Going To Revolutionize Ecommerce,” Sergii Shanin
- “4 Ways Blockchain Will Transform Higher Education — Smarter With Gartner,” Susan Moore
- “How To Learn Solidity: The Ultimate Ethereum Coding Tutorial,” Ryan Molecke
- “Developing Ethereum Smart Contracts For Beginners,” Coursetro
- “Learn about Ethereum,” Ethereum official site