├── README.md ├── 1-introduction-and-contents.md ├── 2-dont-read-code-first.md ├── 3-macro-understanding-of-code-structure.md ├── 4-deep-dive-into-code-details.md └── 4-reading-large-code-challenges-and-practices-4-ex-extra-1000-line-read-in-action.md /README.md: -------------------------------------------------------------------------------- 1 | # Reading-Large-Scale-Code 2 | 3 | 我是 Pluveto,一个热爱学习的开发者。阅读代码是软件工程师的核心能力,但是我发现,关于如何写代码,以及各种系统的代码阅读笔记虽然非常多,但还是缺乏一本小册子能够由浅入深介绍如何阅读代码,恰好近几年接触了一些比较大的项目,包括开源项目和内部项目,我想把这些东西整理下来。 4 | 5 | 所有内容将会在这个仓库中更新。 6 | 7 | 8 | **目录** 9 | 10 | 1. **引言** [链接](https://www.less-bug.com/posts/reading-large-scale-code-challenges-and-practices-1-introduction-and-contents/) 11 | - 1.1 阅读大型、复杂项目代码的挑战 12 | - 1.2 阅读代码的机会成本 13 | - 1.3 本系列文章的目的 14 | 15 | 2. **准备阅读代码** [链接](https://www.less-bug.com/posts/reading-code-at-scale-challenges-and-practices-2-dont-read-code-first/) 16 | - 2.1 阅读需求文档和设计文档 17 | - 2.2 以用户角度深度体验程序 18 | 19 | 3. **宏观理解代码结构** [链接](https://github.com/pluveto/Reading-Large-Scale-Code/blob/main/3-macro-understanding-of-code-structure.md) 20 | - 3.1 抓大放小:宏观视角的重要性 21 | - 3.2 建立概念手册:记录关键抽象和接口 22 | - 3.3 分析目录树 23 | - 3.3.1 命名推测用途 24 | - 3.3.2 文件结构猜测功能 25 | - 3.3.3 代码层面的功能推断 26 | 27 | 4. **深入代码细节** [链接](https://github.com/pluveto/Reading-Large-Scale-Code/blob/main/4-deep-dive-into-code-details.md) [番外篇](https://github.com/pluveto/Reading-Large-Scale-Code/blob/main/4-reading-large-code-challenges-and-practices-4-ex-extra-1000-line-read-in-action.md) 28 | - 4.1 阅读测试代码:单元测试的洞察 29 | - 4.2 函数分析:追踪输入变量的来源 30 | - 4.3 过程块理解 31 | - 4.3.1 排除Guard语句,找到核心逻辑 32 | - 4.3.2 利用命名猜测功能 33 | - 4.3.3 倒序阅读:追溯参数构造 34 | 35 | 5. **分析交互流程** 36 | - 5.1 交互流程法:用户交互到输出的全流程 37 | 38 | 6. **调用关系和深层次逻辑** 39 | - 6.1 可视化调用关系 40 | - 6.1.1 画布法 41 | - 6.1.2 树形结构法 42 | 43 | 7. **辅助工具的使用** 44 | - 7.1 利用AI理解代码 45 | 46 | 8. **专项深入** 47 | - 8.1 阅读算法:理解概念和算法背景 48 | - 8.2 心态调节:将代码视为己出 49 | 50 | 9. **结语** 51 | - 阅读大型代码的心得与建议 52 | 53 | 博客会同步更新: 54 | -------------------------------------------------------------------------------- /1-introduction-and-contents.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: "2023-12-07T21:42:41.027256675+08:00" 3 | slug: reading-large-scale-code-challenges-and-practices-1-introduction-and-contents 4 | title: 阅读大规模代码:挑战与实践(1)目录和引言 5 | --- 6 | 我是 Pluveto,一个善于快速学习的开发者。阅读代码是软件工程师的核心能力,但是我发现,关于如何写代码,以及各种系统的代码阅读笔记虽然非常多,但还是缺乏一本小册子能够由浅入深介绍如何阅读代码,恰好近几年接触了一些比较大的项目,包括开源项目和内部项目,我想把这些东西整理下来。 7 | 8 | 所有内容将会在这个仓库中更新: 9 | 10 | [pluveto/Reading-Large-Scale-Code](https://github.com/pluveto/Reading-Large-Scale-Code/settings) 11 | 12 | 你可以关注或者 star 这个仓库,以便获取更新。我的 Github 主页:[pluveto](https://github.com/pluveto). 13 | 14 | # 阅读大规模代码:挑战与实践(1)目录和引言 15 | 16 | **目录** 17 | 18 | 1. **引言** 19 | - 1.1 阅读大型、复杂项目代码的挑战 20 | - 1.2 阅读代码的机会成本 21 | - 1.3 本系列文章的目的 22 | 23 | 2. **准备阅读代码** 24 | - 2.1 阅读需求文档和设计文档 25 | - 2.2 以用户角度深度体验程序 26 | 27 | 3. **宏观理解代码结构** 28 | - 3.1 抓大放小:宏观视角的重要性 29 | - 3.2 建立概念手册:记录关键抽象和接口 30 | - 3.3 分析目录树 31 | - 3.3.1 命名推测用途 32 | - 3.3.2 文件结构猜测功能 33 | - 3.3.3 代码层面的功能推断 34 | 35 | 4. **深入代码细节** 36 | - 4.1 阅读测试代码:单元测试的洞察 37 | - 4.2 函数分析:追踪输入变量的来源 38 | - 4.3 过程块理解 39 | - 4.3.1 排除Guard语句,找到核心逻辑 40 | - 4.3.2 利用命名猜测功能 41 | - 4.3.3 倒序阅读:追溯参数构造 42 | 43 | 5. **分析交互流程** 44 | - 5.1 交互流程法:用户交互到输出的全流程 45 | 46 | 6. **调用关系和深层次逻辑** 47 | - 6.1 可视化调用关系 48 | - 6.1.1 画布法 49 | - 6.1.2 树形结构法 50 | 51 | 7. **辅助工具的使用** 52 | - 7.1 利用AI理解代码 53 | 54 | 8. **专项深入** 55 | - 8.1 阅读算法:理解概念和算法背景 56 | - 8.2 心态调节:将代码视为己出 57 | 58 | 9. **结语** 59 | - 阅读大型代码的心得与建议 60 | 61 | --- 62 | 63 | # 引言 64 | 65 | 阅读代码是软件开发和维护过程中的一项重要任务,而优秀程序员的一项重要能力就是面对大型项目时能够快速切入并理解其逻辑。 66 | 67 | 我们阅读代码一般出于以下目的: 68 | 69 | 1. 学习。阅读优秀的开源项目代码,学习其设计思想和实现方法。在学习的初期我们往往喜欢自己从头实现,但是随着经验的增长,你几乎必然会发现阅读优秀的代码是一个更快的学习方法。 70 | 2. 维护。你会加入一个现有项目的开发团队,维护一个已经存在的复杂的软件系统。因此需要深入理解代码的结构和逻辑,以便能够进行有效的修改、调试和优化工作。 71 | 72 | 然而,阅读大型、复杂项目代码面临巨大的挑战,这也是我梳理本系列文章的目的,希望记录我在阅读大型代码过程中的一些心得和经验。 73 | 74 | ## 阅读大型、复杂项目代码的挑战 75 | 76 | 大规模代码的阅读与阅读小规模代码存在明显的区别。从单纯代码量来看,大型项目的规模从几十万行到上千万行不等,大型项目通常由多个模块、库和组件组成,代码量庞大,逻辑复杂,关联性强。这使得理解整个系统的结构和工作原理变得更加困难。同时,大型项目往往存在长期的演进和迭代过程,可能存在过时的代码、不一致的设计和文档,增加了理解的难度。 77 | 78 | ## 阅读代码的机会成本 79 | 80 | 人生苦短,时间和精力是有限的。把代码写复杂比写简洁更容易,有多年经验的开发者,写出的代码质量也未必能超过善于学习的新程序员。事实上,一个优秀工程师,无论从开发效率还是代码质量来看,都可能十倍于一个开发年数多余他的老程序员。复杂的项目不一定是优秀的项目,阅读复杂的代码不一定能够带来更多的收益。 81 | 82 | 因此,**阅读低质量、高复杂度的项目是愚蠢的**。你必须学会判断代码质量,进而判断是否值得阅读。也应当明确你阅读的目的,以便在阅读过程中保持高效。你是为了快速加入一个功能,还是修复一个 Bug,或者是长期维护?不同的目的需要不同的阅读策略。 83 | 84 | ## 本系列文章的目的 85 | 86 | 本系列文章旨在探讨阅读大规模代码的挑战与实践,提供一些实用的方法和技巧,帮助读者更好地理解和分析复杂的代码系统。我们将从准备阅读代码开始,逐步深入到代码的宏观结构和微观细节,探讨如何分析交互流程、调用关系和深层次逻辑。此外,我们还将介绍一些辅助工具的使用,以及在阅读代码过程中保持积极心态的重要性。 87 | 88 | 通过本系列文章的学习,我希望读者将能够提高阅读大规模代码的能力,更快地理解和掌握复杂项目的结构和逻辑,从而在软件开发和维护中更加高效地工作。我更希望优秀的读者们能够给予我指导。 89 | 90 | 在接下来的章节中,我们将详细介绍每个阅读代码的步骤和技巧。让我们一起开始探索之旅吧! 91 | -------------------------------------------------------------------------------- /2-dont-read-code-first.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: "2023-12-07T21:42:41.027500881+08:00" 3 | draft: false 4 | mathjax: true 5 | slug: reading-code-at-scale-challenges-and-practices-2-dont-read-code-first 6 | title: 阅读大规模代码:挑战与实践(2)不要直接开始阅读代码 7 | --- 8 | # 阅读大规模代码:挑战与实践(2)不要直接开始阅读代码 9 | 10 | ```toc 11 | **目录** 12 | 13 | 1. **引言** 14 | - 1.1 阅读大型、复杂项目代码的挑战 15 | - 1.2 阅读代码的机会成本 16 | - 1.3 本系列文章的目的 17 | 18 | 2. **准备阅读代码(不要直接开始阅读代码)** 19 | - 2.1 阅读需求文档和设计文档 20 | - 2.2 以用户角度深度体验程序 21 | 22 | 3. **宏观理解代码结构** 23 | - 3.1 抓大放小:宏观视角的重要性 24 | - 3.2 建立概念手册:记录关键抽象和接口 25 | - 3.3 分析目录树 26 | - 3.3.1 命名推测用途 27 | - 3.3.2 文件结构猜测功能 28 | - 3.3.3 代码层面的功能推断 29 | 30 | 4. **深入代码细节** 31 | - 4.1 阅读测试代码:单元测试的洞察 32 | - 4.2 函数分析:追踪输入变量的来源 33 | - 4.3 过程块理解 34 | - 4.3.1 排除Guard语句,找到核心逻辑 35 | - 4.3.2 利用命名猜测功能 36 | - 4.3.3 倒序阅读:追溯参数构造 37 | 38 | 5. **分析交互流程** 39 | - 5.1 交互流程法:用户交互到输出的全流程 40 | 41 | 6. **调用关系和深层次逻辑** 42 | - 6.1 可视化调用关系 43 | - 6.1.1 画布法 44 | - 6.1.2 树形结构法 45 | 46 | 7. **辅助工具的使用** 47 | - 7.1 利用AI理解代码 48 | 49 | 8. **专项深入** 50 | - 8.1 阅读算法:理解概念和算法背景 51 | - 8.2 心态调节:将代码视为己出 52 | 53 | 9. **结语** 54 | - 阅读大型代码的心得与建议 55 | ``` 56 | 57 | --- 58 | 59 | > 阅读代码取得成效的关键,是建立了一个可靠的心智模型(Mental Model),此时你会感到自己就是这个系统的作者 60 | 61 | ## 阅读需求文档和设计文档 62 | 63 | 阅读代码最大的误区是直接阅读代码。我举一个例子你就懂了。 64 | 65 | ![Inkscaple](https://i.pcmag.com/imagery/reviews/06WGs4ZCSJkWOuXDw3RzZkj-1..v1569469972.png) 66 | 67 | 这是一个开源的矢量图形编辑器,名为 Inkscaple。它光 commit 数量就接近 30000,代码量保守估计接近百万,而且基本都是 C++ 代码,是一个非常大的项目。 68 | 69 | 请问,如果你没有亲自使用过 Inkscaple,不知道它有哪些功能,与某些 UI 如何交互,交互之后会大致发生什么,你就直接去读源代码,你能读懂吗?就举一个非常简单的例子,你不亲自使用这个软件,能从代码中梳理出用户绘制一个多边形所涉及的 UI 交互和经历的调用链路吗?这几乎不可能,哪怕是你是 C++ 专家。 70 | 71 | 这一章节之所以放到如此靠前的位置,是因为它真的太重要了,一旦没有做好这一点,会有大量的时间被浪费掉,真的,比你想象的要多得多。 72 | 73 | 因此,本章节我提出两个公式。 74 | 75 | ### 第一个公式 E = k * e^(rU) 76 | 77 | 阅读代码的效率(我们可以称之为E)与业务理解程度(我们可以称之为U)成指数级正比的关系 78 | 79 | $$ 80 | E = k \cdot e^{rU} 81 | $$ 82 | 83 | 在这个公式中: 84 | 85 | - $E$ 表示阅读代码的效率。 86 | - $U$ 表示业务理解的程度。 87 | - $k$ 是一个比例常数,表示当 $U = 0$ 时的基础效率。 88 | - $r$ 是一个正的常数,表示业务理解对效率提升的影响程度。 89 | - $e$ 是自然对数的底数(约等于2.71828),这里表示指数关系。 90 | 91 | > 这只是一个简单的模型,实际情况可能更加复杂,我只是想强调业务理解对阅读代码的重要性。 92 | 93 | 这些道理谁都懂,但是一旦开始着手,很多人就会忘记这个公式。他们会直接开始看代码,搭建代码运行环境等等。常见症状: 94 | 95 | 1. 发现时间浪费在研究一个函数是干什么的。 96 | 2. 知道了一些模块的作用,但不知道这些模块为什么存在。 97 | 3. 重新写一遍的效率可能比你阅读代码的效率还要高。 98 | 99 | “业务理解”是一个非常宽泛的词语,所以第二个公式我们会更加具体说明。 100 | 101 | ### 第二个公式:R > A > I 102 | 103 | 在阅读代码之前,首先应该理解需求,其次是理解设计,最后才是阅读代码。这个顺序非常重要,因为: 104 | 105 | 1. **理解需求(Requirements)**:这是基础,涉及到了为什么要建造这个系统以及这个系统需要解决哪些问题。这个问题涉及到哪些参与者,他们之间如何交互,产生了哪些需要解决的业务需求,为了解决这些需求,系统需要提供哪些功能。 106 | 107 | 2. **理解设计(Architecture)**:设计文档通常描述了系统是如何构建的,包括它的架构、组件、数据流、接口等。理解设计可以帮助你知道代码中的每一部分是如何和其他部分协作的,以及它们是如何共同解决业务需求的。 108 | 109 | 3. **阅读代码(Implementation)**:这是实现阶段,是具体实现设计和满足需求的地方。如果没有前两个步骤的理解,你可能会迷失在代码的细节中,无法把握代码的整体结构和目的。 110 | 111 | 不妨以 XFS 文件系统为例,其设计文档(在[这里](http://ftp.ntu.edu.tw/linux/utils/fs/xfs/docs/xfs_filesystem_structure.pdf))包含了关于 XFS 的算法和数据结构的详细信息。如果你在阅读源码之前先阅读这个设计文档,你会对 XFS 如何在内部工作有一个清晰的理解,这将使得阅读实际的代码变得更加容易和有意义。你会知道哪些数据结构用于表示文件系统中的文件和目录,你会知道哪些结构是磁盘结构,哪些是内存结构,哪些是用于数据交换的中间结构。这样,当你实际查看代码时,你会更容易理解它的逻辑和结构。 112 | 113 | 不是所有情况下你都能找到或者有权限访问文档,因此你需要尽可能多地收集信息,例如别人的博客、论文、演讲等等。以及利用好源代码中的配置文件、测试用例、注释。 114 | 115 | ## 以用户角度深度体验程序 116 | 117 | 在你打算以任何方式研究实现之前,最好以用户的角度深度体验程序,极致情况下,你可以把自己当成一个测试工程师。这是一种有效的准备阅读代码的方法。通过模拟用户的使用场景和操作流程,你能够更好地理解程序的功能和交互方式。这种体验可以帮助你建立起对程序整体流程和模块之间的关联性的直观认识,从而更有针对性地进行代码阅读和分析。 118 | 119 | **注意**:深度体验程序并不意味着你要覆盖所有的情况和功能,实际上这和你的初心相关。只有很少的情况你需要深入体验所有的功能,多数情况下我们只是涉及到某个子集。还是以 XFS 为例,他的日志系统是一个非常复杂的模块,但是如果你只是想了解 XFS 的基本工作原理,磁盘结构等,你可以只体验一下基本的格式化、文件读写操作,用 xfs_check,xfs_db,xfs_info 等工具查看文件系统的状态,这样就足够了。文档同理,你可能最开始只需要阅读核心概念、宏观介绍的文档。因为一开始你可能并不真的理解文档,但是在阅读代码的过程中,你会发现你需要回头阅读文档,这时候你就会发现你已经有了一些基础的理解,经过这样的交叉迭代,你最终会得到一个坚实的心智模型。 120 | 121 | --- 122 | 123 | 上述两个步骤,能够让你在深入代码之前建立起对系统整体的认知,并且有利于你更加高效地理解和分析代码。这将为接下来的代码阅读和理解工作奠定坚实的基础。 124 | -------------------------------------------------------------------------------- /3-macro-understanding-of-code-structure.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: "2023-12-07T21:42:41.027500881+08:00" 3 | draft: false 4 | lang: zh 5 | mathjax: true 6 | slug: reading-code-at-scale-challenges-and-practices-3-macro-understanding-of-code-structure 7 | title: 阅读大规模代码:挑战与实践(3)宏观理解代码结构 8 | --- 9 | 10 | # 阅读大规模代码:挑战与实践(3)宏观理解代码结构 11 | 12 | 本文链接: 13 | 14 | **目录** 15 | 16 | 1. **引言** [链接](https://github.com/pluveto/Reading-Large-Scale-Code/blob/main/1-introduction-and-contents.md) 17 | - 1.1 阅读大型、复杂项目代码的挑战 18 | - 1.2 阅读代码的机会成本 19 | - 1.3 本系列文章的目的 20 | 21 | 2. **准备阅读代码** [链接](https://github.com/pluveto/Reading-Large-Scale-Code/blob/main/2-dont-read-code-first.md) 22 | - 2.1 阅读需求文档和设计文档 23 | - 2.2 以用户角度深度体验程序 24 | 25 | 3. **宏观理解代码结构** [链接](https://github.com/pluveto/Reading-Large-Scale-Code/blob/main/3-macro-understanding-of-code-structure.md) 26 | - 3.1 抓大放小:宏观视角的重要性 27 | - 3.2 建立概念手册:记录关键抽象和接口 28 | - 3.3 分析目录树 29 | - 3.3.1 命名推测用途 30 | - 3.3.2 文件结构猜测功能 31 | - 3.3.3 代码层面的功能推断 32 | 33 | 4. **深入代码细节** 34 | - 4.1 阅读测试代码:单元测试的洞察 35 | - 4.2 函数分析:追踪输入变量的来源 36 | - 4.3 过程块理解 37 | - 4.3.1 排除Guard语句,找到核心逻辑 38 | - 4.3.2 利用命名猜测功能 39 | - 4.3.3 倒序阅读:追溯参数构造 40 | 41 | 5. **分析交互流程** 42 | - 5.1 交互流程法:用户交互到输出的全流程 43 | 44 | 6. **调用关系和深层次逻辑** 45 | - 6.1 可视化调用关系 46 | - 6.1.1 画布法 47 | - 6.1.2 树形结构法 48 | 49 | 7. **辅助工具的使用** 50 | - 7.1 利用AI理解代码 51 | 52 | 8. **专项深入** 53 | - 8.1 阅读算法:理解概念和算法背景 54 | - 8.2 心态调节:将代码视为己出 55 | 56 | 9. **结语** 57 | - 阅读大型代码的心得与建议 58 | 59 | --- 60 | 61 | ## 抓大放小:宏观视角的重要性 62 | 63 | 在阅读代码时,往往面临着海量的代码文件和复杂的代码逻辑。如果我们只关注细节和具体实现,很容易陷入细枝末节的琐碎细节中,导致挫败感和低效率。 64 | 65 | 宏观视角意味着我们要从整体上抓住代码的结构和组织方式,将代码划分为不同的模块、组件或功能单元。通过宏观视角,我们可以快速了解代码的大致框架,理解各个模块之间的关系和交互方式。这种全局的认知有助于我们更好地理解代码的功能和设计,从而更高效地进行后续的代码阅读和分析工作。 66 | 67 | ## 建立概念手册:记录关键抽象和接口 68 | 69 | 这里我们提出了第一个实用的代码阅读方法:建立**概念手册**(Concept Manual)。 70 | 71 | 概念手册是一个记录关键抽象和接口的文档,用于帮助我们理清代码中的重要概念和它们之间的关系。 72 | 73 | 尽管许多系统表面上提供简单的接口,但是在实现细节中,它们必然会涉及到一些开发者自己发明的或是领域内通行的抽象。例如 jemalloc 内存分配器涉及到了 `size_class`、`bin`、`arena` 等抽象,并且反复在代码中出现。我们可以首先简单记录下来这些名字,然后在阅读代码时,逐渐理解并完善概念手册。 74 | 75 | > 这里给出一篇写得非常好的文章,作者首先介绍了基础概念和结构,以及主要字段,然后分析了相关的算法实现,文章篇幅不长但是已经让人大致知道了整个系统的工作原理: 76 | > [JeMalloc](https://zhuanlan.zhihu.com/p/48957114)(作者 UncP) 77 | 78 | 建立概念手册应该力求事半功倍,**一种最常见的误区是逐个研究各个字段的含义**,这样立刻你会陷入细节,递归式翻遍全网,然后因为一些小问题卡一整天。举个例子,`mm_struct` 是 Linux 内核中最重要的结构之一,它的定义如下: 79 | 80 | ```c 81 | struct mm_struct { 82 | struct { 83 | struct { 84 | atomic_t mm_count; 85 | } ____cacheline_aligned_in_smp; 86 | 87 | struct maple_tree mm_mt; 88 | #ifdef CONFIG_MMU 89 | unsigned long (*get_unmapped_area) (struct file *filp, 90 | unsigned long addr, unsigned long len, 91 | unsigned long pgoff, unsigned long flags); 92 | #endif 93 | unsigned long mmap_base; 94 | unsigned long mmap_legacy_base; 95 | #ifdef CONFIG_HAVE_ARCH_COMPAT_MMAP_BASES 96 | 97 | unsigned long mmap_compat_base; 98 | unsigned long mmap_compat_legacy_base; 99 | #endif 100 | unsigned long task_size; 101 | pgd_t * pgd; 102 | 103 | #ifdef CONFIG_MEMBARRIER 104 | atomic_t membarrier_state; 105 | #endif 106 | 107 | atomic_t mm_users; 108 | 109 | #ifdef CONFIG_SCHED_MM_CID 110 | struct mm_cid __percpu *pcpu_cid; 111 | unsigned long mm_cid_next_scan; 112 | #endif 113 | #ifdef CONFIG_MMU 114 | atomic_long_t pgtables_bytes; 115 | #endif 116 | int map_count; 117 | 118 | spinlock_t page_table_lock; 119 | struct rw_semaphore mmap_lock; 120 | 121 | struct list_head mmlist; 122 | #ifdef CONFIG_PER_VMA_LOCK 123 | int mm_lock_seq; 124 | #endif 125 | 126 | unsigned long hiwater_rss; 127 | unsigned long hiwater_vm; 128 | 129 | unsigned long total_vm; 130 | unsigned long locked_vm; 131 | atomic64_t pinned_vm; 132 | unsigned long data_vm; 133 | unsigned long exec_vm; 134 | unsigned long stack_vm; 135 | unsigned long def_flags; 136 | 137 | seqcount_t write_protect_seq; 138 | 139 | spinlock_t arg_lock; 140 | 141 | unsigned long start_code, end_code, start_data, end_data; 142 | unsigned long start_brk, brk, start_stack; 143 | unsigned long arg_start, arg_end, env_start, env_end; 144 | 145 | unsigned long saved_auxv[AT_VECTOR_SIZE]; 146 | 147 | struct percpu_counter rss_stat[NR_MM_COUNTERS]; 148 | 149 | struct linux_binfmt *binfmt; 150 | 151 | mm_context_t context; 152 | 153 | unsigned long flags; 154 | 155 | #ifdef CONFIG_AIO 156 | spinlock_t ioctx_lock; 157 | struct kioctx_table __rcu *ioctx_table; 158 | #endif 159 | #ifdef CONFIG_MEMCG 160 | struct task_struct __rcu *owner; 161 | #endif 162 | struct user_namespace *user_ns; 163 | 164 | struct file __rcu *exe_file; 165 | #ifdef CONFIG_MMU_NOTIFIER 166 | struct mmu_notifier_subscriptions *notifier_subscriptions; 167 | #endif 168 | #if defined(CONFIG_TRANSPARENT_HUGEPAGE) && !USE_SPLIT_PMD_PTLOCKS 169 | pgtable_t pmd_huge_pte; 170 | #endif 171 | #ifdef CONFIG_NUMA_BALANCING 172 | unsigned long numa_next_scan; 173 | 174 | unsigned long numa_scan_offset; 175 | 176 | int numa_scan_seq; 177 | #endif 178 | atomic_t tlb_flush_pending; 179 | #ifdef CONFIG_ARCH_WANT_BATCHED_UNMAP_TLB_FLUSH 180 | 181 | atomic_t tlb_flush_batched; 182 | #endif 183 | struct uprobes_state uprobes_state; 184 | #ifdef CONFIG_PREEMPT_RT 185 | struct rcu_head delayed_drop; 186 | #endif 187 | #ifdef CONFIG_HUGETLB_PAGE 188 | atomic_long_t hugetlb_usage; 189 | #endif 190 | struct work_struct async_put_work; 191 | 192 | #ifdef CONFIG_IOMMU_SVA 193 | u32 pasid; 194 | #endif 195 | #ifdef CONFIG_KSM 196 | unsigned long ksm_merging_pages; 197 | unsigned long ksm_rmap_items; 198 | unsigned long ksm_zero_pages; 199 | #endif 200 | #ifdef CONFIG_LRU_GEN 201 | struct { 202 | 203 | struct list_head list; 204 | 205 | unsigned long bitmap; 206 | #ifdef CONFIG_MEMCG 207 | 208 | struct mem_cgroup *memcg; 209 | #endif 210 | } lru_gen; 211 | #endif 212 | } __randomize_layout; 213 | 214 | unsigned long cpu_bitmap[]; 215 | }; 216 | ``` 217 | 218 | 这里有很多字段,虽然说它的注释非常丰富(这里篇幅所限有删减),但它涉及到很多概念、机制,妄图努努力花个一两天就能理解它的含义是不现实的,也是不理智和不值得的。没有人学习 Linux 的目的是搞懂所有内核源码,即便是 Linus 本人也不敢保证内核中每行代码每个字段他都了然于心。所以如果如果你的目的是研究某个特定的功能,那么你应该**只关注于你需要的部分,以及无论如何都绕不过的部分**,而不是试图把所有的细节都搞懂。 219 | 220 | 下面详细介绍如何建立概念手册。 221 | 222 | 总体而言,在建立概念手册时,我们可以首先识别代码中的关键抽象,例如类、接口、数据结构等。对于每个关键抽象,可以记录其名称、核心功能、核心字段以及与其他抽象的关联关系。这个过程不是直接在阅读代码之前完成,而是交替进行。 223 | 224 | 我们以 [spaskalev/buddy_alloc](https://github.com/spaskalev/buddy_alloc/blob/main/buddy_alloc.h) 这个项目为例,它是一个内存分配器,不要小看它只有两千行,这里两千行不是那种简单的业务代码,而是涉及到了很多算法和数据结构。而且一些比较大的项目,但凡设计比较合理的,拆解之后一个原子模块往往也不会超过一万行,大多也就是几千行。 225 | 226 | 阅读源码前你应该已经知道了什么是内存分配器,什么是 buddy 分配。 227 | 228 | 源码的前五百行内基本上是一些接口定义,你可以粗略地浏览一遍,不过应该还是一头雾水。现在看代码还太早了! 229 | 230 | 现在请你阅读下面的“分析目录树章节”。 231 | 232 | 回到 buddy_alloc 项目,我们现在知道,应该先阅读 README 可以了解到用法(Usage)。 233 | 234 | > 如果 README 里没有,那么你可以去看看测试代码,测试代码可能有更丰富的用法。关注 tests 和 examples 目录! 235 | 236 | ```c 237 | size_t arena_size = 65536; 238 | /* You need space for the metadata and for the arena */ 239 | void *buddy_metadata = malloc(buddy_sizeof(arena_size)); 240 | void *buddy_arena = malloc(arena_size); 241 | struct buddy *buddy = buddy_init(buddy_metadata, buddy_arena, arena_size); 242 | 243 | /* Allocate using the buddy allocator */ 244 | void *data = buddy_malloc(buddy, 2048); 245 | /* Free using the buddy allocator */ 246 | buddy_free(buddy, data); 247 | 248 | free(buddy_metadata); 249 | free(buddy_arena); 250 | ``` 251 | 252 | 这里非常好地展现了如何使用这个库,以及库中最关键的用户接口、结构等。你应该已经能推测他们的大致用途,请跳转到“代码层面的功能推断”章节继续。 253 | 254 | > 当一个库提供非常多的 API 时,你应该先找到最关键的接口,然后从这些接口开始阅读代码。 255 | 256 | ## 分析目录树 257 | 258 | 分析代码的目录结构也是宏观理解代码结构的重要步骤之一。代码的目录结构通常反映了代码的模块划分和组织方式,通过分析目录树,我们可以获取关于代码结构的一些线索和信息。 259 | 260 | 先看一个比较简单的,spaskalev/buddy_alloc 项目的目录树: 261 | 262 | ```text 263 | .gitignore 264 | CMakeLists.txt 265 | CONTRIBUTING.md 266 | LICENSE.md 267 | Makefile 268 | README.md 269 | SECURITY.md 270 | _config.yml 271 | bench.c 272 | buddy_alloc.h 273 | test-fuzz.c 274 | testcxx.cpp 275 | tests.c 276 | ``` 277 | 278 | 你心里应该对它进行归类: 279 | 280 | ```text 281 | # 文档 282 | README.md 283 | CONTRIBUTING.md 284 | LICENSE.md 285 | 286 | # 测试代码 287 | bench.c 288 | test-fuzz.c 289 | testcxx.cpp 290 | tests.c 291 | 292 | # 核心代码 293 | buddy_alloc.h 294 | 295 | # 构建相关 296 | CMakeLists.txt 297 | Makefile 298 | 299 | # 其它 300 | .gitignore 301 | SECURITY.md 302 | _config.yml 303 | ``` 304 | 305 | 当然,不是所有项目都这么简单,但分类之后大致也是这些类别,例如 [LLVM](https://github.com/llvm/llvm-project),一种分类如下: 306 | 307 | ```text 308 | # 项目配置文件 309 | .arcconfig 310 | .arclint 311 | .clang-format 312 | .clang-tidy 313 | .git-blame-ignore-revs 314 | .gitignore 315 | .mailmap 316 | 317 | # 项目文档 318 | CODE_OF_CONDUCT.md 319 | CONTRIBUTING.md 320 | LICENSE.TXT 321 | README.md 322 | SECURITY.md 323 | 324 | # 持续集成/持续部署配置 325 | .ci/ 326 | .github/ 327 | 328 | # LLVM项目主要组件 329 | llvm/ 330 | clang/ 331 | clang-tools-extra/ 332 | lld/ 333 | lldb/ 334 | mlir/ 335 | polly/ 336 | 337 | # 运行时库 338 | compiler-rt/ 339 | libc/ 340 | libclc/ 341 | libcxx/ 342 | libcxxabi/ 343 | libunwind/ 344 | openmp/ 345 | 346 | # 其他语言和工具支持 347 | flang/ 348 | bolt/ 349 | 350 | # 跨项目测试 351 | cross-project-tests/ 352 | 353 | # 第三方库 354 | third-party/ 355 | 356 | # 通用工具 357 | utils/ 358 | 359 | # 并行STL实现 360 | pstl/ 361 | 362 | # LLVM运行时环境 363 | runtimes/ 364 | 365 | # CMake支持 366 | cmake/ 367 | ``` 368 | 369 | 对于第一次阅读这个项目的人,你可能感到无法分类,因为看不出来。这种情况下,我会立刻去看 CONTRIBUTING.md、README.md 文件。根据里面的指引,你可能会得到官方提供的目录树介绍,也可能直接重定向到一个专门的文档网站,例如 [LLVM](https://llvm.org/docs/)。一般比较大的项目都会有这样的文档,并且也会有比较丰富的非官方文档、教程和博客。 370 | 371 | ### 命名推测用途 372 | 373 | 首先,我们可以通过目录和文件的命名来推测其用途和功能。通常,良好的命名规范能够提供一些关于代码功能和模块划分的线索。 374 | 375 | 例如,如果一个目录名为 "utils",那么很可能它包含了一些通用的工具函数;如果一个文件名以 "controller" 结尾,那么它可能是一个控制器模块的实现。 376 | 377 | 下面我整理了一些比较通用的常见的文件/目录命名惯例。 378 | 379 | - `src`:常用于存放源代码。 380 | - `utils`:通常用于存放通用的工具函数或类。 381 | - `config`:常用于存放配置文件,例如应用程序的配置项、数据库连接配置等。 382 | - `tests`:通常用于存放单元测试或集成测试的代码。 383 | - `examples`:常用于存放示例代码,例如如何使用某个库或框架的示例。 384 | - `docs`:通常用于存放文档,例如项目的说明文档、API 文档等。 385 | - `scripts`:常用于存放脚本文件,例如构建脚本、部署脚本等。 386 | - `dist` / `build`:通常用于存放构建后的发布版本,例如编译后的可执行文件或打包后的压缩包。通常会被添加到 `.gitignore` 中,因为它们可以通过源代码构建而来。 387 | - `lib`:通常用于存放库文件。 388 | - `include`:常用于存放头文件。 389 | - `bin`:通常用于存放可执行文件,一般也会被添加到 `.gitignore` 中。 390 | 391 | 我们自己写代码时也最好遵循惯例,保持一致性和可读性,以便别人能够轻松理解代码结构和功能。 392 | 393 | 对于不同类型的项目,目录结构会有所变化。下面是一些特定类型的项目以及它们可能包含的目录: 394 | 395 | Web 类项目: 396 | 397 | - `controllers`:常用于存放控制器模块的实现,负责处理请求和控制应用逻辑。 398 | - `models`:通常用于存放数据模型的定义和操作,例如数据库表的映射类或数据结构的定义。 399 | - `views`:常用于存放视图文件,即用户界面的展示层。 400 | - `services`:通常用于存放服务层的实现,负责处理业务逻辑和与数据访问层的交互。 401 | - `public`:常用于存放公共资源文件,例如静态文件(CSS、JavaScript)或上传的文件。 402 | - `routes`:通常用于存放路由配置文件或路由处理函数,负责处理不同 URL 路径的请求分发。 403 | - `middlewares`:常用于存放中间件的实现,用于在请求和响应之间进行处理或拦截。 404 | 405 | 数据科学/机器学习项目: 406 | 407 | - `data`:用于存放数据文件,如数据集、预处理后的数据等。 408 | - `notebooks`:用于存放Jupyter笔记本,常用于数据分析和探索性数据分析(EDA)。 409 | - `models`:用于存放训练好的模型文件,如`.h5`、`.pkl`等。 410 | - `reports`:用于存放生成的分析报告,可以包括图表、表格等。 411 | - `features`:用于存放特征工程相关的代码。 412 | - `scripts`:用于存放数据处理或分析的独立脚本。 413 | 414 | 移动应用项目: 415 | 416 | - `assets`:用于存放图像、字体和其他静态资源文件。 417 | - `lib`:在像Flutter这样的框架中,用于存放Dart源文件。 418 | - `res`:在Android开发中,用于存放资源文件,如布局XML、字符串定义等。 419 | - `ios`/`android`:用于存放特定平台的原生代码和配置文件。 420 | 421 | 游戏开发项目: 422 | 423 | - `assets`:用于存放游戏资源,如纹理、模型、音效、音乐等。 424 | - `scripts`:用于存放游戏逻辑脚本,如Unity中的C#脚本。 425 | - `scenes`:用于存放游戏场景文件。 426 | - `prefabs`:在Unity中用于存放预设(可重用游戏对象模板)。 427 | 428 | 嵌入式系统/ IoT项目: 429 | 430 | - `src`:用于存放源代码,可能会进一步细分为不同的功能模块。 431 | - `include`:用于存放头文件,特别是在C/C++项目中。 432 | - `drivers`:用于存放与硬件通信的驱动程序代码。 433 | - `firmware`:用于存放固件代码。 434 | - `tools`:用于存放与硬件通信或调试的工具。 435 | - `sdk`:用于存放软件开发工具包。 436 | 437 | ### 文件结构猜测功能 438 | 439 | 如果一个目录的名字不足以让你推测出它的用途,那么你可以进一步分析目录中的文件结构。例如 LLVM 有一个目录叫做 BinaryFormat,光看名字会有点迷惑,但是如果看下面的文件: 440 | 441 | ``` 442 | BinaryFormat/ 443 | ELFRelocs/ 444 | AMDGPUMetadataVerifier.h 445 | COFF.h 446 | DXContainer.h 447 | DXContainerConstants.def 448 | Dwarf.def 449 | Dwarf.h 450 | DynamicTags.def 451 | ELF.h 452 | GOFF.h 453 | MachO.def 454 | MachO.h 455 | Magic.h 456 | Minidump.h 457 | MinidumpConstants.def 458 | MsgPack.def 459 | MsgPack.h 460 | MsgPackDocument.h 461 | MsgPackReader.h 462 | MsgPackWriter.h 463 | Swift.def 464 | Swift.h 465 | Wasm.h 466 | WasmRelocs.def 467 | WasmTraits.h 468 | XCOFF.h 469 | ``` 470 | 471 | 就知道这个 Binary 原来是指的是 ELF 之类的二进制文件格式。再看到 WASM(一种浏览器里运行的二进制)COFF(Windows 系统的二进制)啥的,可以推测这个目录是用于存放二进制文件格式的定义和解析的,并且支持了很多种格式包括 ELF、MachO、WASM 等。你看,光从一个目录结构就能推测出这么多信息,某种意义上比你钻进去读代码更快。 472 | 473 | > 有时候我们会猜错,但是没关系,我们可以在阅读代码的过程中不断完善概念手册。 474 | 475 | ### 代码层面的功能推断 476 | 477 | 除了目录和文件的分析,我们还可以通过观察代码的功能和调用关系来推断代码的整体结构。通过仔细观察代码中的函数、方法、类之间的调用关系,我们可以推断出它们之间的依赖关系和组织方式。 478 | 479 | - 核心公开接口的特征:在示例代码中被频繁使用 480 | - 核心、底层功能的特征:一个函数/方法/类被多个其他方法调用。 481 | - 包装器的特征:短小的函数,并且基本上是对另一个/类模块的调用。 482 | - 上层代码的特征:调用了多个其他模块的函数/方法/类,很长的 import 列表。 483 | - ... 484 | 485 | 回到 buddy_alloc 项目,我们阅读示例代码,可以推测出一些比较重要的概念和函数: 486 | 487 | - `buddy_metadata` - 用于存储 buddy 分配器的元数据 488 | - `buddy_arena` - 一块一次性分配好的内存,后面的 buddy 分配器会从这里细分分配内存 489 | - `buddy` - buddy 分配器的实例 490 | - `buddy_init` - 初始化 buddy 分配器 491 | - `buddy_malloc` - 分配内存 492 | - `buddy_free` - 释放内存 493 | 494 | 我们还能推测出 buddy_metadata、buddy 应该是两个需要重点了解的核心结构,buddy_init、buddy_malloc、buddy_free 是三个重要的公开函数。 495 | 496 | 你可以把这些先记到概念手册里。然后进一步阅读代码的过程中,会涉及到他们的具体实现,以及更多隐含的概念。比如二叉树、tree_order、depth、index、目标深度、size_for_order、internal_position、local_offset、bitset 等。这是一个不断探索的过程。 497 | 498 | --- 499 | 500 | 本章介绍了宏观理解代码结构的重要性,并提供了一些实用的方法和技巧。通过采用宏观视角,建立概念手册,分析目录树以及观察代码的功能和调用关系,我们可以更好地理解代码的整体结构,为后续的代码阅读和分析工作打下坚实的基础。在下一章中,我们将介绍如何进行代码的细节分析,以更深入地理解代码的实现细节。 501 | 502 | -------------------------------------------------------------------------------- /4-deep-dive-into-code-details.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: "2024-11-30T11:31:13.68560091+08:00" 3 | lang: zh 4 | slug: reading-large-code-challenges-and-practices-4-deep-dive-into-code-details 5 | title: 阅读大规模代码:挑战与实践(4)深入代码细节 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 | 让我们以下面的 LLVM 某个单元测试代码为例。(你不需要事先对 LLVM 有所了解) 34 | 35 | ```cpp 36 | TEST(UseTest, sort) { 37 | LLVMContext C; 38 | 39 | const char *ModuleString = "define void @f(i32 %x) {\n" 40 | "entry:\n" 41 | " %v0 = add i32 %x, 0\n" 42 | " %v2 = add i32 %x, 2\n" 43 | " %v5 = add i32 %x, 5\n" 44 | " %v1 = add i32 %x, 1\n" 45 | " %v3 = add i32 %x, 3\n" 46 | " %v7 = add i32 %x, 7\n" 47 | " %v6 = add i32 %x, 6\n" 48 | " %v4 = add i32 %x, 4\n" 49 | " ret void\n" 50 | "}\n"; 51 | SMDiagnostic Err; 52 | char vnbuf[8]; 53 | std::unique_ptr M = parseAssemblyString(ModuleString, Err, C); 54 | Function *F = M->getFunction("f"); 55 | ASSERT_TRUE(F); 56 | ASSERT_TRUE(F->arg_begin() != F->arg_end()); 57 | Argument &X = *F->arg_begin(); 58 | ASSERT_EQ("x", X.getName()); 59 | 60 | X.sortUseList([](const Use &L, const Use &R) { 61 | return L.getUser()->getName() < R.getUser()->getName(); 62 | }); 63 | unsigned I = 0; 64 | for (User *U : X.users()) { 65 | format("v%u", I++).snprint(vnbuf, sizeof(vnbuf)); 66 | EXPECT_EQ(vnbuf, U->getName()); 67 | } 68 | ASSERT_EQ(8u, I); 69 | 70 | X.sortUseList([](const Use &L, const Use &R) { 71 | return L.getUser()->getName() > R.getUser()->getName(); 72 | }); 73 | I = 0; 74 | for (User *U : X.users()) { 75 | format("v%u", (7 - I++)).snprint(vnbuf, sizeof(vnbuf)); 76 | EXPECT_EQ(vnbuf, U->getName()); 77 | } 78 | ASSERT_EQ(8u, I); 79 | } 80 | ``` 81 | 82 | 首先,我们看到这是一个测试函数 `UseTest.sort`。这个测试用例的目的是检验 LLVM 中 `Use` 列表的排序功能。 83 | 84 | #### 1. 创建 LLVM 上下文和模块 85 | 86 | ```cpp 87 | LLVMContext C; 88 | const char *ModuleString = "define void @f(i32 %x) {...}"; 89 | SMDiagnostic Err; 90 | std::unique_ptr M = parseAssemblyString(ModuleString, Err, C); 91 | ``` 92 | 93 | - **LLVMContext**: 这是 LLVM 的一个核心类,用于管理编译过程中的全局数据,比如类型和常量。通过上下文,我们能确保多线程环境下的数据安全。 94 | - **Module**: 代表一个 LLVM 模块,是 LLVM IR 中的顶级容器,包含函数和全局变量等。 95 | - **parseAssemblyString**: 这个函数将 LLVM IR 字符串解析为一个模块对象。我们在这里了解到 LLVM 提供了丰富的 API 来处理和操作 IR。 96 | 97 | 这几行代码让我们意识到,对一个模块源码进行解析,得到一个关联到上下文的模块对象。这让我们对 LLVM 的模块组织有了一个印象。 98 | 99 | #### 2. 获取函数和参数 100 | 101 | ```cpp 102 | Function *F = M->getFunction("f"); 103 | ASSERT_TRUE(F); 104 | ASSERT_TRUE(F->arg_begin() != F->arg_end()); 105 | Argument &X = *F->arg_begin(); 106 | ASSERT_EQ("x", X.getName()); 107 | ``` 108 | 109 | 这些代码意味着 LLVM IR 中 `define void @f(i32 %x)` 表示一个函数 `f`,其参数名为 `x`。并且我们学到了可以通过 `*F->arg_begin()` 获取函数的第一个参数。这看起来像个迭代器,推测可以遍历函数的所有参数。我们还学到了可以通过 `getName()` 方法获取参数的名称。 110 | 111 | > 而这事先不需要你对 LLVM IR 有所了解。 112 | 113 | #### 3. 使用列表排序 114 | 115 | ```cpp 116 | X.sortUseList([](const Use &L, const Use &R) { 117 | return L.getUser()->getName() < R.getUser()->getName(); 118 | }); 119 | ``` 120 | 121 | 这里显然定义排序规则,根据 User 的 Name 进行排序。 122 | 123 | #### 4. 验证排序结果 124 | 125 | ```cpp 126 | unsigned I = 0; 127 | for (User *U : X.users()) { 128 | format("v%u", I++).snprint(vnbuf, sizeof(vnbuf)); 129 | EXPECT_EQ(vnbuf, U->getName()); 130 | } 131 | ASSERT_EQ(8u, I); 132 | ``` 133 | 这里我们学到的是可以通过 `users()` 方法获取 `Use` 列表,并遍历它。根据断言,我们得到的顺序是 `v0` 到 `v7`。 134 | 135 | 结合 X 的定义,我们推测 Use 表示哪些“users”用到了 x。 136 | 137 | > 实际上这是编译原理的 Use-Def 分析的一部分,虽然我们不懂编译原理,但我们通过阅读测试代码开始有点了解了! 138 | 139 | #### 5. 逆序排序 140 | 141 | ```cpp 142 | X.sortUseList([](const Use &L, const Use &R) { 143 | return L.getUser()->getName() > R.getUser()->getName(); 144 | }); 145 | ``` 146 | 147 | - 这部分代码用相反的排序规则对 `Use` 列表进行逆序排列。略。 148 | 149 | 小结一下,这里我们仅仅通过测试,就开始有点理解一门新语言 LLVM IR,还对编译中的 Use 关系有了一定的了解。这可比去读 LLVM IR Parser 或者编译原理的书来的快多了!(当然,如果你真的要从事这方面的工作,还是需要去啃的) 150 | 151 | ### 函数分析:追踪输入变量的来源 152 | 153 | 现在祭出一个能大大提高阅读效率的方法——变量来源分析。 154 | 155 | ```rust 156 | impl SelfTime { 157 | ... 158 | pub fn self_duration(&mut self, self_range: Range) -> Duration { 159 | if self.child_ranges.is_empty() { 160 | return self_range.end - self_range.start; 161 | } 162 | 163 | // by sorting child ranges by their start time, 164 | // we make sure that no child will start before the last one we visited. 165 | self.child_ranges 166 | .sort_by(|left, right| left.start.cmp(&right.start).then(left.end.cmp(&right.end))); 167 | // self duration computed by adding all the segments where the span is not executing a child 168 | let mut self_duration = Duration::from_nanos(0); 169 | 170 | // last point in time where we are certain that this span was not executing a child. 171 | let mut committed_point = self_range.start; 172 | 173 | for child_range in &self.child_ranges { 174 | if child_range.start > committed_point { 175 | // we add to the self duration the point between the end of the latest span and the beginning of the next span 176 | self_duration += child_range.start - committed_point; 177 | } 178 | if committed_point < child_range.end { 179 | // then we set ourselves to the end of the latest span 180 | committed_point = child_range.end; 181 | } 182 | } 183 | 184 | self_duration 185 | } 186 | } 187 | ``` 188 | 189 | > 摘自:https://github.com/meilisearch/meilisearch/blob/main/crates/tracing-trace/src/processor/span_stats.rs 190 | 191 | 看见这一大坨代码不要慌,先看函数签名 `fn self_duration(&mut self, self_range: Range) -> Duration`,这说明输入一个 Duration,输出也是 Duration,而且函数可能会修改当前对象的状态。 192 | 193 | 然后我们直接跳到返回值 `self_duration` 双击选中,看到其实修改它的就一处 `self_duration += child_range.start - committed_point;` 194 | 195 | ![gh](https://raw.githubusercontent.com/pluveto/0images/master/obsidian/1732934445000ld1ivk.png) 196 | 197 | 此时就知道它其实就是把一系列的区间长度求和,区间的开始为 `child_range.start`,区间的结束为 `committed_point`。 198 | 199 | 然后我们对 committed_point 还是不了解,再双击选中 committed_point。 200 | 201 | ![gh](https://raw.githubusercontent.com/pluveto/0images/master/obsidian/1732934566000f8kplv.png) 202 | 203 | 发现对它的修改也只有一处,就是当 committed_point < child_range.end 时,committed_point 被设置为 child_range.end。 204 | 205 | 两相结合就是说 self_duration 其实就是本次的 child_range.start 剪掉上次 committed_point 之间的长度。也就是说反映了 child_range 与 committed_point 之间没有执行的区间(空隙)长度。 206 | 207 | 所以整体代码的意思就是: 208 | 209 | 1. 先对 child_ranges 进行排序,保证 child_range.start 都小于等于前一个 child_range.end。 210 | 2. 然后遍历 child_ranges,将其间的空隙累加到 self_duration,并更新 committed_point。committed_point 表示上次 child_range 的末尾。 211 | 3. 最后返回 self_duration。 212 | 213 | 再看开头的条件,就是说如果 child_ranges 为空,那么 self_duration 就是 self_range.end - self_range.start。即没有子区间,则空隙就是整个区间。 214 | 215 | 216 | 217 | 218 | Start 219 | End 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | Self Range 234 | 235 | 236 | Child Ranges 237 | 238 | 239 | Self Duration 240 | 241 | 242 | 总结一下:来源分析的方法,就是从后往前分析变量的生成和传递路径,从而理解变量的来源。这比你顺着读要好读很多,因为越靠前的语句,越有可能只是用来凑参数的!实际上我们平时写代码也是这样,代码的核心语句就一两条,此上很多条都是为了给这几个核心语句计算参数。 243 | 244 | ### 过程块理解 245 | 246 | 理解代码中的过程块(如条件块、函数块等)关键在于删繁就简。 247 | 248 | #### 排除Guard语句,找到核心逻辑 249 | 250 | Guard 语句通常用于提前退出函数或处理异常情况。排除这些语句,可以让你更专注于函数的主要逻辑。这种代码在业务代码中尤其常见。 251 | 252 | > 下面摘自:https://github.com/kubernetes/kubernetes/blob/master/pkg/registry/core/pod/strategy.go 253 | 254 | ```go 255 | // ResourceLocation returns a URL to which one can send traffic for the specified pod. 256 | func ResourceLocation(ctx context.Context, getter ResourceGetter, rt http.RoundTripper, id string) (*url.URL, http.RoundTripper, error) { 257 | // Allow ID as "podname" or "podname:port" or "scheme:podname:port". 258 | // If port is not specified, try to use the first defined port on the pod. 259 | scheme, name, port, valid := utilnet.SplitSchemeNamePort(id) 260 | if !valid { 261 | return nil, nil, errors.NewBadRequest(fmt.Sprintf("invalid pod request %q", id)) 262 | } 263 | 264 | pod, err := getPod(ctx, getter, name) 265 | if err != nil { 266 | return nil, nil, err 267 | } 268 | 269 | // Try to figure out a port. 270 | if port == "" { 271 | for i := range pod.Spec.Containers { 272 | if len(pod.Spec.Containers[i].Ports) > 0 { 273 | port = fmt.Sprintf("%d", pod.Spec.Containers[i].Ports[0].ContainerPort) 274 | break 275 | } 276 | } 277 | } 278 | podIP := getPodIP(pod) 279 | if ip := netutils.ParseIPSloppy(podIP); ip == nil || !ip.IsGlobalUnicast() { 280 | return nil, nil, errors.NewBadRequest("address not allowed") 281 | } 282 | 283 | loc := &url.URL{ 284 | Scheme: scheme, 285 | } 286 | if port == "" { 287 | // when using an ipv6 IP as a hostname in a URL, it must be wrapped in [...] 288 | // net.JoinHostPort does this for you. 289 | if strings.Contains(podIP, ":") { 290 | loc.Host = "[" + podIP + "]" 291 | } else { 292 | loc.Host = podIP 293 | } 294 | } else { 295 | loc.Host = net.JoinHostPort(podIP, port) 296 | } 297 | return loc, rt, nil 298 | } 299 | ``` 300 | 301 | 这里很多语句都是验证参数之类的,删掉之后变成这样(port 为空的逻辑,一定程度上也可以看做 edge case,虽然对程序正确性至关重要,但是阅读的时候瞟一眼就行): 302 | 303 | ```go 304 | // ResourceLocation returns a URL to which one can send traffic for the specified pod. 305 | func ResourceLocation(ctx context.Context, getter ResourceGetter, rt http.RoundTripper, id string) (*url.URL, http.RoundTripper, error) { 306 | // Allow ID as "podname" or "podname:port" or "scheme:podname:port". 307 | // If port is not specified, try to use the first defined port on the pod. 308 | scheme, name, port, _ := utilnet.SplitSchemeNamePort(id) 309 | pod, _ := getPod(ctx, getter, name) 310 | podIP := getPodIP(pod) 311 | loc := &url.URL{ 312 | Scheme: scheme, 313 | } 314 | loc.Host = net.JoinHostPort(podIP, port) 315 | return loc, rt, nil 316 | } 317 | ``` 318 | 319 | 是不是感觉一下子代码变得慈眉善目了?严格来说这个代码的核心逻辑就两句话: 320 | 321 | 1. 通过 `utilnet.SplitSchemeNamePort` 解析不同部分。 322 | 2. 构造一个 URL 对象,并设置 Host 字段。 323 | 324 | #### 利用命名猜测功能 325 | 326 | 良好的命名可以提供大量信息,结合上下文,能帮助你快速理解代码的功能和目的。通过分析变量、函数和类的命名,可以推测其作用和关联。 327 | 328 | 不良好的命名,咱也可以先大胆猜测。下面的代码摘自 https://github.com/nodejs/node/blob/main/src/large_pages/node_large_page.cc 329 | 330 | ```cpp 331 | struct text_region FindNodeTextRegion() { 332 | struct text_region nregion; 333 | #if defined(__linux__) || defined(__FreeBSD__) 334 | dl_iterate_params dl_params; 335 | uintptr_t lpstub_start = reinterpret_cast(&__start_lpstub); 336 | 337 | #if defined(__FreeBSD__) 338 | // On FreeBSD we need the name of the binary, because `dl_iterate_phdr` does 339 | // not pass in an empty string as the `dlpi_name` of the binary but rather its 340 | // absolute path. 341 | { 342 | char selfexe[PATH_MAX]; 343 | size_t count = sizeof(selfexe); 344 | if (uv_exepath(selfexe, &count)) 345 | return nregion; 346 | dl_params.exename = std::string(selfexe, count); 347 | } 348 | #endif // defined(__FreeBSD__) 349 | 350 | if (dl_iterate_phdr(FindMapping, &dl_params) == 1) { 351 | Debug("start: %p - sym: %p - end: %p\n", 352 | reinterpret_cast(dl_params.start), 353 | reinterpret_cast(dl_params.reference_sym), 354 | reinterpret_cast(dl_params.end)); 355 | 356 | dl_params.start = dl_params.reference_sym; 357 | if (lpstub_start > dl_params.start && lpstub_start <= dl_params.end) { 358 | Debug("Trimming end for lpstub: %p\n", 359 | reinterpret_cast(lpstub_start)); 360 | dl_params.end = lpstub_start; 361 | } 362 | 363 | if (dl_params.start < dl_params.end) { 364 | char* from = reinterpret_cast(hugepage_align_up(dl_params.start)); 365 | char* to = reinterpret_cast(hugepage_align_down(dl_params.end)); 366 | Debug("Aligned range is %p - %p\n", from, to); 367 | if (from < to) { 368 | size_t pagecount = (to - from) / hps; 369 | if (pagecount > 0) { 370 | nregion.found_text_region = true; 371 | nregion.from = from; 372 | nregion.to = to; 373 | } 374 | } 375 | } 376 | } 377 | #elif defined(__APPLE__) 378 | struct vm_region_submap_info_64 map; 379 | mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_COUNT_64; 380 | vm_address_t addr = 0UL; 381 | vm_size_t size = 0; 382 | natural_t depth = 1; 383 | 384 | while (true) { 385 | if (vm_region_recurse_64(mach_task_self(), &addr, &size, &depth, 386 | reinterpret_cast(&map), 387 | &count) != KERN_SUCCESS) { 388 | break; 389 | } 390 | 391 | if (map.is_submap) { 392 | depth++; 393 | } else { 394 | char* start = reinterpret_cast(hugepage_align_up(addr)); 395 | char* end = reinterpret_cast(hugepage_align_down(addr+size)); 396 | 397 | if (end > start && (map.protection & VM_PROT_READ) != 0 && 398 | (map.protection & VM_PROT_EXECUTE) != 0) { 399 | nregion.found_text_region = true; 400 | nregion.from = start; 401 | nregion.to = end; 402 | break; 403 | } 404 | 405 | addr += size; 406 | size = 0; 407 | } 408 | } 409 | #endif 410 | Debug("Found %d huge pages\n", (nregion.to - nregion.from) / hps); 411 | return nregion; 412 | } 413 | 414 | ``` 415 | 416 | 我们先把非核心代码删掉,把某些逻辑块替换成伪代码: 417 | 418 | ```cpp 419 | struct text_region FindNodeTextRegion() { 420 | struct text_region nregion; 421 | dl_iterate_params dl_params; 422 | uintptr_t lpstub_start = reinterpret_cast(&__start_lpstub); 423 | 424 | if (dl_iterate_phdr(FindMapping, &dl_params) == 1) { 425 | dl_params.start = dl_params.reference_sym; 426 | if (lpstub_start > dl_params.start && lpstub_start <= dl_params.end) { 427 | dl_params.end = lpstub_start; 428 | } 429 | 430 | if (dl_params.start < dl_params.end) { 431 | char* from = hugepage_align_up(dl_params.start); 432 | char* to = hugepage_align_down(dl_params.end); 433 | Debug("Aligned range is %p - %p\n", from, to); 434 | if (from < to) { 435 | size_t pagecount = (to - from) / hps; 436 | if (pagecount > 0) { 437 | Set nregion data; 438 | } 439 | } 440 | } 441 | } 442 | return nregion; 443 | } 444 | ``` 445 | 446 | 删繁就简之后,我们直接定位 nregion 的赋值点,找到了核心逻辑:寻找一个 hugepage 区域。 447 | 448 | `dl_iterate_phdr` 这个函数虽然事先不知道什么意思,但是从名字来看有迭代,意味着其内部会循环地执行,从使用来看,返回 == 1 应该是表示找到了东西。而参数 FindMapping 应该是表示查找的回调函数。因此我们推测 `dl_iterate_phdr` 的功能应该是遍历进程的一些操作系统管理的数据,找到其中满足 FindMapping 条件的,把结果放到 dl_params。 449 | 450 | 实际上 dl 是动态链接(dynamic linking)的缩写。phdr: 程序头(program header)的缩写。所以整个函数就是遍历当前进程加载的所有程序头(包括程序和库)的函数。不同领域会有不同的缩写, 451 | 452 | 比如: 453 | 454 | - irq: Interrupt Request 455 | - pcb: Process Control Block 456 | - mmu: Memory Management Unit 457 | - dma: Direct Memory Access 458 | - vfs: Virtual File System 459 | - ipc: Inter-Process Communication 460 | - pid: Process Identifier 461 | - tss: Task State Segment 462 | - smp: Symmetric Multiprocessing 463 | - numa: Non-Uniform Memory Access 464 | 465 | 熟悉之后可以帮助我们快速理解代码,避免频繁查文档,递归式查各种文档耽误时间。 466 | -------------------------------------------------------------------------------- /4-reading-large-code-challenges-and-practices-4-ex-extra-1000-line-read-in-action.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: "2024-12-02T16:49:46.8051208+08:00" 3 | lang: zh 4 | slug: reading-large-code-challenges-and-practices-4-ex-extra-1000-line-read-in-action 5 | title: 阅读大规模代码:挑战与实践(4-EX)番外篇:试炼阅读某1000行的函数 6 | --- 7 | 上一章我们讲了怎么利用反向追踪的方法读代码。但是示范的代码太短了,实战中,一个函数几百上千行很常见(即使在一些比较优秀的开源项目中也可能存在)。 8 | 9 | 这章我们将带你阅读一个1000行的函数,不过这么长的代码,抱有目的地去读会比较有效率。 10 | 11 | 下面来阅读这个函数,摘自 rust-lightning 的 `ChannelManager::read` 函数。它的作用是从磁盘等任何二进制数据源中恢复一个 `ChannelManager` 对象。 12 | 13 | 我们设定的目标是,**理解它到底从磁盘读了哪些东西出来**,以便分析这些数据存放在磁盘是否安全。 14 | 15 | > 设立一个合理的目标很重要。一千行代码都够有的语言实现一套编译器了,没有必要浪费时间在普通的数据系统项目上。 16 | 17 | ## 代码 18 | 19 | 请自己试一试。 20 | 21 | ```rust 22 | impl<'a, M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref> 23 | ReadableArgs> 24 | for (BlockHash, ChannelManager) 25 | where 26 | M::Target: chain::Watch<::EcdsaSigner>, 27 | T::Target: BroadcasterInterface, 28 | ES::Target: EntropySource, 29 | NS::Target: NodeSigner, 30 | SP::Target: SignerProvider, 31 | F::Target: FeeEstimator, 32 | R::Target: Router, 33 | L::Target: Logger, 34 | { 35 | fn read( 36 | reader: &mut Reader, mut args: ChannelManagerReadArgs<'a, M, T, ES, NS, SP, F, R, L>, 37 | ) -> Result { 38 | let _ver = read_ver_prefix!(reader, SERIALIZATION_VERSION); 39 | 40 | let chain_hash: ChainHash = Readable::read(reader)?; 41 | let best_block_height: u32 = Readable::read(reader)?; 42 | let best_block_hash: BlockHash = Readable::read(reader)?; 43 | 44 | let mut failed_htlcs = Vec::new(); 45 | 46 | let channel_count: u64 = Readable::read(reader)?; 47 | let mut funding_txo_set = hash_set_with_capacity(cmp::min(channel_count as usize, 128)); 48 | let mut funded_peer_channels: HashMap>> = 49 | hash_map_with_capacity(cmp::min(channel_count as usize, 128)); 50 | let mut outpoint_to_peer = hash_map_with_capacity(cmp::min(channel_count as usize, 128)); 51 | let mut short_to_chan_info = hash_map_with_capacity(cmp::min(channel_count as usize, 128)); 52 | let mut channel_closures = VecDeque::new(); 53 | let mut close_background_events = Vec::new(); 54 | let mut funding_txo_to_channel_id = hash_map_with_capacity(channel_count as usize); 55 | for _ in 0..channel_count { 56 | let mut channel: Channel = Channel::read( 57 | reader, 58 | ( 59 | &args.entropy_source, 60 | &args.signer_provider, 61 | best_block_height, 62 | &provided_channel_type_features(&args.default_config), 63 | args.color_source.clone(), 64 | ), 65 | )?; 66 | let logger = WithChannelContext::from(&args.logger, &channel.context); 67 | let funding_txo = channel.context.get_funding_txo().ok_or(DecodeError::InvalidValue)?; 68 | funding_txo_to_channel_id.insert(funding_txo, channel.context.channel_id()); 69 | funding_txo_set.insert(funding_txo.clone()); 70 | if let Some(ref mut monitor) = args.channel_monitors.get_mut(&funding_txo) { 71 | if channel.get_cur_holder_commitment_transaction_number() 72 | > monitor.get_cur_holder_commitment_number() 73 | || channel.get_revoked_counterparty_commitment_transaction_number() 74 | > monitor.get_min_seen_secret() 75 | || channel.get_cur_counterparty_commitment_transaction_number() 76 | > monitor.get_cur_counterparty_commitment_number() 77 | || channel.context.get_latest_monitor_update_id() 78 | < monitor.get_latest_update_id() 79 | { 80 | // But if the channel is behind of the monitor, close the channel: 81 | log_error!( 82 | logger, 83 | "A ChannelManager is stale compared to the current ChannelMonitor!" 84 | ); 85 | log_error!(logger, " The channel will be force-closed and the latest commitment transaction from the ChannelMonitor broadcast."); 86 | if channel.context.get_latest_monitor_update_id() 87 | < monitor.get_latest_update_id() 88 | { 89 | log_error!(logger, " The ChannelMonitor for channel {} is at update_id {} but the ChannelManager is at update_id {}.", 90 | &channel.context.channel_id(), monitor.get_latest_update_id(), channel.context.get_latest_monitor_update_id()); 91 | } 92 | if channel.get_cur_holder_commitment_transaction_number() 93 | > monitor.get_cur_holder_commitment_number() 94 | { 95 | log_error!(logger, " The ChannelMonitor for channel {} is at holder commitment number {} but the ChannelManager is at holder commitment number {}.", 96 | &channel.context.channel_id(), monitor.get_cur_holder_commitment_number(), channel.get_cur_holder_commitment_transaction_number()); 97 | } 98 | if channel.get_revoked_counterparty_commitment_transaction_number() 99 | > monitor.get_min_seen_secret() 100 | { 101 | log_error!(logger, " The ChannelMonitor for channel {} is at revoked counterparty transaction number {} but the ChannelManager is at revoked counterparty transaction number {}.", 102 | &channel.context.channel_id(), monitor.get_min_seen_secret(), channel.get_revoked_counterparty_commitment_transaction_number()); 103 | } 104 | if channel.get_cur_counterparty_commitment_transaction_number() 105 | > monitor.get_cur_counterparty_commitment_number() 106 | { 107 | log_error!(logger, " The ChannelMonitor for channel {} is at counterparty commitment transaction number {} but the ChannelManager is at counterparty commitment transaction number {}.", 108 | &channel.context.channel_id(), monitor.get_cur_counterparty_commitment_number(), channel.get_cur_counterparty_commitment_transaction_number()); 109 | } 110 | let mut shutdown_result = 111 | channel.context.force_shutdown(true, ClosureReason::OutdatedChannelManager); 112 | if shutdown_result.unbroadcasted_batch_funding_txid.is_some() { 113 | return Err(DecodeError::InvalidValue); 114 | } 115 | if let Some((counterparty_node_id, funding_txo, channel_id, update)) = 116 | shutdown_result.monitor_update 117 | { 118 | close_background_events.push( 119 | BackgroundEvent::MonitorUpdateRegeneratedOnStartup { 120 | counterparty_node_id, 121 | funding_txo, 122 | channel_id, 123 | update, 124 | }, 125 | ); 126 | } 127 | failed_htlcs.append(&mut shutdown_result.dropped_outbound_htlcs); 128 | channel_closures.push_back(( 129 | events::Event::ChannelClosed { 130 | channel_id: channel.context.channel_id(), 131 | user_channel_id: channel.context.get_user_id(), 132 | reason: ClosureReason::OutdatedChannelManager, 133 | counterparty_node_id: Some(channel.context.get_counterparty_node_id()), 134 | channel_capacity_sats: Some(channel.context.get_value_satoshis()), 135 | channel_funding_txo: channel.context.get_funding_txo(), 136 | }, 137 | None, 138 | )); 139 | for (channel_htlc_source, payment_hash) in channel.inflight_htlc_sources() { 140 | let mut found_htlc = false; 141 | for (monitor_htlc_source, _) in monitor.get_all_current_outbound_htlcs() { 142 | if *channel_htlc_source == monitor_htlc_source { 143 | found_htlc = true; 144 | break; 145 | } 146 | } 147 | if !found_htlc { 148 | // If we have some HTLCs in the channel which are not present in the newer 149 | // ChannelMonitor, they have been removed and should be failed back to 150 | // ensure we don't forget them entirely. Note that if the missing HTLC(s) 151 | // were actually claimed we'd have generated and ensured the previous-hop 152 | // claim update ChannelMonitor updates were persisted prior to persising 153 | // the ChannelMonitor update for the forward leg, so attempting to fail the 154 | // backwards leg of the HTLC will simply be rejected. 155 | log_info!(logger, 156 | "Failing HTLC with hash {} as it is missing in the ChannelMonitor for channel {} but was present in the (stale) ChannelManager", 157 | &channel.context.channel_id(), &payment_hash); 158 | failed_htlcs.push(( 159 | channel_htlc_source.clone(), 160 | *payment_hash, 161 | channel.context.get_counterparty_node_id(), 162 | channel.context.channel_id(), 163 | )); 164 | } 165 | } 166 | } else { 167 | channel.on_startup_drop_completed_blocked_mon_updates_through( 168 | &logger, 169 | monitor.get_latest_update_id(), 170 | ); 171 | log_info!(logger, "Successfully loaded channel {} at update_id {} against monitor at update id {} with {} blocked updates", 172 | &channel.context.channel_id(), channel.context.get_latest_monitor_update_id(), 173 | monitor.get_latest_update_id(), channel.blocked_monitor_updates_pending()); 174 | if let Some(short_channel_id) = channel.context.get_short_channel_id() { 175 | short_to_chan_info.insert( 176 | short_channel_id, 177 | ( 178 | channel.context.get_counterparty_node_id(), 179 | channel.context.channel_id(), 180 | ), 181 | ); 182 | } 183 | if let Some(funding_txo) = channel.context.get_funding_txo() { 184 | outpoint_to_peer 185 | .insert(funding_txo, channel.context.get_counterparty_node_id()); 186 | } 187 | match funded_peer_channels.entry(channel.context.get_counterparty_node_id()) { 188 | hash_map::Entry::Occupied(mut entry) => { 189 | let by_id_map = entry.get_mut(); 190 | by_id_map.insert( 191 | channel.context.channel_id(), 192 | ChannelPhase::Funded(channel), 193 | ); 194 | }, 195 | hash_map::Entry::Vacant(entry) => { 196 | let mut by_id_map = new_hash_map(); 197 | by_id_map.insert( 198 | channel.context.channel_id(), 199 | ChannelPhase::Funded(channel), 200 | ); 201 | entry.insert(by_id_map); 202 | }, 203 | } 204 | } 205 | } else if channel.is_awaiting_initial_mon_persist() { 206 | // If we were persisted and shut down while the initial ChannelMonitor persistence 207 | // was in-progress, we never broadcasted the funding transaction and can still 208 | // safely discard the channel. 209 | let _ = channel.context.force_shutdown(false, ClosureReason::DisconnectedPeer); 210 | channel_closures.push_back(( 211 | events::Event::ChannelClosed { 212 | channel_id: channel.context.channel_id(), 213 | user_channel_id: channel.context.get_user_id(), 214 | reason: ClosureReason::DisconnectedPeer, 215 | counterparty_node_id: Some(channel.context.get_counterparty_node_id()), 216 | channel_capacity_sats: Some(channel.context.get_value_satoshis()), 217 | channel_funding_txo: channel.context.get_funding_txo(), 218 | }, 219 | None, 220 | )); 221 | } else { 222 | log_error!( 223 | logger, 224 | "Missing ChannelMonitor for channel {} needed by ChannelManager.", 225 | &channel.context.channel_id() 226 | ); 227 | log_error!(logger, " The chain::Watch API *requires* that monitors are persisted durably before returning,"); 228 | log_error!(logger, " client applications must ensure that ChannelMonitor data is always available and the latest to avoid funds loss!"); 229 | log_error!( 230 | logger, 231 | " Without the ChannelMonitor we cannot continue without risking funds." 232 | ); 233 | log_error!(logger, " Please ensure the chain::Watch API requirements are met and file a bug report at https://github.com/lightningdevkit/rust-lightning"); 234 | return Err(DecodeError::InvalidValue); 235 | } 236 | } 237 | 238 | for (funding_txo, monitor) in args.channel_monitors.iter() { 239 | if !funding_txo_set.contains(funding_txo) { 240 | let logger = WithChannelMonitor::from(&args.logger, monitor); 241 | let channel_id = monitor.channel_id(); 242 | log_info!( 243 | logger, 244 | "Queueing monitor update to ensure missing channel {} is force closed", 245 | &channel_id 246 | ); 247 | let monitor_update = ChannelMonitorUpdate { 248 | update_id: CLOSED_CHANNEL_UPDATE_ID, 249 | counterparty_node_id: None, 250 | updates: vec![ChannelMonitorUpdateStep::ChannelForceClosed { 251 | should_broadcast: true, 252 | }], 253 | channel_id: Some(monitor.channel_id()), 254 | }; 255 | close_background_events.push( 256 | BackgroundEvent::ClosedMonitorUpdateRegeneratedOnStartup(( 257 | *funding_txo, 258 | channel_id, 259 | monitor_update, 260 | )), 261 | ); 262 | } 263 | } 264 | 265 | const MAX_ALLOC_SIZE: usize = 1024 * 64; 266 | let forward_htlcs_count: u64 = Readable::read(reader)?; 267 | let mut forward_htlcs = hash_map_with_capacity(cmp::min(forward_htlcs_count as usize, 128)); 268 | for _ in 0..forward_htlcs_count { 269 | let short_channel_id = Readable::read(reader)?; 270 | let pending_forwards_count: u64 = Readable::read(reader)?; 271 | let mut pending_forwards = Vec::with_capacity(cmp::min( 272 | pending_forwards_count as usize, 273 | MAX_ALLOC_SIZE / mem::size_of::(), 274 | )); 275 | for _ in 0..pending_forwards_count { 276 | pending_forwards.push(Readable::read(reader)?); 277 | } 278 | forward_htlcs.insert(short_channel_id, pending_forwards); 279 | } 280 | 281 | let claimable_htlcs_count: u64 = Readable::read(reader)?; 282 | let mut claimable_htlcs_list = 283 | Vec::with_capacity(cmp::min(claimable_htlcs_count as usize, 128)); 284 | for _ in 0..claimable_htlcs_count { 285 | let payment_hash = Readable::read(reader)?; 286 | let previous_hops_len: u64 = Readable::read(reader)?; 287 | let mut previous_hops = Vec::with_capacity(cmp::min( 288 | previous_hops_len as usize, 289 | MAX_ALLOC_SIZE / mem::size_of::(), 290 | )); 291 | for _ in 0..previous_hops_len { 292 | previous_hops.push(::read(reader)?); 293 | } 294 | claimable_htlcs_list.push((payment_hash, previous_hops)); 295 | } 296 | 297 | let peer_state_from_chans = |channel_by_id| PeerState { 298 | channel_by_id, 299 | inbound_channel_request_by_id: new_hash_map(), 300 | latest_features: InitFeatures::empty(), 301 | pending_msg_events: Vec::new(), 302 | in_flight_monitor_updates: BTreeMap::new(), 303 | monitor_update_blocked_actions: BTreeMap::new(), 304 | actions_blocking_raa_monitor_updates: BTreeMap::new(), 305 | is_connected: false, 306 | }; 307 | 308 | let peer_count: u64 = Readable::read(reader)?; 309 | let mut per_peer_state = hash_map_with_capacity(cmp::min( 310 | peer_count as usize, 311 | MAX_ALLOC_SIZE / mem::size_of::<(PublicKey, Mutex>)>(), 312 | )); 313 | for _ in 0..peer_count { 314 | let peer_pubkey = Readable::read(reader)?; 315 | let peer_chans = funded_peer_channels.remove(&peer_pubkey).unwrap_or(new_hash_map()); 316 | let mut peer_state = peer_state_from_chans(peer_chans); 317 | peer_state.latest_features = Readable::read(reader)?; 318 | per_peer_state.insert(peer_pubkey, Mutex::new(peer_state)); 319 | } 320 | 321 | let event_count: u64 = Readable::read(reader)?; 322 | let mut pending_events_read: VecDeque<(events::Event, Option)> = 323 | VecDeque::with_capacity(cmp::min( 324 | event_count as usize, 325 | MAX_ALLOC_SIZE / mem::size_of::<(events::Event, Option)>(), 326 | )); 327 | for _ in 0..event_count { 328 | match MaybeReadable::read(reader)? { 329 | Some(event) => pending_events_read.push_back((event, None)), 330 | None => continue, 331 | } 332 | } 333 | 334 | let background_event_count: u64 = Readable::read(reader)?; 335 | for _ in 0..background_event_count { 336 | match ::read(reader)? { 337 | 0 => { 338 | // LDK versions prior to 0.0.116 wrote pending `MonitorUpdateRegeneratedOnStartup`s here, 339 | // however we really don't (and never did) need them - we regenerate all 340 | // on-startup monitor updates. 341 | let _: OutPoint = Readable::read(reader)?; 342 | let _: ChannelMonitorUpdate = Readable::read(reader)?; 343 | }, 344 | _ => return Err(DecodeError::InvalidValue), 345 | } 346 | } 347 | 348 | let _last_node_announcement_serial: u32 = Readable::read(reader)?; // Only used < 0.0.111 349 | let highest_seen_timestamp: u32 = Readable::read(reader)?; 350 | 351 | let pending_inbound_payment_count: u64 = Readable::read(reader)?; 352 | let mut pending_inbound_payments: HashMap = 353 | hash_map_with_capacity(cmp::min( 354 | pending_inbound_payment_count as usize, 355 | MAX_ALLOC_SIZE / (3 * 32), 356 | )); 357 | for _ in 0..pending_inbound_payment_count { 358 | if pending_inbound_payments 359 | .insert(Readable::read(reader)?, Readable::read(reader)?) 360 | .is_some() 361 | { 362 | return Err(DecodeError::InvalidValue); 363 | } 364 | } 365 | 366 | let pending_outbound_payments_count_compat: u64 = Readable::read(reader)?; 367 | let mut pending_outbound_payments_compat: HashMap = 368 | hash_map_with_capacity(cmp::min( 369 | pending_outbound_payments_count_compat as usize, 370 | MAX_ALLOC_SIZE / 32, 371 | )); 372 | for _ in 0..pending_outbound_payments_count_compat { 373 | let session_priv = Readable::read(reader)?; 374 | let payment = PendingOutboundPayment::Legacy { 375 | session_privs: hash_set_from_iter([session_priv]), 376 | }; 377 | if pending_outbound_payments_compat.insert(PaymentId(session_priv), payment).is_some() { 378 | return Err(DecodeError::InvalidValue); 379 | }; 380 | } 381 | 382 | // pending_outbound_payments_no_retry is for compatibility with 0.0.101 clients. 383 | let mut pending_outbound_payments_no_retry: Option>> = 384 | None; 385 | let mut pending_outbound_payments = None; 386 | let mut pending_intercepted_htlcs: Option> = 387 | Some(new_hash_map()); 388 | let mut received_network_pubkey: Option = None; 389 | let mut fake_scid_rand_bytes: Option<[u8; 32]> = None; 390 | let mut probing_cookie_secret: Option<[u8; 32]> = None; 391 | let mut claimable_htlc_purposes = None; 392 | let mut claimable_htlc_onion_fields = None; 393 | let mut pending_claiming_payments = Some(new_hash_map()); 394 | let mut monitor_update_blocked_actions_per_peer: Option>)>> = 395 | Some(Vec::new()); 396 | let mut events_override = None; 397 | let mut in_flight_monitor_updates: Option< 398 | HashMap<(PublicKey, OutPoint), Vec>, 399 | > = None; 400 | let mut decode_update_add_htlcs: Option>> = None; 401 | read_tlv_fields!(reader, { 402 | (1, pending_outbound_payments_no_retry, option), 403 | (2, pending_intercepted_htlcs, option), 404 | (3, pending_outbound_payments, option), 405 | (4, pending_claiming_payments, option), 406 | (5, received_network_pubkey, option), 407 | (6, monitor_update_blocked_actions_per_peer, option), 408 | (7, fake_scid_rand_bytes, option), 409 | (8, events_override, option), 410 | (9, claimable_htlc_purposes, optional_vec), 411 | (10, in_flight_monitor_updates, option), 412 | (11, probing_cookie_secret, option), 413 | (13, claimable_htlc_onion_fields, optional_vec), 414 | (14, decode_update_add_htlcs, option), 415 | }); 416 | let mut decode_update_add_htlcs = decode_update_add_htlcs.unwrap_or_else(|| new_hash_map()); 417 | if fake_scid_rand_bytes.is_none() { 418 | fake_scid_rand_bytes = Some(args.entropy_source.get_secure_random_bytes()); 419 | } 420 | 421 | if probing_cookie_secret.is_none() { 422 | probing_cookie_secret = Some(args.entropy_source.get_secure_random_bytes()); 423 | } 424 | 425 | if let Some(events) = events_override { 426 | pending_events_read = events; 427 | } 428 | 429 | if !channel_closures.is_empty() { 430 | pending_events_read.append(&mut channel_closures); 431 | } 432 | 433 | if pending_outbound_payments.is_none() && pending_outbound_payments_no_retry.is_none() { 434 | pending_outbound_payments = Some(pending_outbound_payments_compat); 435 | } else if pending_outbound_payments.is_none() { 436 | let mut outbounds = new_hash_map(); 437 | for (id, session_privs) in pending_outbound_payments_no_retry.unwrap().drain() { 438 | outbounds.insert(id, PendingOutboundPayment::Legacy { session_privs }); 439 | } 440 | pending_outbound_payments = Some(outbounds); 441 | } 442 | let pending_outbounds = OutboundPayments { 443 | pending_outbound_payments: Mutex::new(pending_outbound_payments.unwrap()), 444 | retry_lock: Mutex::new(()), 445 | color_source: args.color_source.clone(), 446 | }; 447 | 448 | // We have to replay (or skip, if they were completed after we wrote the `ChannelManager`) 449 | // each `ChannelMonitorUpdate` in `in_flight_monitor_updates`. After doing so, we have to 450 | // check that each channel we have isn't newer than the latest `ChannelMonitorUpdate`(s) we 451 | // replayed, and for each monitor update we have to replay we have to ensure there's a 452 | // `ChannelMonitor` for it. 453 | // 454 | // In order to do so we first walk all of our live channels (so that we can check their 455 | // state immediately after doing the update replays, when we have the `update_id`s 456 | // available) and then walk any remaining in-flight updates. 457 | // 458 | // Because the actual handling of the in-flight updates is the same, it's macro'ized here: 459 | let mut pending_background_events = Vec::new(); 460 | macro_rules! handle_in_flight_updates { 461 | ($counterparty_node_id: expr, $chan_in_flight_upds: expr, $funding_txo: expr, 462 | $monitor: expr, $peer_state: expr, $logger: expr, $channel_info_log: expr 463 | ) => {{ 464 | let mut max_in_flight_update_id = 0; 465 | $chan_in_flight_upds.retain(|upd| upd.update_id > $monitor.get_latest_update_id()); 466 | for update in $chan_in_flight_upds.iter() { 467 | log_trace!( 468 | $logger, 469 | "Replaying ChannelMonitorUpdate {} for {}channel {}", 470 | update.update_id, 471 | $channel_info_log, 472 | &$monitor.channel_id() 473 | ); 474 | max_in_flight_update_id = cmp::max(max_in_flight_update_id, update.update_id); 475 | pending_background_events.push( 476 | BackgroundEvent::MonitorUpdateRegeneratedOnStartup { 477 | counterparty_node_id: $counterparty_node_id, 478 | funding_txo: $funding_txo, 479 | channel_id: $monitor.channel_id(), 480 | update: update.clone(), 481 | }, 482 | ); 483 | } 484 | if $chan_in_flight_upds.is_empty() { 485 | // We had some updates to apply, but it turns out they had completed before we 486 | // were serialized, we just weren't notified of that. Thus, we may have to run 487 | // the completion actions for any monitor updates, but otherwise are done. 488 | pending_background_events.push(BackgroundEvent::MonitorUpdatesComplete { 489 | counterparty_node_id: $counterparty_node_id, 490 | channel_id: $monitor.channel_id(), 491 | }); 492 | } 493 | if $peer_state 494 | .in_flight_monitor_updates 495 | .insert($funding_txo, $chan_in_flight_upds) 496 | .is_some() 497 | { 498 | log_error!( 499 | $logger, 500 | "Duplicate in-flight monitor update set for the same channel!" 501 | ); 502 | return Err(DecodeError::InvalidValue); 503 | } 504 | max_in_flight_update_id 505 | }}; 506 | } 507 | 508 | for (counterparty_id, peer_state_mtx) in per_peer_state.iter_mut() { 509 | let mut peer_state_lock = peer_state_mtx.lock().unwrap(); 510 | let peer_state = &mut *peer_state_lock; 511 | for phase in peer_state.channel_by_id.values() { 512 | if let ChannelPhase::Funded(chan) = phase { 513 | let logger = WithChannelContext::from(&args.logger, &chan.context); 514 | 515 | // Channels that were persisted have to be funded, otherwise they should have been 516 | // discarded. 517 | let funding_txo = 518 | chan.context.get_funding_txo().ok_or(DecodeError::InvalidValue)?; 519 | let monitor = args 520 | .channel_monitors 521 | .get(&funding_txo) 522 | .expect("We already checked for monitor presence when loading channels"); 523 | let mut max_in_flight_update_id = monitor.get_latest_update_id(); 524 | if let Some(in_flight_upds) = &mut in_flight_monitor_updates { 525 | if let Some(mut chan_in_flight_upds) = 526 | in_flight_upds.remove(&(*counterparty_id, funding_txo)) 527 | { 528 | max_in_flight_update_id = cmp::max( 529 | max_in_flight_update_id, 530 | handle_in_flight_updates!( 531 | *counterparty_id, 532 | chan_in_flight_upds, 533 | funding_txo, 534 | monitor, 535 | peer_state, 536 | logger, 537 | "" 538 | ), 539 | ); 540 | } 541 | } 542 | if chan.get_latest_unblocked_monitor_update_id() > max_in_flight_update_id { 543 | // If the channel is ahead of the monitor, return DangerousValue: 544 | log_error!(logger, "A ChannelMonitor is stale compared to the current ChannelManager! This indicates a potentially-critical violation of the chain::Watch API!"); 545 | log_error!(logger, " The ChannelMonitor for channel {} is at update_id {} with update_id through {} in-flight", 546 | chan.context.channel_id(), monitor.get_latest_update_id(), max_in_flight_update_id); 547 | log_error!( 548 | logger, 549 | " but the ChannelManager is at update_id {}.", 550 | chan.get_latest_unblocked_monitor_update_id() 551 | ); 552 | log_error!(logger, " The chain::Watch API *requires* that monitors are persisted durably before returning,"); 553 | log_error!(logger, " client applications must ensure that ChannelMonitor data is always available and the latest to avoid funds loss!"); 554 | log_error!(logger, " Without the latest ChannelMonitor we cannot continue without risking funds."); 555 | log_error!(logger, " Please ensure the chain::Watch API requirements are met and file a bug report at https://github.com/lightningdevkit/rust-lightning"); 556 | return Err(DecodeError::DangerousValue); 557 | } 558 | } else { 559 | // We shouldn't have persisted (or read) any unfunded channel types so none should have been 560 | // created in this `channel_by_id` map. 561 | debug_assert!(false); 562 | return Err(DecodeError::InvalidValue); 563 | } 564 | } 565 | } 566 | 567 | if let Some(in_flight_upds) = in_flight_monitor_updates { 568 | for ((counterparty_id, funding_txo), mut chan_in_flight_updates) in in_flight_upds { 569 | let channel_id = funding_txo_to_channel_id.get(&funding_txo).copied(); 570 | let logger = WithContext::from(&args.logger, Some(counterparty_id), channel_id); 571 | if let Some(monitor) = args.channel_monitors.get(&funding_txo) { 572 | // Now that we've removed all the in-flight monitor updates for channels that are 573 | // still open, we need to replay any monitor updates that are for closed channels, 574 | // creating the neccessary peer_state entries as we go. 575 | let peer_state_mutex = per_peer_state 576 | .entry(counterparty_id) 577 | .or_insert_with(|| Mutex::new(peer_state_from_chans(new_hash_map()))); 578 | let mut peer_state = peer_state_mutex.lock().unwrap(); 579 | handle_in_flight_updates!( 580 | counterparty_id, 581 | chan_in_flight_updates, 582 | funding_txo, 583 | monitor, 584 | peer_state, 585 | logger, 586 | "closed " 587 | ); 588 | } else { 589 | log_error!(logger, "A ChannelMonitor is missing even though we have in-flight updates for it! This indicates a potentially-critical violation of the chain::Watch API!"); 590 | log_error!( 591 | logger, 592 | " The ChannelMonitor for channel {} is missing.", 593 | if let Some(channel_id) = channel_id { 594 | channel_id.to_string() 595 | } else { 596 | format!("with outpoint {}", funding_txo) 597 | } 598 | ); 599 | log_error!(logger, " The chain::Watch API *requires* that monitors are persisted durably before returning,"); 600 | log_error!(logger, " client applications must ensure that ChannelMonitor data is always available and the latest to avoid funds loss!"); 601 | log_error!(logger, " Without the latest ChannelMonitor we cannot continue without risking funds."); 602 | log_error!(logger, " Please ensure the chain::Watch API requirements are met and file a bug report at https://github.com/lightningdevkit/rust-lightning"); 603 | log_error!( 604 | logger, 605 | " Pending in-flight updates are: {:?}", 606 | chan_in_flight_updates 607 | ); 608 | return Err(DecodeError::InvalidValue); 609 | } 610 | } 611 | } 612 | 613 | // Note that we have to do the above replays before we push new monitor updates. 614 | pending_background_events.append(&mut close_background_events); 615 | 616 | // If there's any preimages for forwarded HTLCs hanging around in ChannelMonitors we 617 | // should ensure we try them again on the inbound edge. We put them here and do so after we 618 | // have a fully-constructed `ChannelManager` at the end. 619 | let mut pending_claims_to_replay = Vec::new(); 620 | 621 | { 622 | // If we're tracking pending payments, ensure we haven't lost any by looking at the 623 | // ChannelMonitor data for any channels for which we do not have authorative state 624 | // (i.e. those for which we just force-closed above or we otherwise don't have a 625 | // corresponding `Channel` at all). 626 | // This avoids several edge-cases where we would otherwise "forget" about pending 627 | // payments which are still in-flight via their on-chain state. 628 | // We only rebuild the pending payments map if we were most recently serialized by 629 | // 0.0.102+ 630 | for (_, monitor) in args.channel_monitors.iter() { 631 | let counterparty_opt = outpoint_to_peer.get(&monitor.get_funding_txo().0); 632 | if counterparty_opt.is_none() { 633 | let logger = WithChannelMonitor::from(&args.logger, monitor); 634 | for (htlc_source, (htlc, _)) in monitor.get_pending_or_resolved_outbound_htlcs() 635 | { 636 | if let HTLCSource::OutboundRoute { 637 | payment_id, session_priv, path, .. 638 | } = htlc_source 639 | { 640 | if path.hops.is_empty() { 641 | log_error!(logger, "Got an empty path for a pending payment"); 642 | return Err(DecodeError::InvalidValue); 643 | } 644 | 645 | let path_amt = path.final_value_msat(); 646 | let mut session_priv_bytes = [0; 32]; 647 | session_priv_bytes[..].copy_from_slice(&session_priv[..]); 648 | match pending_outbounds 649 | .pending_outbound_payments 650 | .lock() 651 | .unwrap() 652 | .entry(payment_id) 653 | { 654 | hash_map::Entry::Occupied(mut entry) => { 655 | let newly_added = 656 | entry.get_mut().insert(session_priv_bytes, &path); 657 | log_info!(logger, "{} a pending payment path for {} msat for session priv {} on an existing pending payment with payment hash {}", 658 | if newly_added { "Added" } else { "Had" }, path_amt, log_bytes!(session_priv_bytes), htlc.payment_hash); 659 | }, 660 | hash_map::Entry::Vacant(entry) => { 661 | let path_fee = path.fee_msat(); 662 | entry.insert(PendingOutboundPayment::Retryable { 663 | retry_strategy: None, 664 | attempts: PaymentAttempts::new(), 665 | payment_params: None, 666 | session_privs: hash_set_from_iter([session_priv_bytes]), 667 | payment_hash: htlc.payment_hash, 668 | payment_secret: None, // only used for retries, and we'll never retry on startup 669 | payment_metadata: None, // only used for retries, and we'll never retry on startup 670 | keysend_preimage: None, // only used for retries, and we'll never retry on startup 671 | custom_tlvs: Vec::new(), // only used for retries, and we'll never retry on startup 672 | pending_amt_msat: path_amt, 673 | pending_fee_msat: Some(path_fee), 674 | total_msat: path_amt, 675 | starting_block_height: best_block_height, 676 | remaining_max_total_routing_fee_msat: None, // only used for retries, and we'll never retry on startup 677 | }); 678 | log_info!(logger, "Added a pending payment for {} msat with payment hash {} for path with session priv {}", 679 | path_amt, &htlc.payment_hash, log_bytes!(session_priv_bytes)); 680 | }, 681 | } 682 | } 683 | } 684 | for (htlc_source, (htlc, preimage_opt)) in 685 | monitor.get_all_current_outbound_htlcs() 686 | { 687 | match htlc_source { 688 | HTLCSource::PreviousHopData(prev_hop_data) => { 689 | let pending_forward_matches_htlc = |info: &PendingAddHTLCInfo| { 690 | info.prev_funding_outpoint == prev_hop_data.outpoint 691 | && info.prev_htlc_id == prev_hop_data.htlc_id 692 | }; 693 | // The ChannelMonitor is now responsible for this HTLC's 694 | // failure/success and will let us know what its outcome is. If we 695 | // still have an entry for this HTLC in `forward_htlcs` or 696 | // `pending_intercepted_htlcs`, we were apparently not persisted after 697 | // the monitor was when forwarding the payment. 698 | decode_update_add_htlcs.retain(|scid, update_add_htlcs| { 699 | update_add_htlcs.retain(|update_add_htlc| { 700 | let matches = *scid == prev_hop_data.short_channel_id && 701 | update_add_htlc.htlc_id == prev_hop_data.htlc_id; 702 | if matches { 703 | log_info!(logger, "Removing pending to-decode HTLC with hash {} as it was forwarded to the closed channel {}", 704 | &htlc.payment_hash, &monitor.channel_id()); 705 | } 706 | !matches 707 | }); 708 | !update_add_htlcs.is_empty() 709 | }); 710 | forward_htlcs.retain(|_, forwards| { 711 | forwards.retain(|forward| { 712 | if let HTLCForwardInfo::AddHTLC(htlc_info) = forward { 713 | if pending_forward_matches_htlc(&htlc_info) { 714 | log_info!(logger, "Removing pending to-forward HTLC with hash {} as it was forwarded to the closed channel {}", 715 | &htlc.payment_hash, &monitor.channel_id()); 716 | false 717 | } else { true } 718 | } else { true } 719 | }); 720 | !forwards.is_empty() 721 | }); 722 | pending_intercepted_htlcs.as_mut().unwrap().retain(|intercepted_id, htlc_info| { 723 | if pending_forward_matches_htlc(&htlc_info) { 724 | log_info!(logger, "Removing pending intercepted HTLC with hash {} as it was forwarded to the closed channel {}", 725 | &htlc.payment_hash, &monitor.channel_id()); 726 | pending_events_read.retain(|(event, _)| { 727 | if let Event::HTLCIntercepted { intercept_id: ev_id, .. } = event { 728 | intercepted_id != ev_id 729 | } else { true } 730 | }); 731 | false 732 | } else { true } 733 | }); 734 | }, 735 | HTLCSource::OutboundRoute { 736 | payment_id, session_priv, path, .. 737 | } => { 738 | if let Some(preimage) = preimage_opt { 739 | let pending_events = Mutex::new(pending_events_read); 740 | // Note that we set `from_onchain` to "false" here, 741 | // deliberately keeping the pending payment around forever. 742 | // Given it should only occur when we have a channel we're 743 | // force-closing for being stale that's okay. 744 | // The alternative would be to wipe the state when claiming, 745 | // generating a `PaymentPathSuccessful` event but regenerating 746 | // it and the `PaymentSent` on every restart until the 747 | // `ChannelMonitor` is removed. 748 | let compl_action = 749 | EventCompletionAction::ReleaseRAAChannelMonitorUpdate { 750 | channel_funding_outpoint: monitor.get_funding_txo().0, 751 | channel_id: monitor.channel_id(), 752 | counterparty_node_id: path.hops[0].pubkey, 753 | }; 754 | pending_outbounds.claim_htlc( 755 | payment_id, 756 | preimage, 757 | session_priv, 758 | path, 759 | false, 760 | compl_action, 761 | &pending_events, 762 | &&logger, 763 | ); 764 | pending_events_read = pending_events.into_inner().unwrap(); 765 | } 766 | }, 767 | } 768 | } 769 | } 770 | 771 | // Whether the downstream channel was closed or not, try to re-apply any payment 772 | // preimages from it which may be needed in upstream channels for forwarded 773 | // payments. 774 | let outbound_claimed_htlcs_iter = monitor 775 | .get_all_current_outbound_htlcs() 776 | .into_iter() 777 | .filter_map(|(htlc_source, (htlc, preimage_opt))| { 778 | if let HTLCSource::PreviousHopData(_) = htlc_source { 779 | if let Some(payment_preimage) = preimage_opt { 780 | Some(( 781 | htlc_source, 782 | payment_preimage, 783 | htlc.amount_msat, 784 | htlc.amount_rgb, 785 | // Check if `counterparty_opt.is_none()` to see if the 786 | // downstream chan is closed (because we don't have a 787 | // channel_id -> peer map entry). 788 | counterparty_opt.is_none(), 789 | counterparty_opt 790 | .cloned() 791 | .or(monitor.get_counterparty_node_id()), 792 | monitor.get_funding_txo().0, 793 | monitor.channel_id(), 794 | )) 795 | } else { 796 | None 797 | } 798 | } else { 799 | // If it was an outbound payment, we've handled it above - if a preimage 800 | // came in and we persisted the `ChannelManager` we either handled it and 801 | // are good to go or the channel force-closed - we don't have to handle the 802 | // channel still live case here. 803 | None 804 | } 805 | }); 806 | for tuple in outbound_claimed_htlcs_iter { 807 | pending_claims_to_replay.push(tuple); 808 | } 809 | } 810 | } 811 | 812 | if !forward_htlcs.is_empty() 813 | || !decode_update_add_htlcs.is_empty() 814 | || pending_outbounds.needs_abandon() 815 | { 816 | // If we have pending HTLCs to forward, assume we either dropped a 817 | // `PendingHTLCsForwardable` or the user received it but never processed it as they 818 | // shut down before the timer hit. Either way, set the time_forwardable to a small 819 | // constant as enough time has likely passed that we should simply handle the forwards 820 | // now, or at least after the user gets a chance to reconnect to our peers. 821 | pending_events_read.push_back(( 822 | events::Event::PendingHTLCsForwardable { time_forwardable: Duration::from_secs(2) }, 823 | None, 824 | )); 825 | } 826 | 827 | let inbound_pmt_key_material = args.node_signer.get_inbound_payment_key_material(); 828 | let expanded_inbound_key = inbound_payment::ExpandedKey::new(&inbound_pmt_key_material); 829 | 830 | let mut claimable_payments = hash_map_with_capacity(claimable_htlcs_list.len()); 831 | if let Some(purposes) = claimable_htlc_purposes { 832 | if purposes.len() != claimable_htlcs_list.len() { 833 | return Err(DecodeError::InvalidValue); 834 | } 835 | if let Some(onion_fields) = claimable_htlc_onion_fields { 836 | if onion_fields.len() != claimable_htlcs_list.len() { 837 | return Err(DecodeError::InvalidValue); 838 | } 839 | for (purpose, (onion, (payment_hash, htlcs))) in purposes 840 | .into_iter() 841 | .zip(onion_fields.into_iter().zip(claimable_htlcs_list.into_iter())) 842 | { 843 | let existing_payment = claimable_payments.insert( 844 | payment_hash, 845 | ClaimablePayment { purpose, htlcs, onion_fields: onion }, 846 | ); 847 | if existing_payment.is_some() { 848 | return Err(DecodeError::InvalidValue); 849 | } 850 | } 851 | } else { 852 | for (purpose, (payment_hash, htlcs)) in 853 | purposes.into_iter().zip(claimable_htlcs_list.into_iter()) 854 | { 855 | let existing_payment = claimable_payments.insert( 856 | payment_hash, 857 | ClaimablePayment { purpose, htlcs, onion_fields: None }, 858 | ); 859 | if existing_payment.is_some() { 860 | return Err(DecodeError::InvalidValue); 861 | } 862 | } 863 | } 864 | } else { 865 | // LDK versions prior to 0.0.107 did not write a `pending_htlc_purposes`, but do 866 | // include a `_legacy_hop_data` in the `OnionPayload`. 867 | for (payment_hash, htlcs) in claimable_htlcs_list.drain(..) { 868 | if htlcs.is_empty() { 869 | return Err(DecodeError::InvalidValue); 870 | } 871 | let purpose = match &htlcs[0].onion_payload { 872 | OnionPayload::Invoice { _legacy_hop_data } => { 873 | if let Some(hop_data) = _legacy_hop_data { 874 | events::PaymentPurpose::Bolt11InvoicePayment { 875 | payment_preimage: match pending_inbound_payments.get(&payment_hash) 876 | { 877 | Some(inbound_payment) => inbound_payment.payment_preimage, 878 | None => match inbound_payment::verify( 879 | payment_hash, 880 | &hop_data, 881 | 0, 882 | &expanded_inbound_key, 883 | &args.logger, 884 | ) { 885 | Ok((payment_preimage, _)) => payment_preimage, 886 | Err(()) => { 887 | log_error!(args.logger, "Failed to read claimable payment data for HTLC with payment hash {} - was not a pending inbound payment and didn't match our payment key", &payment_hash); 888 | return Err(DecodeError::InvalidValue); 889 | }, 890 | }, 891 | }, 892 | payment_secret: hop_data.payment_secret, 893 | } 894 | } else { 895 | return Err(DecodeError::InvalidValue); 896 | } 897 | }, 898 | OnionPayload::Spontaneous(payment_preimage) => { 899 | events::PaymentPurpose::SpontaneousPayment(*payment_preimage) 900 | }, 901 | }; 902 | claimable_payments 903 | .insert(payment_hash, ClaimablePayment { purpose, htlcs, onion_fields: None }); 904 | } 905 | } 906 | 907 | let mut secp_ctx = Secp256k1::new(); 908 | secp_ctx.seeded_randomize(&args.entropy_source.get_secure_random_bytes()); 909 | 910 | let our_network_pubkey = match args.node_signer.get_node_id(Recipient::Node) { 911 | Ok(key) => key, 912 | Err(()) => return Err(DecodeError::InvalidValue), 913 | }; 914 | if let Some(network_pubkey) = received_network_pubkey { 915 | if network_pubkey != our_network_pubkey { 916 | log_error!( 917 | args.logger, 918 | "Key that was generated does not match the existing key. left: {}, right: {}", 919 | network_pubkey, 920 | our_network_pubkey 921 | ); 922 | return Err(DecodeError::InvalidValue); 923 | } 924 | } 925 | 926 | let mut outbound_scid_aliases = new_hash_set(); 927 | for (_peer_node_id, peer_state_mutex) in per_peer_state.iter_mut() { 928 | let mut peer_state_lock = peer_state_mutex.lock().unwrap(); 929 | let peer_state = &mut *peer_state_lock; 930 | for (chan_id, phase) in peer_state.channel_by_id.iter_mut() { 931 | if let ChannelPhase::Funded(chan) = phase { 932 | let logger = WithChannelContext::from(&args.logger, &chan.context); 933 | if chan.context.outbound_scid_alias() == 0 { 934 | let mut outbound_scid_alias; 935 | loop { 936 | outbound_scid_alias = fake_scid::Namespace::OutboundAlias 937 | .get_fake_scid( 938 | best_block_height, 939 | &chain_hash, 940 | fake_scid_rand_bytes.as_ref().unwrap(), 941 | &args.entropy_source, 942 | ); 943 | if outbound_scid_aliases.insert(outbound_scid_alias) { 944 | break; 945 | } 946 | } 947 | chan.context.set_outbound_scid_alias(outbound_scid_alias); 948 | } else if !outbound_scid_aliases.insert(chan.context.outbound_scid_alias()) { 949 | // Note that in rare cases its possible to hit this while reading an older 950 | // channel if we just happened to pick a colliding outbound alias above. 951 | log_error!( 952 | logger, 953 | "Got duplicate outbound SCID alias; {}", 954 | chan.context.outbound_scid_alias() 955 | ); 956 | return Err(DecodeError::InvalidValue); 957 | } 958 | if chan.context.is_usable() { 959 | if short_to_chan_info 960 | .insert( 961 | chan.context.outbound_scid_alias(), 962 | (chan.context.get_counterparty_node_id(), *chan_id), 963 | ) 964 | .is_some() 965 | { 966 | // Note that in rare cases its possible to hit this while reading an older 967 | // channel if we just happened to pick a colliding outbound alias above. 968 | log_error!( 969 | logger, 970 | "Got duplicate outbound SCID alias; {}", 971 | chan.context.outbound_scid_alias() 972 | ); 973 | return Err(DecodeError::InvalidValue); 974 | } 975 | } 976 | } else { 977 | // We shouldn't have persisted (or read) any unfunded channel types so none should have been 978 | // created in this `channel_by_id` map. 979 | debug_assert!(false); 980 | return Err(DecodeError::InvalidValue); 981 | } 982 | } 983 | } 984 | 985 | let bounded_fee_estimator = LowerBoundedFeeEstimator::new(args.fee_estimator); 986 | 987 | for (_, monitor) in args.channel_monitors.iter() { 988 | for (payment_hash, payment_preimage) in monitor.get_stored_preimages() { 989 | if let Some(payment) = claimable_payments.remove(&payment_hash) { 990 | log_info!(args.logger, "Re-claiming HTLCs with payment hash {} as we've released the preimage to a ChannelMonitor!", &payment_hash); 991 | let mut claimable_amt_msat = 0; 992 | let mut receiver_node_id = Some(our_network_pubkey); 993 | let phantom_shared_secret = payment.htlcs[0].prev_hop.phantom_shared_secret; 994 | if phantom_shared_secret.is_some() { 995 | let phantom_pubkey = args 996 | .node_signer 997 | .get_node_id(Recipient::PhantomNode) 998 | .expect("Failed to get node_id for phantom node recipient"); 999 | receiver_node_id = Some(phantom_pubkey) 1000 | } 1001 | for claimable_htlc in &payment.htlcs { 1002 | claimable_amt_msat += claimable_htlc.value; 1003 | 1004 | // Add a holding-cell claim of the payment to the Channel, which should be 1005 | // applied ~immediately on peer reconnection. Because it won't generate a 1006 | // new commitment transaction we can just provide the payment preimage to 1007 | // the corresponding ChannelMonitor and nothing else. 1008 | // 1009 | // We do so directly instead of via the normal ChannelMonitor update 1010 | // procedure as the ChainMonitor hasn't yet been initialized, implying 1011 | // we're not allowed to call it directly yet. Further, we do the update 1012 | // without incrementing the ChannelMonitor update ID as there isn't any 1013 | // reason to. 1014 | // If we were to generate a new ChannelMonitor update ID here and then 1015 | // crash before the user finishes block connect we'd end up force-closing 1016 | // this channel as well. On the flip side, there's no harm in restarting 1017 | // without the new monitor persisted - we'll end up right back here on 1018 | // restart. 1019 | let previous_channel_id = claimable_htlc.prev_hop.channel_id; 1020 | if let Some(peer_node_id) = 1021 | outpoint_to_peer.get(&claimable_htlc.prev_hop.outpoint) 1022 | { 1023 | let peer_state_mutex = per_peer_state.get(peer_node_id).unwrap(); 1024 | let mut peer_state_lock = peer_state_mutex.lock().unwrap(); 1025 | let peer_state = &mut *peer_state_lock; 1026 | if let Some(ChannelPhase::Funded(channel)) = 1027 | peer_state.channel_by_id.get_mut(&previous_channel_id) 1028 | { 1029 | let logger = 1030 | WithChannelContext::from(&args.logger, &channel.context); 1031 | channel.claim_htlc_while_disconnected_dropping_mon_update( 1032 | claimable_htlc.prev_hop.htlc_id, 1033 | payment_preimage, 1034 | &&logger, 1035 | ); 1036 | } 1037 | } 1038 | if let Some(previous_hop_monitor) = 1039 | args.channel_monitors.get(&claimable_htlc.prev_hop.outpoint) 1040 | { 1041 | previous_hop_monitor.provide_payment_preimage( 1042 | &payment_hash, 1043 | &payment_preimage, 1044 | &args.tx_broadcaster, 1045 | &bounded_fee_estimator, 1046 | &args.logger, 1047 | ); 1048 | } 1049 | } 1050 | pending_events_read.push_back(( 1051 | events::Event::PaymentClaimed { 1052 | receiver_node_id, 1053 | payment_hash, 1054 | purpose: payment.purpose, 1055 | amount_msat: claimable_amt_msat, 1056 | htlcs: payment.htlcs.iter().map(events::ClaimedHTLC::from).collect(), 1057 | sender_intended_total_msat: payment 1058 | .htlcs 1059 | .first() 1060 | .map(|htlc| htlc.total_msat), 1061 | }, 1062 | None, 1063 | )); 1064 | } 1065 | } 1066 | } 1067 | 1068 | for (node_id, monitor_update_blocked_actions) in 1069 | monitor_update_blocked_actions_per_peer.unwrap() 1070 | { 1071 | if let Some(peer_state) = per_peer_state.get(&node_id) { 1072 | for (channel_id, actions) in monitor_update_blocked_actions.iter() { 1073 | let logger = WithContext::from(&args.logger, Some(node_id), Some(*channel_id)); 1074 | for action in actions.iter() { 1075 | if let MonitorUpdateCompletionAction::EmitEventAndFreeOtherChannel { 1076 | downstream_counterparty_and_funding_outpoint: 1077 | Some(( 1078 | blocked_node_id, 1079 | _blocked_channel_outpoint, 1080 | blocked_channel_id, 1081 | blocking_action, 1082 | )), 1083 | .. 1084 | } = action 1085 | { 1086 | if let Some(blocked_peer_state) = per_peer_state.get(blocked_node_id) { 1087 | log_trace!(logger, 1088 | "Holding the next revoke_and_ack from {} until the preimage is durably persisted in the inbound edge's ChannelMonitor", 1089 | blocked_channel_id); 1090 | blocked_peer_state 1091 | .lock() 1092 | .unwrap() 1093 | .actions_blocking_raa_monitor_updates 1094 | .entry(*blocked_channel_id) 1095 | .or_insert_with(Vec::new) 1096 | .push(blocking_action.clone()); 1097 | } else { 1098 | // If the channel we were blocking has closed, we don't need to 1099 | // worry about it - the blocked monitor update should never have 1100 | // been released from the `Channel` object so it can't have 1101 | // completed, and if the channel closed there's no reason to bother 1102 | // anymore. 1103 | } 1104 | } 1105 | if let MonitorUpdateCompletionAction::FreeOtherChannelImmediately { 1106 | .. 1107 | } = action 1108 | { 1109 | debug_assert!(false, "Non-event-generating channel freeing should not appear in our queue"); 1110 | } 1111 | } 1112 | } 1113 | peer_state.lock().unwrap().monitor_update_blocked_actions = 1114 | monitor_update_blocked_actions; 1115 | } else { 1116 | log_error!( 1117 | WithContext::from(&args.logger, Some(node_id), None), 1118 | "Got blocked actions without a per-peer-state for {}", 1119 | node_id 1120 | ); 1121 | return Err(DecodeError::InvalidValue); 1122 | } 1123 | } 1124 | 1125 | let channel_manager = ChannelManager { 1126 | chain_hash, 1127 | fee_estimator: bounded_fee_estimator, 1128 | chain_monitor: args.chain_monitor, 1129 | tx_broadcaster: args.tx_broadcaster, 1130 | router: args.router, 1131 | 1132 | best_block: RwLock::new(BestBlock::new(best_block_hash, best_block_height)), 1133 | 1134 | inbound_payment_key: expanded_inbound_key, 1135 | pending_inbound_payments: Mutex::new(pending_inbound_payments), 1136 | pending_outbound_payments: pending_outbounds, 1137 | pending_intercepted_htlcs: Mutex::new(pending_intercepted_htlcs.unwrap()), 1138 | 1139 | forward_htlcs: Mutex::new(forward_htlcs), 1140 | decode_update_add_htlcs: Mutex::new(decode_update_add_htlcs), 1141 | claimable_payments: Mutex::new(ClaimablePayments { 1142 | claimable_payments, 1143 | pending_claiming_payments: pending_claiming_payments.unwrap(), 1144 | }), 1145 | outbound_scid_aliases: Mutex::new(outbound_scid_aliases), 1146 | outpoint_to_peer: Mutex::new(outpoint_to_peer), 1147 | short_to_chan_info: FairRwLock::new(short_to_chan_info), 1148 | fake_scid_rand_bytes: fake_scid_rand_bytes.unwrap(), 1149 | 1150 | probing_cookie_secret: probing_cookie_secret.unwrap(), 1151 | 1152 | our_network_pubkey, 1153 | secp_ctx, 1154 | 1155 | highest_seen_timestamp: AtomicUsize::new(highest_seen_timestamp as usize), 1156 | 1157 | per_peer_state: FairRwLock::new(per_peer_state), 1158 | 1159 | pending_events: Mutex::new(pending_events_read), 1160 | pending_events_processor: AtomicBool::new(false), 1161 | pending_background_events: Mutex::new(pending_background_events), 1162 | total_consistency_lock: RwLock::new(()), 1163 | background_events_processed_since_startup: AtomicBool::new(false), 1164 | 1165 | event_persist_notifier: Notifier::new(), 1166 | needs_persist_flag: AtomicBool::new(false), 1167 | 1168 | funding_batch_states: Mutex::new(BTreeMap::new()), 1169 | 1170 | pending_offers_messages: Mutex::new(Vec::new()), 1171 | 1172 | pending_broadcast_messages: Mutex::new(Vec::new()), 1173 | 1174 | entropy_source: args.entropy_source, 1175 | node_signer: args.node_signer, 1176 | signer_provider: args.signer_provider, 1177 | 1178 | logger: args.logger, 1179 | default_configuration: args.default_config, 1180 | color_source: args.color_source, 1181 | }; 1182 | 1183 | for htlc_source in failed_htlcs.drain(..) { 1184 | let (source, payment_hash, counterparty_node_id, channel_id) = htlc_source; 1185 | let receiver = 1186 | HTLCDestination::NextHopChannel { node_id: Some(counterparty_node_id), channel_id }; 1187 | let reason = HTLCFailReason::from_failure_code(0x4000 | 8); 1188 | channel_manager.fail_htlc_backwards_internal(&source, &payment_hash, &reason, receiver); 1189 | } 1190 | 1191 | for ( 1192 | source, 1193 | preimage, 1194 | downstream_value, 1195 | downstream_rgb, 1196 | downstream_closed, 1197 | downstream_node_id, 1198 | downstream_funding, 1199 | downstream_channel_id, 1200 | ) in pending_claims_to_replay 1201 | { 1202 | // We use `downstream_closed` in place of `from_onchain` here just as a guess - we 1203 | // don't remember in the `ChannelMonitor` where we got a preimage from, but if the 1204 | // channel is closed we just assume that it probably came from an on-chain claim. 1205 | channel_manager.claim_funds_internal( 1206 | source, 1207 | preimage, 1208 | Some(downstream_value), 1209 | downstream_rgb, 1210 | None, 1211 | downstream_closed, 1212 | true, 1213 | downstream_node_id, 1214 | downstream_funding, 1215 | downstream_channel_id, 1216 | None, 1217 | ); 1218 | } 1219 | 1220 | //TODO: Broadcast channel update for closed channels, but only after we've made a 1221 | //connection or two. 1222 | 1223 | Ok((best_block_hash.clone(), channel_manager)) 1224 | } 1225 | } 1226 | ``` 1227 | 1228 | ## 教学 1229 | 1230 | 下面我告诉你怎么读。 1231 | 1232 | 首先,我根本不读!我直接选中 `Readable::read` 然后按下 `Ctrl+Shift+L`,这在我的编辑器中表示选中所有的相同文本。然后我进而选中整行。 1233 | 1234 | 考虑到还有一些反序列化位于循环语句中,再手动浏览一遍将其补上。得到下面的代码: 1235 | 1236 | ```rust 1237 | let chain_hash: ChainHash = Readable::read(reader)?; 1238 | let best_block_height: u32 = Readable::read(reader)?; 1239 | let best_block_hash: BlockHash = Readable::read(reader)?; 1240 | let channel_count: u64 = Readable::read(reader)?; 1241 | let forward_htlcs_count: u64 = Readable::read(reader)?; 1242 | for _ in 0..forward_htlcs_count { 1243 | let short_channel_id = Readable::read(reader)?; 1244 | let pending_forwards_count: u64 = Readable::read(reader)?; 1245 | pending_forwards.push(Readable::read(reader)?); 1246 | } 1247 | let claimable_htlcs_count: u64 = Readable::read(reader)?; 1248 | for _ in 0..claimable_htlcs_count { 1249 | let payment_hash = Readable::read(reader)?; 1250 | let previous_hops_len: u64 = Readable::read(reader)?; 1251 | } 1252 | let peer_count: u64 = Readable::read(reader)?; 1253 | for _ in 0..peer_count { 1254 | let peer_pubkey = Readable::read(reader)?; 1255 | peer_state.latest_features = Readable::read(reader)?; 1256 | } 1257 | let event_count: u64 = Readable::read(reader)?; 1258 | for _ in 0..event_count { 1259 | match MaybeReadable::read(reader)? { 1260 | } 1261 | let background_event_count: u64 = Readable::read(reader)?; 1262 | for _ in 0..background_event_count { 1263 | let _: OutPoint = Readable::read(reader)?; 1264 | let _: ChannelMonitorUpdate = Readable::read(reader)?; 1265 | } 1266 | let _last_node_announcement_serial: u32 = Readable::read(reader)?; // Only used < 0.0.111 1267 | let highest_seen_timestamp: u32 = Readable::read(reader)?; 1268 | let pending_inbound_payment_count: u64 = Readable::read(reader)?; 1269 | for _ in 0..pending_inbound_payment_count { 1270 | .insert(Readable::read(reader)?, Readable::read(reader)?) 1271 | } 1272 | let pending_outbound_payments_count_compat: u64 = Readable::read(reader)?; 1273 | for _ in 0..pending_outbound_payments_count_compat { 1274 | let session_priv = Readable::read(reader)?; 1275 | } 1276 | ``` 1277 | 1278 | 然后,这样做可能有漏网之鱼,再搜索 `reader` 找到遗漏的: 1279 | 1280 | ```rust 1281 | let _ver = read_ver_prefix!(reader, SERIALIZATION_VERSION); 1282 | for _ in 0..channel_count { 1283 | let mut channel: Channel = Channel::read( 1284 | reader, 1285 | ( 1286 | &args.entropy_source, 1287 | &args.signer_provider, 1288 | best_block_height, 1289 | &provided_channel_type_features(&args.default_config), 1290 | args.color_source.clone(), 1291 | ), 1292 | )?; 1293 | } 1294 | for _ in 0..claimable_htlcs_count { 1295 | for _ in 0..previous_hops_len { 1296 | previous_hops.push(::read(reader)?); 1297 | } 1298 | } 1299 | for _ in 0..background_event_count { 1300 | match ::read(reader)? { 1301 | 0 => { 1302 | // LDK versions prior to 0.0.116 wrote pending `MonitorUpdateRegeneratedOnStartup`s here, 1303 | // however we really don't (and never did) need them - we regenerate all 1304 | // on-startup monitor updates. 1305 | }, 1306 | _ => return Err(DecodeError::InvalidValue), 1307 | } 1308 | } 1309 | read_tlv_fields!(reader, { 1310 | (1, pending_outbound_payments_no_retry, option), 1311 | (2, pending_intercepted_htlcs, option), 1312 | (3, pending_outbound_payments, option), 1313 | (4, pending_claiming_payments, option), 1314 | (5, received_network_pubkey, option), 1315 | (6, monitor_update_blocked_actions_per_peer, option), 1316 | (7, fake_scid_rand_bytes, option), 1317 | (8, events_override, option), 1318 | (9, claimable_htlc_purposes, optional_vec), 1319 | (10, in_flight_monitor_updates, option), 1320 | (11, probing_cookie_secret, option), 1321 | (13, claimable_htlc_onion_fields, optional_vec), 1322 | (14, decode_update_add_htlcs, option), 1323 | }); 1324 | ``` 1325 | 1326 | 好的,齐活了。 1327 | 1328 | ## 最终成果 1329 | 1330 | 1331 | | **序号** | **字段** | **类型** | **描述** | 1332 | |-------|-----------------------------------------|--------------------------------|---------------------------------------------------------------------| 1333 | | 1 | `chain_hash` | `ChainHash` | 区块链网络的标识符。 | 1334 | | 2 | `best_block_height` | `u32` | 最优(最近)区块的高度。 | 1335 | | 3 | `best_block_hash` | `BlockHash` | 最优区块的哈希值。 | 1336 | | 4 | `channel_count` | `u64` | 管理的通道总数。 | 1337 | | 5 | `forward_htlcs_count` | `u64` | 转发 HTLC 的数量。 | 1338 | | 6 | **转发 HTLCs** | | | 1339 | | |    `short_channel_id` | `ShortChannelId` | 短通道的标识符。 | 1340 | | |    `pending_forwards_count` | `u64` | 当前通道中挂起转发 HTLC 的数量。 | 1341 | | |    `pending_forwards` | `PendingForward` (列表) | 挂起转发 HTLC 的列表。 | 1342 | | 7 | `claimable_htlcs_count` | `u64` | 可领取的 HTLC 数量。 | 1343 | | 8 | **可领取的 HTLCs** | | | 1344 | | |    `payment_hash` | `PaymentHash` | 与 HTLC 相关的支付哈希。 | 1345 | | |    `previous_hops_len` | `u64` | HTLC 路径中的前一跳数量。 | 1346 | | 9 | `peer_count` | `u64` | 连接的节点总数。 | 1347 | | 10 | **节点** | | | 1348 | | |    `peer_pubkey` | `PubKey` | 节点的公钥。 | 1349 | | |    `peer_state.latest_features` | `LatestFeatures` | 节点支持的最新功能集。 | 1350 | | 11 | `event_count` | `u64` | 记录的事件数量。 | 1351 | | 12 | **事件** | | 使用 `MaybeReadable` 反序列化的事件列表。 | 1352 | | 13 | `background_event_count` | `u64` | 后台事件的数量。 | 1353 | | 14 | **后台事件** | | | 1354 | | |    `OutPoint` | `OutPoint` | 引用特定 UTXO 的输入。 | 1355 | | |    `ChannelMonitorUpdate` | `ChannelMonitorUpdate` | 通道监控的更新信息。 | 1356 | | 15 | `_last_node_announcement_serial` | `u32` | (已弃用)最后一个节点公告的序列号(仅适用于版本 < 0.0.111)。 | 1357 | | 16 | `highest_seen_timestamp` | `u32` | 最近一次事件的时间戳。 | 1358 | | 17 | `pending_inbound_payment_count` | `u64` | 挂起的入站支付数量。 | 1359 | | 18 | **挂起的入站支付** | | | 1360 | | |    `PaymentId` | `PaymentId` | 入站支付的标识符。 | 1361 | | |    `PaymentData` | `PaymentData` | 与挂起入站支付相关的数据。 | 1362 | | 19 | `pending_outbound_payments_count_compat` | `u64` | 挂起的出站支付数量(兼容层)。 | 1363 | | 20 | **挂起的出站支付(兼容层)** | | | 1364 | | |    `session_priv` | `SessionPriv` | 出站支付的会话私有数据。 | 1365 | | 21 | `_ver` | `u8` (版本前缀) | 序列化的版本前缀(与 `SERIALIZATION_VERSION` 验证)。 | 1366 | | 22 | **通道** | | | 1367 | | |    `Channel` | `Channel` (列表) | 通道状态的列表,通过上下文参数反序列化。 | 1368 | | 23 | **可领取 HTLC 的前一跳** | | | 1369 | | |    `ClaimableHTLC` | `ClaimableHTLC` (列表) | 每个可领取 HTLC 的前一跳详细信息。 | 1370 | | 24 | **后台事件验证** | | | 1371 | | |    `u8` | `u8` | 验证后台事件,仅接受 `0`(LDK < 0.0.116)。 | 1372 | | 25 | **TLV 字段** | | | 1373 | | |    `1: pending_outbound_payments_no_retry` | 可选字段 | 无重试的挂起出站支付。 | 1374 | | |    `2: pending_intercepted_htlcs` | 可选字段 | 挂起的拦截 HTLCs。 | 1375 | | |    `3: pending_outbound_payments` | 可选字段 | 可重试的挂起出站支付。 | 1376 | | |    `4: pending_claiming_payments` | 可选字段 | 可领取的挂起支付。 | 1377 | | |    `5: received_network_pubkey` | 可选字段 | 从节点接收到的网络公钥。 | 1378 | | |    `6: monitor_update_blocked_actions_per_peer` | 可选字段 | 每个节点阻止的监控更新操作。 | 1379 | | |    `7: fake_scid_rand_bytes` | 可选字段 | 假短通道 ID (SCID) 的随机字节。 | 1380 | | |    `8: events_override` | 可选字段 | 事件处理的重写选项。 | 1381 | | |    `9: claimable_htlc_purposes` | 可选向量字段 | 与可领取 HTLC 相关的用途。 | 1382 | | |    `10: in_flight_monitor_updates` | 可选字段 | 当前正在进行的监控更新。 | 1383 | | |    `11: probing_cookie_secret` | 可选字段 | 用于探测 cookie 的密钥。 | 1384 | | |    `13: claimable_htlc_onion_fields` | 可选向量字段 | 可领取 HTLC 的洋葱包字段。 | 1385 | | |    `14: decode_update_add_htlcs` | 可选字段 | 添加 HTLC 的更新解码。 | 1386 | 1387 | ## 总结 1388 | 1389 | 效率!效率!还是效率! 1390 | 1391 | 当阅读这种业务复杂、逻辑简单但又特别长的代码时,我们核心点在于不要试图死磕,而是着力于函数各个输入具体被怎样操作,产生怎样的输出或 side effect。这样可以有效提高阅读效率,达成阅读的目标。在这个过程中,你可以自行探索各种技巧(欢迎 PR 补充!)。 1392 | --------------------------------------------------------------------------------