有很多不同的解決方案可以創(chuàng)建Dapp,這些Dapp可以接觸到數(shù)千甚至數(shù)百萬實時用戶,如Plasam和狀態(tài)通道。在本文中,您將了解狀態(tài)通道如何工作,以及如何在以太坊中創(chuàng)建可擴展的Dapp。
什么是狀態(tài)通道?
狀態(tài)通道是一種2層擴展解決方案,可以用于創(chuàng)建Dapp和智能合約,幾乎可以被數(shù)百萬用戶實時使用。它們通過在2個或多個用戶之間啟動多個通道來工作,并執(zhí)行事務(wù)的信息交換加密的簽名消息。
它們被稱為“狀態(tài)”,是因為每個交互都必須具有可以更新的狀態(tài)。例如游戲得分或銀行余額。
我們需要什么來建立一個狀態(tài)通道?
1. 一個狀態(tài)通道需要至少2個或多個用戶同時交互才能打開。類似即時聊天工具一樣。
2. 具有打開和關(guān)閉狀態(tài)通道邏輯的智能合約。
3. 如果將在游戲中使用狀態(tài)通道,則兩個用戶都需要進行托管。在打開狀態(tài)通道時,以太網(wǎng)中的托管都將存儲在智能合約中。
4. 一個javascript應(yīng)用程序,它將生成簽名消息,這些消息將在用戶之間的鏈外交換。
5. Metamask或用于簽名消息的類似工具。簽名消息不需要損耗gas,并會立即執(zhí)行。兩個用戶都需要對消息進行簽名,以保證tehy是生成此類事務(wù)的人。
6. 通過電子郵件或任何外部應(yīng)用程序交換這些簽名郵件。
狀態(tài)通道如何工作?
狀態(tài)通道設(shè)置起來有點復雜,因為你必須確保兩個玩家都受到保護,以防出現(xiàn)任何問題,這就是為什么我們需要一個智能合約。步驟如下:
1. 在2個用戶之間的狀態(tài)通道中,第一個用戶部署智能合約,該合約將“打開”該通道。
2. 第二個執(zhí)行智能合約的以“加入”功能進入該狀態(tài)通道。
3. 然后他們可以開始為應(yīng)用程序交換簽名的消息。兩個用戶都可以訪問自定義javascript應(yīng)用程序,以生成鏈外消息,這些消息包含他們在智能合約中可以執(zhí)行的信息。
4. 事務(wù)的速度取決于每個用戶創(chuàng)建和簽署這些消息的速度。他們需要不停地交換信息,不停地玩,直到他們決定游戲結(jié)束。
5. 當他們結(jié)束游戲后,他們中的任何一人都可以進入智能合約并執(zhí)行一個功能來完成它,這將開始“協(xié)商”階段。
6. 在此階段,兩個用戶都有超時1天的時間將最新的2條消息上傳到智能合約。智能合約檢查最新消息并釋放資金以基于該信息結(jié)束游戲。每條消息都包含先前交互的結(jié)果,因此只檢查最新的消息是安全的。
在本文中,我將向您展示如何在兩個用戶之間為一個以太坊游戲創(chuàng)建一個狀態(tài)通道。請記住,狀態(tài)通道可以用于具有“狀態(tài)”或“計數(shù)器”的任何類型的應(yīng)用程序。這就是為什么狀態(tài)通道應(yīng)用于游戲是非常理想的。因為你可以追蹤每一場比賽的勝利者,所以每一場比賽都有一個狀態(tài)可以更新。
我們將創(chuàng)建一個骰子游戲,玩家1選擇指定自己想要的數(shù)字,玩家2必須猜測該數(shù)字才能獲勝。他們可以任意進行游戲,而無需在區(qū)塊鏈上執(zhí)行交易。我們還有一個Web應(yīng)用程序來顯示游戲界面。
這是我們要創(chuàng)建Dapp的索引:
1. 創(chuàng)建可視化Web應(yīng)用程序。它將用作交換狀態(tài)通道的簽名消息的媒介。
2. 創(chuàng)建簽名和加密消息所需的功能。
3. 創(chuàng)建智能合約。
1.創(chuàng)建可視化Web應(yīng)用程序
在開始編寫代碼之前,我想確保我們弄清楚了Web應(yīng)用程序的完整細節(jié)。它看起來怎么樣,關(guān)注的焦點是什么。
在這種情況下,我們希望為兩個玩家展示類似的東西。玩家1將看到骰子的6個面并且他將必須選擇哪個面為結(jié)果展示出來,然后第二個玩家,還必須在這些面之間進行選擇并且他將能夠看到結(jié)果。
所以框架是這樣的:
1、玩家1進入應(yīng)用程序,點擊一個按鈕說“開始新游戲”,然后他做一個metamask事務(wù)來部署和設(shè)置智能合約。他收到一個智能合約地址,可以發(fā)送給其他玩家開始游戲。
2、玩家2進入應(yīng)用程序,點擊一個顯示“加入現(xiàn)有游戲”的按鈕,其中包含從玩家1收到的合同地址,然后他進行metamask交易以設(shè)置現(xiàn)有游戲并發(fā)送一個托管。
那么讓我們開始,在Web應(yīng)用程序的中間創(chuàng)建一個帶有2個按鈕的框。創(chuàng)建一個名為dice的文件夾和一個名為index.html的文件。這是代碼:
《!DOCTYPE html》
《html lang=“en” dir=“l(fā)tr”》
《head》
《meta charset=“utf-8”》
《title》Dice ethereum game《/TItle》
《/head》
《body》
《div class=“main-content”》
《button》Start new game《/button》
《button》Join exisTIng game《/button》
《/div》
《/body》
《/html》
在這段代碼中,我剛創(chuàng)建了一個基本的HTML結(jié)構(gòu),其中包含一個包含按鈕和標題的div。請注意,div有一個名為main-content的類,我們稍后會使用它。
讓我們用一些css修飾一下界面。使用以下代碼創(chuàng)建一個名為index.css的文件:
body {
font-family: sans-serif;
}
.main-content {
margin: auto;
max-width: 500px;
background-color: whitesmoke;
padding: 50px;
border-radius: 10px;
display: grid;
grid-template-rows: 1fr 1fr;
grid-template-columns: 1fr 1fr;
grid-column-gap: 10px;
}
.main-content h1 {
grid-column: 1 / span 2;
}
.main-content button {
border: none;
color: white;
background-color: #007dff;
padding: 20px;
border-radius: 5px;
cursor: pointer;
}
.main-content button:hover {
opacity: 0.8;
}
.main-content button:acTIve {
opacity: 0.6;
}
我為HTML添加了一個h1標題以使其看起來更好,請確保通過向CSS添加鏈接來更新HTML:
《!DOCTYPE html》
《html lang=“en” dir=“l(fā)tr”》
《head》
《meta charset=“utf-8”》
《link rel=“stylesheet” href=“index.css”》
《TItle》Dice ethereum game《/title》
《/head》
《body》
《div class=“main-content”》
《h1》Ethereum Dice《/h1》
《button》Start new game《/button》
《button》Join existing game《/button》
《/div》
《/body》
《/html》
我決定顯示用戶所需的下一個操作的最佳方法是在javascript中顯示包含所需信息的div。所以當他點擊“開始新游戲”時,他會看到一個盒子,詢問他想要為游戲設(shè)置多少托管。
他點擊“加入現(xiàn)有游戲”,他將被要求提供現(xiàn)有游戲的托管和合同地址。
以下是按鈕操作的響應(yīng)方式:
為了實現(xiàn)這一點,我用一些JavaScript邏輯創(chuàng)建了一個index.js文件。
function start() {
document.querySelector(‘#new-game’).addEventListener(‘click’, () =》 {
const classNewGameBox = document.querySelector(‘.new-game-setup’).className
// Toggle hidden box to display it or hide it
if(classNewGameBox === ‘new-game-setup’) {
// To hide the box
document.querySelector(‘.new-game-setup’).className = ‘hidden new-game-setup’
document.querySelector(‘#button-continue’).className = ‘hidden’
document.querySelector(‘#join-game’).disabled = false
} else {
// To show the box
document.querySelector(‘.new-game-setup’).className = ‘new-game-setup’
document.querySelector(‘#button-continue’).className = ‘’
document.querySelector(‘#join-game’).disabled = true
}
})
document.querySelector(‘#join-game’).addEventListener(‘click’, () =》 {
const classJoinGameBox = document.querySelector(‘.join-game-setup’).className
// Toggle hidden box to display it or hide it
if(classJoinGameBox === ‘join-game-setup’) {
document.querySelector(‘.new-game-setup’).className = ‘hidden new-game-setup’
document.querySelector(‘.join-game-setup’).className = ‘hidden join-game-setup’
document.querySelector(‘#button-continue’).className = ‘hidden’
document.querySelector(‘#new-game’).disabled = false
} else {
document.querySelector(‘.new-game-setup’).className = ‘new-game-setup’
document.querySelector(‘.join-game-setup’).className = ‘join-game-setup’
document.querySelector(‘#button-continue’).className = ‘’
document.querySelector(‘#new-game’).disabled = true
}
})
}
start()
解釋一下我做了什么:
1. 首先,我創(chuàng)建了一個名為start()的函數(shù),該函數(shù)將會立即執(zhí)行并打包內(nèi)容,以便它包含在一個大函數(shù)中。
2. 然后我創(chuàng)建了2個事件監(jiān)聽器,每當我單擊html文件中的啟動或連接按鈕時,它們就會被激活。一個用于#new-game按鈕,另一個用于#joall-game按鈕。使用document.querySelector(),這是在js代碼中選擇任何內(nèi)容的最有效方法之一。
3. 在這些監(jiān)聽器中,我顯示或隱藏每個相應(yīng)元素的div框?;旧线x擇帶有querySelector的盒子并刪除或添加隱藏的類,它在css中設(shè)置為display:none;。
然后我們可以將js文件與我們的modifie index.html連接:
《!DOCTYPE html》
《html lang=“en” dir=“l(fā)tr”》
《head》
《meta charset=“utf-8”》
《link rel=“stylesheet” href=“index.css”》
《title》Dice ethereum game《/title》
《/head》
《body》
《div class=“main-content”》
《h1》Ethereum Dice《/h1》
《button id=“new-game”》Start new game《/button》
《button id=“join-game”》Join existing game《/button》
《div class=“hidden new-game-setup”》
《h3》How much escrow will you use in ETH?《/h3》
《input type=“number” placeholder=“2.。.”》
《/div》
《div class=“hidden join-game-setup”》
《h3》What‘s the smart contract address of the existing game?《/h3》
《input type=“text” placeholder=“0x38dfj39.。.”》
《/div》
《button id=“button-continue” class=“hidden”》Continue《/button》
《/div》
《script src=“index.js”》《/script》
《/body》
《/html》
我把添加的新代碼塊加粗。以下是更新后的CSS,用于設(shè)置新信息的樣式:
body {
font-family: sans-serif;
}
.hidden {
display: none;
}
.main-content {
margin: auto;
max-width: 500px;
background-color: whitesmoke;
padding: 50px;
border-radius: 10px;
display: grid;
grid-template-rows: 1fr 80px auto;
grid-template-columns: 1fr 1fr;
grid-column-gap: 10px;
}
.main-content h1 {
grid-column: 1 / span 2;
}
.main-content button {
border: none;
color: white;
background-color: #007dff;
padding: 20px;
border-radius: 5px;
cursor: pointer;
}
.main-content button:hover {
opacity: 0.8;
}
.main-content button:active {
opacity: 0.6;
}
.main-content button:disabled {
opacity: 0.5;
background-color: grey;
cursor: auto;
}
.main-content input {
width: 100%;
border-radius: 10px;
padding: 10px;
border: 1px solid lightgrey;
}
.main-content div.new-game-setup, .main-content div.join-game-setup {
grid-column: 1 / span 2;
}
#button-continue {
grid-column: 1 / span 2;
margin-top: 20px;
}
“Continue”按鈕現(xiàn)在不起任何作用,所以讓我們創(chuàng)建一個功能來部署新的智能合約,并在用戶希望在下一節(jié)中創(chuàng)建新游戲時打開狀態(tài)通道。
2.創(chuàng)建并連接初始智能合約
現(xiàn)在是創(chuàng)建智能合約的并使用web3.js將其與JavaScript連接的時候了?,F(xiàn)在我們只需要構(gòu)造函數(shù)和一些基本信息,并將這段代碼自己寫在一個名為Dice.sol的新文件中:
pragma solidity 0.4.25;
contract Dice {
address public player1;
address public player2;
uint256 public player1Escrow;
uint256 public player2Escrow;
constructor() public payable {
require(msg.value 》 0);
player1 = msg.sender;
player1Escrow = msg.value;
}
function setupPlayer2() public payable {
require(msg.value 》 0);
player2 = msg.sender;
player2Escrow = msg.value;
}
}
有2個函數(shù),構(gòu)造函數(shù)用于設(shè)置第一個播放器的地址和托管,setupPlayer2()函數(shù)用于設(shè)置第二個播放器的信息。
我們希望在用戶單擊“continue”按鈕時部署智能合約并使用指定的msg.value執(zhí)行構(gòu)造函數(shù)。為此,我們必須在智能合約中實施web3.js。因為它是與瀏覽器上的區(qū)塊鏈進行通信的主要方式。
請點擊連接進行下載,單擊raw查看完整代碼并復制代碼以將其粘貼到項目文件夾中名為web3.js的新文件中:https://github.com/ethereum/web3.js/blob/develop/dist/web3.js
如果您使用metamask,則不必執(zhí)行此操作,因為metamask為您注入了web3.js的版本,但如果metamask不可用,則需要項目中的web3庫與區(qū)塊鏈進行交互。
我們使用metamask與區(qū)塊鏈交互。但是當您在瀏覽器上打開index.html文件時,會打不開文件,因為metamask不支持file://擴展名。
我們需要運行一個本地服務(wù)器,將文件提交給http:// localhost:8080 url,因為當您直接打開index.html文件時,metamask不起作用。為此,請打開終端并安裝:
npm i -g http-server
然后,在項目文件夾中執(zhí)行http-server以啟動index.html的本地服務(wù)器:
http-server
這將為localhost:8080上的文件提供服務(wù),這樣您就可以訪問它們并從metamask注入Web3。
在這種情況下,讓我們集中精力部署我們剛從我們的Web應(yīng)用程序創(chuàng)建的合同,就在用戶單擊“continue”時。
要部署新合同,我們需要ABI,構(gòu)造函數(shù)參數(shù)和字節(jié)碼。這些是web3.js的要求。
要生成ABI,請轉(zhuǎn)到remix.ethereum.org,將代碼粘貼到主部分,然后單擊ABI:
這將復制ABI代碼。轉(zhuǎn)到項目文件夾并創(chuàng)建一個名為contractData.js的文件,將代碼粘貼到一個名為abi的變量,如下所示:
const abi = [
{
“constant”: true,
“inputs”: [],
“name”: “player2Escrow”,
“outputs”: [
{
“name”: “”,
“type”: “uint256”
}
],
“payable”: false,
“stateMutability”: “view”,
“type”: “function”
},
{
“constant”: true,
“inputs”: [],
“name”: “player1Escrow”,
“outputs”: [
{
“name”: “”,
“type”: “uint256”
}
],
“payable”: false,
“stateMutability”: “view”,
“type”: “function”
},
{
“constant”: true,
“inputs”: [],
“name”: “player2”,
“outputs”: [
{
“name”: “”,
“type”: “address”
}
],
“payable”: false,
“stateMutability”: “view”,
“type”: “function”
},
{
“constant”: false,
“inputs”: [],
“name”: “setupPlayer2”,
“outputs”: [],
“payable”: true,
“stateMutability”: “payable”,
“type”: “function”
},
{
“constant”: true,
“inputs”: [],
“name”: “player1”,
“outputs”: [
{
“name”: “”,
“type”: “address”
}
],
“payable”: false,
“stateMutability”: “view”,
“type”: “function”
},
{
“inputs”: [],
“payable”: true,
“stateMutability”: “payable”,
“type”: “constructor”
}
]
2.現(xiàn)在我們需要智能合約的bytecode。bytecode是將被部署到區(qū)塊鏈的已編譯的智能合約,我們需要該信息才能部署它。要使bytecode再次重新混合并單擊此按鈕:
并在contractData.js中創(chuàng)建另一個變量,稱為betycode,其信息如下:
const abi = [
{
“constant”: true,
“inputs”: [],
“name”: “player2Escrow”,
“outputs”: [
{
“name”: “”,
“type”: “uint256”
}
],
“payable”: false,
“stateMutability”: “view”,
“type”: “function”
},
{
“constant”: true,
“inputs”: [],
“name”: “player1Escrow”,
“outputs”: [
{
“name”: “”,