「我總偏向將權利分散於網絡。這樣一來,就沒任何組織能輕鬆獲取控制。我不相信巨大的中央組織,天性使然。」——Bob Tayor, ARPANET締造者javascript
本文原創發表於所問HT前端團隊,原文地址:ht.askingdata.com/article/5af…html
今年年初的「三點鐘區塊鏈羣」完全激起了以加密數字貨幣的主的區塊鏈浪潮,資本的驅動加深了人們對區塊鏈技術的狂熱。然而,做爲區塊鏈3.0的時代的更廣的應用來臨,技術如何落地也是在初期須要解決的第一道坎。前端
因此今天就來試一下如何在以太坊上創建智能合約應用(Dapp),開發一個普通應用的該有的登陸註冊,以便咱們第一時間嚐鮮。java
做爲一名標準的web開發人員,在開始一門新技術以前,須要仔細考慮一個問題就是:基於現有的業務若是用上區塊鏈會更好嗎?node
回答這個問題以前,你須要瞭解區塊鏈是什麼?優點是什麼?這個問題就不在這裏展開了,這是個至關大的話題,不清楚的同窗能夠移步到這裏參考一下。《區塊鏈-百度百科》、《區塊鏈技術是什麼?將來可能用於哪些方面?》git
那麼區塊鏈本質上就是一個去中心化的數據庫,只不過這個數據庫沒有中心服務器、數據沒法篡改同時必定程度上可以很好的保護數據隱私。對現在的互聯網來講,聽起來很具備革命性的技術。因此若是對於一款涉及到數據私密性、永久性安全性高的應用,這個確實是很是適合的。github
如今在金融、醫療、溯源、社交等等領域,不少公司逐漸開始試水更普遍的應用。而只靠發幣炒幣,這畢竟是種投機取巧的行爲。web
接下來將會從零開始搭建基於以太坊web3js項目,開始閱讀以前,你須要熟練前端或後臺JavaScript語法,熟悉區塊鏈思想和原理,若是能瞭解solidity語法更好,由於接下來咱們會用到它,和js很像的一門語言。算法
爲了可以方便你們可以快速的瞭解,提供了下面幾個資料供參考:數據庫
瞭解上面的知識以後,就能夠開始DAPP搭建之旅了,將從下面的路線講解:
項目代碼可點擊查看github.com/Elliottssu/…
若是已經有以太坊環境的同窗能夠跳過,接下來以mac系統爲例介紹,windows也差很少。
經過Homebrew來安裝go-ethereum
brew tap ethereum/ethereum
能夠添加--devel如下命令來安裝開發分支(建議用這個):
brew install ethereum --devel
執行geth version
查看版本號,若是正常的話即安裝成功。
在比特幣系統裏,這個創世塊是被寫入源碼,但對於以太坊而言,創世塊能夠是任何你喜歡的東西。你也能夠把這個當成是系統的一個漏洞。可是共識算法確保其它人除非歸入你的創世塊,不然是不會有效的。
創世區塊的目的是搭建私有鏈,做爲鏈上的第一個塊,若是直接運行節點的話會同步公鏈的數據,數據量會很是大。若是想在同一個網絡中獲取數據,創世區塊也必需要同樣。
新建genesis.json文件內容以下:
{
"config": {},
"nonce": "0x0000000000000042",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"difficulty": "0x100",
"alloc": {},
"coinbase": "0x0000000000000000000000000000000000000000",
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x00",
"gasLimit": "0xffffffffffff"
}
複製代碼
上面定義了一些如挖礦難度、以太幣數量、gas消耗限制等等信息。
在當前目錄下執行geth init genesis.json
來初始化創世區塊節點。
至此,環境配置方面已經完成。咱們能夠經過下面這個命令在8545端口來啓動節點:
geth --rpc --rpccorsdomain "*" --rpcapi "personal,web3,eth,net" console
首先咱們須要建立第一個帳戶密碼是12345678,執行命令:
personal.newAccount('12345678')
建立帳戶以後就能夠挖礦了,注意若是有多個賬戶,挖到的以太幣默認都會進入第一個帳戶的餘額裏。
miner.start()
啓動挖礦,miner.stop()
中止挖礦
截止到如今,咱們已經成功的啓動以太坊節點,並能夠經過命令來新建帳戶,執行挖礦來獲取以太幣操做。但是經過命令咱們可能沒法直觀的感覺在以太坊上帳戶和餘額的變化。
如今經過以太坊官方提供的錢包,來管理帳戶和餘額。下載地址github.com/ethereum/mi…
注意:推薦安裝V0.8.10版本,能夠刪除已經部署的合約,方便調試,最新版的移除掉了改功能。
若是有創世區塊,是私有鏈的話,以太坊錢包會默認開啓私有節點,不然默認同步公鏈上的數據。
本身能夠嘗試用主帳號給其餘帳戶轉帳,也能夠新建帳號和查詢帳戶餘額。
首先咱們須要清楚一個問題,什麼是智能合約?智能合約概念能夠歸納爲: 一段代碼 (智能合約),運行在可複製、共享的帳本上的計算機程序,能夠處理信息,接收、儲存和發送價值。通俗的來說就是能夠在區塊鏈上執行的代碼,由於以太坊之前的區塊鏈只能存儲比特幣上的交易信息,沒法作其餘事情。而智能合約的出現,能夠在鏈上執行簡單的業務邏輯,這也是區塊鏈應用落地的關鍵。
咱們基礎已經準備就緒,接下來就用solidity語言來寫數據的增長和查詢邏輯。
Solidity中合約的含義就是一組代碼(它的 函數 )和數據(它的 狀態 ),它們位於以太坊區塊鏈的一個特定地址上。 代碼行 uint time
; 聲明一個類型爲 uint
(256位無符號整數)的狀態變量,叫作 time 。 你能夠認爲它是數據庫裏的一個位置,能夠經過調用管理數據庫代碼的函數進行查詢和變動。對於以太坊來講,上述的合約就是擁有合約(owning contract)。在這種狀況下,函數 set
和 get
能夠用來變動或取出變量的值。
1. 定義數據結構和變量
這裏只作一個最簡單的帳戶體系,定義個一個用戶的數據結構包含用戶名、用戶地址和註冊時間。
定義用戶列表數據結構是爲了存儲一個用戶名->用戶地址的映射。
//user.sol
//定義用戶數據結構
struct UserStruct {
address userAddress;
string username;
uint time;
uint index;
}
//定義用戶列表數據結構
struct UserListStruct {
address userAddress;
uint index;
}
address[] public userAddresses; //全部地址集合
string[] private usernames; //全部用戶名集合
mapping(address => UserStruct) private userStruct; //帳戶我的信息
mapping(string => UserListStruct) private userListStruct; //用戶名映射地址
複製代碼
address[] private userAddresses;
這一行聲明瞭一個不能夠被公開訪問的 address 類型的狀態變量。 address 類型是一個160位的值,且不容許任何算數操做。這種類型適合存儲合約地址或外部人員的密鑰對。若是是關鍵字 public 容許則你在這個合約以外訪問這個狀態變量的當前值。
mapping(address => UserStruct) private userStruct;
mapping映射將地址映射到用戶數據結構,這個能夠初略理解爲一個地址所對應的值有哪些。
2. 判斷用戶名或地址是否存在
//user.sol
//判斷用戶地址是否存在
function isExitUserAddress(address _userAddress) public constant returns(bool isIndeed) {
if (userAddresses.length == 0) return false;
return (userAddresses[userStruct[_userAddress].index] == _userAddress);
}
//判斷用戶名是否存在
function isExitUsername(string _username) public constant returns(bool isIndeed) {
if (usernames.length == 0) return false;
return (keccak256(usernames[userListStruct[_username].index]) == keccak256(_username));
}
複製代碼
這裏咱們分別去判斷用戶名和地址是否存在,判斷依據是看用戶名或地址是否存在於所對應的數組。
須要注意的是,在JavaScript中判斷一個值是否在數組中用到的indexOf()
,可是在solidity是不支持該函數。有兩種方案:一種是循環集合來判斷是否存在,第二種是建立的時候爲每條數據加index索引,只需按索引取值。
由於第一種須要遍歷整個數組,當數據量很是大的時候效率不高,因此經過索引取值的方式更加快速。
3. 新建數據和查詢數據
對於數據的插入和查詢,其實就是往數組集合中添加和讀取數據。
//user.sol
//根據用戶名查找對於的address
function findUserAddressByUsername(string _username) public constant returns (address userAddress) {
require(isExitUsername(_username));
return userListStruct[_username].userAddress;
}
//建立用戶信息
function createUser(address _userAddress, string _username) public returns (uint index) {
require(!isExitUserAddress(_userAddress)); //若是地址已存在則不容許再建立
userAddresses.push(_userAddress); //地址集合push新地址
userStruct[_userAddress] = UserStruct(_userAddress, _username, now, userAddresses.length - 1);
usernames.push(_username); //用戶名集合push新用戶
userListStruct[_username] = UserListStruct(_userAddress, usernames.length - 1); //用戶所對應的地址集合
return userAddresses.length - 1;
}
//獲取用戶我的信息
function findUser(address _userAddress) public constant returns (address userAddresses, string username, uint time, uint index) {
require(isExitUserAddress(_userAddress));
return (
userStruct[_userAddress].userAddress,
userStruct[_userAddress].username,
userStruct[_userAddress].time,
userStruct[_userAddress].index);
}
複製代碼
固然,除了增長和查詢以外,還可對相應的數組進行修改和刪除。這裏的修改和刪除操做其實並非真正的更改數據,由於區塊鏈上的數據是沒法篡改的。固然除非無可奈何的話,不建議直接在鏈上修改和刪除數據。
如今咱們把智能合約已經寫好了,能夠經過js來讀取和添加數據了,但在這以前須要咱們部署剛纔寫的合約。部署合約有一種比較快捷方便的方法,就是在以太坊錢包裏部署。
完了以後咱們能夠在合約列表中找到剛纔部署的合約。
tips: 第一次合約部署完成,若是想要推出要執行一次exit
,不然合約沒法保存。
這時候能夠點進去,執行寫入數據和讀取數據操做了。那麼怎樣才能使用代碼進行操做呢?
先提早看一下sails文件目錄:
1. 安裝truffle
truffle能夠將solidity語言的智能合約,編譯成.json格式的配置文件,能夠用它來和web3.js交互。
全局安裝truffle,npm install -g truffle
編譯solidity智能合約,truffle compile
執行以後會在build目錄下輸出編譯後的結果。
2. 拷貝編譯後的文件中的abi的值
咱們編譯的目的是爲了拿到abi屬性所對於的配置參數,手動拷貝到,nodejs的配置文件中。
ps: 這種作法雖然有些傻瓜,可是項目官方推薦的合約部署與讀取要簡單不少。
3. web3.js讀取與建立合約內容
先看看web3.js上是如何調用合約的:
讀取用methods.myMethod.call
,將調用「constant」方法並在EVM中執行其智能合約方法,而不發送任何事務。注意調用不能改變智能合約狀態;修改用methods.myMethod. send
,將交易發送到智能合約並執行其方法。請注意,這能夠改變智能合約狀態。
那如今就根據以太坊的合約內容,封裝一些web3.js調用智能合約的類。
//Contract.js
const web3Util = require('./Web3Util.js')
class Contract {
constructor() {
}
//user 合約
/** * 判斷用戶名是否存在 */
static isExitUsername(username, cb) {
web3Util.contractUser.methods.isExitUsername(username).call()
.then(result => {
cb(null, result)
})
.catch(err => {
cb(err.message)
});
}
/** * 根據用戶名查找對於的地址 */
static findUserAddressByUsername(username, cb) {
web3Util.contractUser.methods.findUserAddressByUsername(username).call()
.then(result => {
cb(null, result)
})
.catch(err => {
cb(err.message)
});
}
/** * 查找用戶信息 */
static findUser(userAddress, cb) {
web3Util.contractUser.methods.findUser(userAddress).call()
.then(result => {
cb(null, result)
})
.catch(err => {
cb(err.message)
});
}
/** * 建立用戶信息 (發送合約須要先解鎖) */
static createUser(userAddress, username, cb) {
let options = {
from: Web3Util.ACCOUNT_ADDRESS_MAIN, //建立帳戶用主帳號
gas: 10000000 //最大的gas數值
}
web3Util.contractUser.methods.createUser(userAddress, username).send(options)
.then(result => {
cb(null, result)
})
.catch(err => {
cb(err.message)
});
}
}
module.exports = Contract;
複製代碼
上面的文件中在Web3Util.js定義了一些公共常量,如合約地址,帳戶地址等等。須要注意的是在使用.send()
來建立合約內容的時候要給gas
即小費,讀取內容的時候不須要,這個是以太坊智能合約的必填項,關於gas是如何消耗的你們能夠查閱相關資料瞭解。
截止到目前爲止,咱們已經成功的將js與solidity鏈接在一塊兒而且實現互動,那接下來就是實現登陸和註冊。
登陸其實就是看可否解鎖用戶,而後將用戶的我的資料返回,註冊就是調取智能合約來寫入一條記錄。
解鎖帳戶(只有解鎖才能執行合約)方法:
//Web3Util.js
/** * 解鎖帳戶 * @param account 帳戶名 * @param password 密碼 */
static unlockAccount(account, password, cb) {
Web3.eth.personal.unlockAccount(account, password, 600)
.then(result => {
cb(null, result)
})
.catch(err => {
cb(err.message)
});
}
複製代碼
登陸註冊執行代碼:
//AccountController.js
module.exports = {
//判斷用戶名是否存在
isExitUsername: (req, res) => {
let username = req.query.username;
if (!username) return res.json(Message.errMessage('用戶名不能爲空'));
Contract.isExitUsername(username, (err, result) => {
Message.handleResult(res, err, result)
})
},
//登陸(用戶名或地址登陸)
login: (req, res) => {
let account = req.body.account
let password = req.body.password;
if (!account || !password) return res.json(Message.errMessage('用戶名或密碼不能爲空'));
if (Web3.utils.isAddress(account)) { //account is address
Web3Util.unlockAccount(account, password, (err, result) => {
if (err) return res.json(Message.errMessage('用戶名或密碼錯誤'));
Contract.findUser(account, (err, result) => {
Message.handleResult(res, err, result)
})
})
} else { //account is username
Contract.findUserAddressByUsername(account, (err, address) => {
if (err) return res.json(Message.errMessage('用戶名或密碼錯誤'));
Web3Util.unlockAccount(address, password, (err, result) => {
if (err) return res.json(Message.errMessage('用戶名或密碼錯誤'));
Contract.findUser(address, (err, result) => {
Message.handleResult(res, err, result)
})
})
})
}
},
/** * 註冊帳戶,在以太坊生成address,用戶名會寫在合約中 */
register: (req, res) => {
let username = req.body.username
let password = req.body.password;
if (!username || !password) return res.json(Message.errMessage('用戶名或密碼不能爲空'));
async.waterfall([
function (callback) { //檢查用戶名是否存在
Contract.isExitUsername(username, (err, result) => {
if (result) return res.json(Message.errMessage('用戶名已存在'));
callback(null, result)
})
},
function (result, callback) { //建立用戶 > 生成地址
Web3.eth.personal.newAccount(password).then(address => {
callback(null, address)
})
},
function (address, callback) { //解鎖主帳戶併合約註冊信息
Web3Util.unlockAccount(Web3Util.ACCOUNT_ADDRESS_MAIN, Web3Util.ACCOUNT__PASSWORD_MAIN, (err, result) => {
if (err) return res.json(Message.errMessage(err));
Contract.createUser(address, username, (err, result) => {
if (err) return res.json(Message.errMessage(err));
callback(err, result)
})
})
},
], (err, result) => {
Message.handleResult(res, err, result)
})
},
};
複製代碼
咱們已經在router中配置好了路由,接下來使用接口調試工具來測試一下,這裏使用postman來測試:
注意,開始測試以前須要開啓以太坊節點,保證8545端口開啓:geth --rpc --rpccorsdomain "*" --rpcapi "personal,web3,eth,net" console
由於註冊須要更改合約數據,須要挖礦來肯定交易,因此爲了方便調試,順便開啓挖礦:miner.start()
1.註冊帳號
由於是執行合約交易,註冊完了以後會返回本次交易詳情如塊、消耗的gas等等。若是本次交易失敗,好比再註冊重複的用戶名,在solidity中作了攔截,本次交易會失敗,失敗的標誌是返回的gas是本身設置的最大值。
這樣咱們就在鏈上建立了一個address,以及這個相對應的用戶名和註冊時間信息。
2.登陸帳號(帳號同時支持address和用戶名)
如今以及可以經過接口與智能合約交互了,咱們能夠稍微加個前端頁面,就能夠當成一個正常app了,只是數據庫是區塊鏈,是否是很酷。
固然區塊鏈上只能存儲不多的數據,若是要存儲視頻或者圖片,能夠藉助IPFS,(是永久的、去中心化保存和共享文件的方法,這是一種內容可尋址、版本化、點對點超媒體的分佈式協議。)配合着區塊鏈可以實現更加豐富的功能。
目前的缺點在於,讀取和存儲交易數據比較慢,這也是目前Dapp應用沒法大規模的開展的一部分緣由,但這個並不會阻礙區塊鏈技術的發展,由於它解決的是生產關係,它的思想在於去中心化來防止中央組織的濫用。
在我構思這篇文章的時候,正好是Facebook創始人扎克伯格因數據泄漏醜聞在聽證會被輪流質問,利用幾百萬用戶數據來干涉總統大選。用戶隱私數據一旦被攻破或濫用或商業分析推薦,後果也是很是可怕,這也是當今互聯網全球化所帶來的弊端。
因此,若是想要區塊鏈解決這樣的問題還須要多長的路要走?