❗ 错误处理和数据验证 - 打造坚不可摧的程序!🛡️
🎯 本章目标: 学会像黑客一样思考,像守护者一样防御,让你的程序固若金汤!
🌟 开篇:从漏洞猎人到安全专家!
欢迎来到Solana安全训练营!🏋️ 今天我们要学习如何让程序变得像诺克斯堡金库一样安全。记住:在区块链上,一个小漏洞可能导致数百万美元的损失! 💸
🎮 今日冒险地图
🏁 起点:不安全的程序(满是漏洞)
↓
🎯 自定义错误 → 🔐 安全检查 → 🛡️ 数据验证
↓ ↓ ↓
优雅报错 身份验证 防止攻击
↓
💎 终点:固若金汤的程序!
💡 黑客思维: 编写代码时,始终问自己:"如果我是黑客,我会怎么攻击这个程序?" 🤔
😡 自定义错误 - 让错误信息说人话!
🎯 为什么需要自定义错误?
// ❌ 糟糕的错误处理(用户一脸懵)
return Err(ProgramError::Custom(1)); // 1是什么鬼?😵
// ✅ 优雅的错误处理(清晰明了)
return Err(NoteError::TitleTooLong.into()); // 哦,标题太长了!😊
📝 创建自定义错误枚举
// 🎨 导入必要的工具
use solana_program::{program_error::ProgramError};
use thiserror::Error; // 错误处理的瑞士军刀!
// 🎯 定义自定义错误类型
#[derive(Error, Debug, Clone)] // 派生必要的trait
pub enum NoteError {
// 🚫 权限错误
#[error("❌ 错误的笔记所有者!你不能修改别人的笔记")]
Forbidden,
// 📏 长度错误
#[error("❌ 文本太长了!最多只能输入{0}个字符")]
InvalidLength(usize),
// 🔍 未找到错误
#[error("❌ 找不到指定的笔记")]
NoteNotFound,
// 💰 余额错误
#[error("❌ 余额不足!需要{required},但只有{available}")]
InsufficientFunds { required: u64, available: u64 },
// 🎯 更多错误类型...
#[error("❌ 笔记已经存在,不能重复创建")]
NoteAlreadyExists,
#[error("❌ 无效的PDA种子")]
InvalidSeeds,
}
🔄 转换错误类型
// 🎯 实现从自定义错误到ProgramError的转换
impl From<NoteError> for ProgramError {
fn from(e: NoteError) -> Self {
// 🎨 将自定义错误转换为程序错误
// Custom()接受u32类型的错误码
ProgramError::Custom(e as u32)
}
}
// 💡 这样就能优雅地使用错误了!
pub fn process_instruction(/*...*/) -> ProgramResult {
// 🔍 检查所有权
if pda != *note_pda.key {
msg!("🚨 安全警告:PDA不匹配!");
msg!(" 预期: {}", pda);
msg!(" 实际: {}", note_pda.key);
return Err(NoteError::Forbidden.into()); // 👈 使用.into()转换
}
// ✅ 继续处理...
Ok(())
}
💡 高级错误处理技巧
// 🎯 带上下文信息的错误
pub enum GameError {
#[error("角色属性超出上限!{attribute}不能超过{max}(当前:{current})")]
AttributeTooHigh {
attribute: String,
max: u8,
current: u8,
},
}
// 🎨 使用示例
if character.strength > 100 {
return Err(GameError::AttributeTooHigh {
attribute: "力量".to_string(),
max: 100,
current: character.strength,
}.into());
}
// 📊 错误码映射(方便前端处理)
impl NoteError {
pub fn error_code(&self) -> u32 {
match self {
NoteError::Forbidden => 1001,
NoteError::InvalidLength(_) => 1002,
NoteError::NoteNotFound => 1003,
// ...
}
}
}
🔓 基本安全准则 - 你的程序防护盾!
🎯 安全检查清单
✅ 所有权检查 - 这是你的账户吗?
✅ 签名者检查 - 你签名了吗?
✅ 账户验证 - 这是正确的账户吗?
✅ 数据验证 - 输入合法吗?
✅ 算术检查 - 数字会溢出吗?
1️⃣ 所有权检查 - 验证账户归属 🏠
// 🎯 所有权检查:确保账户属于我们的程序
pub fn verify_ownership(
account: &AccountInfo,
program_id: &Pubkey,
) -> ProgramResult {
// 🔍 检查账户所有者
if account.owner != program_id {
msg!("🚨 安全警报:账户所有者不匹配!");
msg!(" 预期所有者: {}", program_id);
msg!(" 实际所有者: {}", account.owner);
msg!(" 账户地址: {}", account.key);
// 💡 可能的攻击场景:
// 攻击者可能发送了一个结构相似但属于其他程序的账户
// 试图让我们的程序处理不属于它的数据!
return Err(ProgramError::IllegalOwner);
}
msg!("✅ 所有权验证通过");
Ok(())
}
// 🎨 使用示例
pub fn update_note(
program_id: &Pubkey,
accounts: &[AccountInfo],
// ...
) -> ProgramResult {
let note_account = &accounts[0];
// 🛡️ 第一道防线:验证所有权
verify_ownership(note_account, program_id)?;
// ✅ 继续处理...
}
2️⃣ 签名者检查 - 验证身份 ✍️
// 🎯 签名者检查:确保用户已签名
pub fn verify_signer(account: &AccountInfo) -> ProgramResult {
// 🔐 检查是否签名
if !account.is_signer {
msg!("🚨 安全警报:缺少必要的签名!");
msg!(" 账户 {} 必须签名此交易", account.key);
msg!(" 这可能是未经授权的操作尝试!");
// 💡 攻击场景:
// 攻击者可能试图在未经授权的情况下
// 修改其他用户的数据或执行敏感操作
return Err(ProgramError::MissingRequiredSignature);
}
msg!("✅ 签名验证通过: {}", account.key);
Ok(())
}
// 🎨 批量验证签名者
pub fn verify_signers(accounts: &[AccountInfo], signer_indices: &[usize]) -> ProgramResult {
for &index in signer_indices {
if index >= accounts.len() {
return Err(ProgramError::NotEnoughAccountKeys);
}
verify_signer(&accounts[index])?;
}
Ok(())
}
3️⃣ PDA验证 - 确保地址正确 🎯
// 🎯 PDA验证:验证程序派生地址
pub fn verify_pda(
expected_pda: &Pubkey,
actual_account: &AccountInfo,
seeds: &[&[u8]],
program_id: &Pubkey,
) -> ProgramResult {
// 🔍 重新计算PDA
let (pda, bump_seed) = Pubkey::find_program_address(seeds, program_id);
// 🔐 验证PDA匹配
if pda != *actual_account.key {
msg!("🚨 PDA验证失败!");
msg!(" 预期PDA: {}", pda);
msg!(" 实际账户: {}", actual_account.key);
msg!(" 种子: {:?}", seeds);
// 💡 攻击场景:
// 攻击者可能发送了错误的账户
// 试图访问或修改不属于他们的数据
return Err(NoteError::InvalidSeeds.into());
}
msg!("✅ PDA验证通过");
msg!(" PDA: {}", pda);
msg!(" Bump: {}", bump_seed);
Ok(())
}
// 🎨 完整的PDA创建和验证示例
pub fn create_note_pda(
program_id: &Pubkey,
user: &Pubkey,
title: &str,
) -> (Pubkey, u8) {
// 🌱 构建种子
let seeds = &[
b"note", // 固定前缀
user.as_ref(), // 用户公钥
title.as_bytes(), // 笔记标题
];
// 🎯 生成PDA
Pubkey::find_program_address(seeds, program_id)
}
4️⃣ 数据验证 - 防止恶意输入 🛡️
// 🎯 综合数据验证器
pub struct DataValidator;
impl DataValidator {
// 📏 验证字符串长度
pub fn validate_string(
input: &str,
min_length: usize,
max_length: usize,
field_name: &str,
) -> ProgramResult {
let length = input.len();
if length < min_length {
msg!("❌ {} 太短!最少需要{}个字符", field_name, min_length);
return Err(NoteError::InvalidLength(min_length).into());
}
if length > max_length {
msg!("❌ {} 太长!最多只能{}个字符", field_name, max_length);
return Err(NoteError::InvalidLength(max_length).into());
}
// 🔍 检查特殊字符(防止注入攻击)
if input.contains('\0') {
msg!("❌ {} 包含非法字符!", field_name);
return Err(ProgramError::InvalidInstructionData);
}
msg!("✅ {} 验证通过", field_name);
Ok(())
}
// 🔢 验证数值范围
pub fn validate_range(value: u64, min: u64, max: u64) -> ProgramResult {
if value < min || value > max {
msg!("❌ 数值超出范围!");
msg!(" 值: {}", value);
msg!(" 允许范围: {} - {}", min, max);
return Err(ProgramError::InvalidArgument);
}
Ok(())
}
// 💰 验证金额
pub fn validate_amount(
amount: u64,
min_amount: u64,
user_balance: u64,
) -> ProgramResult {
// 最小金额检查
if amount < min_amount {
msg!("❌ 金额太小!最少需要{}", min_amount);
return Err(ProgramError::InsufficientFunds);
}
// 余额检查
if amount > user_balance {
msg!("❌ 余额不足!");
msg!(" 需要: {}", amount);
msg!(" 余额: {}", user_balance);
return Err(ProgramError::InsufficientFunds);
}
Ok(())
}
}
5️⃣ 整数溢出保护 - 防止甘地变核弹! ☢️
// 🎮 历史趣事:文明游戏中的"核弹甘地"Bug
// 甘地的攻击性是1,当玩家采用民主制度后会-2
// 但因为使用u8类型,1-2会溢出变成255(最大攻击性)!
// 结果和平主义者甘地变成了核战狂魔!😱
// ❌ 危险的代码(可能溢出)
pub fn dangerous_math() {
let gandhi_aggression: u8 = 1;
let democracy_bonus: u8 = 2;
// 💥 这会溢出!1 - 2 = 255(在u8中)
// let result = gandhi_aggression - democracy_bonus;
}
// ✅ 安全的代码(使用checked运算)
pub fn safe_math() -> ProgramResult {
let gandhi_aggression: u8 = 1;
let democracy_bonus: u8 = 2;
// 🛡️ 使用checked_sub防止溢出
match gandhi_aggression.checked_sub(democracy_bonus) {
Some(result) => {
msg!("✅ 安全计算结果: {}", result);
Ok(())
},
None => {
msg!("❌ 检测到整数下溢!");
msg!(" {} - {} 会产生负数", gandhi_aggression, democracy_bonus);
Err(ProgramError::ArithmeticOverflow)
}
}
}
// 🎯 安全数学运算工具箱
pub struct SafeMath;
impl SafeMath {
// ➕ 安全加法
pub fn add(a: u64, b: u64) -> Result<u64, ProgramError> {
a.checked_add(b)
.ok_or_else(|| {
msg!("❌ 加法溢出: {} + {}", a, b);
ProgramError::ArithmeticOverflow
})
}
// ➖ 安全减法
pub fn sub(a: u64, b: u64) -> Result<u64, ProgramError> {
a.checked_sub(b)
.ok_or_else(|| {
msg!("❌ 减法下溢: {} - {}", a, b);
ProgramError::ArithmeticOverflow
})
}
// ✖️ 安全乘法
pub fn mul(a: u64, b: u64) -> Result<u64, ProgramError> {
a.checked_mul(b)
.ok_or_else(|| {
msg!("❌ 乘法溢出: {} * {}", a, b);
ProgramError::ArithmeticOverflow
})
}
// ➗ 安全除法
pub fn div(a: u64, b: u64) -> Result<u64, ProgramError> {
if b == 0 {
msg!("❌ 除零错误!");
return Err(ProgramError::ArithmeticOverflow);
}
Ok(a / b)
}
// 💯 百分比计算(避免精度损失)
pub fn percentage(value: u64, percent: u64) -> Result<u64, ProgramError> {
// 先乘后除,保持精度
let temp = Self::mul(value, percent)?;
Self::div(temp, 100)
}
}
🎮 实战案例:游戏角色系统
// 🎯 完整的安全验证示例
pub struct GameCharacter {
pub owner: Pubkey,
pub level: u8,
pub experience: u64,
pub health: u16,
pub strength: u8,
pub agility: u8,
pub intelligence: u8,
}
impl GameCharacter {
// 🛡️ 安全的属性升级
pub fn upgrade_attribute(
&mut self,
attribute: &str,
points: u8,
max_total: u16,
) -> ProgramResult {
// 1️⃣ 验证属性点数
if points == 0 || points > 10 {
msg!("❌ 无效的属性点数: {}", points);
return Err(ProgramError::InvalidArgument);
}
// 2️⃣ 计算当前总属性点
let current_total = SafeMath::add(
self.strength as u64,
SafeMath::add(
self.agility as u64,
self.intelligence as u64,
)?,
)?;
// 3️⃣ 检查是否超过上限
let new_total = SafeMath::add(current_total, points as u64)?;
if new_total > max_total as u64 {
msg!("❌ 属性点总和超过上限!");
msg!(" 当前: {}", current_total);
msg!(" 尝试添加: {}", points);
msg!(" 上限: {}", max_total);
return Err(GameError::AttributeLimitExceeded.into());
}
// 4️⃣ 安全更新属性
match attribute {
"strength" => {
self.strength = self.strength
.checked_add(points)
.ok_or(ProgramError::ArithmeticOverflow)?;
},
"agility" => {
self.agility = self.agility
.checked_add(points)
.ok_or(ProgramError::ArithmeticOverflow)?;
},
"intelligence" => {
self.intelligence = self.intelligence
.checked_add(points)
.ok_or(ProgramError::ArithmeticOverflow)?;
},
_ => {
msg!("❌ 未知的属性: {}", attribute);
return Err(ProgramError::InvalidArgument);
}
}
msg!("✅ 属性升级成功!");
msg!(" {} +{}", attribute, points);
Ok(())
}
}
💡 专业安全技巧
🚀 安全最佳实践
// 🎯 创建安全检查器trait
trait SecurityCheck {
fn verify_all(&self) -> ProgramResult;
}
// 🛡️ 实现完整的安全检查
impl SecurityCheck for TransactionContext {
fn verify_all(&self) -> ProgramResult {
// 按重要性顺序检查
self.verify_program_id()?; // 1. 程序ID
self.verify_signers()?; // 2. 签名者
self.verify_accounts()?; // 3. 账户
self.verify_data()?; // 4. 数据
self.verify_amounts()?; // 5. 金额
Ok(())
}
}
// 🎨 使用宏简化验证
macro_rules! require {
($condition:expr, $error:expr) => {
if !$condition {
return Err($error.into());
}
};
}
// 使用示例
require!(amount > 0, NoteError::InvalidAmount);
require!(user.is_signer, NoteError::Unauthorized);
🐛 调试和测试
// 🧪 单元测试
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_overflow_protection() {
let result = SafeMath::add(u64::MAX, 1);
assert!(result.is_err());
}
#[test]
fn test_string_validation() {
let result = DataValidator::validate_string(
"Hello",
1,
10,
"test"
);
assert!(result.is_ok());
}
}
📊 安全审计清单
✅ 所有用户输入都经过验证
✅ 所有算术运算都使用checked方法
✅ 所有账户都验证了所有权
✅ 所有敏感操作都要求签名
✅ 所有PDA都验证了种子
✅ 所有错误都有清晰的消息
✅ 所有边界条件都有测试
🏆 漏洞赏金提示
💰 常见漏洞类型和赏金
漏洞类型 | 严重程度 | 典型赏金 |
---|---|---|
整数溢出 | 🔴 高 | $10,000 - $50,000 |
权限绕过 | 🔴 高 | $20,000 - $100,000 |
重入攻击 | 🔴 高 | $50,000 - $250,000 |
数据验证缺失 | 🟡 中 | $5,000 - $20,000 |
逻辑错误 | 🟡 中 | $2,000 - $10,000 |
💡 提示: 很多价值数百万美元的漏洞都是因为忽略了基本的安全检查!
🎊 总结
✅ 你学会了
- 🎯 创建清晰的自定义错误
- 🔐 实施全面的安全检查
- 🛡️ 验证所有用户输入
- ☢️ 防止整数溢出
- 🏆 像黑客一样思考
🚀 下一步
- 学习更高级的安全模式
- 研究真实的漏洞案例
- 参与安全审计
- 寻找漏洞赏金!
🌟 金句: "在区块链世界,paranoid(偏执)不是病,是美德!每一行代码都要假设有人想要攻击它。" 🛡️
#SolanaSecurity #RustSafety #Web3Defense 🔒✨🚀