tari-rust/OUTPUT_SMT_SIZE_FIX_SUMMARY.md

6.8 KiB
Raw Blame History

GBT项目Output SMT Size计算修正总结

问题描述

GBT项目计算的output_smt_size与区块链上的实际值不一致:

  • 区块链数据: output_smt_size: 809326
  • GBT计算值: output_smt_size: 809271
  • 差异: 809326 - 809271 = 55

根本原因分析

1. 原有计算方法的缺陷

GBT项目原本使用简化的计算方法

// 错误的计算方法
let current_output_smt_size = prev_output_smt_size + outputs.len() - inputs.len();

这种方法没有考虑:

  • 销毁输出: 销毁输出不应该增加SMT叶子数量
  • 销毁输入: 销毁输入不应该减少SMT叶子数量
  • SMT变化统计: 应该使用new_leavesstale_leaves概念

2. 与Tari核心代码的差异

Tari核心代码的正确计算逻辑

// Tari核心代码正确
let mut size = tip_header.output_smt_size;
size += changes.node_stats.first().map(|s| s.new_leaves).unwrap_or(0) as u64;
size = size.saturating_sub(changes.node_stats.first().map(|s| s.stale_leaves).unwrap_or(0) as u64);

修正方案

1. 实现正确的计算方法

在GBT项目中实现了与Tari核心逻辑一致的calculate_output_smt_size方法:

fn calculate_output_smt_size(
    prev_output_smt_size: u64,
    outputs: &[String],  // JSON字符串格式
    inputs: &[String],   // JSON字符串格式
) -> u64 {
    let mut size = prev_output_smt_size;
    let mut new_leaves = 0u64;
    let mut stale_leaves = 0u64;
    
    // 计算新叶子数量(排除销毁输出)
    for (i, output_json) in outputs.iter().enumerate() {
        match serde_json::from_str::<serde_json::Value>(output_json) {
            Ok(output) => {
                // 检查是否为销毁输出
                let is_burned = if let Some(features) = output.get("features") {
                    if let Some(output_type) = features.get("output_type") {
                        output_type.as_u64() == Some(2) // Burn = 2
                    } else {
                        false
                    }
                } else if let Some(output_type) = output.get("output_type") {
                    output_type.as_u64() == Some(2) // Burn = 2
                } else {
                    false
                };
                
                if !is_burned {
                    new_leaves += 1;
                }
            },
            Err(e) => {
                warn!("Failed to parse output {} JSON: {}, assuming not burned", i, e);
                new_leaves += 1; // 保守策略
            }
        }
    }
    
    // 计算移除叶子数量(排除销毁输入)
    for (i, input_json) in inputs.iter().enumerate() {
        match serde_json::from_str::<serde_json::Value>(input_json) {
            Ok(input) => {
                // 检查是否为销毁输入
                let is_burned = if let Some(features) = input.get("features") {
                    if let Some(output_type) = features.get("output_type") {
                        output_type.as_u64() == Some(2) // Burn = 2
                    } else {
                        false
                    }
                } else if let Some(output_type) = input.get("output_type") {
                    output_type.as_u64() == Some(2) // Burn = 2
                } else {
                    false
                };
                
                if !is_burned {
                    stale_leaves += 1;
                }
            },
            Err(e) => {
                warn!("Failed to parse input {} JSON: {}, assuming not burned", i, e);
                stale_leaves += 1; // 保守策略
            }
        }
    }
    
    size += new_leaves;
    size = size.saturating_sub(stale_leaves);
    
    info!("output_smt_size calculation: {} (prev) + {} (new_leaves) - {} (stale_leaves) = {} (current)", 
          prev_output_smt_size, new_leaves, stale_leaves, size);
    
    size
}

2. 关键改进点

  1. 销毁输出识别: 正确识别output_type = 2的销毁输出
  2. 销毁输入识别: 正确识别销毁输入不减少SMT叶子数量
  3. JSON解析容错: 支持多种JSON结构提供错误处理
  4. 详细日志: 添加调试信息,便于问题排查
  5. 饱和减法: 使用saturating_sub防止下溢

3. 销毁输出类型确认

通过查看Tari核心代码确认

pub enum OutputType {
    Standard = 0,
    Coinbase = 1,
    Burn = 2,  // 销毁输出类型
    ValidatorNodeRegistration = 3,
    CodeTemplateRegistration = 4,
}

测试验证

1. 创建调试脚本

创建了test_output_smt_debug.rs脚本来验证计算逻辑:

  • 模拟GBT项目的计算过程
  • 提供详细的调试输出
  • 支持实际区块数据的测试

2. 测试用例

// 测试用例示例
let outputs = vec![
    r#"{"features": {"output_type": 0}}"#.to_string(), // Standard
    r#"{"features": {"output_type": 1}}"#.to_string(), // Coinbase
    r#"{"features": {"output_type": 2}}"#.to_string(), // Burned
];

let inputs = vec![
    r#"{"features": {"output_type": 0}}"#.to_string(), // Standard
    r#"{"features": {"output_type": 2}}"#.to_string(), // Burned
];

使用方法

1. 在GBT项目中使用

GBT项目已经在get_block_template_and_coinbase方法中使用了新的计算方法:

// 计算当前output_smt_size
let current_output_smt_size = calculate_output_smt_size(prev_output_smt_size, &block_body.outputs, &block_body.inputs);

2. 调试和验证

  1. 启用调试日志: 设置日志级别为DEBUG以查看详细计算过程
  2. 运行调试脚本: 使用test_output_smt_debug.rs验证计算逻辑
  3. 对比实际数据: 将计算结果与区块链上的实际值对比

预期效果

修正后的GBT项目应该能够

  1. 正确计算output_smt_size: 与区块链上的实际值一致
  2. 处理销毁输出: 正确识别和排除销毁输出
  3. 处理销毁输入: 正确识别和排除销毁输入
  4. 提供调试信息: 详细的日志输出便于问题排查
  5. 容错处理: 对JSON解析错误进行合理处理

后续改进建议

1. 数据验证

  • 获取实际的outputs和inputs JSON数据
  • 验证JSON结构与预期是否一致
  • 确认销毁输出的识别逻辑

2. 性能优化

  • 考虑批量JSON解析优化
  • 添加缓存机制减少重复计算
  • 优化日志输出性能

3. 监控和告警

  • 添加计算结果监控
  • 设置差异阈值告警
  • 记录计算性能指标

总结

通过本次修正GBT项目的output_smt_size计算方法现在与Tari核心代码保持一致能够正确处理销毁输出和销毁输入确保计算结果的准确性。修正后的代码提供了详细的调试信息便于问题排查和验证。


修正状态: 已完成 测试状态: 🔄 需要实际数据验证 文档状态: 完整