BrokerHub

0. BrokerHub 可以用来干什么?


BrokerHub一个由中山大学 HuangLab 开发的区块链协议验证平台,名为 BlockEmulator

简单来说,它的用途是帮助研究人员或学生快速“模拟”和“验证”新的区块链技术,特别是“分片(Sharding)”和“共识协议”。它不是一个用来挖矿赚钱的工具,而是一个科研实验工具

0.1它可以帮你做这些事:

  1. 模拟区块链运行
    它可以在一台电脑(或多台)上模拟出一整条分片区块链网络,包含多个分片、多个节点。
  2. 验证“跨分片”机制
    它内置了两种主流的跨分片技术(BrokerChain 的 Broker 机制和 Monoxide 的 Relay 机制),你可以用它来测试不同分片间怎么处理交易。
  3. 验证“共识”算法
    它支持 PBFT(拜占庭容错)等共识协议,你可以修改代码来测试自己写的新共识算法效果如何。
  4. 跑实验、出数据
    它会自动记录 TPS(吞吐量)、延迟等核心指标,并输出成 CSV 文件。写论文或做研究时,可以直接用这些数据画图。
  5. 支持智能合约
    它包含了一个 VM 模块,可以在模拟链上跑 Solidity 智能合约。

总结:如果你在做区块链底层技术的研究(比如想改进分片效率、设计新共识),这个项目就是你的“测试沙盒”,让你不必从零写一条区块链就能验证你的想法。

1. 核心参数说明


项目的核心参数集中在 BrokerHub/params/global_config.go 文件中。

1.1关键参数配置

参数名 默认值 含义/作用 更新建议
Block_Interval 1000 出块间隔 (ms)
每隔多少毫秒产生一个新区块
想让出块更快就改小,模拟高延迟环境可改大
MaxBlockSize_global 500000 区块最大容量
一个区块里最多能装多少笔交易
限制吞吐量上限的参数之一
InjectSpeed 5000 交易注入速度 (TPS)
模拟器每秒向网络发送多少笔交易
最重要的负载参数
想测极限性能就把这个调大,看系统能不能撑住
TotalDataSize 500000 总交易数量
整个实验一共跑多少笔交易后停止
控制实验时长。如果跑太久,可以改小这个值
BatchSize 5000 批处理大小
Supervisor 每次读取并发送多少笔交易
最好比 InjectSpeed 稍微大一点,保证发送顺畅
ShardNum 4 分片数量
整个网络被切分成几个片
验证分片扩展性的核心参数
NodesInShard 4 分片内节点数
每个分片里有多少个节点参与共识
节点越多,共识(PBFT)通常越慢
BrokerNum 1 Broker 数量
辅助跨分片的中间人节点数量
仅在 broker 模式下生效
DatWrite_path "./result/" 数据输出路径
实验结果 CSV 存哪儿
一般不用改

1.2如何修改参数

  1. 打开配置文件
    • 编辑 global_config.go 文件
    • 找到对应的变量值直接修改数字

修改代码后需要重新编译

2. 如何运行BrokerHub


Windows 上大致分两步:

  1. 编译
  2. 运行脚本

2.1编译

1
go build -o blockEmulator_Windows_Precompile.exe ./main.go

2.2运行

1
.\bat_shardNum=3_NodeNum=4_mod=Broker_b2e.bat

直接执行 Windows 启动脚本(会自动启动 3 个分片、每分片 4 个节点并写入配置)。
执行过程如下。

3. BrokerHub 项目实验无法自动终止问题的排查与修复

3.1问题描述

在运行 BrokerHub(BVM + Brokerchain 实验平台)时,执行启动脚本 bat_shardNum=3_NodeNum=4_mod=Broker_b2e.bat 后,实验进程无法自动终止,Supervisor 日志中 epoch 计数持续递增(已超过 500),result 目录始终未生成。

3.2系统停止机制分析

通过阅读源码,梳理出系统的正常关闭流程如下:

supervisor.go 中 SupervisorTxHandling() 的执行顺序为:

1
2
3
4
MsgSendingControl() 返回
→ 轮询 StopSignal.GapEnough()(等待连续收到 2×ShardNum 个空区块上报)
→ 向所有节点发送 CStop 消息
→ 调用 CloseSupervisor(),将测量数据写入 ./result/ 下的 CSV 文件

其中 StopSignal 机制(见 supervisorStopModule.go)要求连续收到 stopThreshold 个空区块消息后才判定实验结束。任何非空区块都会将计数器重置为 0。

3.3逐步排查过程

3.3.1 排查一:supervisor.go 中的调试代码

在 supervisor.go 发现无限循环:

1
2
3
4
add
for true {
time.Sleep(time.Second)
}

程序在这里无限等待,即进入死循环,每秒 sleep 一次。
如果不注释掉注释,Supervisor 在这里会一直卡住,不会继续往下执行(不会发送 stop 信号,也不会关闭和写入结果文件)。

3.3.2 排查二:MsgSendingControl() 死循环(根因)

启动脚本使用 -m 4,对应 CommitteeMethod[4] = "Broker_b2e",实际加载的实现为 committee_brokerhub.go 中的 BrokerhubCommitteeMod.MsgSendingControl()

对比能正常终止的 BrokerCommitteeMod.MsgSendingControl()-m 2),后者从 CSV 文件读取交易,达到 TotalDataSizebreak 退出。而 BrokerhubCommitteeMod 的实现包含两个无退出条件的死循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 死循环 1:无限生成随机交易
go func() {
for {
bcm.hubParams.currentEpoch++
txs := bcm.generateRandomTxs()
bcm.txSending(...)
time.Sleep(time.Second * 2)
}
}()

// 死循环 2:无限处理用户请求队列
for {
time.Sleep(time.Millisecond * 100)
// ...处理队列...
}

由于 MsgSendingControl() 永远不返回,SupervisorTxHandling() 中后续的停止逻辑永远无法执行。值得注意的是,代码中已定义了 simulation_param.endedEpoch = 86 字段,且在其他方法中存在对该字段的判断(第 473、806 行),但 MsgSendingControl()未引用此退出条件。

修复措施:在 MsgSendingControl() 中引入 endedEpoch 退出机制。核心改动如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
done := make(chan struct{})

go func() {
for {
bcm.hubParams.currentEpoch++
if bcm.hubParams.currentEpoch > bcm.hubParams.endedEpoch {
close(done)
return
}
// ...原有交易生成逻辑不变...
}
}()

for {
select {
case <-done:
// 处理剩余队列后退出
return
default:
}
// ...原有队列处理逻辑不变...
}

通过 channel 信号在两个循环间协调退出,确保 epoch 达到上限后 MsgSendingControl() 正常返回。

3.4验证

修复后重新编译运行,程序在约 86 个 epoch(约 3 分钟)后自动终止,result 目录正常生成,包含 Supervisor 测量指标 CSV 与 Broker 余额/收益数据。

3.5结论

本次问题的直接原因是 BrokerhubCommitteeMod.MsgSendingControl() 缺少退出条件,属于功能性缺陷。排查过程中虽也发现了调试代码残留的问题并予以修正。修复思路是复用已有的 endedEpoch 字段,以最小改动实现循环退出,保持与原有架构的一致性。

4. 极端参数压力测试学习法


global_config.go中参数进行极端取值。来观察系统行为,便于理解复杂系统。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package params



var (

    Block_Interval      = 1 // generate new block interval (ms)

    MaxBlockSize_global = 1 // the block contains the maximum number of transactions

    InjectSpeed         = 1 // the transaction inject speed

    TotalDataSize       = 1 // the total number of txs

    BatchSize           = 1 // supervisor read a batch of txs then send them, it should be larger than inject speed

    BrokerNum           = 2

    NodesInShard        = 4

    ShardNum            = 3

    IterNum_B2E         = 5

    Brokerage           = 0.1

    DataWrite_path      = "./result/" // measurement data result output path

    LogWrite_path       = "./log"     // log output path

    KeyWrite_path       = "./key"

    SupervisorAddr      = "127.0.0.1:18800" //supervisor ip address

    FileInput           = "./TXs.csv"       //the raw BlockTransaction data path

    NodeID              uint64

    ShardID             uint64

)


终端输出循环不停。

4.1StopSignal 停止机制

supervisorStopModule.go 中的 StopSignal 维护一个计数器 stopGap,判定逻辑如下:

  • 每当 Supervisor 收到一个 BlockInfoMsgBlockBodyLength == 0(空块),stopGap++
  • 每当收到一个 BlockBodyLength > 0(非空块),stopGap 归零
  • stopGap >= stopThreshold(值为 2 × ShardNum = 6)时,判定实验结束

这意味着:必须连续收到 6 个来自各分片的空区块上报,中间不能被任何一个非空区块打断

4.2区块的完整生命周期

要理解 StopSignal 何时能被满足,需要追踪从交易注入到区块上报的完整链路:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Supervisor (MsgSendingControl)
├── 每 2 秒生成一批随机交易
├── dealTxByBroker() 拆分交易
│ ├── 同片交易 → 直接发送到对应分片
│ └── 跨片交易 → Broker 拆分为 Tx1 + Tx2(见下文)
└── txSending() → 按 InjectSpeed 批量注入各分片

各分片 PBFT 节点 (Propose 循环)
├── 每隔 Block_Interval 毫秒触发一次
├── GenerateBlock()
│ └── Txpool.PackTxs(50000) → 从交易池取出最多 50000 笔交易打包
├── PBFT 三阶段共识(PrePrepare → Prepare → Commit)
└── HandleinCommit() → 区块上链 → 构造 BlockInfoMsg 发送给 Supervisor
└── BlockBodyLength = len(block.Body)

4.3Broker 机制产生的交易放大效应

当 Supervisor 的 dealTxByBroker() 处理一笔跨分片交易时,会产生交易乘法效应

1
2
3
4
5
原始跨片交易 (Sender@Shard0 → Recipient@Shard1)

├── Tx1: Sender → Broker(发到 Shard0)
├── Tx2: Broker → Recipient(发到 Shard1)
└── AllocatedTx: Broker 余额调整交易(可能发到多个分片)

一笔原始交易至少衍生出 2-3 笔链上交易,而这些衍生交易的确认(createConfirm)又会在 HandleBlockInfo 回调中进一步触发 handleBrokerType1MestxSending(tx1s)handleBrokerType2MestxSending(tx2s),形成交易注入 → 上链 → 回调确认 → 再注入的闭环。

4.4极端参数如何破坏停止条件

当前参数为 Block_Interval = 1MaxBlockSize_global = 1InjectSpeed = 1。分析其所导致的时序关系:

4.4.1 出块速率远超交易消化速率

1
2
3
Block_Interval = 1ms → 每个分片每毫秒尝试出一个块
3 个分片 → 系统每毫秒最多产生 3 个区块
每个区块最多容纳 1 笔交易(MaxBlockSize_global = 1,但实际 PackTxs 硬编码为 50000)

注意:虽然 MaxBlockSize_global = 1,但 blockchain.go 第 1129 行PackTxs 的参数被硬编码为 50000,因此 MaxBlockSize_global 这个参数实际上不起作用。每次出块时会把交易池中的所有交易一次性打包。

4.4.2 Broker 衍生交易形成持续注入流

每个 epoch(每 2 秒),Supervisor 生成一批随机交易。其中跨片交易经 Broker 拆分后,产生 Tx1、Tx2、AllocatedTx 注入到不同分片。这些交易上链后,Supervisor 在 HandleBlockInfo 中收到区块上报,触发 createConfirm 回调,回调中又会通过 txSending 向分片注入新的交易(Tx2 的确认触发等)。

时序如下:

1
2
3
4
5
6
7
t=0s    :epoch 1 注入原始交易 → 衍生 Tx1、Tx2 注入各分片
t=1ms :Shard0 出块,打包 Tx1 → 上报非空块 → stopGap 归零
t=2ms :Shard1 出块,打包 Tx2 → 上报非空块 → stopGap 归零
t=3ms :Shard0 出块,可能为空 → stopGap = 1
t=4ms :Supervisor HandleBlockInfo 回调,生成确认交易 → 注入 Shard1
t=5ms :Shard1 出块,打包确认交易 → 上报非空块 → stopGap 归零!
t=2000ms:epoch 2 注入新一批交易 → 整个过程重复

关键问题:由于出块间隔仅 1ms,系统在 epoch 间隙期间并非空闲——之前 epoch 的衍生交易的确认回调会不断产生新交易,这些交易以极高频率(1ms 一个块)被打包上报。3 个分片交替上报非空块,使得 stopGap 计数器在到达 6 之前就被频繁重置。

4.4.3 三分片交替出块的竞态效应

3 个分片独立出块,各自的出块时机不同步。即使某个分片的交易池已空,其他分片可能仍有衍生交易在处理:

1
2
3
4
5
时间线:  --|------|------|------|------|------|--->
Shard0: 空 空 非空 空 空 空
Shard1: 空 非空 空 空 非空 空 ← Broker 确认交易到达
Shard2: 非空 空 空 非空 空 空 ← AllocatedTx 到达
stopGap: 1 0! 0! 0! 1 0! ← 永远无法累积到 6

三个分片的非空块在时间轴上交错出现,使得连续空块窗口始终无法维持到 6 个。

4.5合理参数为何能正常停止

Block_Interval = 1000(1 秒)时:

  • 每个分片每秒只出一个块,给系统充足时间消化所有衍生交易
  • 一个 epoch 的所有交易(原始 + 衍生 + 确认)在几个块内就能全部处理完
  • epoch 间隙(2 秒)内足够出 2 轮空块,3 个分片各出 2 个空块 = 6 个空块上报
  • stopGap 能够顺利累积到阈值 6,触发正常关闭

4.6结论

StopSignal 的设计前提是:交易注入有限、出块间隔合理、衍生交易能在若干轮出块内被完全消化。极端参数(1ms 出块)打破了这一前提——Broker 的交易放大效应 + 确认回调的循环注入 + 多分片交替出块,使得系统中始终存在零星的非空块,stopGap 计数器永远无法连续累积到阈值。

三个分片的非空块在时间轴上交错出现,使得连续空块窗口始终无法维持到 6 个。

4.7合理参数为何能正常停止

Block_Interval = 1000(1 秒)时:

  • 每个分片每秒只出一个块,给系统充足时间消化所有衍生交易
  • 一个 epoch 的所有交易(原始 + 衍生 + 确认)在几个块内就能全部处理完
  • epoch 间隙(2 秒)内足够出 2 轮空块,3 个分片各出 2 个空块 = 6 个空块上报
  • stopGap 能够顺利累积到阈值 6,触发正常关闭

4.8结论

StopSignal 的设计前提是:交易注入有限、出块间隔合理、衍生交易能在若干轮出块内被完全消化。极端参数(1ms 出块)打破了这一前提——Broker 的交易放大效应 + 确认回调的循环注入 + 多分片交替出块,使得系统中始终存在零星的非空块,stopGap 计数器永远无法连续累积到阈值。