如何為以太坊區塊鏈構建 Node.js API
已發表: 2022-03-10區塊鏈技術在過去十年中一直在崛起,並帶來了大量的產品和平台,例如 Chainalysis(金融科技)、Burstiq(健康科技)、Filament(物聯網)、Opus(音樂流媒體)和 Ocular(網絡安全)。
從這些示例中,我們可以看到區塊鏈跨越了許多產品和用例——使其非常重要和有用。 在金融科技(金融科技)中,它被用作 Chain、Chainalysis 等地方的去中心化賬本以確保安全性和透明度,並且在健康科技中也可用於保護 Burstiq 和 Robomed 中敏感健康數據的安全——不要忘記 Opus 等媒體技術和 Audius 也使用區塊鏈來實現版稅透明度,從而獲得全額版稅。
Ocular 使用區塊鏈附帶的安全性來進行生物識別系統的身份管理,而 Filament 使用區塊鏈分類帳進行實時加密通信。 這表明區塊鏈通過改善我們的生活對我們來說變得多麼重要。 但究竟什麼是區塊鏈?
區塊鍊是跨計算機網絡共享的數據庫。 一旦將記錄添加到鏈中,就很難更改。 為了確保數據庫的所有副本都相同,網絡會不斷進行檢查。
那麼我們為什麼需要區塊鏈呢? 與極有可能發生黑客攻擊、錯誤和停機時間的傳統記錄或數據庫相比,區塊鍊是一種記錄活動和保持數據新鮮的安全方式,同時保持其歷史記錄。 數據不會被任何人損壞或意外刪除,您可以從歷史數據跟踪和即時更新的記錄中受益,這些記錄不會因服務器停機而被刪除或無法訪問。
由於整個區塊鏈在多台計算機上複製,任何用戶都可以查看整個區塊鏈。 交易或記錄不是由一個中央管理員處理,而是由一個致力於驗證數據並達成共識的用戶網絡處理。
使用區塊鏈的應用程序稱為dApps (去中心化應用程序)。 今天環顧四周,我們主要會在金融科技中找到去中心化的應用程序,但區塊鏈超越了去中心化金融。 我們擁有健康平台、音樂流媒體/共享平台、電子商務平台、網絡安全平台和物聯網,這些平台正朝著上述去中心化應用程序 (dApp) 發展。
那麼,什麼時候考慮在我們的應用程序中使用區塊鏈而不是標準數據庫或記錄才有意義呢?
區塊鏈的常見應用
- 管理和保護數字關係
任何時候你想保持資產的長期、透明記錄(例如,記錄財產或公寓權利),區塊鏈可能是理想的解決方案。 尤其是以太坊的“智能合約”,非常適合促進數字關係。 使用智能合約,當交易各方同意滿足他們的條件時,可以釋放自動支付。 - 消除中間人/看門人
例如,目前大多數供應商都必須通過一個集中的聚合平台與客人互動,比如 Airbnb 或 Uber(反過來,這些平台會從每筆交易中分一杯羹)。 區塊鏈可以改變這一切。
例如,TUI 深信區塊鏈的力量,它開創了直接連接酒店經營者和客戶的方式。 這樣,他們可以通過區塊鏈以一種簡單、安全和一致的方式進行交易,而不是通過中央預訂平台。 - 記錄合作夥伴之間的安全交易以確保信任
傳統數據庫可能有利於記錄兩方之間的簡單交易,但當事情變得更加複雜時,區塊鏈可以幫助減少瓶頸並簡化關係。 更重要的是,分散系統的附加安全性使區塊鏈成為一般交易的理想選擇。
一個例子是墨爾本大學,它開始將其記錄存儲在區塊鏈中。 區塊鏈在高等教育中最有前途的用例是轉變學位、證書和文憑的“記錄保存”。 這節省了大量用於存儲或記錄的專用服務器的成本。 - 為數據不斷變化的應用程序記錄過去的操作
區塊鍊是一種更好、更安全的方式來記錄活動並保持數據新鮮,同時保留其歷史記錄。 數據不會被任何人損壞或意外刪除,您可以從歷史數據跟踪和即時更新的記錄中受益。 一個好的用例示例是電子商務中的區塊鏈,區塊鍊和電子商務都涉及交易。
區塊鏈使這些交易更安全、更快捷,而電子商務活動則依賴於它們。 區塊鏈技術使用戶能夠自動和手動共享和安全存儲數字資產。 該技術能夠處理用戶活動,例如支付處理、產品搜索、產品購買和客戶服務。 它還減少了用於庫存管理和支付處理的費用。 - 去中心化可以在任何地方使用
與以前由於貨幣兌換政策等各種原因我們不得不將自己限制在特定地區不同,支付網關的限制使得您難以獲得許多不在您所在地區或大陸的國家的金融資源。 隨著區塊鏈去中心化或點對點系統的興起和力量,這變得更容易與其他國家合作。
例如,歐洲的電子商務商店可以在非洲擁有消費者,並且不需要中間人來處理他們的付款請求。 此外,這些技術正在為在線零售商打開大門,利用比特幣(即加密貨幣)利用遙遠國家的消費市場。 - Blockhain 是技術中立的
區塊鏈適用於開發人員使用的所有和任何技術堆棧。 作為 Python 開發人員,您無需學習 Node 即可使用區塊鍊或學習 Golang。 這使得區塊鏈非常易於使用。
實際上,我們可以將它直接與 Vue/React 中的前端應用程序一起使用,區塊鏈作為我們唯一的數據庫,用於簡單不復雜的任務和用例,例如上傳數據或獲取哈希以顯示我們的用戶記錄,或構建像賭場這樣的前端遊戲遊戲和博彩遊戲(需要高度信任)。 另外,借助 web3 的強大功能,我們可以直接將數據存儲在鏈中。
現在,我們已經看到了使用區塊鏈的很多好處,但是我們什麼時候應該完全不使用區塊鏈呢?
區塊鏈的缺點
- 降低數字交易的速度
區塊鏈需要大量的計算能力,這往往會降低數字交易的速度,儘管有一些變通方法,當需要以毫秒為單位的高速交易時,建議使用集中式數據庫。 - 數據不變性
數據不變性一直是區塊鏈的最大缺點之一。 很明顯,多個系統都從中受益,包括供應鏈、金融系統等。 但是,它的缺點是數據一旦寫入,就無法刪除。 地球上的每一個人都有隱私權。 但是,如果同一個人使用基於區塊鏈技術的數字平台,那麼當他不希望它存在時,他將無法從系統中刪除它的痕跡。 簡而言之,他無法刪除他的踪跡——將隱私權分割成碎片。 - 需要專業知識
實施和管理區塊鏈項目很困難。 它需要全面的知識才能完成整個過程。 這就是為什麼很難遇到區塊鏈專家或專家的原因,因為培訓區塊鏈專家需要花費大量時間和精力。 因此,如果您已經開始,這篇文章是一個很好的起點和一個很好的指南。 - 互操作性
多個區塊鍊網絡以獨特的方式努力解決分佈式賬本問題,這使得它們難以關聯或相互集成。 這使得不同鏈之間的通信變得困難。 - 舊版應用程序集成
許多企業和應用程序仍然使用遺留系統和架構; 採用區塊鏈技術需要對這些系統進行徹底改革,我必須說這對其中許多系統來說是不可行的。
區塊鏈一直在不斷發展和成熟,所以如果今天提到的這些缺點後來變成了專業人士,請不要感到驚訝。 作為一種加密貨幣的比特幣是區塊鏈的一個流行例子,除了比特幣加密貨幣之外,一個流行的區塊鏈正在崛起,它是以太坊區塊鏈。 比特幣專注於加密貨幣,而以太坊則更專注於智能合約,而智能合約一直是新技術平台的主要驅動力。
推薦閱讀:比特幣與以太坊:有什麼區別?
讓我們開始構建我們的 API
對區塊鏈有了深入的了解,現在讓我們看看如何構建以太坊區塊鏈並將其集成到 Node.js 中的標準 API 中。 最終目標是更好地了解 dApp 和區塊鏈平台的構建方式。
大多數 dApp 具有相似的架構和結構。 基本上,我們有一個用戶與 dApp 前端交互——無論是 Web 還是移動端——然後與後端 API 交互。 然後,後端應請求通過公共節點與智能合約或區塊鏈交互; 這些要么運行 Node.js 應用程序,要么後端通過直接運行 Node.js 軟件來使用區塊鏈。 從選擇構建完全去中心化的應用程序或半去中心化的應用程序到選擇應該去中心化的內容以及如何安全地存儲私鑰,這些過程之間還有很多事情要做。
推薦閱讀:去中心化應用架構:後端、安全和設計模式
我們應該首先知道的事情
在本教程中,我們將嘗試構建一個去中心化音樂商店應用程序的後端,該應用程序使用以太坊區塊鏈的力量來存儲音樂並共享它以供下載或流式傳輸。
我們正在嘗試構建的應用程序的基本結構包含三個部分:
- 身份驗證,通過電子郵件完成; 當然,我們需要在應用程序中添加加密密碼。
- 數據的存儲,音樂數據首先存儲在ipfs中,存儲地址存儲在區塊鏈中以供檢索。
- 檢索,任何經過身份驗證的用戶都可以訪問我們平台上存儲的數據並使用它。
我們將使用 Node.js 構建它,但您也可以使用 Python 或任何其他編程語言構建它。 我們還將了解如何在 IPFS 中存儲媒體數據、獲取地址並編寫函數來存儲該地址——並使用 Solidity 編程語言從區塊鏈中檢索該地址。
以下是我們應該擁有的一些工具,用於構建或使用以太坊和 Node.js。
- 節點.js
第一個要求是 Node 應用程序。 我們正在嘗試構建一個 Node.js 應用程序,因此我們需要一個編譯器。 請確保您已安裝 Node.js — 並請下載最新的長期支持二進製文件 ( LTS )。 - 松露套房
Truffle 是一個合約開發和測試環境,也是以太坊區塊鏈的資產管道。 它為編譯、流水線和運行腳本提供了一個環境。 一旦你談到開發區塊鏈,Truffle 是一個受歡迎的去處。 查看 Truffle Suite 上的 Truffle Suite:智能合約的甜蜜工具。 - 甘納許 CLI
另一個與 Truffle 配合得很好的工具是 Ganache-CLI。 它由 Truffle Suite 團隊構建和維護。 構建編譯後,需要一個模擬器來開發和運行區塊鏈應用,然後部署智能合約來使用。 Ganache 使您可以更輕鬆地在模擬器中部署合約,而無需使用實際資金來支付交易成本、可循環賬戶等。 在 Ganache CLI 和 Ganache 閱讀有關 Ganache CLI 的更多信息。 - 混音
Remix 就像 Ganache 的替代品,但還帶有一個 GUI 來幫助導航以太坊智能合約的部署和測試。 您可以在 Remix — 以太坊 IDE 和社區上了解更多信息。 您所要做的就是訪問 https://remix.ethereum.org 並使用 GUI 編寫和部署智能合約。 - Web3
Web3 是一個庫集合,允許您與以太坊節點進行交互。 這些可以是通過 HTTP、IPC 或 Web 套接字的合約的本地或遠程節點。 Web3.js 簡介 · 以太坊區塊鏈開發者速成課程是了解 Web3 的好地方。 - IPFS
用於構建 dApp 的核心協議。 星際文件系統(IPFS) 是一種協議和點對點網絡,用於在分佈式文件系統中存儲和共享數據。 IPFS Powers the Distributed Web 解釋了有關 IPFS 及其通常使用方式的更多信息。
從頭開始創建後端 API
所以首先我們必須創建一個要使用的後端,並且我們使用的是 Node.js。 當我們想要創建一個新的 Node.js API 時,我們要做的第一件事就是初始化一個 npm 包。 您可能知道,npm 代表Node Package Manager ,它與 Node.js 二進製文件一起預打包。 因此,我們創建了一個新文件夾並將其命名為“blockchain-music” 。 我們在該文件夾目錄中打開終端,然後運行以下命令:
$ npm init -y && touch server.js routes.js
這將使用package.json文件啟動項目,並對所有提示回答“是”。 然後我們還創建了一個server.js文件和一個routes.js文件,用於在 API 中編寫routes
函數。
畢竟,您將必須安裝我們需要的軟件包,以使我們的構建變得簡單明了。 這個過程是一個連續的過程,也就是說,您可以在項目開發過程中的任何時候安裝一個包。
讓我們安裝我們現在需要的最重要的:
- Express.js
- @松露/合同
- 松露.js
- web3.js
- dotenv
-
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文件,我們可以在其中存儲我們的 MongoDB 數據庫秘密 URI 以供使用。 我們通過在終端中運行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應該是乾淨的,以便於組件的耦合和解耦,因此路由和其他功能將放在其他文件中,例如routes.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
導入我們需要的庫,然後使用app.use
添加一個允許在我們的 API 中使用 JSON 的中間件,然後連接到我們的 MongoDB 數據庫並獲取數據庫訪問權限,然後我們指定哪個數據庫集群我們正在嘗試訪問(對於本教程,它是“Cluster0” )。 在此之後,我們調用該函數並從路由文件中導入它。 最後,我們偵聽端口8082
上的任何嘗試連接。
這個server.js文件只是啟動應用程序的準系統。 請注意,我們導入了 routes.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
然後我們在routes.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
進行訪問數據操作。 有關這方面的更多解釋,您可以查看 Jamie Corkhill 的文章“如何開始使用 Node:API、HTTP 和 ES6+ JavaScript 簡介”。
在上面的代碼中,我們還可以看到一些數據庫操作,比如註冊路由。 我們使用db.findOne
存儲了新用戶的電子郵件,並使用db.createa
在登錄函數中檢查了該電子郵件。 現在,在我們完成所有這些之前,我們需要使用db.collection
方法命名一個集合或表。 這正是我們接下來要介紹的內容。
注意:要了解有關 MongoDB 中數據庫操作的更多信息,請查看 mongo Shell 方法文檔。
用 Solidity 構建一個簡單的區塊鏈智能合約
現在我們將用 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
這將為我們的區塊鏈合約啟動模擬器以連接和工作。 最小化終端並繼續使用您一直在使用的另一個終端。
如果您使用的是 Linux/Mac OS 或 Windows 中的 truffle -config.js ,現在轉到truffle.js文件,然後將這個文件修改為如下所示:
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,在 Truffle 遷移期間使用deployer
功能。
在上述更改之後,我們運行:
$ truffle compile
我們應該在最後看到一些顯示編譯成功的消息,例如:
> Compiled successfully using: - solc: 0.5.16+commit.9c3226ce.Emscripten.clang
接下來,我們通過運行來遷移我們的合約:
$ truffle migrate
一旦我們成功遷移了我們的合約,我們最後應該有這樣的東西:
Summary ======= > Total deployments: 1 > Final cost: 0.00973432 ETH
我們幾乎完成了! 我們已經使用 Node.js 構建了我們的 API,並且還設置並構建了我們的智能合約。
我們還應該為我們的合約編寫測試來測試我們的合約的行為並確保它是期望的行為。 測試通常被編寫並放置在test
文件夾中。 在 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)
使用 Web3 將智能合約集成到後端 API
大多數時候,當您看到教程時,您會看到為將前端直接集成到區塊鏈而構建的去中心化應用程序。 但有時也需要與後端集成,例如在使用第三方後端 API 和服務時,或者在使用區塊鏈構建 CMS 時。
Web3 的使用對此非常重要,因為它可以幫助我們訪問遠程或本地以太坊節點並在我們的應用程序中使用它們。 在繼續之前,我們將討論本地和遠程以太坊節點。 本地節點是部署在我們系統上的節點,使用ganache-cli
等模擬器,但遠程節點是部署在ropsten或rinkeby等在線水龍頭/平台上的節點。 要深入了解,您可以按照關於如何在 ropsten 上部署的教程 5 分鐘指南來使用 Truffle 和 Ropsten 部署智能合約,或者您可以使用 truffle 錢包提供商並通過更簡單的方式部署您的智能合約。
我們在本教程中使用ganache-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 中使用它作為我們的數據庫。 一旦我們有了這些,我們進入路由頁面,我們使用合約中定義的方法來完成諸如保存和檢索音樂數據之類的任務。
最後,我們的 routes.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