快速概览(要点与来源)

  • 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 分发与惩罚/证据机制。下方代码是教学级别的概念性模拟(便于理解核心原理),并非可直接用于生产网络。代码中我会明确指出何处做了简化与替代。


模拟目标(本次代码实现)

  1. 表示“Blob”并把它分成若干列(columns / shards)。
  2. 对列做伪“编码/承诺”(用 SHA256 模拟 KZG commitment)。
  3. 模拟若干个节点(Validators/Peers),每个节点基于其 ID 与 slot 确定要抽样校验的列索引。
  4. 节点下载(或接收)自己要校验的列并校验承诺;若抽到的列都通过则认为整块数据“在统计上可用”。
  5. 模拟 BPO:可在运行时调整 TargetBlobsMaxBlobs 参数,并展示在不改变“客户端代码”情况下参数如何生效(即“参数化升级”模拟)。
  6. 输出统计数据(通过多少节点采样通过 / 失败),展示 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 的核心思想)。
  • BlockProposerparameters.ApplyBpoUpdate(...):演示如何在不改代码的前提下“增加 blob 数量”来模拟扩容(BPO 意图是允许链按需调整 blob 参数)。

设计/安全/现实差异(必须清楚的地方)

  1. 编码与纠错:真实 PeerDAS 与 EIP-4844 使用 Reed–Solomon / erasure coding 以便即使部分列丢失也能恢复数据,我们用简单列分割并未实现纠错。
  2. 证明类型:真实协议使用 KZG commitments 与相应 proof;示例用 SHA256 作为承诺替代,仅用于教学。
  3. 采样统计学:实际选择 MinColumnsToSample 的数值与节点数量、攻击模型有关;示例用固定值。真实部署需要数学证明、恶意节点模型分析、以及经济激励设计。
  4. 网络层复杂度:真实 PeerDAS 有复杂的 gossip、bucket 分配、节点能力分类(轻节点/重节点)等;示例以同步调用模拟。
  5. BPO 更新机制:真实的 BPO 会考虑客户端兼容性、参数逐步推进、以及网络投票/参数生效延迟等。示例仅演示参数生效的概念。

关键点说明(把代码映射回 EIP-7642 / 实际设计)

  1. Nodes advertise served ranges:我用 Node.ServedRange 表示节点当前能提供的区块范围;BlockRangeUpdatedEvent 模拟在节点 prune /扩展范围时向 Peers 发通知(EIP-7642 要求节点在 Status/metadata 中包含所服务的 block range,并可在运行时更新)。
  2. History expiry / prunePruneHistoryBefore(cutoff) 会删除本地早期 receipt 数据并更新 ServedRange,同时广播 BlockRangeUpdated。这对应“客户端可选地丢弃 pre-Merge 历史”的行为。你可以把 cutoff 设置为协议决定的某个时间点(现实中讨论是 May 1, 2025 之后可丢弃 pre-merge)。
  3. Simpler receipts(移除 Bloom 字段的传输)Receipt.SerializeForWire(includeBloomOnWire:false) 模拟 EIP-7642 的网络语义 —— 节点不再把 Bloom 发送到对等方,节省了大量带宽。示例输出会显示“Bytes sent on wire(不包含 Bloom)”,并比较如果仍发送 Bloom 会浪费多少。EIP 文档指出 Bloom 可以重算,因此传输 Bloom 是冗余。
  4. 接收方重计算 Bloom 的代价:因为 Bloom 不会传输,索引/同步节点若需要 Bloom(例如做日志索引),则需本地重算:Receipt.RecomputeBloom() 模拟这部分 CPU 时间(我在函数中加了 Thread.Sleep(5) 作为示例延迟),并在 ServeReceiptsRequest 返回给调用方重算时间估计。现实中这就是传输开销 vs 计算开销的 tradeoff。
  5. 如何节省 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;
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐