如何使用狀態通道在以太坊建立可擴充套件的dApp和智慧合約

買賣虛擬貨幣
有很多不同的解決方案可以建立Dapp,這些Dapp可以接觸到數千甚至數百萬實時使用者,如Plasam和狀態通道。在本文中,您將瞭解狀態通道如何工作,以及如何在以太坊中建立可擴充套件的Dapp。什麼是狀態通道?狀態通道是一種2層擴充套件解決方案,可以用於建立Dapp和智慧合約,幾乎可以被數百萬使用者實時使用。它們透過在2個或多個使用者之間啟動多個通道來工作,並執行事務的資訊交換加密的簽名訊息。它們被稱為“狀態”,是因為每個互動都必須具有可以更新的狀態。例如遊戲得分或銀行餘額。我們需要什麼來建立一個狀態通道?1. 一個狀態通道需要至少2個或多個使用者同時互動才能開啟。類似即時聊天工具一樣。
2. 具有開啟和關閉狀態通道邏輯的智慧合約。3. 如果將在遊戲中使用狀態通道,則兩個使用者都需要進行託管。在開啟狀態通道時,乙太網中的託管都將儲存在智慧合約中。4. 一個javascript應用程式,它將生成簽名訊息,這些訊息將在使用者之間的鏈外交換。5. Metamask或用於簽名訊息的類似工具。簽名訊息不需要損耗gas,並會立即執行。兩個使用者都需要對訊息進行簽名,以保證tehy是生成此類事務的人。6. 透過電子郵件或任何外部應用程式交換這些簽名郵件。狀態通道如何工作?
狀態通道設定起來有點複雜,因為你必須確保兩個玩家都受到保護,以防出現任何問題,這就是為什麼我們需要一個智慧合約。步驟如下:1. 在2個使用者之間的狀態通道中,第一個使用者部署智慧合約,該合約將“開啟”該通道。2. 第二個執行智慧合約的以“加入”功能進入該狀態通道。3. 然後他們可以開始為應用程式交換籤名的訊息。兩個使用者都可以訪問自定義javascript應用程式,以生成鏈外訊息,這些訊息包含他們在智慧合約中可以執行的資訊。4. 事務的速度取決於每個使用者建立和簽署這些訊息的速度。他們需要不停地交換資訊,不停地玩,直到他們決定遊戲結束。5. 當他們結束遊戲後,他們中的任何一人都可以進入智慧合約並執行一個功能來完成它,這將開始“協商”階段。
6. 在此階段,兩個使用者都有超時1天的時間將最新的2條訊息上傳到智慧合約。智慧合約檢查最新訊息並釋放資金以基於該資訊結束遊戲。每條訊息都包含先前互動的結果,因此只檢查最新的訊息是安全的。如何在現實世界中應用狀態通道?在本文中,我將向您展示如何在兩個使用者之間為一個以太坊遊戲建立一個狀態通道。請記住,狀態通道可以用於具有“狀態”或“計數器”的任何型別的應用程式。這就是為什麼狀態通道應用於遊戲是非常理想的。因為你可以追蹤每一場比賽的勝利者,所以每一場比賽都有一個狀態可以更新。我們將建立一個骰子游戲,玩家1選擇指定自己想要的數字,玩家2必須猜測該數字才能獲勝。他們可以任意進行遊戲,而無需在區塊鏈上執行交易。我們還有一個Web應用程式來顯示遊戲介面。這是我們要建立Dapp的索引:1. 建立視覺化Web應用程式。它將用作交換狀態通道的簽名訊息的媒介。
2. 建立簽名和加密訊息所需的功能。3. 建立智慧合約。1.建立視覺化Web應用程式在開始編寫程式碼之前,我想確保我們弄清楚了Web應用程式的完整細節。它看起來怎麼樣,關注的焦點是什麼。在這種情況下,我們希望為兩個玩家展示類似的東西。玩家1將看到骰子的6個面並且他將必須選擇哪個面為結果展示出來,然後第二個玩家,還必須在這些面之間進行選擇並且他將能夠看到結果。所以框架是這樣的:
1、玩家1進入應用程式,點選一個按鈕說“開始新遊戲”,然後他做一個metamask事務來部署和設定智慧合約。他收到一個智慧合約地址,可以傳送給其他玩家開始遊戲。2、玩家2進入應用程式,點選一個顯示“加入現有遊戲”的按鈕,其中包含從玩家1收到的合同地址,然後他進行metamask交易以設定現有遊戲併傳送一個託管。

那麼讓我們開始,在Web應用程式的中間建立一個帶有2個按鈕的框。建立一個名為dice的資料夾和一個名為index.html的檔案。這是程式碼:

<!DOCTYPE html>
<html lang="en" dir="ltr">
    <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>

在這段程式碼中,我剛建立了一個基本的HTML結構,其中包含一個包含按鈕和標題的div。請注意,div有一個名為main-content的類,我們稍後會使用它。

讓我們用一些css修飾一下介面。使用以下程式碼建立一個名為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 h2 {
    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新增了一個h2標題以使其看起來更好,請確保透過向CSS新增連結來更新HTML:

<!DOCTYPE html>
<html lang="en" dir="ltr">
    <head>
        <meta charset="utf-8">
        <link rel="stylesheet" href="index.css">
        <title>Dice ethereum game</title>
    </head>
    <body>
        <div class="main-content">
            <h2>Ethereum Dice</h2>
            <button>Start new game</button>
            <button>Join existing game</button>
        </div>
    </body>
</html>

我決定顯示使用者所需的下一個操作的最佳方法是在javascript中顯示包含所需資訊的div。所以當他點選“開始新遊戲”時,他會看到一個盒子,詢問他想要為遊戲設定多少託管。

他點選“加入現有遊戲”,他將被要求提供現有遊戲的託管和合同地址。

以下是按鈕操作的響應方式:

為了實現這一點,我用一些JavaScript邏輯建立了一個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. 首先,我建立了一個名為start()的函式,該函式將會立即執行並打包內容,以便它包含在一個大函式中。
2. 然後我建立了2個事件監聽器,每當我單擊html檔案中的啟動或連線按鈕時,它們就會被啟用。一個用於#new-game按鈕,另一個用於#joall-game按鈕。使用document.querySelector(),這是在js程式碼中選擇任何內容的最有效方法之一。
3. 在這些監聽器中,我顯示或隱藏每個相應元素的div框。基本上選擇帶有querySelector的盒子並刪除或新增隱藏的類,它在css中設定為display:none;。

然後我們可以將js檔案與我們的modifie index.html連線:

<!DOCTYPE html>
<html lang="en" dir="ltr">
    <head>
        <meta charset="utf-8">
        <link rel="stylesheet" href="index.css">
        <title>Dice ethereum game</title>
    </head>
    <body>
        <div class="main-content">
            <h2>Ethereum Dice</h2>
            <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,用於設定新資訊的樣式:

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 h2 {
    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”按鈕現在不起任何作用,所以讓我們建立一個功能來部署新的智慧合約,並在使用者希望在下一節中建立新遊戲時開啟狀態通道。

2.建立並連線初始智慧合約

現在是建立智慧合約的並使用web3.js將其與JavaScript連線的時候了。現在我們只需要建構函式和一些基本資訊,並將這段程式碼自己寫在一個名為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個函式,建構函式用於設定第一個播放器的地址和託管,setupPlayer2()函式用於設定第二個播放器的資訊。

我們希望在使用者單擊“continue”按鈕時部署智慧合約並使用指定的msg.value執行建構函式。為此,我們必須在智慧合約中實施web3.js。因為它是與瀏覽器上的區塊鏈進行通訊的主要方式。

請點選連線進行下載,單擊raw檢視完整程式碼並複製程式碼以將其貼上到專案資料夾中名為web3.js的新檔案中:https://github.com/ethereum/web3.js/blob/develop/dist/web3.js

如果您使用metamask,則不必執行此操作,因為metamask為您注入了web3.js的版本,但如果metamask不可用,則需要專案中的web3庫與區塊鏈進行互動。

我們使用metamask與區塊鏈互動。但是當您在瀏覽器上開啟index.html檔案時,會打不開檔案,因為metamask不支援file://副檔名。

我們需要執行一個本地伺服器,將檔案提交給http:// localhost:8080 url,因為當您直接開啟index.html檔案時,metamask不起作用。為此,請開啟終端並安裝:

npm i -g http-server

然後,在專案資料夾中執行http-server以啟動index.html的本地伺服器:

http-server

這將為localhost:8080上的檔案提供服務,這樣您就可以訪問它們並從metamask注入Web3。

在這種情況下,讓我們集中精力部署我們剛從我們的Web應用程式建立的合同,就在使用者單擊“continue”時。

要部署新合同,我們需要ABI,建構函式引數和位元組碼。這些是web3.js的要求。

要生成ABI,請轉到remix.ethereum.org,將程式碼貼上到主部分,然後單擊ABI:

這將複製ABI程式碼。轉到專案資料夾並建立一個名為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.現在我們需要智慧合約的bytecode。bytecode是將被部署到區塊鏈的已編譯的智慧合約,我們需要該資訊才能部署它。要使bytecode再次重新混合並單擊此按鈕:

並在contractData.js中建立另一個變數,稱為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": "",

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读

;