交易所风控系统测试方案
版本: v2.0
更新时间: 2026-03-14
适用场景: 加密货币交易所、DeFi平台、区块链钱包
目录
1. 入金风控测试
1.1 正常入金场景
测试用例 1.1.1: 标准入金流程
场景描述: 用户从外部钱包转入ETH到交易所充值地址
测试步骤:
- 获取用户充值地址
- 从外部钱包发送 0.1 ETH
- 监听链上交易
- 等待确认(1个确认)
- 验证入账金额
预期结果:
- ✅ 交易在1个确认后入账
- ✅ 金额准确无误
- ✅ 用户收到入金通知
- ✅ 订单状态更新为"已完成"
风控检查点:
{
"txHash": "0x...",
"from": "外部地址",
"to": "充值地址",
"amount": "0.1",
"confirmations": 1,
"status": "success",
"riskLevel": "low",
"checks": {
"addressWhitelist": "pass",
"amountRange": "pass",
"txPattern": "normal",
"contractCall": false
}
}
测试脚本:
// test_normal_deposit.js
const { ethers } = require('ethers');
async function testNormalDeposit() {
console.log('\n=== 测试1.1.1: 标准入金流程 ===\n');
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
// 交易所充值地址
const depositAddress = '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb';
const amount = ethers.parseEther('0.1');
// 发送入金交易
const tx = await wallet.sendTransaction({
to: depositAddress,
value: amount,
gasLimit: 21000,
});
console.log('✅ 交易已发送');
console.log('交易哈希:', tx.hash);
console.log('发送方:', tx.from);
console.log('接收方:', tx.to);
console.log('金额:', ethers.formatEther(amount), 'ETH');
// 等待确认
console.log('\n⏳ 等待确认...');
const receipt = await tx.wait(1);
console.log('✅ 交易已确认');
console.log('区块号:', receipt.blockNumber);
console.log('Gas使用:', receipt.gasUsed.toString());
console.log('状态:', receipt.status === 1 ? '成功' : '失败');
// 风控检查
const riskCheck = {
txHash: receipt.hash,
from: receipt.from,
to: receipt.to,
amount: ethers.formatEther(amount),
blockNumber: receipt.blockNumber,
confirmations: 1,
gasPrice: receipt.gasPrice.toString(),
// 风控标记
isContract: false,
isKnownAddress: false,
amountSuspicious: parseFloat(ethers.formatEther(amount)) > 10,
timingSuspicious: false,
riskLevel: 'low',
autoApprove: true,
};
console.log('\n📊 风控检查结果:');
console.log(JSON.stringify(riskCheck, null, 2));
return riskCheck;
}
module.exports = testNormalDeposit;
1.2 异常入金场景
测试用例 1.2.1: 小额高频入金(洗钱特征)
场景描述: 短时间内多次小额入金,可能是洗钱行为
测试步骤:
- 5分钟内发送10笔 0.01 ETH
- 观察风控系统响应
- 检查是否触发预警
预期结果:
- ⚠️ 触发"小额高频"预警
- ⚠️ 标记为中等风险
- ⚠️ 需要人工审核
- ⚠️ 暂时冻结资金
风控规则:
{
"ruleName": "小额高频入金检测",
"condition": {
"timeWindow": "5分钟",
"txCount": "> 5笔",
"avgAmount": "< 0.05 ETH",
"sameSource": true
},
"action": {
"riskLevel": "medium",
"autoFreeze": true,
"notifyRiskTeam": true,
"requireManualReview": true
}
}
测试脚本:
// test_small_frequent_deposit.js
async function testSmallFrequentDeposit() {
console.log('\n=== 测试1.2.1: 小额高频入金 ===\n');
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
const depositAddress = '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb';
const transactions = [];
const startTime = Date.now();
// 发送10笔小额交易
for (let i = 0; i < 10; i++) {
const amount = ethers.parseEther('0.01'); // 小额
const tx = await wallet.sendTransaction({
to: depositAddress,
value: amount,
gasLimit: 21000,
});
transactions.push({
hash: tx.hash,
amount: '0.01',
timestamp: Date.now(),
});
console.log(`✅ 交易 ${i + 1}/10 已发送: ${tx.hash}`);
// 间隔30秒
if (i < 9) {
await new Promise(resolve => setTimeout(resolve, 30000));
}
}
const endTime = Date.now();
const totalTime = (endTime - startTime) / 1000 / 60; // 分钟
// 风控分析
const riskAnalysis = {
pattern: '小额高频入金',
txCount: transactions.length,
totalAmount: (transactions.length * 0.01).toFixed(2),
timeWindow: `${totalTime.toFixed(2)}分钟`,
avgInterval: `${(totalTime * 60 / transactions.length).toFixed(0)}秒`,
riskIndicators: {
smallAmount: true, // 单笔金额小
highFrequency: true, // 频率高
sameSource: true, // 同一来源
unusualPattern: true, // 异常模式
},
riskLevel: 'medium',
riskScore: 65,
recommendations: [
'⚠️ 暂时冻结入金',
'⚠️ 通知风控团队',
'⚠️ 要求KYC验证',
'⚠️ 检查资金来源'
]
};
console.log('\n🚨 风控预警:');
console.log(JSON.stringify(riskAnalysis, null, 2));
return riskAnalysis;
}
测试用例 1.2.2: 大额单笔入金
场景描述: 单笔入金金额超过阈值
测试参数:
- 金额: 100 ETH (假设阈值是50 ETH)
- 来源: 未知地址
预期结果:
- 🚨 触发"大额入金"预警
- 🚨 标记为高风险
- 🚨 自动冻结资金
- 🚨 立即通知风控团队
- 🚨 要求提供资金来源证明
风控规则:
{
"ruleName": "大额入金检测",
"threshold": {
"ETH": 50,
"USDT": 100000,
"BTC": 2
},
"action": {
"riskLevel": "high",
"autoFreeze": true,
"immediatAlert": true,
"requireProofOfFunds": true,
"manualApprovalRequired": true
}
}
测试用例 1.2.3: 黑名单地址入金
场景描述: 从已知黑名单地址转入资金
测试步骤:
- 维护黑名单地址列表
- 从黑名单地址发送交易
- 验证系统是否拦截
预期结果:
- 🔴 立即拒绝入账
- 🔴 标记为极高风险
- 🔴 冻结相关账户
- 🔴 触发安全警报
- 🔴 自动生成风险报告
黑名单来源:
const BLACKLIST_SOURCES = [
'Chainalysis Sanctions',
'OFAC SDN List',
'Tornado Cash addresses',
'Known hacker addresses',
'Phishing addresses',
'Exchange hack addresses',
];
// 黑名单检查
async function checkBlacklist(address) {
const blacklist = [
'0x8576acc5c05d6ce88f4e49bf65bdf0c62f91353c', // Tornado Cash
'0x722122df12d4e14e13ac3b6895a86e84145b6967', // Ronin Bridge Hacker
// ... 更多地址
];
const isBlacklisted = blacklist.includes(address.toLowerCase());
if (isBlacklisted) {
return {
isBlacklisted: true,
riskLevel: 'critical',
action: 'reject',
reason: '黑名单地址',
alert: {
priority: 'critical',
notifyList: ['security-team@exchange.com', 'compliance@exchange.com'],
freezeAccount: true,
reportToAuthorities: true,
}
};
}
return { isBlacklisted: false };
}
测试用例 1.2.4: 合约转账入金
场景描述: 从智能合约地址转入资金(可能是套利、闪电贷)
测试步骤:
- 部署测试合约
- 从合约调用transfer
- 观察风控响应
预期结果:
- ⚠️ 标记为"合约转账"
- ⚠️ 延迟入账(增加确认数)
- ⚠️ 中等风险
- ⚠️ 记录合约地址
检测脚本:
// 检测是否是合约地址
async function isContractAddress(address, provider) {
const code = await provider.getCode(address);
return code !== '0x';
}
// 合约转账检测
async function checkContractDeposit(txHash, provider) {
const tx = await provider.getTransaction(txHash);
const receipt = await provider.getTransactionReceipt(txHash);
const fromIsContract = await isContractAddress(tx.from, provider);
if (fromIsContract) {
return {
isContractDeposit: true,
riskLevel: 'medium',
action: {
increaseConfirmations: 12, // 从1增加到12
delayCredit: true,
flagForReview: true,
},
contractInfo: {
address: tx.from,
verified: false, // 是否经过验证
knownContract: false, // 是否已知合约
}
};
}
return { isContractDeposit: false };
}
测试用例 1.2.5: 重放攻击检测
场景描述: 攻击者尝试重复提交同一笔入金交易
测试步骤:
- 提交正常入金交易
- 尝试重复提交相同txHash
- 验证系统是否拦截
预期结果:
- 🔴 检测到重复交易
- 🔴 拒绝重复入账
- 🔴 记录异常尝试
- 🔴 可能冻结账户
防护代码:
// 交易去重检查
const processedTxs = new Set();
async function checkDuplicateTx(txHash) {
if (processedTxs.has(txHash)) {
return {
isDuplicate: true,
action: 'reject',
reason: '重复交易',
alert: {
priority: 'high',
message: '检测到重放攻击尝试',
suspiciousActivity: true,
}
};
}
processedTxs.add(txHash);
return { isDuplicate: false };
}
2. 出金风控测试
2.1 正常提现场景
测试用例 2.1.1: 标准提现流程
场景描述: 用户正常提现ETH到外部地址
测试步骤:
- 用户提交提现请求
- 系统风控检查
- 审批通过
- 执行链上转账
- 确认到账
预期结果:
- ✅ 风控检查通过
- ✅ 自动审批
- ✅ 成功发送交易
- ✅ 用户收到提现通知
风控检查项:
const withdrawalRiskCheck = {
// 用户维度
user: {
accountAge: '> 30天',
kycLevel: 'L2',
tradingHistory: '正常',
previousWithdrawals: '正常',
riskScore: 20,
},
// 提现维度
withdrawal: {
amount: '0.5 ETH',
destination: '0x...',
isNewAddress: false,
addressWhitelisted: true,
frequency: '正常',
},
// 行为维度
behavior: {
loginLocation: '常用地区',
deviceFingerprint: '已识别',
ipReputation: '正常',
timePattern: '正常营业时间',
},
// 综合评分
overallRisk: {
level: 'low',
score: 15,
autoApprove: true,
requiresManualReview: false,
}
};
2.2 异常提现场景
测试用例 2.2.1: 新地址大额提现
场景描述: 首次向新地址提现大额资金
测试参数:
- 金额: 50 ETH
- 目标地址: 从未使用过的新地址
- 用户账户年龄: 7天
预期结果:
- 🚨 触发"新地址大额提现"预警
- 🚨 高风险评分
- 🚨 要求额外验证(2FA、邮件确认、SMS)
- 🚨 人工审核
- 🚨 延迟24小时执行
风控规则:
{
"ruleName": "新地址大额提现",
"conditions": {
"isNewAddress": true,
"amount": "> 10 ETH",
"accountAge": "< 30天"
},
"actions": {
"riskLevel": "high",
"requireAdditionalAuth": true,
"authMethods": ["2FA", "email", "sms"],
"manualReview": true,
"delayExecution": "24小时",
"notifyUser": true,
"notifyRiskTeam": true
}
}
测试脚本:
// test_new_address_large_withdrawal.js
async function testNewAddressLargeWithdrawal() {
console.log('\n=== 测试2.2.1: 新地址大额提现 ===\n');
const withdrawalRequest = {
userId: 'user_12345',
amount: '50',
currency: 'ETH',
destinationAddress: '0x1234567890123456789012345678901234567890', // 新地址
requestTime: Date.now(),
};
// 风控检查
const riskCheck = await performWithdrawalRiskCheck(withdrawalRequest);
console.log('📊 风控检查结果:');
console.log(JSON.stringify(riskCheck, null, 2));
return riskCheck;
}
async function performWithdrawalRiskCheck(request) {
// 1. 检查地址是否首次使用
const isNewAddress = await checkIfNewAddress(request.destinationAddress);
// 2. 检查金额是否超过阈值
const isLargeAmount = parseFloat(request.amount) > 10;
// 3. 检查用户账户年龄
const accountAge = await getAccountAge(request.userId);
const isNewAccount = accountAge < 30; // 天
// 4. 计算风险评分
let riskScore = 0;
const riskFactors = [];
if (isNewAddress) {
riskScore += 30;
riskFactors.push('新提现地址');
}
if (isLargeAmount) {
riskScore += 40;
riskFactors.push('大额提现');
}
if (isNewAccount) {
riskScore += 20;
riskFactors.push('新用户账户');
}
// 5. 决策
let decision = 'approve';
let actions = [];
if (riskScore >= 70) {
decision = 'manual_review';
actions = [
'要求额外身份验证',
'24小时延迟执行',
'人工审核',
'通知风控团队',
];
} else if (riskScore >= 40) {
decision = 'additional_verification';
actions = [
'要求2FA验证',
'发送邮件确认链接',
'短信验证码',
];
}
return {
requestId: `WD_${Date.now()}`,
decision,
riskLevel: riskScore >= 70 ? 'high' : riskScore >= 40 ? 'medium' : 'low',
riskScore,
riskFactors,
actions,
estimatedProcessTime: decision === 'manual_review' ? '24-48小时' : '10分钟',
};
}
// 辅助函数
async function checkIfNewAddress(address) {
// 查询数据库:该地址是否曾被该用户或其他用户使用过
// 模拟实现
return true; // 假设是新地址
}
async function getAccountAge(userId) {
// 查询用户注册天数
// 模拟实现
return 7; // 7天
}
测试用例 2.2.2: 高频提现(可能被盗)
场景描述: 短时间内多次提现,可能账户被盗
测试参数:
- 1小时内提现3次
- 每次金额递增
- 提现地址不同
预期结果:
- 🔴 触发"异常高频提现"
- 🔴 立即冻结账户
- 🔴 暂停所有提现
- 🔴 联系用户确认
- 🔴 极高风险
风控规则:
{
"ruleName": "高频提现检测",
"timeWindow": "1小时",
"conditions": {
"withdrawalCount": ">= 3",
"differentAddresses": true,
"increasingAmounts": true
},
"riskIndicators": {
"accountCompromise": "very_high",
"possibleTheft": true
},
"actions": {
"freezeAccount": true,
"cancelPendingWithdrawals": true,
"notifyUser": {
"email": true,
"sms": true,
"phone": true
},
"requireIdentityVerification": true,
"escalateToSecurity": true
}
}
测试用例 2.2.3: 异常IP/设备提现
场景描述: 从未使用过的IP/设备发起提现
测试参数:
- 登录IP: 来自高风险国家
- 设备指纹: 首次见到
- 与历史登录地点不符
预期结果:
- 🚨 触发"异常设备访问"
- 🚨 要求强制重新登录
- 🚨 要求身份验证
- 🚨 人工审核提现请求
检测脚本:
// 设备指纹检测
async function checkDeviceAnomaly(request) {
const userProfile = await getUserProfile(request.userId);
const checks = {
// IP检查
ipAnomaly: {
currentIP: request.ipAddress,
historicalIPs: userProfile.knownIPs,
isNewIP: !userProfile.knownIPs.includes(request.ipAddress),
ipReputation: await getIPReputation(request.ipAddress),
countryCode: await getIPCountry(request.ipAddress),
isHighRiskCountry: ['XX', 'YY'].includes(await getIPCountry(request.ipAddress)),
},
// 设备检查
deviceAnomaly: {
deviceId: request.deviceFingerprint,
isKnownDevice: userProfile.knownDevices.includes(request.deviceFingerprint),
deviceType: request.deviceType,
browserFingerprint: request.browserFingerprint,
},
// 时间检查
timeAnomaly: {
currentTime: new Date(request.timestamp),
usualActiveHours: userProfile.usualActiveHours,
isUnusualTime: checkUnusualTime(request.timestamp, userProfile.usualActiveHours),
},
};
// 计算异常评分
let anomalyScore = 0;
if (checks.ipAnomaly.isNewIP) anomalyScore += 20;
if (checks.ipAnomaly.isHighRiskCountry) anomalyScore += 30;
if (!checks.deviceAnomaly.isKnownDevice) anomalyScore += 25;
if (checks.timeAnomaly.isUnusualTime) anomalyScore += 15;
return {
checks,
anomalyScore,
riskLevel: anomalyScore >= 50 ? 'high' : anomalyScore >= 30 ? 'medium' : 'low',
recommendation: anomalyScore >= 50 ? 'reject' : anomalyScore >= 30 ? 'additional_verification' : 'approve',
};
}
测试用例 2.2.4: 混币器地址提现
场景描述: 提现到已知混币器地址(隐私币混合服务)
已知混币器:
- Tornado Cash
- Blender
- ChipMixer
- CoinJoin
预期结果:
- 🔴 立即拦截
- 🔴 标记账户
- 🔴 可能涉及洗钱
- 🔴 报告合规部门
检测脚本:
// 混币器地址库
const MIXER_ADDRESSES = {
tornadoCash: [
'0x8576acc5c05d6ce88f4e49bf65bdf0c62f91353c',
'0xd90e2f925DA726b50C4Ed8D0Fb90Ad053324F31b',
// ... 更多地址
],
other: [
// 其他混币器
]
};
async function checkMixerAddress(destinationAddress) {
const allMixers = [
...MIXER_ADDRESSES.tornadoCash,
...MIXER_ADDRESSES.other,
];
const isMixer = allMixers.includes(destinationAddress.toLowerCase());
if (isMixer) {
return {
isMixerAddress: true,
mixerType: 'Tornado Cash', // 实际需要识别具体类型
riskLevel: 'critical',
action: 'reject',
reasons: [
'目标地址为混币器',
'可能涉及洗钱',
'违反AML政策',
],
requiredActions: [
'立即冻结账户',
'报告合规部门',
'记录详细日志',
'可能需要报告监管机构',
]
};
}
return { isMixerAddress: false };
}
3. 异常交易检测
3.1 自交易检测
场景描述: 用户在自己控制的账户间频繁交易(刷量)
检测方法:
async function detectSelfTrading(userId, trades) {
// 分析交易对手
const counterparties = trades.map(t => t.counterpartyId);
const uniqueCounterparties = new Set(counterparties);
// 检查是否存在循环交易
const tradingGraph = buildTradingGraph(trades);
const cycles = detectCycles(tradingGraph);
if (cycles.length > 0) {
return {
isSelfTrading: true,
pattern: 'circular_trading',
cycleCount: cycles.length,
involvedAccounts: cycles.flat(),
riskLevel: 'high',
possibleWashTrading: true,
};
}
return { isSelfTrading: false };
}
3.2 价格操纵检测
场景描述: 异常大单导致价格剧烈波动
检测指标:
- 单笔订单占成交量比例 > 30%
- 价格波动 > 5% in 1分钟
- 订单簿失衡
预期结果:
- 🚨 触发"价格操纵"预警
- 🚨 暂停交易
- 🚨 回滚异常交易
- 🚨 调查相关账户
3.3 抢先交易检测
场景描述: 检测到内幕交易或抢先交易行为
检测方法:
async function detectFrontRunning(trades) {
for (let i = 0; i < trades.length - 1; i++) {
const trade1 = trades[i];
const trade2 = trades[i + 1];
// 检查时间间隔
const timeDiff = trade2.timestamp - trade1.timestamp;
// 检查交易方向和金额
if (
timeDiff < 1000 && // 1秒内
trade1.side !== trade2.side && // 相反方向
trade1.amount * 0.8 < trade2.amount // 金额相近
) {
return {
isFrontRunning: true,
suspiciousTrades: [trade1.id, trade2.id],
timeDiff: `${timeDiff}ms`,
riskLevel: 'high',
};
}
}
return { isFrontRunning: false };
}
4. 账户安全测试
4.1 暴力破解检测
场景描述: 多次密码尝试失败
测试步骤:
- 连续5次输入错误密码
- 观察系统响应
预期结果:
- ⚠️ 第3次失败后显示验证码
- 🔴 第5次失败后锁定账户15分钟
- 🔴 通知用户
- 🔴 记录IP地址
防护代码:
const loginAttempts = new Map();
async function checkLoginAttempts(username, ip) {
const key = `${username}_${ip}`;
const attempts = loginAttempts.get(key) || { count: 0, firstAttempt: Date.now() };
attempts.count++;
loginAttempts.set(key, attempts);
if (attempts.count >= 5) {
return {
allowed: false,
reason: '登录尝试次数过多',
lockDuration: 15 * 60 * 1000, // 15分钟
action: 'account_locked',
};
}
if (attempts.count >= 3) {
return {
allowed: true,
requireCaptcha: true,
};
}
return { allowed: true };
}
4.2 会话劫持检测
场景描述: 同一账户从多个地点同时登录
检测方法:
async function detectSessionHijacking(userId, newSession) {
const activeSessions = await getActiveSessions(userId);
for (const session of activeSessions) {
// 检查地理位置差异
const distance = calculateDistance(
session.location,
newSession.location
);
// 检查时间差异
const timeDiff = newSession.timestamp - session.lastActivity;
// 如果距离太远但时间太短(不可能的旅行)
if (distance > 1000 && timeDiff < 3600000) { // 1000km, 1小时
return {
isHijacking: true,
reason: '不可能的旅行',
distance: `${distance}km`,
timeDiff: `${timeDiff / 60000}分钟`,
action: 'terminate_all_sessions',
requireReauth: true,
};
}
}
return { isHijacking: false };
}
5. 反洗钱(AML)测试
5.1 资金链分析
场景描述: 追踪资金来源和去向
测试步骤:
- 获取入金交易
- 追溯链上历史(向前5跳)
- 分析资金来源
- 评估风险
风险来源:
- 🔴 来自黑客地址
- 🔴 来自暗网交易
- 🔴 来自勒索软件
- 🔴 经过混币器
- ⚠️ 来自高风险交易所
分析脚本:
// 资金链追踪
async function traceFundSource(txHash, depth = 5) {
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const visited = new Set();
const path = [];
async function trace(hash, currentDepth) {
if (currentDepth >= depth || visited.has(hash)) {
return;
}
visited.add(hash);
const tx = await provider.getTransaction(hash);
if (!tx) return;
path.push({
depth: currentDepth,
hash: tx.hash,
from: tx.from,
to: tx.to,
value: ethers.formatEther(tx.value),
blockNumber: tx.blockNumber,
});
// 检查来源地址的风险
const fromRisk = await checkAddressRisk(tx.from);
if (fromRisk.isHighRisk) {
path[path.length - 1].risk = fromRisk;
}
// 继续追溯from地址的历史交易
const fromTxs = await getAddressTransactions(tx.from, 'received');
if (fromTxs.length > 0) {
await trace(fromTxs[0].hash, currentDepth + 1);
}
}
await trace(txHash, 0);
// 分析路径
const analysis = {
totalHops: path.length,
path: path,
riskAssessment: assessPathRisk(path),
fundSource: path[path.length - 1]?.from || 'unknown',
};
return analysis;
}
function assessPathRisk(path) {
let riskScore = 0;
const riskFactors = [];
for (const hop of path) {
if (hop.risk) {
riskScore += hop.risk.score;
riskFactors.push({
address: hop.from,
reason: hop.risk.reason,
});
}
}
return {
score: riskScore,
level: riskScore > 70 ? 'high' : riskScore > 40 ? 'medium' : 'low',
factors: riskFactors,
};
}
async function checkAddressRisk(address) {
// 集成多个数据源
const checks = await Promise.all([
checkChainalysis(address),
checkElliptic(address),
checkInternalBlacklist(address),
]);
const highRiskSources = checks.filter(c => c.isHighRisk);
if (highRiskSources.length > 0) {
return {
isHighRisk: true,
score: 100,
reason: highRiskSources.map(s => s.reason).join(', '),
sources: highRiskSources,
};
}
return { isHighRisk: false, score: 0 };
}
// 模拟外部API调用
async function checkChainalysis(address) {
// 实际应调用 Chainalysis API
return { isHighRisk: false };
}
async function checkElliptic(address) {
// 实际应调用 Elliptic API
return { isHighRisk: false };
}
async function checkInternalBlacklist(address) {
// 检查内部黑名单
return { isHighRisk: false };
}
async function getAddressTransactions(address, type) {
// 获取地址交易记录
// 实际应该调用区块链浏览器API或自建索引
return [];
}
5.2 分层交易检测
场景描述: 资金通过多层账户转移(典型洗钱手法)
特征:
A → B → C → D → E → F
每层金额略有变化
每层间隔一定时间
最终回到A或转到外部
检测逻辑:
async function detectLayering(transactions) {
// 构建资金流图
const graph = {};
for (const tx of transactions) {
if (!graph[tx.from]) graph[tx.from] = [];
graph[tx.from].push({ to: tx.to, amount: tx.amount, time: tx.timestamp });
}
// 检测链式转账
function findChains(start, path = [], visited = new Set()) {
if (path.length >= 5) {
// 发现5层以上的链
return [path];
}
if (!graph[start] || visited.has(start)) {
return [];
}
visited.add(start);
const chains = [];
for (const next of graph[start]) {
const newPath = [...path, { from: start, to: next.to, amount: next.amount }];
chains.push(...findChains(next.to, newPath, new Set(visited)));
}
return chains;
}
const allChains = [];
for (const address in graph) {
allChains.push(...findChains(address));
}
// 分析可疑链
const suspiciousChains = allChains.filter(chain => {
// 检查金额衰减(扣除手续费)
const amounts = chain.map(c => parseFloat(c.amount));
const avgDecay = (amounts[0] - amounts[amounts.length - 1]) / amounts.length;
// 检查时间间隔
const times = chain.map(c => c.time);
const intervals = times.slice(1).map((t, i) => t - times[i]);
const avgInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length;
return (
chain.length >= 5 &&
avgDecay < 0.05 && // 每层损失小于5%
avgInterval < 3600000 && // 平均间隔小于1小时
avgInterval > 60000 // 但大于1分钟
);
});
return {
totalChains: allChains.length,
suspiciousChains: suspiciousChains.length,
details: suspiciousChains.map(chain => ({
layers: chain.length,
startAddress: chain[0].from,
endAddress: chain[chain.length - 1].to,
totalAmount: chain[0].amount,
lossPercentage: ((chain[0].amount - chain[chain.length - 1].amount) / chain[0].amount * 100).toFixed(2) + '%',
riskLevel: 'high',
possibleLayering: true,
})),
};
}
6. 限额控制测试
6.1 单笔限额测试
测试用例:
| 场景 | 限额 | 测试金额 | 预期结果 |
|---|---|---|---|
| 未KYC用户提现 | 1 ETH/天 | 1.5 ETH | ❌ 拒绝 |
| L1 KYC用户提现 | 10 ETH/天 | 5 ETH | ✅ 通过 |
| L2 KYC用户提现 | 100 ETH/天 | 50 ETH | ✅ 通过 |
| VIP用户提现 | 无限制 | 500 ETH | ✅ 通过(需审核) |
实现代码:
// 限额配置
const WITHDRAWAL_LIMITS = {
'unverified': { daily: 1, monthly: 10 },
'kyc_l1': { daily: 10, monthly: 100 },
'kyc_l2': { daily: 100, monthly: 1000 },
'vip': { daily: Infinity, monthly: Infinity },
};
async function checkWithdrawalLimit(userId, amount) {
const user = await getUser(userId);
const limits = WITHDRAWAL_LIMITS[user.kycLevel];
// 查询今日已提现金额
const todayWithdrawals = await getTodayWithdrawals(userId);
const todayTotal = todayWithdrawals.reduce((sum, w) => sum + parseFloat(w.amount), 0);
// 查询本月已提现金额
const monthWithdrawals = await getMonthWithdrawals(userId);
const monthTotal = monthWithdrawals.reduce((sum, w) => sum + parseFloat(w.amount), 0);
// 检查限额
const afterToday = todayTotal + amount;
const afterMonth = monthTotal + amount;
if (afterToday > limits.daily) {
return {
allowed: false,
reason: '超过每日提现限额',
limit: limits.daily,
current: todayTotal,
requested: amount,
remaining: Math.max(0, limits.daily - todayTotal),
};
}
if (afterMonth > limits.monthly) {
return {
allowed: false,
reason: '超过每月提现限额',
limit: limits.monthly,
current: monthTotal,
requested: amount,
remaining: Math.max(0, limits.monthly - monthTotal),
};
}
return {
allowed: true,
remainingDaily: limits.daily - afterToday,
remainingMonthly: limits.monthly - afterMonth,
};
}
7. 设备指纹测试
7.1 设备指纹采集
采集项目:
const deviceFingerprint = {
// 基础信息
userAgent: navigator.userAgent,
platform: navigator.platform,
language: navigator.language,
// 屏幕信息
screenResolution: `${screen.width}x${screen.height}`,
colorDepth: screen.colorDepth,
pixelRatio: window.devicePixelRatio,
// 浏览器特征
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
timezoneOffset: new Date().getTimezoneOffset(),
cookieEnabled: navigator.cookieEnabled,
doNotTrack: navigator.doNotTrack,
// Canvas指纹
canvasFingerprint: getCanvasFingerprint(),
// WebGL指纹
webglVendor: getWebGLVendor(),
webglRenderer: getWebGLRenderer(),
// 音频指纹
audioFingerprint: getAudioFingerprint(),
// 字体检测
installedFonts: detectFonts(),
// 插件
plugins: Array.from(navigator.plugins).map(p => p.name),
// 硬件信息
hardwareConcurrency: navigator.hardwareConcurrency,
deviceMemory: navigator.deviceMemory,
// 网络信息
connection: {
effectiveType: navigator.connection?.effectiveType,
downlink: navigator.connection?.downlink,
},
};
// 生成指纹哈希
const fingerprintHash = sha256(JSON.stringify(deviceFingerprint));
7.2 设备指纹验证
测试场景:
- 正常登录:设备指纹匹配 → ✅ 通过
- 新设备登录:指纹不匹配 → ⚠️ 要求额外验证
- 虚拟机/模拟器:检测到异常 → 🔴 高风险
8. 实时风控规则
8.1 规则引擎
规则配置示例:
const riskRules = [
{
id: 'R001',
name: '大额单笔提现',
condition: (tx) => tx.type === 'withdrawal' && tx.amount > 50,
action: { requireManualReview: true, priority: 'high' },
},
{
id: 'R002',
name: '新用户快速提现',
condition: (tx, user) => user.accountAge < 7 && tx.type === 'withdrawal',
action: { delay: 24 * 3600, notifyRiskTeam: true },
},
{
id: 'R003',
name: '异地登录',
condition: (session, user) => !user.knownLocations.includes(session.location),
action: { requireReauth: true, send2FA: true },
},
{
id: 'R004',
name: '黑名单地址',
condition: (tx) => isBlacklisted(tx.destinationAddress),
action: { reject: true, freezeAccount: true, alertCompliance: true },
},
];
// 规则执行引擎
async function executeRiskRules(transaction, user) {
const triggeredRules = [];
const actions = [];
for (const rule of riskRules) {
try {
if (rule.condition(transaction, user)) {
triggeredRules.push(rule.id);
actions.push(rule.action);
}
} catch (error) {
console.error(`规则 ${rule.id} 执行失败:`, error);
}
}
// 合并所有动作
const finalAction = mergeActions(actions);
return {
triggeredRules,
finalAction,
riskScore: calculateRiskScore(triggeredRules),
};
}
function mergeActions(actions) {
const merged = {};
for (const action of actions) {
if (action.reject) merged.reject = true;
if (action.freezeAccount) merged.freezeAccount = true;
if (action.requireManualReview) merged.requireManualReview = true;
// ... 合并其他动作
}
return merged;
}
9. 压力测试
9.1 风控系统性能测试
测试目标:
- 并发处理能力: 10,000 TPS
- 响应时间: < 100ms (P99)
- 误报率: < 1%
- 漏报率: < 0.1%
测试脚本:
// 压力测试脚本
const { performance } = require('perf_hooks');
async function stressTestRiskSystem() {
console.log('\n=== 风控系统压力测试 ===\n');
const concurrency = 1000;
const totalRequests = 100000;
const batchSize = Math.floor(totalRequests / concurrency);
const startTime = performance.now();
const results = {
total: 0,
success: 0,
failed: 0,
responseTimes: [],
};
// 并发发送请求
const batches = [];
for (let i = 0; i < concurrency; i++) {
batches.push(runBatch(batchSize, results));
}
await Promise.all(batches);
const endTime = performance.now();
const duration = (endTime - startTime) / 1000;
// 统计结果
const sortedTimes = results.responseTimes.sort((a, b) => a - b);
const p50 = sortedTimes[Math.floor(sortedTimes.length * 0.5)];
const p95 = sortedTimes[Math.floor(sortedTimes.length * 0.95)];
const p99 = sortedTimes[Math.floor(sortedTimes.length * 0.99)];
console.log('📊 压力测试结果:');
console.log(` 总请求数: ${results.total}`);
console.log(` 成功: ${results.success}`);
console.log(` 失败: ${results.failed}`);
console.log(` 总耗时: ${duration.toFixed(2)}秒`);
console.log(` TPS: ${(results.total / duration).toFixed(0)}`);
console.log(` 响应时间 P50: ${p50.toFixed(2)}ms`);
console.log(` 响应时间 P95: ${p95.toFixed(2)}ms`);
console.log(` 响应时间 P99: ${p99.toFixed(2)}ms`);
}
async function runBatch(count, results) {
for (let i = 0; i < count; i++) {
const start = performance.now();
try {
// 模拟风控检查
await performRiskCheck({
type: 'withdrawal',
amount: Math.random() * 100,
userId: `user_${Math.floor(Math.random() * 10000)}`,
});
results.success++;
} catch (error) {
results.failed++;
}
const end = performance.now();
results.responseTimes.push(end - start);
results.total++;
}
}
async function performRiskCheck(transaction) {
// 模拟风控检查逻辑
await new Promise(resolve => setTimeout(resolve, Math.random() * 50));
return { riskScore: Math.random() * 100 };
}
10. 自动化测试脚本
10.1 完整测试套件
// full_risk_test_suite.js
const tests = require('./tests');
async function runFullRiskTestSuite() {
console.log('\n' + '='.repeat(80));
console.log('交易所风控系统 - 完整测试套件');
console.log('='.repeat(80) + '\n');
const results = [];
// 1. 入金测试
console.log('📥 [第1部分] 入金风控测试');
results.push(await tests.deposit.testNormalDeposit());
results.push(await tests.deposit.testSmallFrequentDeposit());
results.push(await tests.deposit.testLargeDeposit());
results.push(await tests.deposit.testBlacklistDeposit());
results.push(await tests.deposit.testContractDeposit());
// 2. 出金测试
console.log('\n📤 [第2部分] 出金风控测试');
results.push(await tests.withdrawal.testNormalWithdrawal());
results.push(await tests.withdrawal.testNewAddressLargeWithdrawal());
results.push(await tests.withdrawal.testHighFrequencyWithdrawal());
results.push(await tests.withdrawal.testAnomalousIPWithdrawal());
results.push(await tests.withdrawal.testMixerWithdrawal());
// 3. 交易检测
console.log('\n💱 [第3部分] 异常交易检测');
results.push(await tests.trading.testSelfTrading());
results.push(await tests.trading.testPriceManipulation());
results.push(await tests.trading.testFrontRunning());
// 4. 账户安全
console.log('\n🔐 [第4部分] 账户安全测试');
results.push(await tests.security.testBruteForce());
results.push(await tests.security.testSessionHijacking());
// 5. AML测试
console.log('\n🕵️ [第5部分] 反洗钱测试');
results.push(await tests.aml.testFundTracing());
results.push(await tests.aml.testLayeringDetection());
// 6. 限额测试
console.log('\n📊 [第6部分] 限额控制测试');
results.push(await tests.limits.testDailyLimit());
results.push(await tests.limits.testMonthlyLimit());
// 7. 设备指纹
console.log('\n📱 [第7部分] 设备指纹测试');
results.push(await tests.device.testFingerprintMatching());
results.push(await tests.device.testNewDeviceDetection());
// 8. 规则引擎
console.log('\n⚙️ [第8部分] 规则引擎测试');
results.push(await tests.rules.testRuleExecution());
// 9. 压力测试
console.log('\n⚡ [第9部分] 性能压力测试');
results.push(await tests.performance.testConcurrency());
// 生成报告
console.log('\n' + '='.repeat(80));
console.log('测试完成 - 生成报告');
console.log('='.repeat(80));
generateReport(results);
}
function generateReport(results) {
const passed = results.filter(r => r.status === 'pass').length;
const failed = results.filter(r => r.status === 'fail').length;
const warnings = results.filter(r => r.status === 'warning').length;
console.log('\n📊 测试结果统计:');
console.log(` 总测试数: ${results.length}`);
console.log(` ✅ 通过: ${passed}`);
console.log(` ❌ 失败: ${failed}`);
console.log(` ⚠️ 警告: ${warnings}`);
console.log(` 成功率: ${(passed / results.length * 100).toFixed(2)}%`);
// 高风险发现
const highRisks = results.filter(r => r.riskLevel === 'high' || r.riskLevel === 'critical');
if (highRisks.length > 0) {
console.log('\n🚨 高风险发现:');
highRisks.forEach(r => {
console.log(` - ${r.testName}: ${r.issue}`);
});
}
// 导出报告
const report = {
timestamp: new Date().toISOString(),
summary: { total: results.length, passed, failed, warnings },
details: results,
recommendations: generateRecommendations(results),
};
require('fs').writeFileSync(
`risk_test_report_${Date.now()}.json`,
JSON.stringify(report, null, 2)
);
console.log('\n✅ 报告已生成');
}
function generateRecommendations(results) {
const recommendations = [];
// 根据测试结果生成建议
const failedTests = results.filter(r => r.status === 'fail');
if (failedTests.some(t => t.category === 'blacklist')) {
recommendations.push({
priority: 'critical',
category: 'compliance',
issue: '黑名单检测失败',
recommendation: '立即更新黑名单数据库,加强与Chainalysis等服务的集成',
});
}
if (failedTests.some(t => t.category === 'withdrawal')) {
recommendations.push({
priority: 'high',
category: 'withdrawal',
issue: '出金风控存在漏洞',
recommendation: '加强出金审核流程,增加人工审核环节',
});
}
// ... 更多建议
return recommendations;
}
// 执行测试
if (require.main === module) {
runFullRiskTestSuite()
.then(() => {
console.log('\n✅ 所有测试完成');
process.exit(0);
})
.catch(error => {
console.error('\n❌ 测试失败:', error);
process.exit(1);
});
}
module.exports = { runFullRiskTestSuite };
总结
风控测试核心要点
-
入金风控
- ✅ 黑名单地址检测
- ✅ 小额高频检测
- ✅ 大额入金审核
- ✅ 合约转账识别
- ✅ 重放攻击防护
-
出金风控
- ✅ 新地址验证
- ✅ 大额人工审核
- ✅ 高频提现拦截
- ✅ 异常IP/设备检测
- ✅ 混币器地址拦截
-
交易监控
- ✅ 自交易检测
- ✅ 价格操纵识别
- ✅ 抢先交易检测
-
账户安全
- ✅ 暴力破解防护
- ✅ 会话劫持检测
- ✅ 设备指纹验证
-
反洗钱
- ✅ 资金链追踪
- ✅ 分层交易识别
- ✅ 异常模式分析
-
性能指标
- ✅ 处理能力: 10,000+ TPS
- ✅ 响应时间: < 100ms
- ✅ 误报率: < 1%
- ✅ 可用性: 99.9%
测试环境要求:
- 测试网络: Sepolia/Goerli
- 主网测试: 小额自转账
- 监控工具: Prometheus + Grafana
- 日志系统: ELK Stack
持续优化:
- 定期更新黑名单
- 调整风控阈值
- 机器学习优化
- A/B测试新规则
最后更新: 2026-03-14
维护团队: Risk & Compliance Team
评论区