如何设计以太坊上的高额赌注随机数游戏

这篇文章主要介绍了如何设计以太坊上的高额赌注随机数游戏 ,文中通过代码以及文档配合进行讲解,很详细,它对在座的每个人的研究和工作具有很经典的参考价值。 如果需要,让我们与区块链资料网一起学习。

https://www.interchains.cc/21343.html

如何设计以太坊上的高额赌注随机数游戏是很好的区块链资料,他说明了区块链当中的经典原理,可以给我们提供资料,如何设计以太坊上的高额赌注随机数游戏学习起来其实是很简单的,

不多的几个较为抽象的概念也很容易理解,之所以很多人感觉如何设计以太坊上的高额赌注随机数游戏比较复杂,一方面是因为大多数的文档没有做到由浅入深地讲解,概念上没有注意先后顺序,给读者的理解带来困难

如何设计以太坊eth上的高额赌注随机数游戏

  • 随机数

通过实例学习,建立一个安全的高赌注随机数游戏

  • 原文:https://soliditydeveloper.com/high-stakes-roulette
  • 译文出自:登链翻译计划
  • 译者:翻译小组
  • 校对:Tiny 熊
  • 本文永久链接:learnblockchain.cn/article…

在上一篇,我们介绍了区块链blockchain与随机数,介绍使用承诺模式来安全在智能合约中生成一个随机数。

举一反三的学习总是最好的。 所以,让我们在区块链blockchain上建立一个真实的随机数游戏。 我们将通过增加一个安全的随机数生成器,使其足够安全,允许游戏支持真正的高赌注。

先来讨论一下整体设计。

设计合约

作为开发人员,在进行任何编程之前,我们要正确规划和设计我们的系统。 在随机数游戏中,我们需要弄清楚如何创建一个随机数,如何管理资金以及如何处理超时。

安全地生成一个随机数

合约的核心将是一个承诺模式。在这里可以查看之前的教程。 但总的来说,区块链blockchain中的随机数没有很好的直接来源,因为所有的代码都要确定性地运行。

低赌注的方案: 使用未来的区块哈希是一个可能的解决方案,但矿工对这个值有一定的影响。 他们可以选择不发布新区块,放弃区块奖励。 但如果他们同时在玩一个非常高赌注的随机数游戏,阻止一个区块可能是他们更好的收益策略。

高额赌注方案: 所以对于高赌注的情况,我们需要一个更好的随机数发生器。 幸运的是,在有两个参与者(在本文中,我们设定两个玩家为:银行和玩家)的设置中,我们可以使用承诺模式。 每个玩家承诺秘密随机数,首先发送该数字的keccak256承诺哈希值。 一旦两个哈希值都在合约中,玩家就可以安全地揭示实际的随机数。 该合约验证了keccak256(randomNumber)==承诺哈希,确保双方不能再更改随机数。 最后的随机数将是这个随机数的运算(如:randomNumberBank XOR randomNumberPlayer)。 更多的细节在可参看区块链blockchain与随机数。

如何设计<a href=以太坊eth上的高额赌注随机数游戏” />

完善高额赌注下的设计

针对这种高额赌注的情况,可以从两个方面进行改进:

  1. 让银行先承诺一个秘密数值发送哈希,其实就足够了。 当银行提交后,玩家可以直接揭示自己的数值。 这样就减少了一个游戏回合,避免了玩家也发送承诺哈希。
  2. 假设一个玩家不会只玩一个回合,我们可以建立一个承诺哈希链。 该链的计算方式为:keccak256(keccak256(keccak256( … (randomNumber) …))。 基本上是重复的计算出哈希的哈希值,以此类推,数百万次。最后一个哈希值将发送作为第一个承诺,而所有其他中间哈希值则存储起来供以后使用。 然后,银行揭示的秘密值又将自动作为下一轮的承诺。

利用这两项改进,单场比赛的回合数就可以减少到:

  1. 玩家在下注的同时发送一个秘密数值。 为了简单起见,我们只允许投注红色或黑色,但应该很容易扩展该功能。
  2. 银行发送秘密数值时(揭示阶段),将自动触发支付。

基金管理

我们不想为每一轮游戏发资金。 所以就像在现实世界中一样,我们的赌场会管理自己的资金。 玩家和银行在合约中存入资金,并获得游戏内资金。 他们可以随时提取任何解锁资金或存入更多资金。

处理超时

在承诺模式中,有一种方法可以操纵结果:不发送秘密数值,从而阻止一个回合的结束。 为了处理这种情况,我们需要为玩家提供一个额外的功能,检查银行是否在规定的时间内发送了秘密数值。如果没有发送,则玩家自动获胜。

管理合约中的资金

可以在存储中声明一个简单的映射。

mapping (address => uint256) public registeredFunds;

在这里,我们可以在发送ETH时记录地址存入的金额,或者在取出ETH时减去提取的金额。 你同样可以用ERC-20代币代替ETH。

我们使用.call方法而不是.transfer,因为transfer是不推荐发送ETH的方式了。

function depositFunds() external payable {   require(msg.value > 0, "Must send ETH");   registeredFunds[msg.sender] += msg.value; }  function withdrawFunds() external {   require(registeredFunds[msg.sender] > 0);    uint256 funds = registeredFunds[msg.sender];   registeredFunds[msg.sender] = 0;    (bool success, ) = msg.sender.call{value: funds}("");   require(success, "ETH transfer failed"); }

下注

接下来让我们来创建实际的游戏。 一轮比赛将由GameRound结构来定义。 下面几个值将用来生成随机数:

  • bankHash
  • bankSecretValue
  • userValue

userValue作为选择是赌红还是赌黑(更好的编码方式可能是在这里使用一个枚举,有两个值RED和BLACK)。 将赢取的资金作为lockedFunds。 对于红/黑的投注,lockedFunds将是投注额的两倍。 并且我们还需要存储下注的时间,以实现超时功能。

struct GameRound {     bytes32 bankHash;     uint256 bankSecretValue;     uint256 userValue;     bool hasUserBetOnRed;     uint256 timeWhenSecretUserValueSubmitted;     uint256 lockedFunds; }

现在我们可以创建一个placeBet函数。 确保游戏处于正确的状态,并确保银行和玩家有足够的资金。 我们会存储赌注,锁定资金,存储超时时间。

为什么我们为每个玩家存储一个银行哈希值?你可能会好奇为什么我们不在所有游戏中只使用一个银行哈希值。 这似乎很诱人,因为它减少了银行的复杂性。 不幸的是,它将允许完全操纵随机数。 想象一下,多个玩家同时下注。 现在,银行可以决定为哪位玩家揭示秘密数值。 为了防止这种情况的发生,我们需要根据投注的时间,对每一次的揭晓执行严格的顺序。 这最终会比每个玩家有一个哈希更复杂。

如何设计<a href=以太坊eth上的高额赌注随机数游戏” />

function placeBet(bool hasUserBetOnRed, uint256 userValue,uint256 _betAmount) external {     require(gameRounds[msg.sender].bankHash != 0x0, "Bank hash not yet set");     require(userValue == 0, "Already placed bet");     require(registeredFunds[bankAddress] >= _betAmount, "Not enough bank funds");     require(registeredFunds[msg.sender] >= _betAmount, "Not enough user funds");      gameRounds[msg.sender].userValue = userValue;     gameRounds[msg.sender].hasUserBetOnRed = hasUserBetOnRed;     gameRounds[msg.sender].lockedFunds = _betAmount * 2;     gameRounds[userAddress].timeWhenSecretUserValueSubmitted = block.timestamp;      registeredFunds[msg.sender] -= _betAmount;     registeredFunds[bankAddress] -= _betAmount; }

你可能已经注意到,在第一轮之前,bankhash 会是空的。 所以我们需要两个额外的函数,这些函数只在开始时被玩家调用一次。 通过initializeGame玩家可以请求银行调用setInitialBankHash

function initializeGame() external {     require(!hasRequestedGame[msg.sender],"Already requested");      hasRequestedGame[msg.sender] = true;     emit NewGameRequest(msg.sender); }

银行将运行一个服务器,监听NewGameRequest事件。 收到事件后,将调用 setInitialBankHash。

function setInitialBankHash(     bytes32 bankHash,     address user ) external onlyOwner {     require(         gameRounds[user].bankHash == 0x0,         "Bank hash already set"     );     gameRounds[user].bankHash = bankHash; }

银行揭示秘密数值

现在进行实际游戏,银行需要揭示数值。我们要求游戏回合确实是在(等待)银行揭示数值状态下。 同时我们确保hashReveal等于gameRounds[userAddress].bankHash,因此强制要求银行不能操纵随机数。

function sendBankSecretValue(uint256 bankSecretValue, address user) external {     require(gameRounds[userAddress].userValue != 0, "User has no value set");     require(gameRounds[userAddress].bankSecretValue == 0, "Already revealed");     bytes32 hashedReveal = keccak256(abi.encodePacked(bankSecretValue));     require(hashedReveal == gameRounds[userAddress].bankHash, "Bank reveal not matching commitment");      gameRounds[userAddress].bankSecretValue = bankSecretValue;      _evaluateBet(user);     _resetContractFor(user);      gameRounds[userAddress].bankHash = bytes32(bankSecretValue); }

确定结果

然后我们确定结果,看看谁赢了。 最后我们重新设置下一轮的数据,其中包括自动将银行哈希值设置为当前的秘密数值(根据我们在开始时描述的承诺哈希链设计)。

function _resetContractFor(address user) private {     gameRounds[user] = GameRound(0x0, 0, 0, false, 0, 0); }  function _evaluateBet(address user) private {     uint256 random = gameRounds[user].bankSecretValue         ^ gameRounds[user].userValue;     uint256 number = random % ROULETTE_NUMBER_COUNT;     uint256 winningAmount = gameRounds[user].lockedFunds;      bool isNeitherRedNorBlack = number == 0;     bool isRed = isNumberRed[number];     bool hasUserBetOnRed = gameRounds[user].hasUserBetOnRed;      address winner;      if (isNeitherRedNorBlack) winner = bankAddress;     else if (isRed == hasUserBetOnRed) winner = userAddress;     else winner = bankAddress;      registeredFunds[winner] += winningAmount; }

我们现在有两个由玩家和银行随机选择的号码。 使用按位 OR 可以计算出一个最终的随机数。

使用 随机数 % ROULETTE_NUMBER_COUNT,例如,计算随机数模37,将得到一个0到36之间的随机数,获得任何数字都有相同的概率。

现在对于选出优胜者,我们有三个情况:

  1. 数字是0: 银行赢
  2. 颜色被玩家猜对了
  3. 颜色没有被玩家猜对

为了确定颜色是否为红色,我们可以使用存储bool[37] isNumberRed数组来定义。

处理超时

确定预先定义的超时时间TIMEOUT_FOR_BANK_REVEAL(例如2天),我们可以检查是否有超时。 如果游戏确实是在等待银行发送揭示,并且等待的时间超过了超时时间,玩家可以调用checkBankSecretValueTimeout,将自动赢得游戏回合。

function checkBankSecretValueTimeout() external {    require(gameRounds[msg.sender].bankHash != 0, "Bank hash not set");    require(gameRounds[msg.sender].bankSecretValue == 0, "Bank secret is set");    require(gameRounds[msg.sender].userValue != 0, "User value not set");     uint256 timeout = (gameRounds[msg.sender].timeWhenSecretUserValueSubmitted + TIMEOUT_FOR_BANK_REVEAL);    require(block.timestamp > timeout, "Timeout not yet reached");     registeredFunds[msg.sender] += gameRounds[msg.sender].lockedFunds;    _resetContractFor(msg.sender);    hasRequestedGame[msg.sender] = false; }

完全可行的例子 + 缺少的组件

完整可行的代码如下:

// SPDX-License-Identifier: MIT pragma solidity ^0.8.0;  contract BankOwned {     address public bankAddress;      constructor() {         bankAddress = msg.sender;     }      modifier onlyOwner {         require(msg.sender == bankAddress);         _;     } }  contract Roulette is BankOwned {     uint256 public immutable TIMEOUT_FOR_BANK_REVEAL = 1 days;     uint256 public immutable ROULETTE_NUMBER_COUNT = 37;      // prettier-ignore     bool[37] isNumberRed = [false, true, false, true, false, true, false, true, false, true, false, false, true, false, true, false, true, false, true, true, false, true, false, true, false, true, false, true, false, false, true, false, true, false, true, false, true];      struct GameRound {         bytes32 bankHash;         uint256 bankSecretValue;         uint256 userValue;         bool hasUserBetOnRed;         uint256 timeWhenSecretUserValueSubmitted;         uint256 lockedFunds;     }      mapping(address => bool) public hasRequestedGame;     mapping(address => GameRound) public gameRounds;     mapping(address => uint256) public registeredFunds;      event NewGameRequest(address indexed user);      function increaseFunds() external payable {         require(msg.value > 0, "Must send ETH");         registeredFunds[msg.sender] += msg.value;     }      function withdrawMoney() external {         require(registeredFunds[msg.sender] > 0);          uint256 funds = registeredFunds[msg.sender];         registeredFunds[msg.sender] = 0;          (bool wasSuccessful, ) = msg.sender.call{value: funds}("");         require(wasSuccessful, "ETH transfer failed");     }      function initializeGame() external {         require(!hasRequestedGame[msg.sender], "Already requested game");          hasRequestedGame[msg.sender] = true;         emit NewGameRequest(msg.sender);     }      function setInitialBankHash(bytes32 bankHash, address userAddress) external onlyOwner {         require(gameRounds[userAddress].bankHash == 0x0, "Bank hash already set");         gameRounds[userAddress].bankHash = bankHash;     }      function placeBet(         bool hasUserBetOnRed,         uint256 userValue,         uint256 _betAmount     ) external {         require(gameRounds[msg.sender].bankHash != 0x0, "Bank hash not yet set");         require(userValue == 0, "Already placed bet");         require(registeredFunds[bankAddress] >= _betAmount, "Not enough bank funds");         require(registeredFunds[msg.sender] >= _betAmount, "Not enough user funds");          gameRounds[msg.sender].userValue = userValue;         gameRounds[msg.sender].hasUserBetOnRed = hasUserBetOnRed;         gameRounds[msg.sender].lockedFunds = _betAmount * 2;         gameRounds[userAddress].timeWhenSecretUserValueSubmitted = block.timestamp;          registeredFunds[msg.sender] -= _betAmount;         registeredFunds[bankAddress] -= _betAmount;     }      function sendBankSecretValue(uint256 bankSecretValue, address userAddress) external {         require(gameRounds[userAddress].userValue != 0, "User has no value set");         require(gameRounds[userAddress].bankSecretValue == 0, "Already revealed");         require(keccak256(abi.encodePacked(bankSecretValue)) == gameRounds[userAddress].bankHash, "Bank reveal not matching commitment");          gameRounds[userAddress].bankSecretValue = bankSecretValue;          _evaluateBet(userAddress);         _resetContractFor(userAddress);          gameRounds[userAddress].bankHash = bytes32(bankSecretValue);     }      function checkBankSecretValueTimeout() external {         require(gameRounds[msg.sender].bankHash != 0, "Bank hash not set");         require(gameRounds[msg.sender].bankSecretValue == 0, "Bank secret is set");         require(gameRounds[msg.sender].userValue != 0, "User value not set");         require(block.timestamp > (gameRounds[msg.sender].timeWhenSecretUserValueSubmitted + TIMEOUT_FOR_BANK_REVEAL), "Timeout not yet reached");          registeredFunds[msg.sender] += gameRounds[msg.sender].lockedFunds;         _resetContractFor(msg.sender);     }      function _resetContractFor(address userAddress) private {         gameRounds[userAddress] = GameRound(0x0, 0, 0, false, 0, 0);     }      function _evaluateBet(address userAddress) private {         uint256 random = gameRounds[userAddress].bankSecretValue ^ gameRounds[userAddress].userValue;         uint256 number = random % ROULETTE_NUMBER_COUNT;         uint256 winningAmount = gameRounds[userAddress].lockedFunds;          bool isNeitherRedNorBlack = number == 0;         bool isRed = isNumberRed[number];         bool hasUserBetOnRed = gameRounds[userAddress].hasUserBetOnRed;          address winner;          if (isNeitherRedNorBlack) winner = bankAddress;         else if (isRed == hasUserBetOnRed) winner = userAddress;         else winner = bankAddress;          registeredFunds[winner] += winningAmount;     } }

此代码也可以在这里找到。 不过你要知道,这只是合约代码。 作为银行提供者,你需要一个后台服务器运行,处理监听新投注和发送承诺哈希的逻辑。 通过允许银行同时为多个玩家提交多个哈希,可以进一步改进 gas 消耗。

另外,一个漂亮的前端界面也肯定受玩家们欢迎。


本翻译由 Cell Network 赞助支持。

  • 发表于 2021-02-19 16:16
  • 阅读 ( 1287 )
  • 学分 ( 106 )
  • 分类:以太坊eth

部分转自网络,侵权联系删除www.interchains.cchttps://www.interchains.cc/21343.html

区块链毕设网(www.interchains.cc)全网最靠谱的原创区块链毕设代做网站 部分资料来自网络,侵权联系删除! 最全最大的区块链源码站 ! QQ3039046426
区块链知识分享网, 以太坊dapp资源网, 区块链教程, fabric教程下载, 区块链书籍下载, 区块链资料下载, 区块链视频教程下载, 区块链基础教程, 区块链入门教程, 区块链资源 » 如何设计以太坊上的高额赌注随机数游戏

提供最优质的资源集合

立即查看 了解详情