以太坊学习路线——(五)DApp开发:简易版去中心化微博
這篇博客演示的基本操作系統(tǒng)環(huán)境是CentOS 7,參考書籍:以太坊開發(fā)實(shí)戰(zhàn)——以太坊關(guān)鍵技術(shù)與案例分析 第十一章(吳壽鶴、馮翔、劉濤、周廣益?? 著)。
項(xiàng)目地址,包含該項(xiàng)目所需大部分文件,前端因?yàn)橐蕾嚢?#xff0c;大家自己執(zhí)行命令下載一下,博文末尾有相關(guān)執(zhí)行命令。
我所上傳的文件結(jié)構(gòu):(已通過審核)
eth-weibo/ ├── app │?? ├── dist │?? ├── src │?? │?? ├── css │?? │?? │?? ├── dist │?? │?? │?? │?? ├── css │?? │?? │?? │?? │?? ├── bootstrap.css │?? │?? │?? │?? │?? ├── bootstrap.css.map │?? │?? │?? │?? │?? ├── bootstrap.min.css │?? │?? │?? │?? │?? ├── bootstrap.min.css.map │?? │?? │?? │?? │?? ├── bootstrap-theme.css │?? │?? │?? │?? │?? ├── bootstrap-theme.css.map │?? │?? │?? │?? │?? ├── bootstrap-theme.min.css │?? │?? │?? │?? │?? └── bootstrap-theme.min.css.map │?? │?? │?? │?? ├── fonts │?? │?? │?? │?? │?? ├── glyphicons-halflings-regular.eot │?? │?? │?? │?? │?? ├── glyphicons-halflings-regular.svg │?? │?? │?? │?? │?? ├── glyphicons-halflings-regular.ttf │?? │?? │?? │?? │?? ├── glyphicons-halflings-regular.woff │?? │?? │?? │?? │?? └── glyphicons-halflings-regular.woff2 │?? │?? │?? │?? └── js │?? │?? │?? │?? ├── bootstrap.js │?? │?? │?? │?? ├── bootstrap.min.js │?? │?? │?? │?? └── npm.js │?? │?? │?? └── style.css │?? │?? ├── pages │?? │?? │?? └── index.html │?? │?? └── scripts │?? │?? ├── app.js │?? │?? └── jQuery.js │?? └── webpack.config.js ├── contracts │?? ├── Migrations.sol │?? ├── WeiboAccount.sol │?? └── WeiboRegistry.sol ├── migrations │?? ├── 1_initial_migration.js │?? └── 2_weiboregistry_migration.js └── truffle-config.js一、DApps架構(gòu)
DApps(decentralized applications)即去中心化應(yīng)用。傳統(tǒng)Web應(yīng)用是運(yùn)行在TCP/IP四層模型上的,在TCP/IP網(wǎng)絡(luò)上傳遞的是數(shù)據(jù)。而區(qū)塊鏈相當(dāng)于在TCP/IP四層模型上又加上了一層價(jià)值傳遞層,使原有的四層模型變?yōu)槲鍖印Apps運(yùn)行在區(qū)塊鏈上,所以應(yīng)用天然具有價(jià)值轉(zhuǎn)移的功能,這是DApps與Web應(yīng)用最大的不同。Web需要一個(gè)中心化的服務(wù)機(jī)構(gòu)來進(jìn)行服務(wù)運(yùn)作,而DApps的業(yè)務(wù)邏輯是以智能合約的方式部署在區(qū)塊鏈上的,且智能合約產(chǎn)生的數(shù)據(jù)也是存儲在區(qū)塊鏈上的,這意味著DApps的業(yè)務(wù)邏輯、數(shù)據(jù)都是去中心化的。DApps與Web應(yīng)用區(qū)別:
| ? | Web應(yīng)用 | DApps應(yīng)用 |
| 前端 | HTML、CSS、Javascript | HTML、CSS、Javascript |
| 邏輯 | Java等高級語言,部署在Web服務(wù)器上 | Solidity語言編寫,部署在以太坊區(qū)塊鏈上 |
| 數(shù)據(jù) | 存儲在數(shù)據(jù)庫中 | 存儲在以太坊上 |
| 可使用貨幣 | 法比 | 以太坊的內(nèi)置貨幣或合約創(chuàng)建的貨幣 |
二、去中心化微博開發(fā)
這是一個(gè)運(yùn)行在以太坊上的去中心化微博系統(tǒng),去中心化意味著沒有一個(gè)中心機(jī)構(gòu)能夠控制你發(fā)送的微博,你發(fā)送的微博是由你完全控制的,任何人無法刪除、關(guān)閉你的微博。一旦你的微博發(fā)出去后,只有你自己能夠刪除它。微博系統(tǒng)功能如下:
微博內(nèi)容長度限制在160個(gè)字符以下。
微博賬戶可以接收打賞、捐贈,貨幣是以太幣。
WeiboRegistry合約相當(dāng)于一個(gè)平臺,一個(gè)展示微博帳號的平臺。我們自己創(chuàng)建了一個(gè)微博合約后需要到一個(gè)平臺上注冊一下,這樣其他人就可以通過平臺找到我們了,就可以和我們互動。即使平臺被刪除、屏蔽后我們自己的微博合約還是存在的。
如下圖,平臺信息部分可以看到平臺地址和打賞,已注冊微博用戶部分可以看到所有注冊過的用戶信息。注冊部分可以通過在weibo name對應(yīng)的輸入框輸入id來注冊,成功后,會在右側(cè)的weibo adddress對應(yīng)的輸入框顯示注冊成功所得到的地址,并且刷新已注冊用戶區(qū)域的數(shù)據(jù)。
WeiboAccount是一個(gè)比較重要的合約,每當(dāng)我們需要創(chuàng)建一個(gè)微博帳號時(shí)就需要部署一個(gè)WeiboAccount合約。合約部署完成后會返回一個(gè)合約地址,那就是我們的微博帳號。我們想發(fā)送微博時(shí)就調(diào)用WeiboAccount里的方法,這樣我們發(fā)送的所有微博就會存在WeiboAccount合約中。
如下圖,我的微博頁面,需要先登錄才能使用,將微博平臺所注冊的地址輸入到登錄輸入框,然后點(diǎn)擊登錄,即可成功登錄(非法地址將無法登錄成功)。賬戶信息部分由用戶的id、用戶地址、以及用戶打賞金額。發(fā)微博部分只有登錄后才能使用,否則點(diǎn)擊發(fā)送按鈕會提醒先登錄。我的微博部分也是只有登錄后才能看到用戶所有已發(fā)布的微博信息。
?環(huán)境:該項(xiàng)目在linux系統(tǒng)所編寫并測試成功的,需要node環(huán)境,請自行搜索安裝搭建node環(huán)境。
? ? ? ? ? ? 需要:Truffle安裝
? ? ? ? ? ? 需要:Testrpc
1.創(chuàng)建項(xiàng)目
//創(chuàng)建項(xiàng)目 [root@localhost opt]# mkdir eth-weibo [root@localhost opt]# cd eth-weibo///通過truffle命令初始化dapp,前端使用webpack [root@localhost eth-weibo]# truffle unbox webpack這需要等待一段時(shí)間來下載相關(guān)文件和庫,完成后顯示:
? Preparing to download ? Downloading ? Cleaning up temporary files ? Setting up boxUnbox successful. Sweet!Commands:Compile: truffle compileMigrate: truffle migrateTest contracts: truffle testRun dev server: cd app && npm run devBuild for production: cd app && npm run build哦!!!注意你的相關(guān)環(huán)境和版本,我的Truffle安裝相關(guān)版本信息如下:
//我的truffle版本 [root@localhost myproject]# truffle version Truffle v5.0.10 (core: 5.0.10) Solidity v0.5.0 (solc-js) Node v11.13.0 Web3.js v1.0.0-beta.37項(xiàng)目根目錄結(jié)構(gòu)如下:?
[root@localhost eth-weibo]# tree -L 1 ../eth-weibo/ ../eth-weibo/ ├── app //存放前端應(yīng)用相關(guān)文件 ├── contracts //存放所編寫的solidity文件 ├── migrations //存放合約文件的遷移腳本 ├── test //存放測試文件,可通過truffle test命令進(jìn)行合約測試 └── truffle-config.js //truffle項(xiàng)目的配置文件由于該版本使用truffle命令初始化后會默認(rèn)存在一個(gè)metaCoin的應(yīng)用所以需要作相關(guān)文件刪除,大家也可以去運(yùn)行測試該項(xiàng)目案例,網(wǎng)上教程很多,大家自己上網(wǎng)學(xué)習(xí)。我寫這個(gè)應(yīng)用的時(shí)候也借鑒了該metaCoin應(yīng)用相關(guān)的處理方式,看看別人的應(yīng)用源碼,可以學(xué)習(xí)相關(guān)環(huán)境連接處理方式和別人的編碼風(fēng)格:
//1.刪除contracts目錄下其他文件,只保留遷移合約Migrations.sol contracts/ └── Migrations.sol//2.刪除migrates目錄下其他文件,只保留遷移合約的遷移腳本1_initial_migration.js migrations/ └── 1_initial_migration.js//3.刪除test目錄下所有測試文件 test/0 directories, 0 files?而對于truffle-config.js,該文件中有好幾種運(yùn)行環(huán)境的配置templete,在本地測試的話,只需要如下內(nèi)容就夠了:
module.exports = {networks: {development: {host: "127.0.0.1", // Localhost (default: none)port: 8545, // Standard Ethereum port (default: none)network_id: "*", // Any network (default: none)},}, }?2.合約
(1).WeiboAccount合約
WeiboAccount合約存儲我們發(fā)送的微博內(nèi)容,每一個(gè)微博賬戶對應(yīng)一個(gè)WeiboAccount合約,WeiboAccount合約的所有者是合約創(chuàng)建者,所以你的微博只能由你自己管理,沒有任何一個(gè)機(jī)構(gòu)能夠刪除你的微博合約。WeiboAccount合約主要包含以下幾個(gè)功能:
pragma solidity >=0.4.21 <0.6.0;/**微博賬戶 */ contract WeiboAccount {struct Weibo {uint timestamp;string weiboString;}//這個(gè)微博賬戶的所有微博,weiboID映射微博內(nèi)容mapping (uint => Weibo) _weibos;//賬戶發(fā)的微博數(shù)量uint _numberOfWeibos;//微博賬戶的所有者address _adminAddress;//權(quán)限控制,被這個(gè)修飾符修飾的方法,表示該方法只能被微博所有者操作modifier onlyAdmin {require(msg.sender == _adminAddress);_;}//微博合約的構(gòu)造方法constructor() public {_numberOfWeibos = 0;_adminAddress = msg.sender;}//發(fā)新微博function weibo(string memory weiboString) onlyAdmin public {require(bytes(weiboString).length <= 160);_weibos[_numberOfWeibos].timestamp = now;_weibos[_numberOfWeibos].weiboString = weiboString;_numberOfWeibos++;}//根據(jù)ID查找微博function getWeibo(uint weiboId) view public returns (string memory weiboString, uint timestamp) {weiboString = _weibos[weiboId].weiboString;timestamp = _weibos[weiboId].timestamp;}//返回最新一條微博function getLatestWeibo() view public returns (string memory weiboString, uint timestamp, uint numberOfWeibos) {weiboString = _weibos[_numberOfWeibos - 1].weiboString;timestamp = _weibos[_numberOfWeibos - 1].timestamp;numberOfWeibos = _numberOfWeibos;}//返回賬戶所有者function getOwnerAddress() view public returns (address adminAddress) {return _adminAddress;}//返回微博總數(shù)function getNumberOfWeibos() view public returns (uint numberOfWeibos) {return _numberOfWeibos;}//取回打賞function adminRetrieveDonations(address payable receiver) public {assert(receiver.send(address(this).balance));}//摧毀合約function adminDeleteAccount() onlyAdmin public {selfdestruct(msg.sender);}//記錄每條打賞記錄event LogDonate(address indexed from, uint256 _amount);//接受別人的打賞function() external payable {emit LogDonate(msg.sender, msg.value);} }?(2).WeiboRegistry合約
WeiboRegistry合約為我們提供了一個(gè)展示微博帳號的平臺,在WeiboRegistry維護(hù)著賬戶昵稱、賬戶ID到WeiboRegistry合約之間的映射關(guān)系,這樣其他人就可以通過平臺找到我們,可以和我們進(jìn)行互動。即便平臺被刪除后,我們自己的微博合約還是存在的。WeiboRegistry合約:
pragma solidity >=0.4.21 <0.6.0;/**微博管理平臺 */ contract WeiboRegistry {//根據(jù)賬戶昵稱、ID、地址查找微博賬戶mapping (address => string) _addressToAccountName;mapping (uint => address) _accountIdToAccountAddress;mapping (string => address) _accountNameToAddress;//平臺上的注冊賬戶數(shù)量uint _numberOfAccounts;//微博平臺管理員address _registryAdmin;//權(quán)限控制,被這個(gè)修飾符修飾的方法,表示該方法只能被微博所有者操作modifier onlyRegistryAdmin {require(msg.sender == _registryAdmin);_;}//微博平臺構(gòu)造函數(shù)constructor() public {_registryAdmin = msg.sender;_numberOfAccounts = 0;}//微博平臺上注冊微博:用戶名,微博帳號function register(string memory name, address accountAddress) public {//帳號之前未注冊過require(_accountNameToAddress[name] == address(0));//昵稱之前未注冊過require(bytes(_addressToAccountName[accountAddress]).length == 0);//昵稱不能超過64個(gè)字符require(bytes(name).length < 64);_addressToAccountName[accountAddress] = name;_accountNameToAddress[name] = accountAddress;_accountIdToAccountAddress[_numberOfAccounts] = accountAddress;_numberOfAccounts++; }//返回已注冊賬戶數(shù)量function getNumberOfAccounts() view public returns (uint numberOfAccounts) {numberOfAccounts = _numberOfAccounts;}//返回昵稱對應(yīng)的微博賬戶地址function getAddressOfName(string memory name) view public returns (address addr) {addr = _accountNameToAddress[name];}//返回與微博賬戶地址對應(yīng)的昵稱function getNameOfAddress(address addr) view public returns (string memory name) {name = _addressToAccountName[addr];}//根據(jù)ID返回賬戶function getAddressOfId(uint id) view public returns (address addr) {addr = _accountIdToAccountAddress[id];}//取回打賞function adminRetrieveDonations(address payable receiver) public onlyRegistryAdmin {assert(receiver.send(address(this).balance));}//摧毀合約function adminDeleteRegistry() public onlyRegistryAdmin {selfdestruct(msg.sender);}//記錄每條打賞記錄event LogDonate(address indexed from,uint256 _amount);//接受別人的打賞function() external payable {emit LogDonate(msg.sender,msg.value);} }?智能合約是區(qū)塊鏈技術(shù)體系中非常重要的一環(huán)。將這兩個(gè)sol文件放在contracts目錄下。完成后contracts目錄結(jié)構(gòu)如下:
contracts/ ├── Migrations.sol ├── WeiboAccount.sol └── WeiboRegistry.sol?(3).部署合約
這個(gè)應(yīng)用在Truffle中只需要部署WeiRegistry合約就可以了,WeiboAccount合約可以通過前端頁面部署。在部署WeiRegistry合約需要編寫其遷移即腳本:(存放在migrations目錄下)
$ cat migrations/2_weiboregistry_migration.js //遷移腳本如下: var WeiboRegistry = artifacts.require("WeiboRegistry");module.exports = function(deployer) {deployer.deploy(WeiboRegistry); };完成后migrations目錄結(jié)構(gòu)如下:
migrations/ ├── 1_initial_migration.js └── 2_weiboregistry_migration.js然后通過truffle compile 命令編譯合約,編譯成功后會在項(xiàng)目根目錄下新建build文件夾,其中的contracts目錄里面存放著我們的合約被編譯后生成的json文件。build目錄結(jié)構(gòu)如下:
build/ └── contracts├── Migrations.json├── WeiboAccount.json└── WeiboRegistry.json?然后先重新打開一個(gè)終端,運(yùn)行一個(gè)Testrpc測試服務(wù)(也可使用Ganache服務(wù)或truffle develop命令,但要注意端口不同),testrpc服務(wù)啟動后,在之前的終端執(zhí)行truffle migrate進(jìn)行合約部署,部署成功的話,會打印部署信息,testrpc服務(wù)終端也會打印相關(guān)日志。如下圖,左上角窗口運(yùn)行truffle migrate命令,右上角運(yùn)行testrpc服務(wù)。
如果以上步驟運(yùn)行成功,那么整個(gè)項(xiàng)目目錄結(jié)構(gòu)如下圖,其中test目錄存放測試文件,由于沒有編寫任何測試文件所以海目錄為空。所以至此就剩下app目錄需要處理,app目錄下存放的便是前端應(yīng)用文件,由于node_modules目錄下有大量創(chuàng)建項(xiàng)目時(shí)下載的包,下圖中省略了其大部分內(nèi)容。
./ ├── app │?? ├── node_modules │?? │?? ├── accepts…… …… …… │?? │?? └── yauzl │?? ├── package.json │?? ├── package-lock.json │?? ├── src │?? │?? ├── index.html │?? │?? └── index.js │?? └── webpack.config.js ├── build │?? └── contracts │?? ├── Migrations.json │?? ├── WeiboAccount.json │?? └── WeiboRegistry.json ├── contracts │?? ├── Migrations.sol │?? ├── WeiboAccount.sol │?? └── WeiboRegistry.sol ├── migrations │?? ├── 1_initial_migration.js │?? └── 2_weiboregistry_migration.js ├── test └── truffle-config.js3.前端應(yīng)用?
至此,我們只需要關(guān)注app目錄下的前端應(yīng)用部分,app目錄結(jié)構(gòu)如下:
./ ├── dist //項(xiàng)目編譯成功后,運(yùn)行文件存放在該目錄下(由 | //webpack.config.js文件中的配置所決定)。 ├── node_modules //前端所需要的各種依賴包 ├── package.json //npm包管理配置文件 ├── package-lock.json //npm包管理文件 ├── src //前端頁面文件寶括html、css、js文件 └── webpack.config.js //webpack項(xiàng)目配置文件?(1)、webpack.config.js文件:
首先配置文件webpack.config.js如下圖所視設(shè)置,設(shè)置了入口文件(./src/scripts/app.js)、輸出文件(bundle.js)、前端項(xiàng)目輸出目錄(dist)、主html文件(index.html),添加了相關(guān)模塊:文件加載(file-loader)、css模塊、js、jQuery、字體……
const path = require("path"); const CopyWebpackPlugin = require("copy-webpack-plugin"); //const HtmlWebpackPlugin = require('html-webpack-plugin')module.exports = {mode: 'development',entry: "./src/scripts/app.js",output: {filename: "bundle.js",path: path.resolve(__dirname, "dist"),},plugins: [new CopyWebpackPlugin([{ from: "./src/pages/index.html", to: "index.html" }]),],resolve: {extensions: ['.js', '.vue', '.json'],alias: {'vue$': 'vue/dist/vue.esm.js', '@': './src'}},module: {rules: [{ test: /\.(png|svg|jpg|gif)$/, use: ['file-loader']},{ test: /\.(woff|woff2|eot|ttf|otf)$/, use: ['file-loader']},{ test: /\.css$/, use: ['style-loader', 'css-loader']},{ test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file-loader" },{ test: /\.(woff|woff2)$/, loader: "file-loader?prefix=font/&limit=5000" },{ test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "file-loader?limit=10000&mimetype=application/octet-stream" },{ test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "file-loader?limit=10000&mimetype=image/svg+xml" },{ test: require.resolve('./src/scripts/jQuery.js'), use: [{loader: 'expose-loader', options: 'jQuery'}, {loader: 'expose-loader', options: '$'}]},{ test: /\.(html|html)$/i, use: ['html-withimg-loader']},]},devServer: { contentBase: path.join(__dirname, "dist"), compress: true }, };?(2)、node_modules包目錄
有些模塊未下載需要使用下面的命令來下載:(注意執(zhí)行命令時(shí)要和package.json、node_modules在同一目錄下)
npm install expose-loader --save -dev //下載expose-loader包 npm install file-loader --save -dev //下載file-loader包 npm install style-loader --save -dev //下載style-loader包 npm install css-loader --save -dev //下載css-loader包 npm install truffle-contract --save -dev //下載truffle-contract包若大家在測試時(shí)有這樣的錯(cuò)誤:Module not found: Error: Can't resolve 'css-loader' in…… 就按照上面的命令來下載對應(yīng)的包,基本可以解決問題。(3)、src目錄結(jié)構(gòu)如下。限于篇幅,只能打包上傳到資源,包含該項(xiàng)目所需大部分文件,因?yàn)榍岸艘蕾嚢?#xff0c;大家自己執(zhí)行上述命令下載一下。
src/ ├── css │?? ├── dist │?? │?? ├── css │?? │?? │?? ├── bootstrap.css │?? │?? │?? ├── bootstrap.css.map │?? │?? │?? ├── bootstrap.min.css │?? │?? │?? ├── bootstrap.min.css.map │?? │?? │?? ├── bootstrap-theme.css │?? │?? │?? ├── bootstrap-theme.css.map │?? │?? │?? ├── bootstrap-theme.min.css │?? │?? │?? └── bootstrap-theme.min.css.map │?? │?? ├── fonts │?? │?? │?? ├── glyphicons-halflings-regular.eot │?? │?? │?? ├── glyphicons-halflings-regular.svg │?? │?? │?? ├── glyphicons-halflings-regular.ttf │?? │?? │?? ├── glyphicons-halflings-regular.woff │?? │?? │?? └── glyphicons-halflings-regular.woff2 │?? │?? └── js │?? │?? ├── bootstrap.js │?? │?? ├── bootstrap.min.js │?? │?? └── npm.js │?? └── style.css ├── pages │?? └── index.html └── scripts├── jQuery.js└── app.js4.項(xiàng)目測試?
按照上面的步驟處理完成后,就可以在app目錄下執(zhí)行npm run dev命令來啟動web服務(wù),在打印的信息中復(fù)制網(wǎng)頁地址:
在 chrome瀏覽器(貌似只有chrome才能運(yùn)行成功)中打開該網(wǎng)址,就可以看到如下內(nèi)容(大家應(yīng)該可以執(zhí)行到這里,我在遠(yuǎn)程服務(wù)器上按照上面的步驟又重新操作了一遍,可以運(yùn)行成功):
該項(xiàng)目運(yùn)行機(jī)制大概可以概括為:先運(yùn)行一個(gè)testrpc測試連的交互服務(wù),然后將你編寫的智能合約編譯并部署到區(qū)塊鏈服務(wù)(例如testrpc)中。并將合約編譯結(jié)果json對象提供給前端,實(shí)則提供可調(diào)用API給前端。前端由于web3 API、抽象合約truffle-contract的存在,封裝并提供給前端頁面應(yīng)用,供其交互。
三、問題與BUG
問題1:Error:base fee exceeds gas limit at runCall 或者Out of Gas
解決:原因是因?yàn)楹霞s代碼量較多,導(dǎo)致out of gas,可通過顯式發(fā)送一個(gè)較大的gas
2000000,使用了(testrpc 默認(rèn)gas:20000000000,默認(rèn)gasLimit:90000)
問題2:then鏈內(nèi)給變量所賦的值,在then鏈外調(diào)用該值時(shí)發(fā)現(xiàn)并未賦值?
解決:若需要then鏈方式對變量賦值,可以將所需要的值在then鏈最后return出來,賦值給該變量,調(diào)用時(shí),在另一個(gè)同步函數(shù)中,使用await 的方式獲取同步后該變量的值。比如:在函數(shù)fun1中需要通過then鏈對temp賦值,在函數(shù)fun2中獲取該變量所賦的值。代碼如下:
var temp = null;function fun1() {temp = a.then(function(b) {return b;}); }async function fun2() {var c = await temp; //c就是同步后temp的值 }問題3:火狐瀏覽器建立HttpProvider時(shí),
警告:Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://127.0.0.1:8545/. (Reason: missing token ‘user-agent’ in CORS header ‘Access-Control-Allow-Headers’ from CORS preflight channel).
錯(cuò)誤:Error: Invalid JSON RPC response: ""
解決:還未解決……
總結(jié)
以上是生活随笔為你收集整理的以太坊学习路线——(五)DApp开发:简易版去中心化微博的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 项目实训 - 智能车系统 - 第七周记录
- 下一篇: 模电学习02:晶体三极管