CSM 架构(内部评审)
本文介绍 Lido 社区质押模块(CSM)的架构设计。CSM 是一个无需许可的质押模块,允许社区质押者作为节点运营商参与 Lido 以太坊协议,只需运行验证器并提供债券作为安全抵押。文章详细描述了 CSM 的通用信息、模块特性、验证器生命周期(加入、奖励、惩罚、退出)、性能预言机、惩罚机制、退出流程等核心设计,并讨论了早期采用期和紧急制动等附录内容。关键设计包括:债券不直接关联验证器,而是作为节点运营商的整体抵押品;采用 FIFO 队列分配质押;通过性能预言机奖励表现好的验证器;采用立即和延迟两种惩罚机制。整体上为理解 CSM 的架构和设计决策提供了全面参考。
🏛️ CSM 架构

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

为了方便阅读,上面的图表也可以按单个 validator 的数量(而非 validator 总数)重新绘制。

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

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

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

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

Node Operator 奖励来自 LoE 协议在共识层和执行层奖励中的份额。这些奖励按完整 32 ETH validator 奖励的百分比计算。Node Operator 奖励以相同的方式在所有质押模块之间分配(根据每个模块的活跃 validator 数量按比例分配,其中 active == deposited - exited)。每份 Accounting Oracle 报告都会为 CSM 分配一部分新的质押奖励。分配到的奖励会存储在模块中。然后,CSM Performance Oracle 会在每个 frame 通过 Merkle 树完成对 CSM Node Operators 的 Node Operator 奖励分配,使新的奖励份额可供申领。
Bond 奖励(rebase)来自两个因素:stETH 是一种 rebase 代币,且 bond 以 stETH 形式存储。在每份 Accounting Oracle 报告之后,shareRate 会发生变化(最有可能增加)。因此,相同数量的 stETH 份额现在将对应更多的 stETH 代币。
总奖励的完整等式如下:totalRewards = 32 * moduleFee + bondAmount * shareRateChange。更多细节发布在补充文章中。
总奖励中有相当一部分来自 bond rebase。Bond 和 Node Operator 奖励在申领前合并。最终可供申领的奖励金额计算为 bond + NodeOperatorRewards - bondRequired。这种方法还确保任何缺失的 bond 都将在奖励申领之前由协议补齐。

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

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

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