From 58d4635c8e7098e489faf55446510d58d4a8c17c Mon Sep 17 00:00:00 2001 From: lzx <393768033@qq.com> Date: Mon, 30 Jun 2025 13:51:05 +0800 Subject: [PATCH] update caculate output_smt_size --- OUTPUT_SMT_SIZE_FIX_SUMMARY.md | 219 +++++++++++++++++++++++++++++++++ src/main.rs | 83 ++++++++++++- test_output_smt_debug.rs | 145 ++++++++++++++++++++++ 3 files changed, 446 insertions(+), 1 deletion(-) create mode 100644 OUTPUT_SMT_SIZE_FIX_SUMMARY.md create mode 100644 test_output_smt_debug.rs diff --git a/OUTPUT_SMT_SIZE_FIX_SUMMARY.md b/OUTPUT_SMT_SIZE_FIX_SUMMARY.md new file mode 100644 index 0000000..b768dc5 --- /dev/null +++ b/OUTPUT_SMT_SIZE_FIX_SUMMARY.md @@ -0,0 +1,219 @@ +# GBT项目Output SMT Size计算修正总结 + +## 问题描述 + +GBT项目计算的`output_smt_size`与区块链上的实际值不一致: + +- **区块链数据**: `output_smt_size: 809326` +- **GBT计算值**: `output_smt_size: 809271` +- **差异**: `809326 - 809271 = 55` + +## 根本原因分析 + +### 1. 原有计算方法的缺陷 + +GBT项目原本使用简化的计算方法: +```rust +// 错误的计算方法 +let current_output_smt_size = prev_output_smt_size + outputs.len() - inputs.len(); +``` + +这种方法没有考虑: +- **销毁输出**: 销毁输出不应该增加SMT叶子数量 +- **销毁输入**: 销毁输入不应该减少SMT叶子数量 +- **SMT变化统计**: 应该使用`new_leaves`和`stale_leaves`概念 + +### 2. 与Tari核心代码的差异 + +Tari核心代码的正确计算逻辑: +```rust +// 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`方法: + +```rust +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::(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::(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核心代码确认: +```rust +pub enum OutputType { + Standard = 0, + Coinbase = 1, + Burn = 2, // 销毁输出类型 + ValidatorNodeRegistration = 3, + CodeTemplateRegistration = 4, +} +``` + +## 测试验证 + +### 1. 创建调试脚本 + +创建了`test_output_smt_debug.rs`脚本来验证计算逻辑: +- 模拟GBT项目的计算过程 +- 提供详细的调试输出 +- 支持实际区块数据的测试 + +### 2. 测试用例 + +```rust +// 测试用例示例 +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`方法中使用了新的计算方法: + +```rust +// 计算当前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核心代码保持一致,能够正确处理销毁输出和销毁输入,确保计算结果的准确性。修正后的代码提供了详细的调试信息,便于问题排查和验证。 + +--- + +**修正状态**: ✅ 已完成 +**测试状态**: 🔄 需要实际数据验证 +**文档状态**: ✅ 完整 \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index cde3cdf..09a323a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -133,6 +133,87 @@ pub struct SubmitResult { pub result: u8, // 1表示成功,0表示失败 } +// === 新增: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::(output_json) { + Ok(output) => { + // 尝试多种可能的JSON结构 + 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; + debug!(target: LOG_TARGET, "Output {}: not burned, adding leaf", i); + } else { + debug!(target: LOG_TARGET, "Output {}: burned, skipping", i); + } + }, + Err(e) => { + warn!(target: LOG_TARGET, "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::(input_json) { + Ok(input) => { + // 尝试多种可能的JSON结构 + 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; + debug!(target: LOG_TARGET, "Input {}: not burned, removing leaf", i); + } else { + debug!(target: LOG_TARGET, "Input {}: burned, skipping", i); + } + }, + Err(e) => { + warn!(target: LOG_TARGET, "Failed to parse input {} JSON: {}, assuming not burned", i, e); + stale_leaves += 1; // 保守策略:假设不是销毁输入 + } + } + } + + size += new_leaves; + size = size.saturating_sub(stale_leaves); + + info!(target: LOG_TARGET, "output_smt_size calculation: {} (prev) + {} (new_leaves) - {} (stale_leaves) = {} (current)", + prev_output_smt_size, new_leaves, stale_leaves, size); + + size +} + // 配置结构 #[derive(Debug, Clone)] pub struct GbtConfig { @@ -468,7 +549,7 @@ impl GbtClient { let prev_output_smt_size = self.get_prev_output_smt_size(height).await?; // 计算当前output_smt_size - let current_output_smt_size = prev_output_smt_size + block_body.outputs.len() as u64 - block_body.inputs.len() as u64; + let current_output_smt_size = calculate_output_smt_size(prev_output_smt_size, &block_body.outputs, &block_body.inputs); // 生成任务ID let job_id = Self::generate_job_id(); diff --git a/test_output_smt_debug.rs b/test_output_smt_debug.rs new file mode 100644 index 0000000..3ca0fcd --- /dev/null +++ b/test_output_smt_debug.rs @@ -0,0 +1,145 @@ +// 调试脚本:测试GBT项目的output_smt_size计算 +// 使用实际的区块数据来验证计算是否正确 + +use serde_json::Value; + +// 模拟GBT项目中的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; + + println!("=== 计算新叶子数量(排除销毁输出) ==="); + // 计算新叶子数量(排除销毁输出) + for (i, output_json) in outputs.iter().enumerate() { + match serde_json::from_str::(output_json) { + Ok(output) => { + // 尝试多种可能的JSON结构 + let is_burned = if let Some(features) = output.get("features") { + if let Some(output_type) = features.get("output_type") { + let burned = output_type.as_u64() == Some(2); // Burn = 2 + println!("Output {}: features.output_type = {:?}, burned = {}", i, output_type, burned); + burned + } else { + println!("Output {}: features.output_type not found, assuming not burned", i); + false + } + } else if let Some(output_type) = output.get("output_type") { + let burned = output_type.as_u64() == Some(2); // Burn = 2 + println!("Output {}: output_type = {:?}, burned = {}", i, output_type, burned); + burned + } else { + println!("Output {}: no output_type found, assuming not burned", i); + false + }; + + if !is_burned { + new_leaves += 1; + println!("Output {}: not burned, adding leaf (new_leaves = {})", i, new_leaves); + } else { + println!("Output {}: burned, skipping", i); + } + }, + Err(e) => { + println!("Failed to parse output {} JSON: {}, assuming not burned", i, e); + new_leaves += 1; // 保守策略:假设不是销毁输出 + } + } + } + + println!("\n=== 计算移除叶子数量(排除销毁输入) ==="); + // 计算移除叶子数量(排除销毁输入) + for (i, input_json) in inputs.iter().enumerate() { + match serde_json::from_str::(input_json) { + Ok(input) => { + // 尝试多种可能的JSON结构 + let is_burned = if let Some(features) = input.get("features") { + if let Some(output_type) = features.get("output_type") { + let burned = output_type.as_u64() == Some(2); // Burn = 2 + println!("Input {}: features.output_type = {:?}, burned = {}", i, output_type, burned); + burned + } else { + println!("Input {}: features.output_type not found, assuming not burned", i); + false + } + } else if let Some(output_type) = input.get("output_type") { + let burned = output_type.as_u64() == Some(2); // Burn = 2 + println!("Input {}: output_type = {:?}, burned = {}", i, output_type, burned); + burned + } else { + println!("Input {}: no output_type found, assuming not burned", i); + false + }; + + if !is_burned { + stale_leaves += 1; + println!("Input {}: not burned, removing leaf (stale_leaves = {})", i, stale_leaves); + } else { + println!("Input {}: burned, skipping", i); + } + }, + Err(e) => { + println!("Failed to parse input {} JSON: {}, assuming not burned", i, e); + stale_leaves += 1; // 保守策略:假设不是销毁输入 + } + } + } + + size += new_leaves; + size = size.saturating_sub(stale_leaves); + + println!("\n=== 计算结果 ==="); + println!("output_smt_size calculation: {} (prev) + {} (new_leaves) - {} (stale_leaves) = {} (current)", + prev_output_smt_size, new_leaves, stale_leaves, size); + + size +} + +fn main() { + println!("GBT项目 output_smt_size 计算调试"); + println!("================================\n"); + + // 使用你提供的实际数据 + let prev_output_smt_size = 809326; // 区块链上的实际值 + + // 这里需要实际的outputs和inputs JSON数据 + // 由于你没有提供具体的outputs和inputs数据,我们创建一个示例 + + println!("测试用例1: 基本计算"); + println!("prev_output_smt_size = {}", prev_output_smt_size); + + // 示例数据(你需要用实际的数据替换) + let outputs = vec![ + r#"{"features": {"output_type": 0}}"#.to_string(), // Standard output + r#"{"features": {"output_type": 1}}"#.to_string(), // Coinbase output + r#"{"features": {"output_type": 2}}"#.to_string(), // Burned output + ]; + + let inputs = vec![ + r#"{"features": {"output_type": 0}}"#.to_string(), // Standard input + r#"{"features": {"output_type": 2}}"#.to_string(), // Burned input + ]; + + let calculated_size = calculate_output_smt_size(prev_output_smt_size, &outputs, &inputs); + + println!("\n=== 最终结果 ==="); + println!("期望值: 809326"); + println!("计算值: {}", calculated_size); + println!("差异: {}", 809326i64 - calculated_size as i64); + + if calculated_size == 809326 { + println!("✅ 计算正确!"); + } else { + println!("❌ 计算错误!需要调试JSON数据结构"); + } + + println!("\n=== 调试建议 ==="); + println!("1. 检查实际的outputs和inputs JSON结构"); + println!("2. 确认output_type字段的位置和值"); + println!("3. 验证销毁输出的识别逻辑"); + println!("4. 检查是否有其他影响SMT大小的因素"); +} \ No newline at end of file