CSM 架构 [内部评审]
本文详细介绍了 Lido 协议中的社区质押模块(CSM)的架构设计。CSM 是一个无许可的质押模块,允许社区质押者(如 solo staker)通过提供债券作为抵押来成为节点运营商,参与 Lido 以太坊质押。文章从节点运营商注册、债券机制、质押分配队列、奖励分配(包括 Performance Oracle 驱动的奖励树)、惩罚机制(如削减、MEV 窃取、取款余额不足)以及验证者退出流程等方面进行了深入剖析。CSM 采用了可降低单验证者债券成本的曲线函数、FIFO 质押分配队列、基于 EIP-4788 的无许可惩罚报告等创新设计,并引入了紧急制动和早期采纳期等机制来保障协议安全与去中心化。该文档是理解 Lido 模块化质押生态和未来无许可验证者参与方式的重要技术参考。
2 年前更改
🏛️ CSM 架构

本文档描述了社区质押模块(CSM)的架构。该模块本身实现了社区质押全景中描述的构想。本文档的主要目的是详细描述 CSM 的架构和主要设计决策。
在本文档中,术语 validator、key、validator key 和 deposit data 含义相同。
∑ TL;DR
CSM 是一个无许可质押模块,旨在吸引社区质押者作为 Node Operator 参与 Lido on Ethereum 协议。加入 CSM 成为 Node Operator 的唯一要求是能够运行验证者并提供 bond。在 keys 有效的前提下,质押份额按照 keys 的提供顺序分配给验证者 keys。Bond 与实际验证者质押不直接关联,而是被视为安全抵押品。Bond 是 Node Operator 的一项属性;因此,它是 Node Operator 所有验证者的抵押品。这使得 bond 可以递减:Node Operator 拥有的验证者越多,单个验证者所需的 bond 就越少。Node Operator 从 bond rebase 以及质押奖励中 Node Operator 应得的部分获得奖励。如果验证者的表现高于阈值,Node Operator 的质押奖励部分会被社会化(平均化)。因累积的 CL 罚没导致余额降至存款余额以下,以及被窃取的 EL 奖励,将从 Node Operator 的 bond 中没收。Node Operator 应在协议请求时执行验证者退出,也可以自愿退出。
📓 词汇表
- Staking router(SR)是 Lido on Ethereum 协议中的一个智能合约,负责在不同模块之间进行质押分配和奖励分发;
- Staking module(SM)是连接到 staking router 的一个智能合约或一组智能合约,它:
- 维护底层的 operator 和验证者集合;
- 负责 operator 的接入/退出;
- 维护验证者的存款、提款和退出;
- 维护模块和参与者的费用结构及分配等;
- Bond 是 Node Operator 提供的 ETH 或 stETH,作为不良行为或表现不佳的抵押品;
- Lido DAO 是一个去中心化自治组织,通过治理代币(LDO)的投票权来决定受控流动性质押协议的关键参数;
- Node Operator(NO)是运行验证者的个人或实体;
Lido是 Lido on Ethereum 协议的核心合约,存储协议状态、接受用户提交,并包含 stETH 代币;- stETH 是由
Lido铸造的 ERC-20 代币,代表totalPooledEther的一份份额; - Deposit data 指提交给
DepositContract的、由验证者公钥和存款签名组成的结构。在文本中,该术语也可称为keys。验证者私钥完全由 Node Operator 创建、存储和管理; DepositContract是用于验证者存款的官方合约;DepositSecurityModule或 DSM 是一组智能合约和链下组件,用于缓解漏洞;- 当当前 Node Operator 的 bond 不足以覆盖某个验证者时,该验证者被视为 “unbonded”;
- 如果验证者在收到协议退出信号后未能及时退出,则该验证者被视为 “stuck”;
- Curated module 是 Lido 的首个 staking module,以前称为 Node Operators Registry;
- EasyTrack 是一组智能合约和一种基于否决权的替代投票模型,用于简化 DAO 的日常操作;
- AccountingOracle 是一个合约,收集由链下 oracle 提交的关于 Lido 参与的验证者及其余额状态、协议金库(即提款和执行层奖励金库)中累积的资金量、已退出和 stuck 验证者的数量、协议可以处理的提款请求数量等信息,并分发 Node Operator 奖励;
🌎 基本信息
CSM 是一个提供带 bond 的无许可准入的质押模块。该模块旨在成为独立的社区质押者(solo stakers 或 home stakers)进入 Lido on Ethereum 协议(LoE)Node Operator 集合的清晰路径。Bond 要求是一种至关重要的安全和对齐工具,使得在无损底层质押协议(LoE)安全性或可靠性的前提下实现无许可准入成为可能。
🤓 模块特性
所有 staking modules 都应遵循相同的 IStakingModule 接口。这不可避免地导致模块之间有许多共同或相似的组件和逻辑。CSM 也不例外。例如,key 存储组件基于现有的 Curated module。不过,有几个方面是不同的,值得单独提及。
Exited 和 Withdrawn
Curated module 将验证者的 “exited” 状态(包括 Slashed and Exited 和 Unslashed and Exited)视为会计核算中最后一个有意义的状态,因为在此状态之后,验证者不再对 Beacon 链上的任何职责负责(延迟的同步委员会参与的罕见情况除外)。而 CSM 需要知道每个验证者的确切提款余额,以决定 bond 罚没。因此,该模块仅使用 accounting oracle 报告的 “exited” 计数器来向 staking router 返回正确数量的 “active” keys,并实现了无许可的报告方法,一旦验证者处于 Withdrawable 状态,即可报告验证者的提款余额(实际报告在验证者完成提款后进行)。
质押分配队列
Node Operator 必须提供 bond 才能向 CSM 上传新的验证者 key。按照与 bond 提交顺序相似的顺序来分配质押是合理的。为此,使用了 FIFO(先进先出)质押分配队列。一旦 Staking Router 请求 keys 进行存款,就会从队列中返回接下来的 $X$ 个 keys,保持 bond 提交顺序。
针对 “stuck” keys 的替代措施
Node Operator 存在 “stuck” keys,表明违反了 Lido 退出政策。在这种情况下,模块应对违反政策的 Node Operator 采取措施。CSM 使用的措施与 Curated module 不同。这些措施将在下面的相应章节中描述。
Node Operator 结构
CSM 中的 Node Operator 数据结构与 Curated module 类似,但有以下几个细微差别:
- 对于无许可模块,
name属性因冗余而被省略; rewardAddress仅用作奖励和超额 bond 申领的接收者;- 引入了一个新属性
managerAddress,Node Operator 应从该地址执行方法调用; - 引入了一个新属性
totalWithdrawnKeys,用于统计每个 Node Operator 已提款的 keys 总数; - 引入了一个新属性
depositableValidatorsCount,用于统计当前有资格进行存款的 deposit data 数量; - 引入了一个新属性
enqueuedCount,用于跟踪未入队的 keys 数量;
🔄 CSM 验证者生命周期
描述 CSM 架构的最佳方式是跟随验证者的生命周期。
🚪 第 1 步:加入 CSM
加入 CSM 流程
Node Operator 创建
要成为 CSM 中的 Node Operator,或为现有 Node Operator 注册新的验证者,至少需要提供一个 validator pubkey、相应的 deposit signature 以及相应的 bond 金额。
Deposit data 准备和上传
CSM 接受与 Curated module 相同格式的 deposit data(validator pubkey + deposit signature),主要区别在于要求在提交 deposit data 之前或同时提交 bond。
deposit signature 必须对 (deposit_message, domain) 的根进行签名。其中 domain 用于标识链,deposit_message 具有以下元组形式:
validator pubkey;- 带有实际
Lido Withdrawal Vault 合约地址的withdrawal_credentials; 32 ETH 金额;
Bond
Bond 是 Node Operator 的一项属性,而不是验证者的。Bond 以 stETH 形式存储。Node Operator 可以以 ETH、stETH 和 wstETH 形式提交 bond 代币。提交时,提供的 ETH 会被质押,wstETH 会被解包,以确保 stETH 是 bond 的唯一形式。
所需 bond 的总量取决于 Node Operator 验证者的总数,由函数 getBondAmountByKeysCount(keysCount) 决定。

为了方便读者,可以针对验证者数量(而非总验证者数)重新绘制上图。

可能存在多个 bond “曲线”(getBondAmountByKeysCount 函数的实现)。所有 Node Operator 在创建时都会被分配一个默认曲线。DAO 可以为 Node Operator 设置自定义曲线。
现有 Node Operator 可以在不上传 deposit data 的情况下补充 bond,以弥补罚没损失,或预先存入 bond 资金。
Unbonded 验证者
引入 “unbonded” 一词来指代 bond 无法完全覆盖的验证者。考虑到 bond 对所有 Node Operator 验证者是通用的,可以通过下图所示的方式来确定 unbonded 验证者。在示例中,验证者 $N+1$ 是 unbonded 的。

潜在的 stETH 负 rebase 后果
由于 bond 以 stETH 存储,存在因 stETH 负 rebase 导致 bond 金额减少的风险。这可能导致一些 Node Operator 无法申领奖励(因为实际 bond 低于要求),甚至导致验证者变为 unbonded。此问题在 Lido ADR 中的 Bond 机制中有详细描述。对于本文档而言,值得一提的是,由于 stETH 负 rebase 的概率很低,且 Lido DAO 拥有一笔专门基金可供可能用于补偿,因此 CSM 无需采取额外措施。
Deposit data 验证和失效(又称 vetting 和 unvetting)
鉴于即将到来的 DSM v1.5 升级,CSM 将采用乐观 vetting方法。上传的 deposit data 将被视为有效,除非 DSM 报告其无效。如果检测到无效的 deposit data,DSM 会调用 decreaseOperatorVettedKeys,将 vettedKeys 指针设置为第一个无效 deposit data 之前的 deposit data。
可存款 keys
有几个因素决定是否可以使用相应的 deposit data 进行存款。此信息反映在 Node Operator 的 depositableKeys 属性中。该属性表示从 Node Operator 的 key 存储中最后一个已存款记录开始顺序提取的、可供 staking router 存款的 deposit data 记录数量。该数量按以下方式确定:
- 未设置
targetLimit-> $vettedKeys - depositedKeys - unbondedKeys$ - 设置了
targetLimit-> $\min(vettedKeys, targetLimit) - depositedKeys - unbondedKeys$;如果 Node Operator 有 $stuckKeys \neq 0$,则为 0。
质押分配队列
CSM 中的质押分配队列是传统的 FIFO(先进先出)队列。Node Operator 以 {noId, keysCount} 批次在队列中占位并等待轮到自己。

当队列到达 Node Operator 的批次时,CSM 使用以下公式检查该批次中可以存入多少 keys:$\min(depositableKeys, keysInBatch)$。

可能存在一种情况:Node Operator 有一些 keys 不在队列中,因为它们在队列迭代时当时不可存款因而被跳过。normalizeQueue 方法允许 Node Operator 将所有可存款的 keys 重新放回队列。
CSM 中有关 deposit data 存储的指针有几个。其中,有 totalKeys 和 vettedKeys 指针。在乐观 vetting 方法下,如果没有关于存在无效 deposit data 的报告,这两个指针大多数时候应该是同步的($totalKeys = vettedKeys$)。因此,deposit data 有两种方式进入队列:
- 上传 deposit data 时,如果 $totalKeys = vettedKeys$;
- 调用
normalizeQueue方法后,如果某些 keys 在上传时未放入队列(上传时 $totalKeys \neq vettedKeys$),或者在队列迭代期间被跳过;
有一些方法可以检查接下来的 $X$ 个元素,并移除那些不包含可存款 keys 的元素。这些方法是必需的,以确保即使在导致队列被不可存款 keys 严重“污染”的灾难性场景下,队列也能正常运行。
Deposit data 删除
如果上传的 deposit data 尚未被存款,Node Operator 可以自愿删除。每删除一个 key,removalCharge 会从 Node Operator 的 bond 中没收,以覆盖与队列处理相关的最大可能运营成本。Deposit data 可以按连续批次删除(例如,从索引 5 到 10)。
如果协议已经为与 deposit data 相关的验证者进行了存款,Node Operator 不能删除该 deposit data。停止验证职责的唯一方法是在 CL 上退出验证者。一旦验证者完成全部提款,Node Operator 就可以申领超额 bond。
🤑 第 2 步:奖励
奖励概览
CSM Node Operator 有两种类型的奖励:
- Node Operator 奖励;
- Bond 收益;

Node Operator 奖励来自 LoE 协议从 Consensus 和 Execution 层奖励中获得的份额。这些奖励按一个完整的 32 ETH 验证者奖励的百分比计算。Node Operator 奖励以相同的方式在所有 staking modules 之间分配(根据每个模块的活跃验证者数量按比例分配,其中 $active = deposited - exited$)。每次 Accounting Oracle 报告都会为 CSM 分配一部分新的质押奖励。已分配的奖励存储在模块上。然后,CSM Performance Oracle 每 frame 通过 Merkle 树为 CSM Node Operators 提供 Node Operator 奖励的分配,使新的一部分奖励可供申领。
Bond 收益(rebase)来自 stETH 作为一种可重基代币,以及 bond 以 stETH 存储这一事实。每次 Accounting Oracle 报告后,shareRate 都会变化(很可能增加)。因此,相同数量的 stETH 份额现在将等于更多的 stETH 代币。
总奖励的整体公式如下:$totalRewards = 32 \cdot moduleFee + bondAmount \cdot shareRateChange$。更多细节已发布在补充文章中。
总奖励中有相当一部分来自 bond rebase。Bond 和 Node Operator 奖励会在申领前合并。最终可申领的奖励金额计算公式为 $bond + NodeOperatorRewards - bondRequired$。这种方法还确保任何缺失的 bond 都会在申领奖励之前由协议补足。

此外,任何超额 bond 都将被视为奖励。

Performance Oracle
Performance Oracle 会创建一个包含质押奖励分配的 Merkle 树,并将根上链。为了让用户能够访问原始树,它将发布在 IPFS 和 GitHub 上。与存储多个根不同,每个新树都包含 CSM Node Operator 曾经获得的所有 Node Operator 奖励。因此,只需最新的树即可确定奖励分配。可申领的奖励金额计算公式为 $totalAcquiredRewards - claimedRewards$。
Performance Oracle 以计入包含延迟(inclusion delay)的成功证明(attestation)率作为验证者整体表现的代理指标。利用性能阈值来确定实际 Node Operator 奖励的分配。表现高于阈值的验证者被纳入分配池,其余则不被纳入。激活和退出事件在计算 Node Operator 份额时会被考虑。一旦分配池形成,每个验证者获得的质押奖励部分为 $totalStakingRewardsAccumulated / totalValidatorsInDistributionPool$。这实际上意味着模块获得的所有奖励将在表现良好的验证者之间分配。然后,验证者份额被分配给相应的 Node Operator,每个 Operator 可以一次性申领其所有验证者的奖励。

需要特别注意的是,Performance Oracle 只管理总奖励的一部分。即使验证者在某个 frame 内表现低于阈值,bond 收益(rebase)仍然会获得。可以在此处找到奖励计算的示例。请注意,即使表现低于阈值,每个验证者的奖励仍将高于 solo staking。
建议将 Performance Oracle 报告的 frame 设置为 28 天。这将使 frame 足够长,以涵盖短暂的表现中断(如果 frame 较小,这种缓冲效果会较弱,性能阈值的用处也会减少)。将 frame 设置为超过 28 天将导致奖励分发出现不必要的延迟。
性能阈值应相对于整体网络的证明(attestation)有效性来设定,以确保 Node Operator 无法控制的网络问题不会影响奖励分配。
如果你想了解更多关于 Performance Oracle 实际算法的信息,请查看这份详细文档。
👮♂️ 第 3 步:罚没
即时和延迟
引入了以下罚没方案:
- 即时罚没(适用于无歧义且可以通过无需信任的证明来评估的罚没);
- 带有挑战期的延迟罚没(适用于可能出现误报或需要调查的情况);
延迟罚没的挑战期是通过分离应用罚没过程中涉及的两个角色来实现的。
第一个角色是 “reporter”。该角色的成员可以初步报告一个应导致罚没的事实。在此阶段,bond 资金将被锁定,但不会被销毁或没收。“Reporters”也可以在挑战结果有利于 Node Operator 的情况下撤销初步报告。
第二个角色称为 “settler”。该角色的成员可以最终确定(结算)之前报告的罚没。
分离这两个角色确保了只有在两个独立参与者达成一致时才能应用罚没。
原因
CSM Node Operator 的 bond 被罚没主要有三个原因:
- 验证者被 slashed。在这种情况下,将没收初始(最低)slashing 罚没。罚没金额 = $1$ ETH($EFFECTIVE_BALANCE / 32$);
- Operator 窃取了 EL 奖励(MEV)。罚没金额 = $\text{amount stolen} + \text{fixed stealing fine}$(可以应用于多个 NO 验证者);
- 验证者的提款余额低于
DEPOSIT_AMOUNT(32 ETH)。罚没金额 = $32 - \text{validator's withdrawal balance}$;
第一种罚没使用 EIP-4788 以无许可方式报告,以证明 slashing 的事实。此罚没在报告交易中立即应用。
第二种罚没采用带有挑战期的延迟罚没形式。一个专门的委员会(reporter)检测 MEV 窃取行为并在链上报告此事实,锁定 bond 资金。通过 EasyTrack 动议(settler)进行结算,以确保 DAO 与检测委员会保持一致。一旦罚没被结算(确认),由于违反了协议规则,Node Operator 的所有福利都将被重置。如果罚没在 retention_period 内未被结算,被锁定的 bond 将自动解锁。
第三种罚没类型使用验证者提款余额计算(实际报告在下面的章节中描述)。此罚没在报告交易中立即应用。如果已应用初始 slashing 罚没(第一种罚没类型),会被计入在内,以避免双重罚没。
虽然无许可的 bonded 模块大幅降低了创建验证者的成本,但也降低了对以太坊网络发起潜在攻击的成本。为了确保在模块成熟初期,Lido DAO 能够缓解恶意的模块接管,引入了一种允许基于 DAO 决策进行任意 bond 罚没的方法。此方法带有到期计时器,以确保它不能在模块生命周期的后期阶段使用。一旦到期,它将永远无法被调用。
机制
与 Node Operator bond 罚没相关的机制有两种。
第一种是使用 Burner 销毁 stETH 份额。一旦被没收的份额被销毁,stETH 份额的总量就会减少。因此,shareRate 会增加,实际上是将所有被销毁的 stETH 价值分配给其他 stETH 持有者。
第二种机制是将被没收的 stETH 转移到 Lido DAO Treasury。这种方法适用于支付协议运营成本的罚没(例如 removalCharge)。
对于上一节描述的所有原因,被罚没的资金都会被销毁。目前,转移到 Treasury 的唯一罚没是 removalCharge。
Bond 短缺
如果在应用罚没后,Node Operator 的 bond 不足以覆盖当前 Node Operator 的验证者,所有新的奖励将用于补充 NO 的 bond,直到恢复到所需水平。Node Operator 也可以自行“补充”bond(提交所需的差额),以便再次申领奖励。
如果罚没金额超过 Node Operator 可用的 bond 金额,所有可用资金都将被销毁。
福利重置
与默认曲线不同的 bond 曲线可以被视为 Node Operator 的一项福利。确保在表现不当或违反规则时重置这些福利至关重要。在 CSM 中,有 4 种情况下 Node Operator 的福利可以被重置:
- 检测并确认窃取 EL 奖励;
- NO 的某个验证者被报告 slashing;
- NO 的某个验证者因 CL 余额不足而被驱逐;
- 基于 DAO 的决策;
如果 Node Operator 自愿退出所有验证者并申领全部 bond,则福利不会被重置,因为 Node Operator 没有任何恶意或非法行为。
关于此主题的详细研究在单独文档中呈现。
👋 第 4 步:验证者退出
退出流程概览
自愿退出
鉴于 CSM 的无许可特性,NO 可以随时自愿退出其验证者。
协议发起的退出
为了与核心协议和其他 staking modules 保持一致,CSM 使用 VEBO 来请求或触发验证者退出。
从核心协议方面来看,可以请求验证者退出以覆盖 stETH 持有者的提款请求,或根据 DAO 的决策发起。
从 CSM 方面来看,可以为 unbonded 验证者请求退出。这些退出使用 forcedTargetLimit 自动请求。
forcedTargetLimit目前正在 SR v1.5 中开发。简而言之,它与现有的targetLimit类似,但超出 forcedTargetLimit 的验证者的退出可以立即被请求,即使不需要满足 stETH 持有者的提款请求。
Node Operator 应关注 VEBO 事件(例如,通过使用 Ejector)以确保他们按时退出验证者。如果 Node Operator 在协议请求后拒绝退出验证者,应应用以下罚没和限制措施:
- 将 NO 的 keys 排除在队列之外,并且不重新放入,直到 $stuckKeysCount = 0$;
- 如果在 Performance Oracle 的报告期内,Node Operator 的 $stuckKeysCount$ $> 0$,则不向该 Operator 分配质押奖励;
此外,在特殊情况下,Lido DAO 可以触发 Node Operator 验证者的退出。
长时间低表现
如果验证者在 6 个 frames 内有 3 个 frames 的表现低于性能阈值,则被视为违反协议内良好表现规则的差表现者。具有 3 次 “strikes”(低表现 frames)的验证者可以通过无许可方法从协议中被驱逐。还有一种选择是从 Node Operator 的 bond 中没收此类验证者错过的利润。不过,这个选项仍在考虑中。
要了解更多关于差表现者驱逐的信息,请参阅单独文档。
提款余额报告
释放 bond 和计算退出罚没(如果有的话)需要验证者的提款余额。此余额由 CSM bot 或 Node Operator 自己使用 EIP-4788 以无许可方式报告。
🫡 结论
如果你有任何问题,或者认为缺少了什么,请留下你的评论。
附录 1:紧急制动
为确保协议安全,提出了以下紧急制动措施:
- 禁用来自 staking router 的存款;
- 禁用 NO 创建;
- 禁用 deposit data 上传;
- 禁用奖励分配根提交;
- 禁用奖励申领;
上述某些方法可能会分配给一个专门的多重签名(multisig),以便在 CSM 的早期成熟阶段能够快速反应。
附录 2:方法到参与者映射
| 方法 | 参与者 |
|---|---|
| 设置模块目标份额(SR 方法) | Aragon agent |
设置 Node Operator 的 targetLimit |
Aragon agent |
| 报告验证者被 slashing | 无许可,带证明 |
| 报告验证者提款 | 无许可,带证明 |
应用 removalCharge |
模块代码 |
设置 removalCharge |
Aragon agent |
| 报告无效 keys | DSM |
| 对 Node Operator 应用通用罚没* | Aragon agent |
| 报告 EL 奖励窃取并锁定 bond | MEV 窃取委员会** |
| 销毁被锁定的 bond | 专门的 Easy Track |
| 提交新的奖励根 | Performance Oracle |
| 为 Node Operator 设置 bond 曲线 | Aragon agent |
| 创建 Node Operator | 无许可 |
| 上传 deposit data 和 bond | 无许可 |
| 补充 bond | 无许可 |
| 申领奖励和超额 bond | Node Operator 管理者 |
| 删除 deposit data | Node Operator 管理者 |
| 更改 Node Operator 的管理者地址 | Node Operator 管理者 |
| 更改 Node Operator 的奖励地址 | Node Operator 奖励地址 |
| 重置 Node Operator 的管理者地址 | Node Operator 奖励地址 |
| 禁用来自 staking router 的存款 | Aragon agent 或 DSM |
| 禁用 NO 创建和 deposit data 上传 | Aragon agent 或 EB MS |
| 禁用奖励分配根提交 | Aragon agent 或 EB MS |
| 禁用奖励申领 | Aragon agent 或 EB MS |
| 启用来自 staking router 的存款 | Aragon agent |
| 启用 NO 创建和 deposit data 上传 | Aragon agent |
| 启用奖励分配根提交 | Aragon agent |
| 启用奖励申领 | Aragon agent |
| 重新分配角色成员 | Aragon agent |
* 模块运行 1 年后过期
** 多重签名
附录 3:早期采用期
无许可准入虽然为 Node Operator 提供了有吸引力的条件,但面临的挑战之一是,一个大型参与者可能占据 staking module 中的所有席位。为了克服这一点,提出了将早期采用期作为 CSM 主网生命周期的第一阶段。建议在早期采用期内,使用 Merkle proof 作为进入主网 CSM 的入场券。除了能够加入之外,这些 Node Operator 还将有资格获得“第一个验证者的 bond 折扣”。这将确保在早期采用期内,经过验证的 solo-stakers(来自 Rated 的列表并经 Lido DAO 评估)能够带着一点福利加入。
请参阅详细文档以了解更多关于早期采用期机制的信息。
附录 4:相关文档
- 原文链接: hackmd.io/QmMkXEjbR1e8IF...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~