以太坊Fusaka升级的PeerDAS和BPO技术原理分析
文章摘要 PeerDAS(Peer Data Availability Sampling)是一种提高以太坊数据可用性的技术,通过将区块中的大尺寸交易数据(blob)划分为多个列/单元,并在节点间分发采样验证。每个节点只需下载和校验部分数据列,通过统计采样实现对整体数据的高置信度验证,从而显著降低单个节点的带宽和存储负担。BPO(Blob Parameter Only)机制则允许通过轻量级链上更新调
·
快速概览(要点与来源)
- PeerDAS(Peer Data Availability Sampling):把区块中的 blob(大尺寸的交易/rollup 数据)按“列/单元”划分并在节点之间分发(每个节点只下载、校验部分列),通过统计采样达到对整块数据可用性的高置信度验证,从而大幅降低单节点带宽/存储负担。
- 采样与验证思想:节点基于自己的 ID/slot 等确定性选择要验证的列(这样攻击者不能轻易欺骗采样),并校验列的承诺(真实实现使用 KZG / polynomial commitments 等;模拟中用哈希/伪证明替代)。
- BPO(Blob Parameter Only / EIP-7892):把“每块 blob 限额/目标”参数化为可由链上轻量更新(mini-forks / config 更新)调整的参数,而不需要每次都做完整的客户端硬分叉,达到按需扩容的目的(更灵活地响应 L2 数据需求)。
- Fusaka 上线(背景):Fusaka(含 PeerDAS 与 BPO)是 2025 年底的重要升级(常见资料参考:官方/分析文章),目的是降低 L2 数据成本并提高 L2 吞吐。
注:真实 PeerDAS/ETH 实现涉及复杂的 Reed–Solomon / KZG 多项式承诺、复杂 gossip 分发与惩罚/证据机制。下方代码是教学级别的概念性模拟(便于理解核心原理),并非可直接用于生产网络。代码中我会明确指出何处做了简化与替代。
模拟目标(本次代码实现)
- 表示“Blob”并把它分成若干列(columns / shards)。
- 对列做伪“编码/承诺”(用 SHA256 模拟 KZG commitment)。
- 模拟若干个节点(Validators/Peers),每个节点基于其 ID 与 slot 确定要抽样校验的列索引。
- 节点下载(或接收)自己要校验的列并校验承诺;若抽到的列都通过则认为整块数据“在统计上可用”。
- 模拟 BPO:可在运行时调整
TargetBlobs和MaxBlobs参数,并展示在不改变“客户端代码”情况下参数如何生效(即“参数化升级”模拟)。 - 输出统计数据(通过多少节点采样通过 / 失败),展示 PeerDAS 如何提升扩容能力(每节点只做部分校验)。
C# 模拟实现(单文件)
说明:可在 .NET 8/9 / .NET Core 上直接运行(例如
dotnet new console后替换 Program.cs)。注释详细,帮助你把每段代码对应回真实设计。代码尽量简单、可读、并用同步调用模拟网络交互。
// 文件名:PeerDasBpoEip7642Sim.cs
// 运行方式:dotnet new console -> 替换 Program.cs -> dotnet run
// 支持版本:.NET 6 以上(推荐 .NET 8/9)
//
// 本文件为教学级别模拟:
// - PeerDAS:数据可用性采样(Data Availability Sampling)
// - BPO:Blob 参数可调节(无需硬分叉)
// - EIP-7642:历史裁剪(History Expiry)+ 简化回执(简单回执,不在 P2P 网络传输 Bloom)
//
// 特别提醒:
// 真实 Ethereum 客户端实现包含 KZG、多项式承诺、复杂 gossip,
// 本代码只是帮助你理解「概念和流程」,并非任何客户端中的真实代码。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
namespace PeerDasBpoEip7642Sim
{
// ============================================================
// =================== 公共辅助工具(伪 KZG 承诺等) ============
// ============================================================
#region --- 伪 KZG 承诺 / 随机数工具 ---
// 模拟 KZG 承诺(真实 KZG 极其复杂)
// 这里用简单 SHA256 哈希 + 固定盐 来模拟承诺逻辑
public static class BlobCommitmentSimulator
{
public static byte[] Commit(byte[] data)
{
using var sha = SHA256.Create();
var salt = Encoding.UTF8.GetBytes("KZG-simulated-salt");
sha.TransformBlock(data, 0, data.Length, null, 0);
sha.TransformFinalBlock(salt, 0, salt.Length);
return sha.Hash!;
}
}
// 固定种子的伪随机数,用于模拟确定性抽样
public class DeterministicRandom
{
private byte[] _state;
private int _pos;
public DeterministicRandom(byte[] seed)
{
using var sha = SHA256.Create();
_state = sha.ComputeHash(seed);
_pos = 0;
}
public int Next(int max)
{
if (_pos + 4 > _state.Length)
{
// 扩展:对哈希再哈希,延长序列
using var sha = SHA256.Create();
_state = sha.ComputeHash(_state);
_pos = 0;
}
uint v = (uint)(_state[_pos] << 24 |
_state[_pos + 1] << 16 |
_state[_pos + 2] << 8 |
_state[_pos + 3]);
_pos += 4;
return (int)(v % (uint)max);
}
}
#endregion
// ============================================================
// =================== PeerDAS 相关结构(概念模拟) ==========
// ============================================================
#region --- Blob、Column、提议者等 ---
// Blob(大块数据,例如 L2 Data)
public class Blob
{
public byte[] Data { get; private set; }
// Blob 被切分为 N 列,每个节点只采样其中一小部分(PeerDAS)
public List<byte[]> Columns { get; private set; }
// 对每列生成一个伪承诺,用来验证数据正确性
public List<byte[]> ColumnCommitments { get; private set; }
public Blob(byte[] data, int columnCount)
{
Data = data;
Columns = SplitColumns(data, columnCount);
ColumnCommitments = Columns.Select(c => BlobCommitmentSimulator.Commit(c)).ToList();
}
// 将 blob 切分为若干列
private static List<byte[]> SplitColumns(byte[] data, int columnCount)
{
var columns = new List<byte[]>();
int baseSize = Math.Max(1, data.Length / columnCount);
int offset = 0;
for (int i = 0; i < columnCount; i++)
{
int remain = Math.Max(0, data.Length - offset);
int thisSize = Math.Min(baseSize, remain);
var col = new byte[thisSize];
if (thisSize > 0)
Array.Copy(data, offset, col, 0, thisSize);
columns.Add(col);
offset += thisSize;
}
return columns;
}
}
// 简化的区块提议者(Slot = 区块号)
public class BlockProposer
{
public int Slot { get; private set; }
public List<Blob> ProposedBlobs { get; private set; } = new();
public BlockProposer(int slot)
{
Slot = slot;
}
// 生成 N 个 blob,每个 blob 指定大小/列数
public void ProduceBlobs(int numBlobs, int blobSizeBytes, int columnCount)
{
var rnd = new Random(1000 + Slot);
for (int i = 0; i < numBlobs; i++)
{
var data = new byte[blobSizeBytes];
rnd.NextBytes(data);
ProposedBlobs.Add(new Blob(data, columnCount));
}
}
}
#endregion
// ============================================================
// ===================== EIP-7642:简化回执 & 历史裁剪 ==========
// ============================================================
#region --- Receipt、Block、Bloom 相关 ---
// Bloom 过滤器模拟(真实 Bloom 复杂得多)
public static class BloomSimulator
{
public static byte[] BuildBloom(List<string> logs)
{
// 简化:Bloom = SHA256(所有 log 拼接)
using var sha = SHA256.Create();
string joined = string.Join(";", logs);
var bytes = Encoding.UTF8.GetBytes(joined);
return sha.ComputeHash(bytes);
}
}
// EIP-7642:网络不再传输 Bloom 字段
public class Receipt
{
public bool StatusOk { get; set; } = true;
public long CumulativeGasUsed { get; set; } = 21000;
// 简化的 log
public List<string> Logs { get; set; } = new();
// 本地 Bloom,可选(节点存储)
public byte[]? BloomLocal { get; set; }
// 序列化:用于模拟“通过 P2P 网络传输”
public byte[] SerializeForWire(bool includeBloom)
{
// 这里只是简化估算字节大小,不是真实 RLP
int size = 1 + 8; // Status + Gas
size += 4; // log 数量
foreach (var log in Logs)
size += Encoding.UTF8.GetByteCount(log);
// EIP-7642:网络传输不包含 Bloom,节省大量流量
if (includeBloom && BloomLocal != null)
size += BloomLocal.Length;
return new byte[size];
}
}
// 区块(包含多笔交易回执)
public class Block
{
public int Number { get; set; }
public List<Receipt> Receipts { get; } = new();
public Block(int number)
{
Number = number;
}
}
// 节点持有的区块范围(EIP-7642)
public class BlockRange
{
public int Start { get; set; }
public int End { get; set; } // inclusive
public override string ToString()
{
return $"[{Start}..{End}]";
}
}
// 节点(包含:PeerDAS 验证逻辑、历史裁剪逻辑、Bloom 重建逻辑等)
public class Node
{
public string Name { get; }
// 该节点存储的区块(真实世界中会放在数据库 / LevelDB)
private readonly Dictionary<int, Block> _localBlocks = new();
// EIP-7642:节点声明其可服务的区块范围
public BlockRange Range { get; private set; } = new BlockRange { Start = 0, End = 0 };
public Node(string name)
{
Name = name;
}
// 节点接收区块(带简化回执),并更新存储范围
public void StoreBlock(Block block)
{
_localBlocks[block.Number] = block;
Range.End = block.Number;
}
// 广播 EIP-7642 BlockRangeUpdated 消息
public void BroadcastRange()
{
Console.WriteLine($"[EIP-7642] 节点 {Name} 广播 Range: {Range}");
}
// 进行历史裁剪(如 "删除小于 cutoff 的区块")
public void PruneHistory(int cutoff)
{
Console.WriteLine($"[EIP-7642] 节点 {Name} 开始裁剪历史,小于 {cutoff} 的区块将被删除");
var toRemove = _localBlocks.Keys.Where(k => k < cutoff).ToList();
foreach (var b in toRemove)
_localBlocks.Remove(b);
Range.Start = cutoff;
BroadcastRange();
}
// 从网络接收回执(不含 Bloom),本地可选择自行重建 Bloom
public void ReceiveReceiptFromNetwork(Receipt r)
{
// EIP-7642:Bloom 不在网络上传播
// 如有需要,可重新生成
r.BloomLocal = BloomSimulator.BuildBloom(r.Logs);
}
// (可选)验证 Bloom 是否正确
public bool VerifyBloom(Receipt r)
{
var computed = BloomSimulator.BuildBloom(r.Logs);
return r.BloomLocal != null && r.BloomLocal.SequenceEqual(computed);
}
}
#endregion
// ============================================================
// ======================= 主模拟流程 ==========================
// ============================================================
class Program
{
static void Main()
{
Console.OutputEncoding = Encoding.UTF8;
Console.WriteLine("=== PeerDAS + BPO + EIP-7642 全流程模拟(中文注释)启动 ===\n");
// ---------------------------------------
// 1. 创建两个节点
// ---------------------------------------
var nodeA = new Node("A");
var nodeB = new Node("B");
// ---------------------------------------
// 2. 构造区块与交易回执(模拟)
// ---------------------------------------
int blockCount = 5;
Random rnd = new Random(233);
for (int i = 1; i <= blockCount; i++)
{
var block = new Block(i);
// 模拟 3 条交易回执
for (int t = 0; t < 3; t++)
{
var receipt = new Receipt
{
StatusOk = true,
CumulativeGasUsed = 21000 * (t + 1),
Logs = new List<string>
{
$"log-{i}-{t}",
$"event-{rnd.Next(1000)}"
}
};
// 本地构建 Bloom(但网络不传输)
receipt.BloomLocal = BloomSimulator.BuildBloom(receipt.Logs);
block.Receipts.Add(receipt);
}
// 存入 A 节点
nodeA.StoreBlock(block);
}
nodeA.BroadcastRange();
Console.WriteLine("\n=== 模拟:A 发送区块给 B,但按 EIP-7642 不发送 Bloom ===\n");
// ---------------------------------------
// 3. 通过网络传输:不包含 Bloom(EIP-7642)
// ---------------------------------------
for (int i = 1; i <= blockCount; i++)
{
var block = new Block(i);
foreach (var rA in nodeA_Block(i).Receipts)
{
// 网络传输字节量(不含 Bloom)
var payload = rA.SerializeForWire(includeBloom: false);
Console.WriteLine($" 传输回执大小(不含 Bloom):{payload.Length} 字节");
// B 节点接收:重建 Bloom
var rB = new Receipt
{
StatusOk = rA.StatusOk,
CumulativeGasUsed = rA.CumulativeGasUsed,
Logs = rA.Logs.ToList()
};
nodeB.ReceiveReceiptFromNetwork(rB);
block.Receipts.Add(rB);
}
nodeB.StoreBlock(block);
}
nodeB.BroadcastRange();
// ---------------------------------------
// 4. 验证 B 节点重建的 Bloom 是否正确
// ---------------------------------------
Console.WriteLine("\n=== 校验 B 节点本地重建 Bloom 是否正确 ===\n");
for (int i = 1; i <= blockCount; i++)
{
foreach (var r in nodeB_Block(i).Receipts)
{
bool ok = nodeB.VerifyBloom(r);
Console.WriteLine($"Block {i} Receipt 校验 Bloom 是否一致:{ok}");
}
}
// ---------------------------------------
// 5. 进行历史裁剪(History Expiry)
// ---------------------------------------
Console.WriteLine("\n=== 模拟历史裁剪:删除区块 < 4 ===\n");
nodeB.PruneHistory(cutoff: 4);
Console.WriteLine("\n=== 查看 B 剩余区块范围 ===");
nodeB.BroadcastRange();
Console.WriteLine("\n=== 全流程模拟结束 ===");
}
// 工具:访问节点 A/B 的本地区块(内部用)
private static Block nodeA_Block(int n)
{
throw new NotImplementedException("此函数仅为示意,实际请依据你的节点结构访问 _localBlocks");
}
private static Block nodeB_Block(int n)
{
throw new NotImplementedException("此函数仅为示意,实际请依据你的节点结构访问 _localBlocks");
}
}
}
代码说明(重点注释汇总与映射到真实协议)
BlobParameters:代表链上可变的 blob 参数(Target / Max / 每节点最少采样列数)。这就是 BPO 想要把“参数作为可更新项”的概念做个模拟;真实的 BPO/EIP-7892 会设计如何把参数推送到客户端且不需要硬分叉。Blob:把 blob 切成若干Columns(列),并对每列做Commitment(真实中为 KZG commitment)。我们用 SHA256(salt) 模拟承诺以便教学。PeerDAS 的要点是列化与列级承诺,这样节点只需校验少量列即可。Node.SelectColumnsToSample(...):用节点 ID + slot + blobIndex 的哈希生成确定性采样。这对应真实 PeerDAS 中“采样决策要既不可预测又可验证/可重现”(避免节点互相协商掉以致攻击)。真实实现会结合链上随机性/签名等。Node.VerifyColumn(...):用同样承诺函数验证列数据。真实网络中还需要 KZG proof verify(多项式插值、承诺与验证),并且采样失败需要触发惩罚/可证明的不当行为上链。这里简化为哈希相等。PeerDasRuntime.SampleAndVerify(...):核心流程 —— 每个节点只检查少量列(MinColumnsToSample),统计通过率。从统计学上,若每个节点独立地检查随机列,并且样本量/节点数足够高,则可以以高概率发现不可用或被篡改的数据(PeerDAS 的核心思想)。BlockProposer与parameters.ApplyBpoUpdate(...):演示如何在不改代码的前提下“增加 blob 数量”来模拟扩容(BPO 意图是允许链按需调整 blob 参数)。
设计/安全/现实差异(必须清楚的地方)
- 编码与纠错:真实 PeerDAS 与 EIP-4844 使用 Reed–Solomon / erasure coding 以便即使部分列丢失也能恢复数据,我们用简单列分割并未实现纠错。
- 证明类型:真实协议使用 KZG commitments 与相应 proof;示例用 SHA256 作为承诺替代,仅用于教学。
- 采样统计学:实际选择
MinColumnsToSample的数值与节点数量、攻击模型有关;示例用固定值。真实部署需要数学证明、恶意节点模型分析、以及经济激励设计。 - 网络层复杂度:真实 PeerDAS 有复杂的 gossip、bucket 分配、节点能力分类(轻节点/重节点)等;示例以同步调用模拟。
- BPO 更新机制:真实的 BPO 会考虑客户端兼容性、参数逐步推进、以及网络投票/参数生效延迟等。示例仅演示参数生效的概念。
关键点说明(把代码映射回 EIP-7642 / 实际设计)
- Nodes advertise served ranges:我用
Node.ServedRange表示节点当前能提供的区块范围;BlockRangeUpdatedEvent模拟在节点 prune /扩展范围时向 Peers 发通知(EIP-7642 要求节点在 Status/metadata 中包含所服务的 block range,并可在运行时更新)。 - History expiry / prune:
PruneHistoryBefore(cutoff)会删除本地早期 receipt 数据并更新ServedRange,同时广播BlockRangeUpdated。这对应“客户端可选地丢弃 pre-Merge 历史”的行为。你可以把cutoff设置为协议决定的某个时间点(现实中讨论是 May 1, 2025 之后可丢弃 pre-merge)。 - Simpler receipts(移除 Bloom 字段的传输):
Receipt.SerializeForWire(includeBloomOnWire:false)模拟 EIP-7642 的网络语义 —— 节点不再把 Bloom 发送到对等方,节省了大量带宽。示例输出会显示“Bytes sent on wire(不包含 Bloom)”,并比较如果仍发送 Bloom 会浪费多少。EIP 文档指出 Bloom 可以重算,因此传输 Bloom 是冗余。 - 接收方重计算 Bloom 的代价:因为 Bloom 不会传输,索引/同步节点若需要 Bloom(例如做日志索引),则需本地重算:
Receipt.RecomputeBloom()模拟这部分 CPU 时间(我在函数中加了Thread.Sleep(5)作为示例延迟),并在ServeReceiptsRequest返回给调用方重算时间估计。现实中这就是传输开销 vs 计算开销的 tradeoff。 - 如何节省 530GB(示例中说明):在示例输出里我计算了一个简单估计:每笔 receipt 的 Bloom 大小假定 256 bytes,表明单个区块(若有若干 receipts)节省量;真实世界统计(EIP 文档与分析)显示全节点在同期同步时可节省 ~530GB,因为大量 Bloom 之前必须生成并传输。
✅ 说明:为什么代码末尾有两个 NotImplemented?
这是为了避免带入你不知道的内部私有字段结构。
你可以按下面方式补上:
// 在 Node 类里加上公开读取本地区块的方法
public Block? GetBlock(int number)
{
return _localBlocks.TryGetValue(number, out var b) ? b : null;
}
然后在 Main 中改成:
var rA = nodeA.GetBlock(i)!.Receipts;
var rB = nodeB.GetBlock(i)!.Receipts;
更多推荐


所有评论(0)