2025-12-06-杂记-前端图拓扑渲染优化

First Post:

Last Update:

Page View: loading...

  1. 性能优化 (Web Workers): 目前的数据获取、解析和Diff算法都在主线程运行。当拓扑变大时,计算Diff会导致页面卡顿。建议将这部分移至 Web Worker

  2. D3 渲染优化: 目前的设计倾向于每次更新都全量替换 filteredNodes,这会导致 D3 力导向图重新初始化或位置抖动。应该利用 Diff 结果进行增量渲染 (Enter/Update/Exit)

  3. 状态管理解耦: useTopologyData 承担了太多职责(数据存储、UI状态、标签逻辑)。应该拆分为“数据层”和“视觉层”。

  4. 数据结构优化: 数组查找(.find)效率低,应更多使用 Map/Set 索引。

优化后文件结构

采用了 Core (数据核心) + Visual (视觉逻辑) + Worker (后台计算) 的分层结构。

codeText

1
2
3
4
5
6
7
8
9
10
11
src/
├── types/
│ └── topology.ts // (保持不变) 类型定义
├── workers/
│ └── topology.worker.ts // [新] 负责Fetch、解析数据、计算Diff
├── composables/
│ ├── useTopologyCore.ts // [优化] 负责与Worker通信,持有原始数据
│ ├── useTopologyVisual.ts // [拆分] 负责过滤、标签、高亮等UI逻辑
│ └── useD3Renderer.ts // [新] 封装D3的具体操作,接受Diff指令
└── components/
└── EthereumTopology.vue // [优化] 变得很薄,只负责组装

现在的以太坊拓扑渲染架构采用了 “分层响应式架构” (Layered Reactive Architecture),结合了 Web Worker 多线程计算D3 增量渲染 技术。

这是一种高性能、低耦合的现代前端架构,专门为了处理大量节点(数千级)的实时动态拓扑而设计。

以下是该架构的详细层级解析:

1. 架构总览图

数据流向是单向的,状态管理是响应式的:

codeMermaid

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
graph TD
BackEnd[FastAPI 后端] -->|JSON Stream| Worker[Web Worker (后台线程)]

subgraph "Main Thread (主线程)"
Worker -->|Diff Update (增量/全量)| Store[useTopologyCore (数据层)]
Store -->|Raw Data| Visual[useTopologyVisual (视觉层)]

subgraph "UI Components (视图层)"
Visual -->|Filtered Data| Renderer[useD3Renderer (渲染层)]
Visual -->|Filters/Mode| Controls[TopologyVisualization (控制面板)]
Renderer -->|Click Event| Details[PhysicalTopology (详情抽屉)]
end
end

Renderer -->|Draw| DOM[SVG/Canvas]

2. 核心层级详细解析

第一层:数据处理层 (Worker Thread)

文件: workers/topology.worker.ts
这是架构的“发动机”,完全脱离主线程运行。

  • 职责

    1. 数据拉取:直接发起 Fetch 请求,不占用主线程网络资源。

    2. 数据清洗:解析复杂的 JSON 结构,标准化为 TopologyNode 和 TopologyLink。

    3. 智能 Diff (差异计算)

      • 这是性能优化的关键。它对比新旧数据,计算出新增、删除和更新的节点。

      • 坐标继承:在 Worker 中将旧节点的 x, y 坐标赋值给新节点,防止 D3 在数据更新时重新计算布局导致视图“爆炸”或闪烁。

第二层:状态管理层 (Composables)

这一层利用 Vue 3 的 Composition API 将业务逻辑拆分为独立的模块。

  • 数据持有 (useTopologyCore.ts)

    • 与 Worker 通信的桥梁。

    • 使用 shallowRef 存储庞大的拓扑数据。优化点:shallowRef 不会深度监听节点内部属性的变化(如 x, y 坐标),这极大减轻了 Vue 响应式系统的负担,因为 D3 会直接操作这些原生对象。

  • 视觉逻辑 (useTopologyVisual.ts)

    • 纯粹的计算层。它不关心数据怎么来的,只关心怎么显示。

    • 动态过滤:利用 computed 属性,根据 filters(如隐藏共识节点)实时生成 filteredNodes。

    • 样式映射:集中管理颜色 (getNodeColor) 和标签 (getNodeLabel) 逻辑,实现逻辑与渲染分离。

第三层:渲染驱动层 (Render Engine)

文件: useD3Renderer.ts
这是 D3.js 与 Vue 的结合点。

  • 生命周期接管:它在 onMounted 时初始化 D3 仿真器。

  • 增量渲染 (Incremental Rendering)

    • 使用 Vue 的 watch 监听过滤后的数据。

    • 利用 D3 的 enter(), update(), exit() 模式。

    • Enter: 新节点淡入。

    • Exit: 被过滤或删除的节点淡出移除。

    • Update: 现有节点平滑移动到新位置。

  • 事件桥接:将 D3 的 click、drag 事件转换为 Vue 的回调函数,传递给上层组件。

第四层:视图组装层 (View Integration)

文件: EthereumTopology.vue (父组件)
这是架构的“容器”和“胶水”。

  • 依赖注入 (Dependency Injection)

    • 父组件通过 provide(‘topology-state’, …) 将 mode、filters 等状态下发。

    • 子组件 TopologyVisualization 通过 inject 直接获取并修改这些状态。避免了深层 Prop 传递(Prop Drilling)。

  • 组件编排

    • 负责布局:左上角悬浮控制面板、中间 D3 画布、右侧详情抽屉。

    • 负责联动:当 D3 点击节点时,控制 showPhysicalDetails 变量来弹出侧边栏。


3. 关键性能优化点总结

  1. 非阻塞主线程 (Off-Main-Thread):

    • 旧架构:在组件内 fetch 数据 -> 解析 -> 赋值。数据量大时 UI 会卡顿。

    • 新架构:所有数据处理都在 Worker 中完成,主线程只负责接收“准备好渲染”的数据。

  2. 浅层响应式 (Shallow Reactivity):

    • 使用 shallowRef 代替 ref 存储节点数组。D3 内部高频修改 node.x 和 node.y 时,不会触发 Vue 的依赖更新系统,显著提升动画帧率。
  3. 状态保持 (State Preservation):

    • Worker 在处理新数据时,会查找旧数据的 ID,并将 x, y, vx, vy (速度向量) 复制给新数据。这保证了在轮询更新时,节点不会重置位置,实现了“流式”的平滑更新效果。
  4. 按需计算 (Computed Filtering):

    • 连接 (links) 的过滤依赖于节点 (nodes) 的过滤结果。新架构使用了 Set 来建立索引,将连接过滤的时间复杂度从 O(N*M) 降低到 O(M)(其中 N 是节点数,M 是连接数)。

4. 总结

现在的架构是一个 “重后台、轻前台、数据驱动视图” 的专业可视化架构。

  • Worker 负责“脏活累活”(数据处理)。

  • D3 负责“精细活”(物理仿真和绘图)。

  • Vue 负责“指挥”(状态管理和组件通信)。

这种架构非常适合需要长时间运行、实时监控网络状态的生产级系统。


根据提供的代码(EthereumTopologyHandler 和 RealTopologyService),以太坊网络拓扑的处理流程是一个分层获取、数据融合、格式化输出的过程。核心逻辑依赖于 Neo4j 图数据库(存储P2P关系)和 Docker 守护进程(提供容器运行时信息)。

以下是详细的处理步骤解析:

1. 数据源获取 (Data Acquisition)

系统主要通过两个渠道获取数据:

  • Neo4j 数据库 (核心数据源):存储了爬虫或客户端上报的节点发现数据,包含节点ID、IP、以及节点间的 P2P 连接关系。

  • Docker Client (辅助数据源):用于获取运行中容器的实时状态、名称映射和网络设置。

2. 核心处理流程

整个拓扑生成的逻辑主要集中在 _get_real_topology_from_neo4j 和 _convert_topology_format 方法中。

步骤 A:从 Neo4j 提取原始拓扑结构

代码通过 Cypher 查询语句分三步提取数据:

  1. 查询执行层 (Execution Layer)

    • 查找所有 ExecNode 标签的节点。

    • 查找 EXEC_PEERS_WITH 关系,获取该节点的对等节点(Peers)。

  2. 查询共识层 (Consensus Layer)

    • 查找所有 ConsNode 标签的节点。

    • 查找 CONS_PEERS_WITH 关系。

  3. 查询验证者 (Validators)

    • 查找与共识节点通过 MANAGES_VALIDATOR 关系连接的 Validator 节点。

    • 这反映了哪个信标节点(Beacon Node)管理着哪些验证者客户端。

步骤 B:容器身份映射 (Container Mapping)

  • 目的:数据库中只有 IP 地址,但在前端展示时,最好能显示具体的 Docker 容器名称(如 geth-node-1)。

  • 实现:_create_ip_to_container_mapping 方法遍历所有 Docker 容器,提取其网络设置中的 IP 地址,建立 IP -> ContainerName 的映射表。

步骤 C:构建拓扑对象 (Topology Construction)

系统将原始数据转换为前端可视化的 JSON 格式,包含 nodes 和 links。

1. 节点生成 (Nodes):
代码根据逻辑自动计算节点的坐标 (x, y) 以便可视化布局:

  • 执行层节点

    • type: execution

    • 位置:固定在 Y=150 的水平线上。

  • 共识层节点

    • type: consensus

    • 位置:固定在 Y=350 的水平线上(位于执行层下方)。

  • 验证者节点

    • type: validator

    • 位置:簇拥在所属共识节点的下方 (y + 60),通过计算偏移量排成小方阵。

2. 连接生成 (Links):
系统构建了四种类型的连接:

  • 执行层 P2P (exec_peer):基于 Neo4j 中的 EXEC_PEERS_WITH 关系,表示 Geth/Nethermind 节点间的 Gossip 协议连接。

  • 共识层 P2P (cons_peer):基于 CONS_PEERS_WITH 关系,表示 Lighthouse/Prysm 节点间的连接。

  • 管理关系 (manages_validator):连接共识节点和它管理的验证者节点。

  • 跨层连接 (cross_layer)关键逻辑。代码会自动匹配 IP 地址相同的执行层节点和共识层节点,并创建一个垂直连接。这代表了以太坊客户端组合(Engine API 通信,例如 Geth <-> Lighthouse 在同一台机器/Pod内)。

3. 容错与缓存机制

为了保证性能和稳定性,代码中包含了以下机制:

  • 缓存 (Caching)

    • 使用 self.cache 存储计算好的拓扑。

    • 设置 cache_ttl (20-30秒),防止频繁查询 Neo4j 导致数据库过载。

  • 降级模式 (Fallback - 仅在 Service 中)

    • 如果 Neo4j 连接失败或返回空数据,RealTopologyService 会调用 _get_container_based_topology。

    • Fallback 逻辑:直接扫描 Docker 容器,如果发现名为 ethereum 的容器,就根据容器数量“伪造”一个链式的拓扑结构(非网状),以确保界面上至少能看到节点存在,而不是一片空白。

4. 统计与验证 (Statistics & Validation)

EthereumTopologyHandler 还提供了额外的高级功能:

  • 独立查询:get_nodes 和 get_links 可以不依赖完整拓扑逻辑,直接查询特定层的数据,提高效率。

  • 拓扑验证:validate_topology 检查数据一致性:

    • 节点 ID 是否唯一。

    • 连接的源/目标节点是否存在。

    • 是否存在孤立节点。

    • IP 地址分布是否异常(例如一个 IP 运行了超过2个主要节点)。

总结

该系统处理以太坊拓扑的核心思想是:“数据库定义逻辑关系,Docker 定义物理属性,代码负责视觉组装”

  1. 逻辑层:谁连谁?由 Neo4j 决定。

  2. 物理层:你是谁?由 Docker IP 映射决定。

  3. 视觉层:你在哪?由代码中的分层坐标计算逻辑决定(执行层在上,共识层在中,验证者在下)。


目前的实现逻辑虽然功能完整,但在性能上存在几个显著的瓶颈,特别是在节点数量增多或 Docker 容器较多时,响应速度会明显下降。

以下是针对代码的具体性能优化方案,按提升幅度从大到小排序:

1. 痛点分析:目前的性能瓶颈在哪里?

  1. Docker API 调用过于频繁 (主要瓶颈)

    • _create_ip_to_container_mapping 每次生成拓扑都会被调用。它会遍历所有容器并检查网络设置。Docker API 的响应通常是毫秒级到秒级的,如果不缓存,这会严重阻塞主线程。

    • 在物理拓扑中,_get_link_bandwidth 会对每个连接进入容器执行 tc 命令。如果有 50 个连接,就要串行执行 50 次 docker exec,这是极慢的 IO 操作。

  2. 串行数据库查询

    • 在 _get_real_topology_from_neo4j 中,执行层查询及处理完之后,才开始共识层的查询。这两者没有依赖关系,完全可以并行。
  3. 计算密集型的重复操作

    • 每次请求都重新计算所有节点的坐标和映射关系,即使数据没有变化。

2. 优化方案一:Docker 数据的独立缓存与后台更新

Docker 的元数据(IP、容器名)变化频率远低于 P2P 网络连接的变化频率。不要在每次请求拓扑时都去查询 Docker。

优化策略: 使用“读写分离”的策略,后台任务更新 Docker 映射,前台请求只读内存变量。

codePython

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
import asyncio

class EthereumTopologyHandler(TopologyProvider):
def __init__(self):
# ... 原有初始化 ...
self.ip_container_map_cache = {}
self.map_last_update = 0
self.map_update_lock = asyncio.Lock()

# 启动时预热
# 注意:实际代码中建议使用 apscheduler 或 asyncio.create_task 在后台循环运行

async def _get_ip_to_container_map_optimized(self) -> Dict[str, str]:
"""
优化后的获取映射方法:
1. 优先返回内存缓存
2. 缓存过期(如5分钟)才异步更新
"""
current_time = time.time()
# 缓存有效期设为 300秒 (Docker容器IP不会频繁变动)
if current_time - self.map_last_update < 300 and self.ip_container_map_cache:
return self.ip_container_map_cache

# 如果需要更新,且未被锁定
if not self.map_update_lock.locked():
async with self.map_update_lock:
# 二次检查
if time.time() - self.map_last_update < 300:
return self.ip_container_map_cache

# 执行耗时的 Docker 查询
# 建议:在一个线程池中运行同步的 docker client 操作,避免阻塞事件循环
loop = asyncio.get_running_loop()
self.ip_container_map_cache = await loop.run_in_executor(
None, self._create_ip_to_container_mapping
)
self.map_last_update = time.time()

return self.ip_container_map_cache

3. 优化方案二:物理拓扑带宽检测的“非阻塞化”

在 RealTopologyService 中,物理连接的带宽检测(tc 命令)是极其耗时的。绝对不能在用户请求 API 时实时去跑 tc 命令。

优化策略: 将带宽数据设为“最终一致性”。主接口只返回拓扑结构,带宽字段先返回缓存值或 “Checking…”,后台任务专门负责轮询更新带宽。

codePython

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
class RealTopologyService:
def __init__(self):
# ...
self.bandwidth_cache = {} # Key: "container_ip", Value: "100Mbit"

async def _get_dynamic_physical_topology_from_containers(self):
# ... 前面生成 nodes 和 links 的逻辑保持不变 ...

# --- 优化点:移除实时 await _get_link_bandwidth ---
for link in links:
target_id = link['target']
target_node = nodes_by_id[target_id]
container_name = target_node['container_name']
link_ip = target_node['networks'].get(link['shared_network'])

# 1. 尝试从缓存获取
cache_key = f"{container_name}::{link_ip}"
cached_bw = self.bandwidth_cache.get(cache_key)

if cached_bw:
link['bandwidth'] = cached_bw
else:
link['bandwidth'] = "Querying..."
# 2. 触发后台更新任务 (Fire and Forget)
asyncio.create_task(self._update_bandwidth_cache(container_name, link_ip, cache_key))

return nodes, links

async def _update_bandwidth_cache(self, container_name, link_ip, cache_key):
"""后台单独更新带宽"""
bw = await self._get_link_bandwidth(container_name, link_ip)
self.bandwidth_cache[cache_key] = bw

4. 优化方案三:Neo4j 并发查询

Neo4j 的 Python Driver (尤其是 Bolt 协议) 支持并发。执行层和共识层的查询是独立的,可以使用 asyncio.gather 同时发起查询。

注意:这需要你的 database_manager 支持异步操作。如果底层是同步驱动,可以用 run_in_executor 包装。

codePython

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
# 在 EthereumTopologyHandler 中
async def _get_real_topology_from_neo4j(self) -> Tuple[Dict, Dict]:
try:
# ... 连接检查 ...

# 定义查询函数
async def fetch_exec():
# 这里假设 database_manager 提供了某种异步执行方式,
# 或者我们将同步的 session.run 放入线程池
def run_query():
with self.database_manager.neo4j.session() as session:
result = session.run("MATCH (n:ExecNode)...") # 填入完整的 Cypher
return {r['node_id']: ... for r in result} # 处理结果

loop = asyncio.get_running_loop()
return await loop.run_in_executor(None, run_query)

async def fetch_cons_and_validators():
def run_query():
with self.database_manager.neo4j.session() as session:
# 合并共识节点和验证者的查询逻辑,减少一次 session 创建开销
cons_result = session.run("MATCH (n:ConsNode)...")
# ... 处理共识 ...
validator_result = session.run("MATCH ... validators ...")
# ... 处理验证者 ...
return cons_data

loop = asyncio.get_running_loop()
return await loop.run_in_executor(None, run_query)

# 并发执行
exec_nodes_data, cons_nodes_data = await asyncio.gather(
fetch_exec(),
fetch_cons_and_validators()
)

return exec_nodes_data, cons_nodes_data

except Exception as e:
# ... Error handling

5. 优化方案四:Cypher 查询语句优化

目前的 Cypher 使用了 OPTIONAL MATCH 和 collect,这在数据量大时比较慢。

原查询:

codeCypher

1
2
3
MATCH (n:ExecNode)
OPTIONAL MATCH (n)-[r:EXEC_PEERS_WITH]->(p:ExecNode)
RETURN ... collect(...)

如果在节点很多的情况下,这个查询会扫描整个图。

优化建议:

  1. 分批次:如果节点超过 1000 个,不要一次性 collect 所有 Peers。前端通常不需要展示所有几千条连线(会卡死浏览器)。可以限制返回的 Peer 数量,例如 LIMIT 10。

  2. 索引:确保 node_id, ip 在 Neo4j 中建立了索引 (CREATE INDEX ON :ExecNode(node_id)).

  3. 只查 ID:先查出所有 Node 的基础信息,再查 Relation 表(Links),在内存中组装,通常比 Graph DB 做复杂的聚合(Map/Collect)要快。

6. 综合改进后的代码结构建议

这里给出一个集成优化思路的 EthereumTopologyHandler 核心方法重构示例:

codePython

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
class EthereumTopologyHandler(TopologyProvider):
# ... init ...

async def get_topology(self) -> Dict[str, Any]:
"""获取完整的以太坊拓扑 (优化版)"""
# 1. 检查拓扑缓存 (Short TTL: e.g., 5s)
# 拓扑结构变化很快,缓存时间短一点
if self._is_cache_valid("full_ethereum_topology", ttl=5):
return self.cache["full_ethereum_topology"]

# 2. 并行获取数据
# A. 获取 Docker 映射 (从长效缓存或后台任务获取,极快)
# B. 获取 Neo4j 数据 (并发查询)

task_docker = self._get_ip_to_container_map_optimized() # 优化点1
task_neo4j = self._get_real_topology_from_neo4j() # 优化点3 (并发内部实现)

ip_map, (exec_data, cons_data) = await asyncio.gather(task_docker, task_neo4j)

# 3. 转换数据 (CPU 密集型)
# 如果节点数非常多 (>5000),可以考虑放入 ProcessPoolExecutor
nodes, links = self._convert_topology_format_optimized(exec_data, cons_data, ip_map)

topology_data = {
'nodes': nodes, 'links': links, 'timestamp': time.time(),
# ...
}

# 更新缓存
self.cache["full_ethereum_topology"] = topology_data
self.last_update = time.time()

return topology_data

def _convert_topology_format_optimized(self, exec_data, cons_data, ip_map):
"""
优化点:
1. 使用 ip_map.get 避免重复循环
2. 预计算 positions 字典,避免列表遍历查找
"""
nodes = []
links = []
# 使用字典加速查找: key=node_id, value={idx, type, ...}
node_lookup = {}

# ... 处理 Exec 节点 ...
for i, (nid, data) in enumerate(exec_data.items()):
# 直接查预热好的 Map,O(1)
container = ip_map.get(data['ip'], f"exec-{data['ip']}")
# ... 创建 node ...
node_lookup[nid] = {'type': 'execution', 'id': f'{nid}-exec'}

# ... 处理 Cons 节点 ...

# ... 处理连接 ...
# 只需要遍历一次 exec_data 的 peers,利用 node_lookup 快速验证
for nid, data in exec_data.items():
source_full_id = f"{nid}-exec"
for peer in data.get('peers', []):
target_nid = peer['peer_id']
# O(1) 检查目标是否存在
if target_nid in node_lookup and node_lookup[target_nid]['type'] == 'execution':
links.append({
'source': source_full_id,
'target': f"{target_nid}-exec",
# ...
})

return nodes, links

总结

要进行性能优化,最关键的修改是:

  1. Docker 映射:改为后台定时更新或长缓存,不要每秒都去 inspect 容器。

  2. 物理带宽 (tc):改为异步/后台获取,不要阻塞 API 响应。

  3. Neo4j:使用并发查询,并限制 Cypher 复杂度。


攻击方式(Attack Strategies)

系统目前支持以下6种攻击方式:

1. 节点断网攻击(Node Disconnection Attack)

通过网络隔离手段切断目标节点与其他节点的连接。支持的方法包括:

  • interface_down: 禁用网络接口
  • route_flush: 清除路由表
  • firewall_block: 防火墙阻断
  • p2p_block: P2P连接阻断

2. 通信干扰攻击(Communication Interference Attack)

通过大量无效通信干扰目标节点正常通信。支持的方法包括:

  • json_rpc_flood: JSON-RPC请求泛洪
  • p2p_flood: P2P消息泛洪
  • memory_exhaustion: 内存耗尽攻击

3. 时间攻击(Timestamp Attack)

针对共识机制的时间同步进行攻击。支持的方法包括:

  • time_shift: 时间偏移
  • ntp_block: 阻断NTP时间同步
  • time_drift: 时间漂移

4. 简化Sybil攻击(Simplified Sybil Attack)

创建虚假节点来影响网络。可以配置:

  • 虚假节点数量(1-20)
  • 节点类型(轻节点、全节点、验证者节点)
  • 网络环境(主网、测试网、开发网)
  • 连接真实节点选项

5. 存储攻击(Storage Attack)

针对节点存储系统的攻击。支持的方法包括:

  • disk_fill: 磁盘空间填充
  • database_corruption: 数据库损坏
  • state_pollution: 状态污染
  • chain_data_spam: 链上数据垃圾信息

6. Geth/Lighthouse客户端攻击(Geth/Lighthouse Attack)

针对特定以太坊客户端的攻击。支持的方法包括:

  • process_kill: 终止进程
  • db_corruption: 数据库损坏
  • port_blocking: 端口阻断
  • config_modification: 配置文件修改

攻击模式(Execution Modes)

系统支持三种攻击执行模式:

1. 一次性攻击(One-shot)

执行一次攻击,持续指定时间后自动清理恢复。 配置参数:

  • duration_seconds: 攻击持续时间(秒)

2. 重复攻击(Repeated)

按指定间隔重复执行多次攻击。 配置参数:

  • interval_seconds: 攻击间隔时间(秒)
  • repeat_count: 重复次数
  • duration_seconds: 每次攻击持续时间(秒)

3. 持续攻击(Continuous)

持续不断地执行攻击,直到手动停止。 配置参数:

  • interval_seconds: 攻击间隔时间(秒)
  • duration_seconds: 每次攻击持续时间(秒)

动态目标攻击

系统还支持一种特殊的动态目标攻击功能,可以根据网络拓扑分析结果自动选择攻击目标。支持的中心性指标包括:

  • 度中心性(Degree Centrality)
  • 介数中心性(Betweenness Centrality)
  • 接近中心性(Closeness Centrality)
  • 特征向量中心性(Eigenvector Centrality)

通过这些攻击方式和模式的组合,系统可以模拟各种真实的以太坊网络攻击场景,帮助评估网络的安全性和鲁棒性。


1. 发起普通攻击 (Standard Attack)

此接口用于对明确指定的静态 IP 列表发起攻击。

Prompt / 接口说明:

  • 接口地址: POST /api/simulate

  • 功能: 发起针对特定静态目标(IP/ID列表)的攻击模拟。

  • 逻辑约束:

    1. parameters.target_nodes 必须是字符串数组 [“ip1”, “ip2”]。

    2. 支持所有三种执行模式 (one_shot, repeated, continuous)。

  • 请求体构建规则:

    • Level 1 (执行配置): 决定攻击的时间维度。

      • 若是 one_shot: 仅需 duration_seconds。

      • 若是 repeated: 需额外提供 interval_seconds 和 repeat_count。

    • Level 2 (策略参数): 决定攻击的具体手段。

      • 必须包含 strategy 字段(枚举值)。

      • 其余字段根据 strategy 变化(如 storage_attack 需要 size_mb,而 node_disconnection 不需要)。

请求示例 (JSON):

codeJSON

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 场景:对两个节点进行重复的 P2P 洪水攻击
{
"execution_config": {
"mode": "repeated",
"duration_seconds": 30,
"interval_seconds": 60,
"repeat_count": 5
},
"parameters": {
"strategy": "communication_interference",
"method": "p2p_flood",
"intensity": "high",
"target_nodes": ["192.168.1.10", "192.168.1.11"]
}
}

2. 发起自适应攻击 (Adaptive Attack)

此接口用于动态目标攻击,系统会在每一轮攻击开始前重新计算受害者(例如:总是攻击网络中连接数最多的节点)。

Prompt / 接口说明:

  • 接口地址: POST /api/simulate/adaptive

  • 功能: 发起自适应攻击,目标由后端实时计算。

  • 关键区别:

    1. 不支持 one_shot 模式(因为一次性攻击不需要”自适应”变化)。必须是 repeated 或 continuous。

    2. parameters.target_nodes 必须是特定格式的字符串指令,以 dynamic: 开头。

  • 目标指令语法: dynamic:{指标}:{选择策略}

    • 示例: dynamic:degree:top:5 (度中心性最高的前5个)

    • 示例: dynamic:betweenness:highest (介数中心性最高的1个)

请求示例 (JSON):

codeJSON

1
2
3
4
5
6
7
8
9
10
11
12
13
// 场景:持续攻击网络中度中心性最高的前5个节点
{
"execution_config": {
"mode": "continuous",
"duration_seconds": 60,
"interval_seconds": 30
},
"parameters": {
"strategy": "node_disconnection",
"method": "firewall_block",
"target_nodes": "dynamic:degree:top:5" // 注意这里是字符串
}
}

3. 前端分流逻辑 (Frontend Logic)

这是前端 Vue 组件如何决定调用哪个接口的核心逻辑说明。

Prompt / 逻辑说明:

前端在点击”发起攻击”按钮时,必须执行以下判断逻辑:

  1. 检查目标类型:

    • 获取用户在表单中输入的目标配置。

    • 如果目标是字符串指令且以 dynamic: 开头 -> 标记为 isDynamic。

    • 如果目标是手动输入的 IP 列表 -> 标记为 isStatic。

  2. 检查执行模式:

    • 获取用户选择的模式 (one_shot, repeated, continuous)。
  3. 路由决策树:

    • IF (isDynamic == True AND mode == one_shot):

      • 报错: 自适应攻击不支持一次性模式。
    • IF (isDynamic == True AND mode != one_shot):

      • 调用接口: POST /api/simulate/adaptive

      • 注意: 此时 target_nodes 字段发送字符串。

    • ELSE (即静态目标,无论什么模式):

      • 调用接口: POST /api/simulate

      • 注意: 此时 target_nodes 字段必须转换为数组 [] 发送。


4. 停止攻击 (Stop Attack)

Prompt / 接口说明:

  • 接口地址: DELETE /api/simulations/{attack_id}

  • 功能: 立即终止一个正在运行 (running) 或挂起 (pending) 的攻击任务。

  • 适用场景:

    • 用户点击”紧急停止”按钮。

    • 用于中断 continuous (无限持续) 类型的攻击。

    • 用于中断剩余轮次尚未执行的 repeated 攻击。

  • 后端行为:

    • 取消对应的 asyncio.Task。

    • 执行清理逻辑(如恢复防火墙规则、删除垃圾文件)。

    • 将数据库中的状态更新为 stopped。


5. 状态轮询与监控 (Monitoring)

Prompt / 接口说明:

为了在前端展示”实时状态”和”系统日志”,需要配合使用以下两个接口:

  1. 获取活跃列表: GET /api/simulations/active

    • 用途: 判断当前是否有攻击在跑 (isRunning 状态)。

    • 频率: 建议每 3-5 秒轮询一次。

    • 返回: 包含 progress (进度百分比) 和 current_round (当前轮次)。

  2. 获取详情/日志: GET /api/simulations/{attack_id}

    • 用途: 获取特定攻击的详细日志流。

    • 返回: 包含 logs 数组 ([“Attack started”, “Round 1 finished”])。

    • 前端展示: 将 logs 渲染到控制台面板中。


总结:数据结构对照表 (Type Mapping)

参数字段 描述 类型限制
execution_config
mode 执行模式 “one_shot” \ “repeated” \ “continuous”
duration_seconds 单次持续时长 Integer (秒)
interval_seconds 轮次间隔 Integer (秒), 仅 repeated/continuous 有效
repeat_count 重复次数 Integer, 仅 repeated 有效
parameters
strategy 攻击策略 “node_disconnection” \ “storage_attack” …
target_nodes 攻击目标 Array [str] (普通) OR String dynamic:… (自适应)
method 具体手段 依赖于 strategy (如 firewall_block, disk_fill)
其他参数 依赖于 strategy (如 size_mb, intensity)

1. 全局枚举定义 (Global Enums)

这些枚举值用于填充请求体中的特定字段。

枚举类型 字段名 可选值 (Value) 说明
执行模式 mode one_shot 一次性:执行一次,持续指定时间后恢复。
repeated 重复执行:按间隔重复执行多次。
continuous 持续执行:按间隔无限执行,直到手动停止。
攻击策略 strategy node_disconnection 节点断连攻击
communication_interference 通信干扰攻击
storage_attack 存储耗尽攻击
timestamp_attack 时间/NTP攻击
simplified_sybil_attack 简化版女巫攻击
geth_lighthouse_attack 客户端特定攻击 (Geth/Lighthouse)

2. 执行配置 (execution_config)

根据 mode 的不同,所需字段不同。注意:自适应攻击接口 (/simulate/adaptive) 不支持 one_shot。

模式 (Mode) 字段名 类型 必填 默认值 约束/说明
通用 duration_seconds Int 30 攻击生效持续时间 (秒),>=1
Repeated interval_seconds Int 60 轮次间隔时间 (秒),>=1
(重复) repeat_count Int - 重复执行的总轮数,>=1
Continuous interval_seconds Int 60 轮次间隔时间 (秒),>=1

3. 策略参数详情 (parameters)

此部分为多态结构,根据 strategy 字段的值,JSON 结构发生变化。

3.1 节点断连 (node_disconnection)

字段名 类型 必填 默认值 描述/选项
method String - interface_down (网卡下线), route_flush (清空路由), firewall_block (防火墙), p2p_block (P2P阻断)
target_nodes Array/Str - 静态IP列表 或 动态指令 (见第4节)

3.2 通信干扰 (communication_interference)

字段名 类型 必填 默认值 描述/选项
method String - json_rpc_flood (RPC泛洪), p2p_flood (P2P泛洪), memory_exhaustion (内存耗尽)
intensity String medium low, medium, high, extreme
target_nodes Array/Str - 静态IP列表 或 动态指令

3.3 存储攻击 (storage_attack)

字段名 类型 必填 默认值 描述/选项
method String - disk_fill (填充), database_corruption (脏数据), state_pollution (状态污染), chain_data_spam (链上垃圾)
size_mb Int 1000 填充大小 (MB),100 - 10000
file_count Int 100 生成文件数量,10 - 1000
target_nodes Array/Str - 静态IP列表 或 动态指令

3.4 时间攻击 (timestamp_attack)

字段名 类型 必填 默认值 描述/选项
method String - time_shift (平移), ntp_block (NTP阻断), time_drift (漂移)
time_shift String +1 hour 偏移量 (如 +1 hour, -30 minutes),仅 time_shift 方法有效
drift_seconds Int 3600 漂移秒数,-86400 到 86400,仅 time_drift 方法有效
target_nodes Array/Str - 静态IP列表 或 动态指令

3.5 女巫攻击 (simplified_sybil_attack)

字段名 类型 必填 默认值 描述/选项
fake_node_count Int 5 虚假节点数量 (1-20)
node_type String light light (轻节点), full (全节点), validator (验证者)
network String testnet mainnet, testnet, devnet
connect_to_real Bool True 是否连接真实节点
min_connections Int 3 最小连接数 (0-10)
target_nodes - - - 注意:此策略通常不需要指定具体目标节点

3.6 客户端攻击 (geth_lighthouse_attack)

字段名 类型 必填 默认值 描述/选项
method String - process_kill, db_corruption, port_blocking, config_modification
attack_type String - geth, lighthouse (指定攻击的客户端类型)
target_nodes Array/Str - 静态IP列表 或 动态指令

4. 目标节点配置 (target_nodes)

target_nodes 字段在不同接口下有严格的格式要求。

接口端点 格式类型 数据结构示例 说明
/api/simulate 静态列表 [“192.168.1.10”, “node_id_123”] 明确指定要攻击的节点列表。
/api/simulate/adaptive 动态指令 “dynamic:degree:top:5” 字符串格式,后端自动计算目标。

动态指令语法: dynamic:{指标}:{选择器}

  1. 指标 (Metric):

    • degree (度中心性)

    • betweenness (介数中心性)

    • closeness (接近中心性)

    • eigenvector (特征向量中心性)

  2. 选择器 (Selector):

    • highest (选最高的1个)

    • top:N (选前 N 个,N为数字)


5. 防护配置 (/defense/enable)

字段名 类型 必填 示例 说明
enabled Bool true 是否启用防护
rules Object {“rate_limit”: 100} 防护规则字典,具体Key由后端逻辑决定

6. 响应结构概览

所有接口通常遵循统一的响应格式:

codeJSON

1
2
3
4
5
{
"status": "success", //"error"
"message": "操作描述",
"data": { ... } // 具体业务数据
}

关键数据字段 (data):

  • attack_id: (String) 攻击任务的唯一标识符。

  • status: (Enum) pending, running, completed, failed, stopped, cancelled。

  • logs: (Array[Str]) 攻击日志列表。

{
“nodes”: [
{
“id”: “0xdAC17F958D2ee523a2206206994597C13D831ec7”,
“name”: “Tether USD (USDT)”,
“type”: “ERC20”,
“ip_address”: “”,
“status”: “active”,
“layer”: “contract”,
“container_name”: null,
“neighbor_count”: 5
},
{
“id”: “0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48”,
“name”: “USD Coin (USDC)”,
“type”: “ERC20”,
“ip_address”: “”,
“status”: “active”,
“layer”: “contract”,
“container_name”: null,
“neighbor_count”: 3
},
{
“id”: “0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984”,
“name”: “Uniswap V3: Router”,
“type”: “Router”,
“ip_address”: “”,
“status”: “active”,
“layer”: “contract”,
“container_name”: null,
“neighbor_count”: 8
},
{
“id”: “0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45”,
“name”: “Uniswap V3: SwapRouter02”,
“type”: “SwapRouter”,
“ip_address”: “”,
“status”: “active”,
“layer”: “contract”,
“container_name”: null,
“neighbor_count”: 6
},
{
“id”: “0xE592427A0AEce92De3Edee1F18E0157C05861564”,
“name”: “Uniswap V3: Quoter”,
“type”: “Quoter”,
“ip_address”: “”,
“status”: “active”,
“layer”: “contract”,
“container_name”: null,
“neighbor_count”: 4
},
{
“id”: “0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D”,
“name”: “Uniswap V2: Router”,
“type”: “Router”,
“ip_address”: “”,
“status”: “active”,
“layer”: “contract”,
“container_name”: null,
“neighbor_count”: 7
},
{
“id”: “0x881D40237659C251811CEC9c364ef91dC08D300C”,
“name”: “Curve: 3pool Controller”,
“type”: “CurvePool”,
“ip_address”: “”,
“status”: “active”,
“layer”: “contract”,
“container_name”: null,
“neighbor_count”: 4
},
{
“id”: “0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7”,
“name”: “Curve: 3pool Gauge”,
“type”: “Gauge”,
“ip_address”: “”,
“status”: “active”,
“layer”: “contract”,
“container_name”: null,
“neighbor_count”: 2
}
],
“links”: [
{ “source”: “0xdAC17F958D2ee523a2206206994597C13D831ec7”, “target”: “0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45”, “type”: “transfer_approve” },
{ “source”: “0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45”, “target”: “0xdAC17F958D2ee523a2206206994597C13D831ec7”, “type”: “swap_out” },
{ “source”: “0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45”, “target”: “0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48”, “type”: “swap_in” },
{ “source”: “0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48”, “target”: “0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984”, “type”: “call” },
{ “source”: “0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984”, “target”: “0xE592427A0AEce92De3Edee1F18E0157C05861564”, “type”: “quote” },
{ “source”: “0xdAC17F958D2ee523a2206206994597C13D831ec7”, “target”: “0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D”, “type”: “approve” },
{ “source”: “0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D”, “target”: “0xdAC17F958D2ee523a2206206994597C13D831ec7”, “type”: “swap” },
{ “source”: “0xdAC17F958D2ee523a2206206994597C13D831ec7”, “target”: “0x881D40237659C251811CEC9c364ef91dC08D300C”, “type”: “deposit” },
{ “source”: “0x881D40237659C251811CEC9c364ef91dC08D300C”, “target”: “0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7”, “type”: “stake” }
],
“timestamp”: 1765095677.891234,
“data_source”: “real_web3”,
“topology_type”: “contract”
}

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
graph TB
A[ETH前端] --> B[src]
A --> C[package.json]
A --> D[index.html]
A --> E[vite.config.ts]
A --> F[README.md]
A --> G[public]

B --> H[__tests__]
B --> I[api]
B --> J[assets]
B --> K[components]
B --> L[composables]
B --> M[router]
B --> N[services]
B --> O[types]
B --> P[utils]
B --> Q[views]
B --> R[App.vue]
B --> S[main.ts]

I --> T[api_docs]
I --> U[attack]

J --> V[styles]
V --> W[global.css]
V --> X[tailwind.css]

K --> Y[blockchain]
K --> Z[common]
K --> AA[layout]
K --> AB[tabs]
K --> AC[topology]

Y --> AD[BlockchainCanvas.vue]
Y --> AE[BlockchainInfoPanel.vue]
Y --> AF[BlockchainModal.vue]
Y --> AG[BlockchainVisualization.vue]
Y --> AH[composables]
Y --> AI[types]

AH --> AJ[useBlockchainAPI.ts]
AH --> AK[useBlockchainAnimations.ts]
AH --> AL[useBlockchainData.ts]
AH --> AM[useBlockchainEvents.ts]
AH --> AN[useBlockchainRenderer.ts]
AH --> AO[useBlockchainScrolling.ts]

Z --> AP[ContainerTerminal.vue]
Z --> AQ[DEP-TERM.vue]
Z --> AR[StandaloneTerminal.vue]
Z --> AS[Terminal.vue]
Z --> AT[websocket_terminal8080.vue]

AA --> AU[DashboardHeader.vue]
AA --> AV[LeftPanel.vue]
AA --> AW[PanelSplitter.vue]
AA --> AX[RightPanel.vue]

AB --> AY[AttackMonitoringTab.vue]
AB --> AZ[Attack_sys]
AB --> BA[BlockchainBrowserTab.css]
AB --> BB[BlockchainBrowserTab.vue]
AB --> BC[ContainerListTab.vue]
AB --> BD[NetworkTopologyTab.vue]
AB --> BE[sections]
AB --> BF[tabstyle.css]

AZ --> BG[AttackSystemTab.vue]
BE --> BH[Network-analysis.vue]
BE --> BI[NodeInfoPanel.vue]
BE --> BJ[RealTimeMonitoring.vue]

AC --> BK[ContractTopology.vue]
AC --> BL[Ethereum_Topology]
AC --> BM[Physical_Topology]
AC --> BN[TopologyVisualization.vue]
AC --> BO[TransactionTopology.vue]
AC --> BP[composables]
AC --> BQ[types]

BL --> BR[types]
BL --> BS[workers]
BL --> BT[EthereumTopology_new.vue]
BL --> BU[useD3Renderer.ts]
BL --> BV[use_topology_core.ts]
BL --> BW[use_topology_visuals.ts]

BM --> BX[composables]
BM --> BY[PhysicalTopology.vue]

BP --> BZ[index.ts]
BP --> CA[topology.css]
BP --> CB[useTopologyAPI.ts]
BP --> CC[useTopologyData.ts]
BP --> CD[useTopologyRendererBase.ts]

L --> CE[useDashboardData.ts]
L --> CF[useDashboardLayout.ts]
L --> CG[useDashboardTabs.ts]

M --> CH[index.ts]

N --> CI[analysis.ts]
N --> CJ[api.ts]
N --> CK[apiService.ts]
N --> CL[attack.ts]
N --> CM[blockchain.ts]
N --> CN[daily-operations.ts]
N --> CO[device-monitoring.ts]
N --> CP[execution.ts]
N --> CQ[foundation.ts]
N --> CR[monitoring.ts]
N --> CS[readme.md]
N --> CT[root-api.ts]
N --> CU[security.ts]
N --> CV[temporal.ts]
N --> CW[topology.ts]

O --> CX[topology.ts]

P --> CY[http.ts]
P --> CZ[index.ts]
P --> DA[types.ts]

Q --> DB[BlockchainTest.vue]
Q --> DC[Dashboard.vue]