抢跑(Front-Running)是区块链中独特的攻击向量,源于区块链的透明性和交易排序机制。在传统金融市场中这是非法的,但在区块链上由于技术特性,变得难以完全防范。理解抢跑攻击对于构建安全的 DeFi 应用至关重要。
抢跑(Front-Running):攻击者监控内存池(mempool)中的待处理交易,在目标交易被打包前插入自己的交易,从而获利。
类比: 想象你在拍卖会上出价 100 元买一幅画,但在拍卖师确认前,有人听到了你的出价,抢先出价 101 元。这就是抢跑。
攻击者完全替代受害者的交易。
场景:抢注 ENS 域名
contract ENSRegistrar {
mapping(bytes32 => address) public owners;
function register(string memory name) public payable {
bytes32 nameHash = keccak256(abi.encodePacked(name));
// ❌ 漏洞:先到先得,容易被抢跑
require(owners[nameHash] == address(0), "Already registered");
require(msg.value >= registrationFee, "Insufficient fee");
owners[nameHash] = msg.sender;
}
}
攻击过程:
攻击者在受害者交易前后插入交易获利。
场景:DEX 套利
// 简化的 DEX
contract SimpleDE X {
uint public price = 100; // 1 ETH = 100 tokens
function buyTokens() public payable {
// ❌ 大额购买会影响价格
uint amount = msg.value * price;
price = price * 110 / 100; // 价格上涨 10%
tokens[msg.sender] += amount;
}
function sellTokens(uint amount) public {
price = price * 90 / 100; // 价格下跌 10%
uint ethAmount = amount / price;
payable(msg.sender).transfer(ethAmount);
}
}
攻击:
1. Alice 的交易:买入 10 ETH 的代币(在 mempool 中)
2. 攻击者前置交易:买入 5 ETH 的代币(gas 更高)
- 价格从 100 涨到 110
3. Alice 的交易执行:以更高价格(110)买入
- 价格从 110 涨到 121
4. 攻击者后置交易:卖出代币(gas 更高)
- 以 121 的价格卖出,获利
攻击者通过高 gas 费阻止某些交易执行。
场景:清算竞争
contract LendingProtocol {
function liquidate(address borrower) public {
// 清算获得 5% 奖励
uint reward = debt[borrower] * 5 / 100;
// ❌ 可能被抑制
require(isUnderCollateralized(borrower), "Not liquidatable");
payable(msg.sender).transfer(reward);
}
}
攻击:
MEV(Maximal Extractable Value):矿工/验证者通过控制交易顺序可以提取的最大价值。
Uniswap 套利机器人:
区块 N:
1. 矿工看到一笔大额 USDC->ETH 交易
2. 矿工在前面插入:ETH->USDC(价格低)
3. 受害者交易执行:USDC->ETH(推高 ETH 价格)
4. 矿工在后面插入:USDC->ETH(价格高,获利)
2020-2021 年,MEV 为矿工带来了数亿美元的额外收入。
将操作分为两步:提交哈希,稍后揭示。
pragma solidity ^0.8.0;
contract CommitReveal {
struct Commitment {
bytes32 commit;
uint revealBlock;
bool revealed;
}
mapping(address => Commitment) public commitments;
// 第 1 步:提交哈希
function commit(bytes32 _commit) public {
require(commitments[msg.sender].revealBlock == 0, "Already committed");
commitments[msg.sender] = Commitment({
commit: _commit,
revealBlock: block.number + 10, // 10 个区块后才能揭示
revealed: false
});
}
// 第 2 步:揭示原始值
function reveal(string memory value, bytes32 salt) public {
Commitment storage c = commitments[msg.sender];
require(c.revealBlock != 0, "Not committed");
require(block.number >= c.revealBlock, "Too early");
require(!c.revealed, "Already revealed");
// 验证哈希
bytes32 hash = keccak256(abi.encodePacked(value, salt, msg.sender));
require(hash == c.commit, "Invalid reveal");
c.revealed = true;
// 执行业务逻辑
processValue(value);
}
function processValue(string memory value) internal {
// 实际的业务逻辑
}
}
优点:
缺点:
设置可接受的最大/最小价格。
pragma solidity ^0.8.0;
contract DEXWithSlippage {
function swap(
address tokenIn,
address tokenOut,
uint amountIn,
uint minAmountOut // ✅ 滑点保护
) public {
uint amountOut = calculateSwap(tokenIn, tokenOut, amountIn);
// 检查滑点
require(amountOut >= minAmountOut, "Slippage too high");
// 执行交换
executeSwap(tokenIn, tokenOut, amountIn, amountOut);
}
function calculateSwap(
address tokenIn,
address tokenOut,
uint amountIn
) internal view returns (uint) {
// 计算输出数量
}
function executeSwap(
address tokenIn,
address tokenOut,
uint amountIn,
uint amountOut
) internal {
// 执行实际的交换
}
}
前端实现:
// 计算滑点
const expectedOutput = await dex.calculateSwap(tokenIn, tokenOut, amountIn);
const slippageTolerance = 0.5; // 0.5%
const minOutput = expectedOutput * (1 - slippageTolerance / 100);
// 发送交易
await dex.swap(tokenIn, tokenOut, amountIn, minOutput);
Flashbots 允许用户直接向验证者发送交易,绕过公共 mempool。
优点:
使用方式:
// 使用 Flashbots RPC
const flashbotsProvider = await FlashbotsBundleProvider.create(
provider,
signer
);
// 构建交易束(bundle)
const bundle = [
{
signedTransaction: signedTx
}
];
// 发送到 Flashbots
const bundleReceipt = await flashbotsProvider.sendBundle(bundle, targetBlock);
contract BatchProcessor {
struct Order {
address user;
uint amount;
uint timestamp;
}
Order[] public pendingOrders;
uint public constant BATCH_INTERVAL = 1 hours;
uint public lastBatchTime;
// 用户提交订单
function submitOrder(uint amount) public {
pendingOrders.push(Order({
user: msg.sender,
amount: amount,
timestamp: block.timestamp
}));
}
// 批量处理订单(任何人都可以调用)
function processBatch() public {
require(
block.timestamp >= lastBatchTime + BATCH_INTERVAL,
"Too early"
);
// 获取当前价格
uint price = getCurrentPrice();
// 处理所有待处理订单
for (uint i = 0; i < pendingOrders.length; i++) {
Order memory order = pendingOrders[i];
executeOrder(order.user, order.amount, price);
}
// 清空队列
delete pendingOrders;
lastBatchTime = block.timestamp;
}
function executeOrder(address user, uint amount, uint price) internal {
// 执行订单
}
function getCurrentPrice() internal view returns (uint) {
// 获取价格
}
}
优点:
缺点:
contract AntiManipulation {
uint public constant MIN_TRADE_SIZE = 1 ether;
function trade(uint amount) public {
// ✅ 限制最小交易规模
require(amount >= MIN_TRADE_SIZE, "Trade too small");
// 交易逻辑...
}
}
原理:增加攻击成本,使小额抢跑不经济。
实现滑点保护
require(amountOut >= minAmountOut, "Slippage too high");
使用 Commit-Reveal(适用场景)
// 分两步执行敏感操作
限制价格影响
uint priceImpact = calculateImpact(amount);
require(priceImpact <= MAX_IMPACT, "Impact too high");
时间加权平均价格(TWAP)
uint price = getTWAP(30 minutes); // 使用 30 分钟 TWAP
设置合理的滑点
使用限价单而非市价单
选择合适的 gas 价格
分批执行大额交易
抢跑是区块链独特的挑战:
🔍 理解威胁
🛡️ 多层防御
记住:完全防止抢跑很难,但可以通过多种手段降低风险和影响。