区块链研究实验室|如何使用状态通道在以太坊创建可扩展的dApp和智能合约

区块链研究实验室  2019-07-11  区块链/区块链Blockchain栏目  

  有很多不同的解决方案可以创建Dapp,这些Dapp可以接触到数千甚至数百万实时用户,如Plasam和状态通道。在本文中,您将了解状态通道如何工作,以及如何在以太坊中创建可扩展的Dapp。

  什么是状态通道?

  状态通道是一种2层扩展解决方案,可以用于创建Dapp和智能合约,几乎可以被数百万用户实时使用。它们通过在2个或多个用户之间启动多个通道来工作,并执行事务的信息交换加密的签名消息。

  它们被称为“状态”,是因为每个交互都必须具有可以更新的状态。例如游戏得分或银行余额。

  我们需要什么来建立一个状态通道?

  一个状态通道需要至少2个或多个用户同时交互才能打开。类似即时聊天工具一样。

  具有打开和关闭状态通道逻辑的智能合约。

  如果将在游戏中使用状态通道,则两个用户都需要进行托管。在打开状态通道时,以太网中的托管都将存储在智能合约中。

  一个javascript应用程序,它将生成签名消息,这些消息将在用户之间的链外交换。

  Metamask或用于签名消息的类似工具。签名消息不需要损耗gas,并会立即执行。两个用户都需要对消息进行签名,以保证tehy是生成此类事务的人。

  通过电子邮件或任何外部应用程序交换这些签名邮件。

  状态通道如何工作?

  状态通道设置起来有点复杂,因为你必须确保两个玩家都受到保护,以防出现任何问题,这就是为什么我们需要一个智能合约。步骤如下:

  在2个用户之间的状态通道中,第一个用户部署智能合约,该合约将“打开”该通道。

  第二个执行智能合约的以“加入”功能进入该状态通道。

  然后他们可以开始为应用程序交换签名的消息。两个用户都可以访问自定义javascript应用程序,以生成链外消息,这些消息包含他们在智能合约中可以执行的信息。

  事务的速度取决于每个用户创建和签署这些消息的速度。他们需要不停地交换信息,不停地玩,直到他们决定游戏结束。

  当他们结束游戏后,他们中的任何一人都可以进入智能合约并执行一个功能来完成它,这将开始“协商”阶段。

  在此阶段,两个用户都有超时1天的时间将最新的2条消息上传到智能合约。智能合约检查最新消息并释放资金以基于该信息结束游戏。每条消息都包含先前交互的结果,因此只检查最新的消息是安全的。

  如何在现实世界中应用状态通道?

  在本文中,我将向您展示如何在两个用户之间为一个以太坊游戏创建一个状态通道。请记住,状态通道可以用于具有“状态”或“计数器”的任何类型的应用程序。这就是为什么状态通道应用于游戏是非常理想的。因为你可以追踪每一场比赛的胜利者,所以每一场比赛都有一个状态可以更新。

  我们将创建一个骰子游戏,玩家1选择指定自己想要的数字,玩家2必须猜测该数字才能获胜。他们可以任意进行游戏,而无需在区块链上执行交易。我们还有一个Web应用程序来显示游戏界面。

  这是我们要创建Dapp的索引:

  创建可视化Web应用程序。它将用作交换状态通道的签名消息的媒介。

  创建签名和加密消息所需的功能。

  创建智能合约。

  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 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="ltr">
    <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。所以当他点击“开始新游戏”时,他会看到一个盒子,询问他想要为游戏设置多少托管。

  他点击“加入现有游戏”,他将被要求提供现有游戏的托管和合同地址。

  以下是按钮操作的响应方式:

  

  为了实现这一点,我用一些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()

  解释一下我做了什么:

  首先,我创建了一个名为start()的函数,该函数将会立即执行并打包内容,以便它包含在一个大函数中。

  然后我创建了2个事件监听器,每当我单击html文件中的启动或连接按钮时,它们就会被激活。一个用于#new-game按钮,另一个用于#joall-game按钮。使用document.querySelector(),这是在js代码中选择任何内容的最有效方法之一。

  在这些监听器中,我显示或隐藏每个相应元素的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">
            <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,用于设置新信息的样式:

  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”按钮现在不起任何作用,所以让我们创建一个功能来部署新的智能合约,并在用户希望在下一节中创建新游戏时打开状态通道。

  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": "",
                "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"
    }
]

const bytecode = {
    "linkReferences": {},
    "object": "608060405260003411151561001357600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055503460028190555061025c806100696000396000f30060806040526004361061006d576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063329f86a814610072578063451e408a1461009d57806359a5f12d146100c857806367bf59541461011f578063d30895e414610129575b600080fd5b34801561007e57600080fd5b50610087610180565b6040518082815260200191505060405180910390f35b3480156100a957600080fd5b506100b2610186565b6040518082815260200191505060405180910390f35b3480156100d457600080fd5b506100dd61018c565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6101276101b2565b005b34801561013557600080fd5b5061013e61020b565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b60035481565b60025481565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000341115156101c157600080fd5b33600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034600381905550565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff16815600a165627a7a72305820b5200d0e6af4291c54776e5e9e789c9a85aa428048be4365e835e50d2b5eaca70029",
    "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x0 CALLVALUE GT ISZERO ISZERO PUSH2 0x13 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST CALLER PUSH1 0x0 DUP1 PUSH2 0x100 EXP DUP2 SLOAD DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND MUL OR SWAP1 SSTORE POP CALLVALUE PUSH1 0x2 DUP2 SWAP1 SSTORE POP PUSH2 0x25C DUP1 PUSH2 0x69 PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN STOP PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x4 CALLDATASIZE LT PUSH2 0x6D JUMPI PUSH1 0x0 CALLDATALOAD PUSH29 0x100000000000000000000000000000000000000000000000000000000 SWAP1 DIV PUSH4 0xFFFFFFFF AND DUP1 PUSH4 0x329F86A8 EQ PUSH2 0x72 JUMPI DUP1 PUSH4 0x451E408A EQ PUSH2 0x9D JUMPI DUP1 PUSH4 0x59A5F12D EQ PUSH2 0xC8 JUMPI DUP1 PUSH4 0x67BF5954 EQ PUSH2 0x11F JUMPI DUP1 PUSH4 0xD30895E4 EQ PUSH2 0x129 JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x7E JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x87 PUSH2 0x180 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0xA9 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0xB2 PUSH2 0x186 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0xD4 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0xDD PUSH2 0x18C JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x127 PUSH2 0x1B2 JUMP JUMPDEST STOP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x135 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x13E PUSH2 0x20B JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH1 0x3 SLOAD DUP2 JUMP JUMPDEST PUSH1 0x2 SLOAD DUP2 JUMP JUMPDEST PUSH1 0x1 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 JUMP JUMPDEST PUSH1 0x0 CALLVALUE GT ISZERO ISZERO PUSH2 0x1C1 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST CALLER PUSH1 0x1 PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND MUL OR SWAP1 SSTORE POP CALLVALUE PUSH1 0x3 DUP2 SWAP1 SSTORE POP JUMP JUMPDEST PUSH1 0x0 DUP1 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 JUMP STOP LOG1 PUSH6 0x627A7A723058 KECCAK256 0xb5 KECCAK256 0xd 0xe PUSH11 0xF4291C54776E5E9E789C9A DUP6 0xaa TIMESTAMP DUP1 0x48 0xbe NUMBER PUSH6 0xE835E50D2B5E 0xac 0xa7 STOP 0x29 ",
    "sourceMap": "27:447:0:-;;;239:1;227:9;:13;219:22;;;;;;;;262:10;252:7;;:20;;;;;;;;;;;;;;;;;;299:9;283:13;:25;;;;27:447;;;;;;"
}

  如果您的智能合约与我上面创建的完全一致,您可以复制相同的代码。

  在index.js文件之前的html中导入javascript文件,以获得abi和bytecode变量:

  <script src="contractData.js"></script>
<script src="index.js"></script>

  在创建javascript合约之前,我们需要在“开始新游戏”部分的“continue”按钮中添加一个事件监听器:

  <!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">
            <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 id="eth-value" type="number" placeholder="2...">
            </div>

            <div class="hidden join-game-setup">
                <h3>What's the smart contract address of the existing game</h3>
                <input id="eth-address" type="text" placeholder="0x38dfj39...">
            </div>

            <button id="button-continue" class="hidden">Continue</button>
            <b id="display-address"></b>
        </div>

        <script src="contractData.js"></script>
        <script src="index.js"></script>
    </body>
</html>

  解释一下我做了哪些操作:

  

  我在输入中添加了id,用户被问到如果他加入一个现有的游戏,他想在托管中放入多少以太币以及合同的地址。

  然后我在index.js上面添加了javascript import <script src =“contractData.js> </ script>,因为我们希望在index.js中使用abi和bytecode,因为它必须先导入。

  然后我们添加所需的逻辑以使该按钮工作。我们将检查HTML中的合同地址输入是否为空。

  如果它不是空的,那么我们假设玩家正在启动一个新游戏,有趣的是,如果您将地址保留为空,那么可以使用join按钮启动一个游戏。

  在我向您展示整个代码之前,我想向您解释如何使用web3.js部署智能合约。 

  因此,当用户点击“开始新游戏”时,他会向我们提供以太网托管金额及其地址,我们可以使用此功能部署新合同:

  Contract = web3.eth.contract(abi)

contractInstance = Contract.new({
    value: web3.toWei(valueSelected),
    data: bytecode.object,
    gas: 7e6
}, (err, result) => {
    // This callback will be called twice, the second time includes the contract address
    if(!result.address) {
        console.log('wait until the block is mined with the contract creation transaction')
    } else {
        console.log("here's the contract address just deployed", result.address)
    }
})

  基本上你用abi创建智能合约实例,然后用字节码执行该契约的方法.new()。

  然后,在回调中,如果有任何结果对象,则会出现错误。结果对象将包含矿工处理交易时部署的合同的.address。

  这意味着此回调将执行2次。一个是您执行合同创建,另一个是当该合同的地址可用时。

  您可以使用简单的if语句检查合同地址何时可用:

  if(!result.address) {
    // The contract creation has started
} else {
    // The contract has been deployed and you can use the address with result.address
}

  这就是你与web3部署智能合约的方式。

  但是如果你想访问区块链上的现有智能合约怎么办?

  这正是我们需要“加入”骰子游戏,创建智能合约实例。为此,我们只需要ABI和智能合约的地址,字节码不是必需的。以下是您在web3中的操作方法:

  Contract = web3.eth.contract(abi)
contractInstance = Contract.at(addressSelected)

  之后,您可以执行该合同的功能,如下所示:

  contractInstance.setupPlayer2({
  value: web3.toWei(valueSelected),
  gas: 4e6
}, (err, result) => { 
    // Do something after executing the function
})

  您只需要实例,函数名称,参数(如果有)和回调函数。

  既然您已了解智能合约的部署和实例化如何在javascript上运行,我将向您展示该应用程序的完整代码:

  let Contract
let contractInstance

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
        }
    })

    document.querySelector('#button-continue').addEventListener('click', () => {
        const valueSelected = document.querySelector('#eth-value').value
        const addressSelected = document.querySelector('#eth-address').value.trim()
        Contract = web3.eth.contract(abi)

        if(addressSelected.length === 0) {
            contractInstance = Contract.new({
                value: web3.toWei(valueSelected),
                data: bytecode.object,
                gas: 7e6
            }, (err, result) => {
                // This callback will be called twice, the second time includes the contract address
                if(!result.address) {
                    document.querySelector('#display-address').innerHTML = 'The transaction is being processed, wait until the block is mined to see the address here...'
                } else {
                    document.querySelector('#display-address').innerHTML = 'Contract address: '   result.address
                }
            })
        } else {
            let interval
            contractInstance = Contract.at(addressSelected)
            contractInstance.setupPlayer2({
                value: web3.toWei(valueSelected),
                gas: 4e6
            }, (err, result) => {
                interval = setInterval(() => {
                    web3.eth.getTransaction(result, (err, result) => {
                        if(result.blockNumber != null) {
                            document.querySelector('#display-address').innerHTML = 'Game ready'
                            clearInterval(interval)
                        }
                    })
                }, 1e3)
            })
        }
    })
}

start()

  忽略上面的所有内容,你必须关注的是'#button-continue'监听器的块:

document.querySelector('#button-continue').addEventListener()

  因为您只需关心当玩家1或玩家2点击“continue”按钮时会发生什么。

  当任何玩家点击该按钮时,将执行此事件侦听器

  在内部,如果玩家加入现有游戏,我会获得设置托管的输入值和已部署合同的地址。那些是valueSelected和addressSelected变量。

  然后我用abi创建合同设置变量,这对两个玩家都是必需的。

  之后,我看看是否设置了已部署合同的地址。如果地址为空,则表示玩家点击“开始新游戏”,因为在这种情况下他不会看到地址输入。

  这意味着我为选择了托管的玩家部署了一个新的游戏或智能合约。

  第一个玩家将看到部署的智能合约的地址。因为你需要两个玩家,所以他必须和另一个玩家分享这个地址才能开始一个骰子游戏。

  如果他提供了一个地址,这意味着他想加入一个现有的游戏。我们可以通过使用智能合约的地址创建智能合约的实例,然后执行函数setupplayer2()来实现这一点。

  我使用setinterval函数每1秒检查一次玩家2的设置是否已经完成或没有开始游戏。

  太好了!如果你做到了这一点,那就意味着你得到学习到如何部署。在下一篇文章中,您将看到如何用JavaScript为您的游戏创建状态通道,以创建可扩展的以太坊Dapp。

  

  描下放二维码添加我,拉您进入技术交流群

  扫码

  关注我们

  

  获得

版权信息
作者:链三丰
来源:区块链研究实验室
您可能想了解的区块链知识

关于我们

联系我们

作者进驻

手机版

Copyright © 2013 比特巴 www.btb8.com
始建于2013年,提供比特币 区块链及数字货币新闻、技术教程、测评、项目周报、人物等资讯
本页面提供的是区块链新闻资讯,区块链(Blockchain)是比特币的一个重要概念,它本质上是一个去中介化的数据库。