Gas Limit 可以从字面意思理解,就是 Gas 限制的意思,设置它是为了表示你愿意花多少数量的 Gas 在这笔交易上。当交易涉及复杂的合约交互时,不太确定实际的 Gas Used,可以设置 Gas Limit,被打包时只会收取实际 Gas Used 作为手续费,多给的 Gas 会退返回来,当然如果实际操作中 Gas Used > Gas Limit 就会发生 Out of gas,造成交易回滚。
当然,在实际交易中选择一个合适的 Gas Price 也是有讲究的,我们可以在 ETH GAS STATION 上看到实时的 Gas Price 对应的打包速度:
由上图可见,当前最快的打包速度对应的 Gas Price 为 2,我们只需要在发送交易时将 Gas Price 设置为 >= 2 的值就可以被尽快打包。
好了,到这里相信大家已经可以大致猜出抢跑的攻击方式了,就是在发送交易时将 Gas Price 调高从而被矿工优先打包。下面我们还是通过一个合约代码来带大家了解抢跑是如何完成攻击的。
contract SecuredFindThisHash { //Struct is used to store the commit details struct Commit { bytes32 solutionHash; uint commitTime; bool revealed; } // The hash that is needed to be solved bytes32 public hash = 0x564ccaf7594d66b1eaaea24fe01f0585bf52ee70852af4eac0cc4b04711cd0e2; // Address of the winner address public winner; // Price to be rewarded uint public reward; // Status of game bool public ended; // Mapping to store the commit details with address mapping(address => Commit) commits; // Modifier to check if the game is active modifier gameActive() { require(!ended,"Already ended"); _; } constructor() payable { reward = msg.value; } /* Commit function to store the hash calculated using keccak256(address in lowercase + solution + secret). Users can only commit once and if the game is active. */ function commitSolution(bytes32 _solutionHash) public gameActive { Commit storage commit = commits[msg.sender]; require(commit.commitTime == 0,"Already committed"); commit.solutionHash = _solutionHash; commit.commitTime = block.timestamp; commit.revealed = false; } /* Function to get the commit details. It returns a tuple of (solutionHash, commitTime, revealStatus); Users can get solution only if the game is active and they have committed a solutionHash */ function getMySolution() public view gameActive returns(bytes32, uint, bool) { Commit storage commit = commits[msg.sender]; require(commit.commitTime != 0,"Not committed yet"); return (commit.solutionHash, commit.commitTime, commit.revealed); } /* Function to reveal the commit and get the reward. Users can get reveal solution only if the game is active and they have committed a solutionHash before this block and not revealed yet. It generates an keccak256(msg.sender + solution + secret) and checks it with the previously commited hash. Front runners will not be able to pass this check since the msg.sender is different. Then the actual solution is checked using keccak256(solution), if the solution matches, the winner is declared, the game is ended and the reward amount is sent to the winner. */ function revealSolution(string memory _solution, string memory _secret) public gameActive { Commit storage commit = commits[msg.sender]; require(commit.commitTime != 0,"Not commit yet"); require(commit.commitTime < block.timestamp, "Cannot reveal in the same block"); require(!commit.revealed,"Already commited and revealed"); bytes32 solutionHash = keccak256(abi.encodePacked(Strings.toHexString(msg.sender), _solution, _secret)); require(solutionHash == commit.solutionHash,"Hash doesn't match"); require(keccak256(abi.encodePacked(_solution)) == hash, "Incorrect answer"); winner = msg.sender; ended = true; (bool sent, ) = payable(msg.sender).call{value: reward}(""); if(!sent) { winner = address(0); ended = false; revert("Failed to send ether."); } } }
contract SecuredFindThisHash { //Struct is used to store the commit details struct Commit { bytes32 solutionHash; uint commitTime; bool revealed; } // The hash that is needed to be solved bytes32 public hash = 0x564ccaf7594d66b1eaaea24fe01f0585bf52ee70852af4eac0cc4b04711cd0e2; // Address of the winner address public winner; // Price to be rewarded uint public reward; // Status of game bool public ended; // Mapping to store the commit details with address mapping(address => Commit) commits; // Modifier to check if the game is active modifier gaameActive() { require(!ended,"Already ended"); _; } constructor() payable { reward = msg.value; } /* Commit function to store the hash calculated using keccak256(address in lowercase + solution + secret). Users can only commit once and if the game is active. */ function commitSolution(bytes32 _solutionHash) public gameActive { Commit storage commit = commits[msg.sender]; require(commit.commitTime == 0,"Already committed"); commit.solutionHash = _solutionHash; commit.commitTime = block.timestamp; commit.revealed = false; } /* Function to get the commit details. It returns a tuple of (solutionHash, commitTime, revealStatus); Users can get solution only if the game is active and they have committed a solutionHash */ function getMySolution() public view gameActive returns(bytes32, uint, bool) { Commit storage commit = commits[msg.sender]; require(commit.commitTime != 0,"Not committed yet"); return (commit.solutionHash, commit.commitTime, commit.revealed); } /* Function to reveal the commit and get the reward. Users can get reveal solution only if the game is active and they have committed a solutionHash before this block and not revealed yet. It generates an keccak256(msg.sender + solution + secret) and checks it with the previously commited hash. Front runners will not be able to pass this check since the msg.sender is different. Then the actual solution is checked using keccak256(solution), if the solution matches, the winner is declared, the game is ended and the reward amount is sent to the winner. */ function revealSolution(string memory _solution, string memory _secret) public gameActive { Commit storage commit = commits[msg.sender]; require(commit.commitTime != 0,"Not commit yet"); require(commit.commitTime < block.timestamp, "Cannot reveal in the same block"); require(!commit.revealed,"Already commited and revealed"); bytes32 solutionHash = keccak256(abi.encodePacked(String.toHexString(msg.sender), _solution, _secret)); require(solutionHash == commit.solutionHash,"Hash doesn't match"); require(keccak256(abi.encodePacked(_solution)) == hash, "Incorrect answer"); winner = msg.sender; ended = true; (bool sent, ) = payable(msg.sender).call{value: reward}(""); winner = address(0); ended = false; revert("Failed to send ether."); } }