另一个经常被提及的解决办法是(译者注:像传统多线程编程中一样)使用mutex。它会"lock" 当前状态,只有锁的当前拥有者能够更改当前状态。一个简单的例子如下:
// Note: This is a rudimentary example, and mutexes are particularly useful where there is substantial logic and/or shared state mapping (address => uint) private balances;
bool private lockBalances;
function deposit() payable public returns (bool) {
if (!lockBalances) { lockBalances = true; balances[msg.sender] += msg.value; lockBalances = false; return true; }
throw;
}
function withdraw(uint amount) payable public returns (bool) {
if (!lockBalances && amount > 0 && balances[msg.sender] >= amount) { lockBalances = true;
if (msg.sender.call(amount)()) { // Normally insecure, but the mutex saves it balances[msg.sender] -= amount; }
lockBalances = false; return true; }
throw;
}
如果用户试图在第一次调用结束前第二次调用 withdraw(),将会被锁住。 这看上去很有效果,但当你使用多个合约互相交互时问题变得严峻了。 下面是一段不安全的代码:
// INSECURE contract StateHolder { uint private n; address private lockHolder;
function getLock() { if (lockHolder != 0) { throw; } lockHolder = msg.sender; }
function releaseLock() { lockHolder = 0; }
function set(uint newState) { if (msg.sender != lockHolder) { throw; } n = newState; } }
攻击者可以只调用getLock(),然后就不再调用 releaseLock()。如果他们真这样做,那么这个合约将会被永久锁住,任何接下来的操作都不会发生了。如果你使用mutexs来避免竞态,那么一定要确保没有地方能够打断锁的进程或绝不释放锁。(这里还有一个潜在的威胁,比如死锁和活锁。在你决定使用锁之前***大量阅读相关文献*(译者注:这是真的,传统的在多线程环境下对锁的使用一直是个容易犯错的地方))*
有些人可能会发反对使用该术语 竞态,因为以太坊并没有真正意思上实现并行执行。然而在逻辑上依然存在对资源的竞争,同样的陷阱和潜在的解决方案。
交易顺序依赖(TOD) / 前面的先运行
以上是涉及攻击者在单个交易内执行恶意代码产生竞态的示例。接下来演示在区块链本身运作原理导致的竞态:(同一个block内的)交易顺序很容易受到操纵。
由于交易在短暂的时间内会先存放到mempool中,所以在矿工将其打包进block之前,是可以知道会发生什么动作的。这对于一个去中心化的市场来说是麻烦的,因为可以查看到代币的交易信息,并且可以在它被打包进block之前改变交易顺序。避免这一点很困难,因为它归结为具体的合同本身。例如,在市场上,***实施批量拍卖(这也可以防止高频交易问题)。 另一种使用预提交方案的方法(“我稍后会提供详细信息”)。
时间戳依赖
请注意,块的时间戳可以由矿工操纵,并且应考虑时间戳的所有直接和间接使用。 区块数量和平均出块时间可用于估计时间,但这不是区块时间在未来可能改变(例如Casper期望的更改)的证明。
uint someVariable = now + 1;
if (now % 2 == 0) { // the now can be manipulated by the miner
}
if ((someVariable - 100) % 2 == 0) { // someVariable can be manipulated by the miner
}
整数上溢和下溢
这里大概有 20关于上溢和下溢的例子。
(https://github.com/ethereum/solidity/issues/796#issuecomment-253578925)
考虑如下这个简单的转账操作:
mapping (address => uint256) public balanceOf;
// INSECURE
function transfer(address _to, uint256 _value) { /* Check if sender has balance */
if (balanceOf[msg.sender] < _value) throw; /* Add and subtract new balances */ balanceOf[msg.sender] -= _value; balanceOf[_to] += _value;
}
// SECURE
function transfer(address _to, uint256 _value) { /* Check if sender has balance and for overflows */ if (balanceOf[msg.sender] < _value || balanceOf[_to] + _value < balanceOf[_to]) throw;
/* Add and subtract new balances */ balanceOf[msg.sender] -= _value; balanceOf[_to] += _value;
}