├── 1.md ├── 10.md ├── 11.md ├── 12.md ├── 13.md ├── 14.md ├── 15.md ├── 2.md ├── 3.md ├── 4.md ├── 5.md ├── 6.md ├── 7.md ├── 8.md ├── 9.md ├── img ├── active.png ├── filecoin-1.png ├── filecoin_arch.png ├── ipfsandfilecoin.png ├── payment.png ├── post.png ├── retrieval_protocol.png ├── sle.png ├── sle2.png ├── storage_protocol.png └── wikiissue1.png └── readme.md /1.md: -------------------------------------------------------------------------------- 1 | # filecoin技术架构分析之一:filecoin概念 2 | 3 | 4 | > 作者:杨尉(waynewyang),转载请注明出处 5 | 6 | 7 | ## 目录 8 | - [1 filecoin概念](#filecoin概念) 9 | - [1.1 filecoin的定义](#filecoin的定义) 10 | - [1.2 filecoin的设计目的](#filecoin的设计目的) 11 | - [1.3 filecoin与IPFS的关系](#filecoin与ipfs的关系) 12 | - [1.3.1 filecoin与IPFS属性对比](#filecoin与ipfs属性对比) 13 | - [1.3.2 IPFS的对标对象](#ipfs的对标对象) 14 | - [1.3.3 filecoin的对标对象](#filecoin的对标对象) 15 | - [1.4 filecoin网络中的角色](#filecoin网络中的角色) 16 | - [1.4.1 矿工收益方式类比理解](#矿工收益方式类比理解) 17 | - [1.4.2 存储矿工](#存储矿工) 18 | - [1.4.3 检索矿工](#检索矿工) 19 | - [1.4.4 客户(检索客户和存储客户)选择filecoin的理由](#检索客户和存储客户) 20 | 21 | ## 1 filecoin概念 22 | ### 1.1 filecoin的定义 23 | - Filecoin是一个去中心化的存储网络(**DSN**),是一个**云存储的自由交易市场**,通过Filecoin**项目**来实现其**协议**。矿工通过提供数据存储或检索来获得**token**(也称为“filecoin”)。相反,客户向矿工支付token以存储或分发数据并进行检索。 24 | 25 | - Filecoin的多重含义: 26 | - 网络 27 | - 市场 28 | - 项目 29 | - 协议 30 | - Token 31 | 32 | - [回到目录](#目录) 33 | 34 | 35 | ### 1.2 filecoin的设计目的 36 | - filecoin设计符合**激励相容**,每一个参与者的最有利可图的选择(包括目标客户,矿工,投资者和开发人员)将是采取行动提高网络服务质量,这也是他们的最优策略。 37 | 38 | - 以超高竞争力的价格可靠地存储文件(低成本、高效率) 39 | 40 | - 客户可以调整其存储策略以满足他们的需求,在冗余,检索速度和成本之间创建自定义平衡。全球的Filecoin存储和检索市场使供应商竞争以最优惠的价格为客户提供灵活的选择 41 | 42 | - [回到目录](#目录) 43 | 44 | 45 | ### 1.3 filecoin与IPFS的关系 46 | 47 | #### 1.3.1 filecoin与IPFS属性对比 48 | |类别| IPFS | Filecoin | 49 | |:-------:|:--------|:--------| 50 | |功能 |基于内容寻址的分布式存储基础设施|IPFS网络之上的激励层,提供一个云存储领域的自由交易市场| 51 | |对标对象|HTTP|大型集中式孤岛存储提供商,如国外的aws、国内的aliyun等| 52 | |存储权限|对有所有权的IPFS节点具备存储权限|1 除对有所有权的IPFS节点具备存储权限外
2 还可以通过支付的方式,在其供应商的节点之上具备存储权限| 53 | |读取权限|ALL(只要知道内容cid)|ALL(只要知道内容cid)| 54 | |架构设计|**另行文章补充分析**|原则上需要无缝对接到IPFS
1Filecoin将IPLD用于区块链数据结构
2 Filecoin节点使用libp2p建立彼此的安全连接
3 节点和Filecoin块传播之间的消息传递使用libp2p pubsub| 55 | |使用场景|1 存储自己的节点数据,分享数据等,类似BT
2 基于IPFS或其中部分组件构建企业自己的分布式云存储架构、区块链架构等|1 成为filecoin矿工,提供分布式检索及存储服务
2 成为filecoin客户,支付费用享受filecoin网络的检索及存储服务
3 基于filecoin,开发第三方管理系统| 56 | 57 | - IPFS现在和将来都可以免费下载,运行和使用,并且将独立于Filecoin运行。一旦Filecoin正式网络启动,IPFS节点还可以免费或利润地在Filecoin检索市场上提供其文件的检索。 58 | 59 | - [回到目录](#目录) 60 | 61 | 62 | #### 1.3.2 IPFS的对标对象 63 | |特点| HTTP | IPFS | 64 | |:-------:|:-------:|:-------:| 65 | |**寻址方式** |**位置寻址**
一维寻址,低效、脆弱|**内容寻址**
多维寻址,高效、稳定| 66 | |效率|低效|高效| 67 | |稳定性|脆弱|稳定| 68 | |开放性|封闭、垄断|开放、共享| 69 | 70 | - [回到目录](#目录) 71 | 72 | #### 1.3.3 filecoin的对标对象 73 | |特点| 传统云存储提供商(大型集中式孤岛存储网络) | Filecoin | 74 | |:-------:|:--------|:-------:| 75 | |网络模式 |集中式 |DSN| 76 | |加入门槛 |高,从硬件底层基础设施、一直到软件、服务的提供,小企业很难插足 |低、自由交易市场,Filecoin做好基础设施| 77 | |宏观视野:闲置存储空间 |高|低| 78 | |价格 |昂贵,垄断、可人为保持高水平|便宜,自由竞争市场| 79 | |安全性 |差,破坏隐私
1 云存储上可查看用户隐私
2 甚至许多密码鉴权信息都没有隐私可言
3 单个提供商的故障影响大|强
1 无第三方或者中心机构,文件加密安全得到保障
2 单个云提供商的故障小| 80 | |利益分配群体 |巨头 |All| 81 | 82 | - [回到目录](#目录) 83 | 84 | ### 1.4 filecoin网络中的角色 85 | |角色| 说明 |主要影响因素| 86 | |:-------:|:--------|:--------| 87 | |存储矿工 |存储矿工通过为客户存储数据来获得Filecoin;获得区块奖励和交易费用的概率与矿工对Filecoin网络的存储量成正比 |存储容量| 88 | |检索矿工 |检索矿工的带宽和交易的出价/响应时间(即延迟和与客户的接近度)将决定其在网络上关闭检索交易的能力 |带宽| 89 | |检索客户|支付filecoin获取检索服务|| 90 | |存储客户|支付filecoin获取存储服务|| 91 | 92 | - [回到目录](#目录) 93 | 94 | ### 1.4.1 矿工收益方式类比理解 95 | - 类比**filecoin为一家股份公司,类比存储矿工为股东(股份出资人)** 96 | 97 | |收益来源| 类比分析 | 98 | |:-------:|:--------| 99 | |提供存储服务|存储矿工收益来自两部分
1 工资(提供存储并收取服务费用)
2 按照出资比例分红(区块奖励就是按照有效存储占比来实现的) | 100 | |提供检索服务|检索矿工是offchain的,不参与挖矿,收益来自
1 工资(提供检索并收取服务费用) | 101 | 102 | - [回到目录](#目录) 103 | 104 | ### 1.4.2 存储矿工 105 | 存储两类数据,**存储整个区块链所需的总存储量将远低于矿工为交易存储的密封数据**。 106 | - 密封客户的存储数据 107 | - blockchain数据的数据的副本 108 | 109 | - [回到目录](#目录) 110 | 111 | ### 1.4.3 检索矿工 112 | - 提供检索的途径 113 | - 可以存储热门数据(非存储矿工),以便更优质提供服务 114 | - 自己同时做存储矿工,或者从存储矿工处获取 115 | - 不限于从filecoin网络获取,可以从免费的IPFS网络获取 116 | - 检索效率的保证 117 | - 检索矿工是不运行在blockchain中的,是off blockchain的。 118 | - 全球分布式 119 | 120 | - [回到目录](#目录) 121 | 122 | ### 1.4.4 客户(检索客户和存储客户)选择filecoin的理由 123 | - 企业客户愿意使用filecoin来支付数据存储和检索的理由 124 | - filecoin是一套激励相容的系统,filecoin的设计目标保证了每个参与者(包括客户,矿工,投资者和开发人员)的最有利可图的选择或者说是最优策略是采取行动来提高网络的服务质量。具备技术先进性。 125 | - 数据更为安全 126 | - 抵押机制促使矿工提供稳定安全服务,预计会出现声誉系统。矿工需要自行保证系统内的稳定性。 127 | - 即便提供商出现故障,**filecoin网络可以在多个存储提供商之间进行额外的修复。** 128 | - 客户可以根据数据安全等级选择副本数量。 129 | 130 | - 价格更为廉价 131 | - 内容寻址的本质决定了其全局冗余度低。 132 | - filecoin作为全球性的分布式存储系统,可以做全球性去重,从而降低整个网络存储成本。 133 | 134 | - 个人客户选择使用filecoin的理由 135 | - 预计filecoin将提供允许一方支付另一方来检索数据的结构 136 | - 包括web 2.0网站的主要内容分发模型,在该模型中,网站所有者为基础设施服务付费,以免费向其用户提供数据,然后以其他方式通过内容获利。 137 | - filecoin的设计目标,让用户和内容创作者能够探索各种新的内容分发和经济模型。 138 | - 例如版权问题的解决 139 | 140 | - [回到目录](#目录) 141 | -------------------------------------------------------------------------------- /10.md: -------------------------------------------------------------------------------- 1 | # filecoin技术架构分析之十:filecoin源码分析之支撑包分析(2/2) 2 | 3 | > 作者:杨尉(waynewyang),转载请注明出处 4 | 5 | ## 目录 6 | - 10 filecoin源码分析之支撑包分析(2/2) 7 | - 10.1 repo 8 | - 10.2 proofs和sectorbuilder 9 | - 10.3 type 10 | - 10.4 abi 11 | - 10.5 pubsub 12 | 13 | 14 | > 本章续上一章的支撑包介绍,主要为便于后面章节的源码理解 15 | 16 | ## 10.1 repo 17 | 18 | - 提供功能 19 | - 实例化fs资源或者mem资源 20 | - 提供读取、设置API地址方法 21 | - 提供存储已被校验区块的方法 22 | - 提供阶段密封数据存储方法 23 | - 提供密封完成数据存储方法 24 | - 提供读取配置方法 25 | - 提供通用数据存储方法 26 | - 提供交易数据存储方法 27 | - 提供钱包信息存储方法 28 | - 提供存储密钥方法 29 | - 提供快照配置存储方法 30 | - 提供版本号读取方法 31 | 32 | ``` 33 | ▼ package 34 | repo 35 | 36 | ▶ imports 37 | 38 | ▼ constants 39 | // 当前为1,可以cat ~/.filecoin/version确认 40 | +Version : uint 41 | 42 | ▼+Datastore : interface 43 | [embedded] 44 | // 包含datastore的read、write、batch 45 | +datastore.Batching 46 | 47 | // Repo接口分别由fsrepo及memrepo实现 48 | ▼+Repo : interface 49 | [methods] 50 | // 读取API地址 51 | +APIAddr() : string, error 52 | // 存储已被校验过的区块数据 53 | +ChainDatastore() : Datastore 54 | // 关闭 55 | +Close() : error 56 | // 读取配置,对应上一章中的config 57 | +Config() : *config.Config 58 | // 存储通用数据 59 | +Datastore() : Datastore 60 | // 交易数据存储 61 | +DealsDatastore() : Datastore 62 | // 存储密钥相关 63 | +Keystore() : keystore.Keystore 64 | // 存储倒数第二个配置 65 | +ReplaceConfig(cfg *config.Config) : error 66 | // 存储密封扇区 67 | +SealedDir() : string 68 | // 设置API地址 69 | +SetAPIAddr(string) : error 70 | // 存储分段密封扇区 71 | +StagingDir() : string 72 | // 读取版本号 73 | +Version() : uint 74 | // 存储钱包信息 75 | +WalletDatastore() : Datastore 76 | ``` 77 | 78 | ``` 79 | location: repo/fsrepo.go 80 | 81 | ▼ package 82 | repo 83 | 84 | ▼ constants 85 | // api文件 86 | +APIFile 87 | // chain目录:chain 88 | -chainDatastorePrefix 89 | // 配置文件名称,对应上一章中的config 90 | -configFilename 91 | // 交易目录:deals 92 | -dealsDatastorePrefix 93 | // 资源目录锁文件:repo.lock 94 | -lockFile 95 | // 快照文件前缀名 snapshot 96 | -snapshotFilenamePrefix 97 | // 快照目录;配置快照 98 | -snapshotStorePrefix 99 | // 临时配置文件名称 100 | -tempConfigFilename 101 | // version文件名称 102 | -versionFilename 103 | // 钱包目录名称wallet 104 | -walletDatastorePrefix 105 | 106 | ▼ variables 107 | -log 108 | 109 | ▼+FSRepo : struct 110 | [fields] 111 | -cfg : *config.Config 112 | -chainDs : Datastore 113 | -dealsDs : Datastore 114 | -ds : Datastore 115 | -keystore : keystore.Keystore 116 | -lk : sync.RWMutex 117 | -lockfile : io.Closer 118 | // 资源目录路径 119 | -path : string 120 | // 资源目录版本 121 | -version : uint 122 | -walletDs : Datastore 123 | [methods] 124 | +APIAddr() : string, error 125 | +ChainDatastore() : Datastore 126 | +Close() : error 127 | +Config() : *config.Config 128 | +Datastore() : Datastore 129 | +DealsDatastore() : Datastore 130 | +Keystore() : keystore.Keystore 131 | +ReplaceConfig(cfg *config.Config) : error 132 | +SealedDir() : string 133 | +SetAPIAddr(maddr string) : error 134 | // 快照存储 135 | +SnapshotConfig(cfg *config.Config) : error 136 | +StagingDir() : string 137 | +Version() : uint 138 | +WalletDatastore() : Datastore 139 | -loadConfig() : error 140 | -loadFromDisk() : error 141 | -loadVersion() : uint, error 142 | -openChainDatastore() : error 143 | -openDatastore() : error 144 | -openDealsDatastore() : error 145 | -openKeystore() : error 146 | -openWalletDatastore() : error 147 | -removeAPIFile() : error 148 | -removeFile(path string) : error 149 | [functions] 150 | // 打开已被初始化过的资源目录 151 | +OpenFSRepo(p string) : *FSRepo, error 152 | 153 | ▼+NoRepoError : struct 154 | [fields] 155 | +Path : string 156 | [methods] 157 | +Error() : string 158 | 159 | ▼ functions 160 | // 从文件中读取api file 161 | +APIAddrFromFile(apiFilePath string) : string, error 162 | // 初始化资源目录 163 | +InitFSRepo(p string, cfg *config.Config) : error 164 | -checkWritable(dir string) : error 165 | -fileExists(file string) : bool 166 | -genSnapshotFileName() : string 167 | -initConfig(p string, cfg *config.Config) : error 168 | -initVersion(p string, version uint) : error 169 | -isInitialized(p string) : bool, error 170 | ``` 171 | 172 | ``` 173 | ▼ package 174 | repo 175 | 176 | ▼ imports 177 | 178 | ▼+MemRepo : struct 179 | [fields] 180 | +C : *config.Config 181 | +Chain : Datastore 182 | +D : Datastore 183 | +DealsDs : Datastore 184 | +Ks : keystore.Keystore 185 | +W : Datastore 186 | -apiAddress : string 187 | -lk : sync.RWMutex 188 | -sealedDir : string 189 | -stagingDir : string 190 | -version : uint 191 | [methods] 192 | +APIAddr() : string, error 193 | +ChainDatastore() : Datastore 194 | +CleanupSectorDirs() 195 | +Close() : error 196 | +Config() : *config.Config 197 | +Datastore() : Datastore 198 | +DealsDatastore() : Datastore 199 | +Keystore() : keystore.Keystore 200 | +ReplaceConfig(cfg *config.Config) : error 201 | +SealedDir() : string 202 | +SetAPIAddr(addr string) : error 203 | +StagingDir() : string 204 | +Version() : uint 205 | +WalletDatastore() : Datastore 206 | [functions] 207 | // 实例化内存资源接口,会调用NewInMemoryRepoWithSectorDirectories 208 | +NewInMemoryRepo() : *MemRepo 209 | // 实例化内存资源接口,指定阶段密封和最终密封目录 210 | +NewInMemoryRepoWithSectorDirectories(staging, sealedDir string) : *MemRepo 211 | ``` 212 | 213 | ## 10.2 proofs和sectorbuilder 214 | 215 | - proofs提供功能 216 | - 校验时空证明的方法 217 | - 校验密封证明的方法 218 | - 更细节的注释见如下代码笔者增加的注释 219 | - rustverifier实现具体的方法 220 | 221 | ``` 222 | location: proofs/types.go 223 | 224 | ▼ package 225 | proofs 226 | 227 | ▼ constants 228 | // merkle根长度 229 | +CommitmentBytesLen : uint 230 | // 时空证明挑战参数长度:32bytes 231 | +PoStChallengeSeedBytesLen : uint 232 | // 密封复制证明长度:384bytes 233 | +SealBytesLen : uint 234 | // 时空证明长度:192bytes 235 | +SnarkBytesLen : uint 236 | 237 | // 原始数据的merkle根,由PoRep输出 238 | +CommD : []byte 239 | 240 | // 副本数据的merkle根,由PoRep输出 241 | +CommR : []byte 242 | 243 | // 中间层的merkle根,由PoRep输出 244 | +CommRStar : []byte 245 | 246 | // 挑战随机参数,32bytes,256bits,PoSt的输入 247 | +PoStChallengeSeed : []byte 248 | 249 | // 时空证明输出,192bytes 250 | +PoStProof : []byte 251 | 252 | // 密封复制证明,384bytes 253 | +SealProof : []byte 254 | ``` 255 | 256 | ``` 257 | location: proofs/interface.go 258 | 259 | ▼ package 260 | proofs 261 | 262 | ▼ constants 263 | +Live 264 | +Test 265 | 266 | +SectorStoreType : int 267 | 268 | // 校验时空证明校验请求 269 | ▼+VerifyPoSTRequest : struct 270 | [fields] 271 | // 挑战参数 272 | +ChallengeSeed : PoStChallengeSeed 273 | +CommRs : []CommR 274 | +Faults : []uint64 275 | +Proof : PoStProof 276 | +StoreType : SectorStoreType 277 | 278 | ▼+VerifyPoSTResponse : struct 279 | [fields] 280 | +IsValid : bool 281 | 282 | // 向特定矿工&特定扇区发起密封校验请求 283 | ▼+VerifySealRequest : struct 284 | [fields] 285 | // 来自于密封的返回参数 286 | +CommD : CommD 287 | +CommR : CommR 288 | +CommRStar : CommRStar 289 | +Proof : SealProof 290 | // 矿工标识 291 | +ProverID : [31]byte 292 | // 扇区ID 293 | +SectorID : [31]byte 294 | // 用于控制密封校验效率 295 | +StoreType : SectorStoreType 296 | 297 | ▼+VerifySealResponse : struct 298 | [fields] 299 | +IsValid : bool 300 | 301 | ▼+Verifier : interface 302 | [methods] 303 | // 校验时空证明 304 | +VerifyPoST(VerifyPoSTRequest) : VerifyPoSTResponse, error 305 | // 校验密封证明 306 | +VerifySeal(VerifySealRequest) : VerifySealResponse, error 307 | ``` 308 | 309 | ``` 310 | location: proofs/rustverifier.go 311 | 312 | ▼ package 313 | proofs 314 | 315 | ▶ imports 316 | 317 | ▼ variables 318 | -log 319 | 320 | // RustVerifier 实现VerifyPoST与VerifySeal接口 321 | ▼+RustVerifier : struct 322 | [methods] 323 | +VerifyPoST(req VerifyPoSTRequest) : VerifyPoSTResponse, error 324 | +VerifySeal(req VerifySealRequest) : VerifySealResponse, error 325 | 326 | ▼ functions 327 | +CSectorStoreType(cfg SectorStoreType) : *C.ConfiguredStore, error 328 | -cUint64s(src []uint64) : *C.uint64_t, C.size_t 329 | -elapsed(what string) : func() 330 | ``` 331 | 332 | - sectorbuilder 333 | - 提供向unsealed扇区写入pieces的方法 334 | - 提供生成时空证明的方法 335 | - 提供从特定扇区读取特定pieces的方法 336 | - 提供密封完成通知的方法 337 | - 提供批量密封所有未完成的分段扇区 338 | - 与rust-fil-proof交互,更深入的逻辑需要参见rust 339 | 340 | ``` 341 | location: proofs/sectorbuilder/interface.go 342 | 343 | package sectorbuilder 344 | 345 | ▶ imports 346 | 347 | // 生成生成时空证明请求 348 | ▼+GeneratePoSTRequest : struct 349 | [fields] 350 | +ChallengeSeed : proofs.PoStChallengeSeed 351 | +CommRs : []proofs.CommR 352 | 353 | // 生成生成时空证明响应 354 | ▼+GeneratePoSTResponse : struct 355 | [fields] 356 | +Faults : []uint64 357 | +Proof : proofs.PoStProof 358 | 359 | ▼+PieceInfo : struct 360 | [fields] 361 | +Ref : cid.Cid 362 | +Size : uint64 363 | 364 | // 密封元数据 365 | ▼+SealedSectorMetadata : struct 366 | [fields] 367 | +CommD : proofs.CommD 368 | // 副本哈希后续将被删除 369 | +CommR : proofs.CommR 370 | +CommRStar : proofs.CommRStar 371 | // Pieces后续将被删除 372 | +Pieces : []*PieceInfo 373 | +Proof : proofs.SealProof 374 | +SectorID : uint64 375 | 376 | // 密封结果 377 | ▼+SectorSealResult : struct 378 | [fields] 379 | +SealingErr : error 380 | +SealingResult : *SealedSectorMetadata 381 | +SectorID : uint64 382 | 383 | // SectorBuilder提供相关功能 384 | // 1 写入、密封pieces至扇区 385 | // 2 unseal、读取pieces 386 | ▼+SectorBuilder : interface 387 | [methods] 388 | // 向unsealed扇区写入pieces 389 | +AddPiece(ctx context.Context, pi *PieceInfo) : uint64, error 390 | +Close() : error 391 | // 生成时空证明 392 | +GeneratePoST(GeneratePoSTRequest) : GeneratePoSTResponse, error 393 | +GetMaxUserBytesPerStagedSector() : uint64, error 394 | // 从扇区中读取特定pieces 395 | +ReadPieceFromSealedSector(pieceCid cid.Cid) : io.Reader, error 396 | // 密封所有未完成的分段扇区 397 | +SealAllStagedSectors(ctx context.Context) : error 398 | // 密封完成的通知 399 | +SectorSealResults() : chan SectorSealResult 400 | 401 | ▼ functions 402 | -init() 403 | ``` 404 | 405 | ``` 406 | location: proofs/sectorbuilder/poller.go 407 | 408 | // 当pieces加入后,会进行FFI调用,定时执行密封 409 | const SealedSectorPollingInterval = 1 * time.Second 410 | ``` 411 | 412 | 413 | ## 10.3 type 414 | 415 | 如下对一些主要结构进行简析 416 | 417 | - AttoFIL(10*-18 FIL) 418 | - 提供AttoFIL的算数运算方法 419 | - 提供AttoFIL的逻辑运算方法 420 | 421 | - Block 422 | - 区块结构 423 | 424 | ``` 425 | ▼+Block : struct 426 | [fields] 427 | +Height : Uint64 428 | +MessageReceipts : []*MessageReceipt 429 | +Messages : []*SignedMessage 430 | +Miner : address.Address 431 | +Nonce : Uint64 432 | +ParentWeight : Uint64 433 | +Parents : SortedCidSet 434 | +Proof : proofs.PoStProof 435 | +StateRoot : cid.Cid 436 | +Ticket : Signature 437 | -cachedBytes : []byte 438 | -cachedCid : cid.Cid 439 | [methods] 440 | +Cid() : cid.Cid 441 | +Equals(other *Block) : bool 442 | +IsParentOf(c Block) : bool 443 | +Score() : uint64 444 | +String() : string 445 | +ToNode() : node.Node 446 | [functions] 447 | +DecodeBlock(b []byte) : *Block, error 448 | ``` 449 | 450 | - BlockHeight 451 | - 区块高度相关操作方法 452 | 453 | ``` 454 | ▼+BlockHeight : struct 455 | [fields] 456 | -val : *big.Int 457 | [methods] 458 | +Add(y *BlockHeight) : *BlockHeight 459 | +AsBigInt() : *big.Int 460 | +Bytes() : []byte 461 | +Equal(y *BlockHeight) : bool 462 | +GreaterEqual(y *BlockHeight) : bool 463 | +GreaterThan(y *BlockHeight) : bool 464 | +LessEqual(y *BlockHeight) : bool 465 | +LessThan(y *BlockHeight) : bool 466 | +String() : string 467 | +Sub(y *BlockHeight) : *BlockHeight 468 | [functions] 469 | +NewBlockHeight(x uint64) : *BlockHeight 470 | +NewBlockHeightFromBytes(buf []byte) : *BlockHeight 471 | +NewBlockHeightFromString(s string, base int) : *BlockHeight, bool 472 | ``` 473 | 474 | - BytesAmount (*big.Int) 475 | - 提供相关的算数逻辑运算 476 | 477 | - ChannelID(支付通道结构体) 478 | 479 | ``` 480 | ▼+ChannelID : struct 481 | [fields] 482 | -val : *big.Int 483 | [methods] 484 | +Bytes() : []byte 485 | +Equal(y *ChannelID) : bool 486 | +Inc() : *ChannelID 487 | +KeyString() : string 488 | +String() : string 489 | [functions] 490 | +NewChannelID(x uint64) : *ChannelID 491 | +NewChannelIDFromBytes(buf []byte) : *ChannelID 492 | +NewChannelIDFromString(s string, base int) : *ChannelID, bool 493 | ``` 494 | 495 | - 一些变量定义 496 | - 创建各类actor对象 497 | 498 | ``` 499 | func init() { 500 | AccountActorCodeObj = dag.NewRawNode([]byte("accountactor")) 501 | AccountActorCodeCid = AccountActorCodeObj.Cid() 502 | StorageMarketActorCodeObj = dag.NewRawNode([]byte("storagemarket")) 503 | StorageMarketActorCodeCid = StorageMarketActorCodeObj.Cid() 504 | PaymentBrokerActorCodeObj = dag.NewRawNode([]byte("paymentbroker")) 505 | PaymentBrokerActorCodeCid = PaymentBrokerActorCodeObj.Cid() 506 | MinerActorCodeObj = dag.NewRawNode([]byte("mineractor")) 507 | MinerActorCodeCid = MinerActorCodeObj.Cid() 508 | BootstrapMinerActorCodeObj = dag.NewRawNode([]byte("bootstrapmineractor")) 509 | BootstrapMinerActorCodeCid = BootstrapMinerActorCodeObj.Cid() 510 | } 511 | ``` 512 | 513 | - Message相关 514 | - 消息结构及方法 515 | - filecoin网络的交易由一些列的Message组成 516 | 517 | ``` 518 | ▼+Message : struct 519 | [fields] 520 | +From : address.Address 521 | +Method : string 522 | +Nonce : Uint64 523 | +Params : []byte 524 | +To : address.Address 525 | +Value : *AttoFIL 526 | [methods] 527 | +Cid() : cid.Cid, error 528 | +Marshal() : []byte, error 529 | +String() : string 530 | +Unmarshal(b []byte) : error 531 | [functions] 532 | +NewMessage(from, to address.Address, nonce uint64, value *AttoFIL, method string, params []byte) : *Message 533 | ``` 534 | 535 | ``` 536 | ▼+MessageReceipt : struct 537 | [fields] 538 | +ExitCode : uint8 539 | +GasAttoFIL : *AttoFIL 540 | +Return : [][]byte 541 | ``` 542 | 543 | ``` 544 | ▼+MeteredMessage : struct 545 | [fields] 546 | +GasLimit : GasUnits 547 | +GasPrice : AttoFIL 548 | [embedded] 549 | +Message : Message 550 | [methods] 551 | +Marshal() : []byte, error 552 | +Unmarshal(b []byte) : error 553 | [functions] 554 | +NewMeteredMessage(msg Message, gasPrice AttoFIL, gasLimit GasUnits) : *MeteredMessage 555 | ``` 556 | 557 | ``` 558 | ▼+SignedMessage : struct 559 | [fields] 560 | +Signature : Signature 561 | [embedded] 562 | +MeteredMessage : MeteredMessage 563 | [methods] 564 | +Cid() : cid.Cid, error 565 | +Marshal() : []byte, error 566 | +RecoverAddress(r Recoverer) : address.Address, error 567 | +String() : string 568 | +Unmarshal(b []byte) : error 569 | +VerifySignature() : bool 570 | [functions] 571 | +NewSignedMessage(msg Message, s Signer, gasPrice AttoFIL, gasLimit GasUnits) : *SignedMessage, error 572 | ``` 573 | 574 | - TipSet 575 | - 区块集合 576 | 577 | ``` 578 | +Tip : Block 579 | 580 | ▼+TipSet : map[cid.Cid]*Tip 581 | [methods] 582 | +AddBlock(b *Block) : error 583 | +Clone() : TipSet 584 | +Equals(ts2 TipSet) : bool 585 | +Height() : uint64, error 586 | +MinTicket() : Signature, error 587 | +ParentWeight() : uint64, error 588 | +Parents() : SortedCidSet, error 589 | +String() : string 590 | +ToSlice() : []*Block 591 | +ToSortedCidSet() : SortedCidSet 592 | ``` 593 | 594 | ## 10.4 abi 595 | - abi 596 | - 对filecoin中的各类数据定义数据类型 597 | - 提供abi编解码操作方法 598 | 599 | 600 | ## 10.5 pubsub 601 | 602 | - 提供功能 603 | - 提供订阅实例化以及订阅方法 604 | - 提供发布实例化以及发布方法 605 | 606 | ``` 607 | ▼ package 608 | pubsub 609 | 610 | ▶ imports 611 | 612 | ▼+Subscriber : struct 613 | [fields] 614 | -pubsub : *libp2p.PubSub 615 | [methods] 616 | +Subscribe(topic string) : Subscription, error 617 | [functions] 618 | +NewSubscriber(sub *libp2p.PubSub) : *Subscriber 619 | 620 | ▼-subscriptionWrapper : struct 621 | [embedded] 622 | +*libp2p.Subscription : *libp2p.Subscription 623 | [methods] 624 | +Next(ctx context.Context) : Message, error 625 | 626 | ▼+Message : interface 627 | [methods] 628 | +GetData() : []byte 629 | +GetFrom() : peer.ID 630 | 631 | ▼+Subscription : interface 632 | [methods] 633 | +Cancel() 634 | +Next(ctx context.Context) : Message, error 635 | +Topic() : string 636 | ``` 637 | 638 | ``` 639 | ▼ package 640 | pubsub 641 | 642 | ▶ imports 643 | 644 | ▼+Publisher : struct 645 | [fields] 646 | -pubsub : *pubsub.PubSub 647 | [methods] 648 | +Publish(topic string, data []byte) : error 649 | [functions] 650 | +NewPublisher(sub *pubsub.PubSub) : *Publisher 651 | ``` 652 | -------------------------------------------------------------------------------- /11.md: -------------------------------------------------------------------------------- 1 | # filecoin技术架构分析之十一:filecoin源码分析之内部接口层api包分析 2 | 3 | > 作者:杨尉(waynewyang),转载请注明出处 4 | 5 | 6 | ## 目录 7 | - 11.filecoin源码分析之内部接口层api包分析 8 | - 11.1 api 9 | - 11.2 actor 10 | - 11.3 address 11 | - 11.4 client 12 | - 11.5 config 13 | - 11.6 daemon 14 | - 11.7 dag 15 | - 11.8 id 16 | - 11.11.log 17 | - 11.10 miner 18 | - 11.11 mining 19 | - 11.12 ping 20 | - 11.13 retrieval_client 21 | - 11.14 swarm 22 | 23 | > api包提供内部接口,供协议层、command/REST使用 24 | 25 | > 较大程度依赖node包 26 | 27 | ## 11.1 api 28 | ### 11.1.1 api的接口定义 29 | 30 | > 如下所示,包含了一系列子接口 31 | 32 | ``` 33 | type API interface { 34 | Actor() Actor 35 | Address() Address 36 | Client() Client 37 | Daemon() Daemon 38 | Dag() Dag 39 | ID() ID 40 | Log() Log 41 | Miner() Miner 42 | Mining() Mining 43 | Paych() Paych 44 | Ping() Ping 45 | RetrievalClient() RetrievalClient 46 | Swarm() Swarm 47 | Version() Version 48 | } 49 | ``` 50 | 51 | ### 11.1.2 api的接口实现 52 | 53 | ``` 54 | ▼ package 55 | impl 56 | 57 | ▶ imports 58 | 59 | // nodeAPI来实现其接口定义 60 | ▼-nodeAPI : struct 61 | [fields] 62 | // 合约 63 | -actor : *nodeActor 64 | // 地址 65 | -address : *nodeAddress 66 | // 客户端 67 | -client : *nodeClient 68 | // daemon 69 | -daemon : *nodeDaemon 70 | // dag 71 | -dag : *nodeDag 72 | // 节点ID 73 | -id : *nodeID 74 | // 日志 75 | -log : *nodeLog 76 | // 日志 77 | -logger : logging.EventLogger 78 | // 矿工 79 | -miner : *nodeMiner 80 | // 挖矿 81 | -mining : *nodeMining 82 | // 节点 83 | -node : *node.Node 84 | // 支付通道 85 | -paych : *nodePaych 86 | // ping 87 | -ping : *nodePing 88 | // 检索客户端 89 | -retrievalClient : *nodeRetrievalClient 90 | // swarm 91 | -swarm : *nodeSwarm 92 | // 版本 93 | -version : *nodeVersion 94 | 95 | [methods] 96 | // 如下为实现API接口 97 | +Actor() : api.Actor 98 | +Address() : api.Address 99 | +Client() : api.Client 100 | +Daemon() : api.Daemon 101 | +Dag() : api.Dag 102 | +ID() : api.ID 103 | +Log() : api.Log 104 | +Miner() : api.Miner 105 | +Mining() : api.Mining 106 | +Paych() : api.Paych 107 | +Ping() : api.Ping 108 | +RetrievalClient() : api.RetrievalClient 109 | +Swarm() : api.Swarm 110 | +Version() : api.Version 111 | 112 | ▼ functions 113 | // 实例化API 114 | // 1 获取高层API porcelainAPI 指针,miner与paych有用到 115 | // 2 调用各子系统的实例化函数逐一实例化 116 | +New(node *node.Node) : api.API 117 | ``` 118 | 119 | ## 11.2 actor 120 | ### 11.2.1 actor的接口定义 121 | 122 | ``` 123 | ▼ package 124 | api 125 | 126 | ▶ imports 127 | 128 | ▼+ActorView : struct 129 | [fields] 130 | // actor类型 131 | +ActorType : string 132 | // actor地址 133 | +Address : string 134 | // actor余额 135 | +Balance : *types.AttoFIL 136 | // actor代码-CID 137 | +Code : cid.Cid 138 | // 导出符号集合 139 | +Exports : ReadableExports 140 | // 表征actor实例的状态 141 | +Head : cid.Cid 142 | // 消息计数器,仅为account actors与外部发生交互的时候计算 143 | +Nonce : uint64 144 | 145 | // 导出符号集合 146 | +ReadableExports : map[string]*ReadableFunctionSignature 147 | 148 | ▼+ReadableFunctionSignature : struct 149 | [fields] 150 | // 参数 151 | +Params : []string 152 | // 返回 153 | +Return : []string 154 | 155 | ▼+Actor : interface 156 | // 目前接口只有查看功能,返回合约的具体信息 157 | [methods] 158 | +Ls(ctx context.Context) : []*ActorView, error 159 | ``` 160 | ### 11.2.2 actor的接口实现 161 | 162 | ``` 163 | ▼ package 164 | impl 165 | 166 | ▶ imports 167 | 168 | // 使用nodeActor来实现Actor接口 169 | ▼-nodeActor : struct 170 | [fields] 171 | -api : *nodeAPI 172 | [methods] 173 | // 调用ls方法实现查询功能 174 | +Ls(ctx context.Context) : []*api.ActorView, error 175 | [functions] 176 | // 实例化nodeActor,由api实现代码中调用 177 | -newNodeActor(api *nodeAPI) : *nodeActor 178 | 179 | ▼ functions 180 | // 获取合约类型 181 | // 1 account actor 182 | // 2 存储市场actor 183 | // 3 支付通道actor 184 | // 4 矿工actor 185 | // 4 BootstrapMiner actor 186 | -getActorType(actType exec.ExecutableActor) : string 187 | // 查询合约状态 188 | -ls(ctx context.Context, fcn *node.Node, actorGetter state.GetAllActorsFunc) : []*api.ActorView, error 189 | -makeActorView(act *actor.Actor, addr string, actType exec.ExecutableActor) : *api.ActorView 190 | -makeReadable(f *exec.FunctionSignature) : *api.ReadableFunctionSignature 191 | -presentExports(e exec.Exports) : api.ReadableExports 192 | ``` 193 | 194 | ## 11.3 address 195 | - 提供功能 196 | - 地址显示方法 197 | - 地址查找方法 198 | - 创建地址方法 199 | - 导出地址方法 200 | - 导入地址方法 201 | 202 | ``` 203 | ▼ package 204 | api 205 | 206 | ▶ imports 207 | 208 | ▼+Address : interface 209 | [methods] 210 | +Addrs() : Addrs 211 | +Export(ctx context.Context, addrs []address.Address) : []*types.KeyInfo, error 212 | +Import(ctx context.Context, f files.File) : []address.Address, error 213 | 214 | ▼+Addrs : interface 215 | [methods] 216 | +Lookup(ctx context.Context, addr address.Address) : peer.ID, error 217 | +Ls(ctx context.Context) : []address.Address, error 218 | +New(ctx context.Context) : address.Address, error 219 | ``` 220 | 221 | ## 11.4 client 222 | - 提供如下功能 223 | - 查询piece数据(DAG格式) 224 | - 导入数据(相当于ipfs add) 225 | - 列出所有订单 226 | - 支付 227 | - 发起存储交易 228 | - 查询存储交易 229 | 230 | ``` 231 | ▼+Ask : struct 232 | [fields] 233 | +Error : error 234 | +Expiry : *types.BlockHeight 235 | +ID : uint64 236 | +Miner : address.Address 237 | +Price : *types.AttoFIL 238 | 239 | ▼+Client : interface 240 | [methods] 241 | +Cat(ctx context.Context, c cid.Cid) : uio.DagReader, error 242 | +ImportData(ctx context.Context, data io.Reader) : ipld.Node, error 243 | +ListAsks(ctx context.Context) : chan Ask, error 244 | +Payments(ctx context.Context, dealCid cid.Cid) : []*paymentbroker.PaymentVoucher, error 245 | +ProposeStorageDeal(ctx context.Context, data cid.Cid, miner address.Address, ask uint64, duration uint64, allowDuplicates bool) : *storage.DealResponse, error 246 | +QueryStorageDeal(ctx context.Context, prop cid.Cid) : *storage.DealResponse, error 247 | ``` 248 | 249 | ## 11.5 config 250 | - 提供功能 251 | - Get配置 252 | - Set配置 253 | 254 | ## 11.6 daemon 255 | - 提供功能 256 | - 启动进程相关 257 | - 具体的业务启动逻辑会调用到node包 258 | 259 | 260 | ``` 261 | ▼ package 262 | api 263 | 264 | ▶ imports 265 | 266 | ▼+DaemonInitConfig : struct 267 | [fields] 268 | // 如果配置,定期检查并密封staged扇区 269 | +AutoSealIntervalSeconds : uint 270 | +DefaultAddress : address.Address 271 | // 指定网络 272 | +DevnetNightly : bool 273 | +DevnetTest : bool 274 | +DevnetUser : bool 275 | // 创世文件 276 | +GenesisFile : string 277 | +PeerKeyFile : string 278 | // repo目录 279 | +RepoDir : string 280 | // 指定矿工 281 | +WithMiner : address.Address 282 | 283 | +DaemonInitOpt : func(*DaemonInitConfig) 284 | 285 | ▼+Daemon : interface 286 | [methods] 287 | +Init(ctx context.Context, opts ...DaemonInitOpt) : error 288 | +Start(ctx context.Context) : error 289 | +Stop(ctx context.Context) : error 290 | ``` 291 | 292 | ## 11.7 dag 293 | - 提供功能 294 | - dag查询功能 295 | - 类似ipfs block get 296 | 297 | ## 11.8 id 298 | - 提供功能 299 | - ID详细信息 300 | - 如多地址、协议版本、导出公钥等 301 | 302 | ``` 303 | ▼+IDDetails : struct 304 | [fields] 305 | +Addresses : []ma.Multiaddr 306 | +AgentVersion : string 307 | +ID : peer.ID 308 | +ProtocolVersion : string 309 | +PublicKey : []byte 310 | [methods] 311 | +MarshalJSON() : []byte, error 312 | +UnmarshalJSON(data []byte) : error 313 | 314 | ▼+ID : interface 315 | [methods] 316 | +Details() : *IDDetails, error 317 | 318 | ▼ functions 319 | -decode(idd map[string]*json.RawMessage, key string, dest interface{}) : error 320 | ``` 321 | 322 | ## 11.11.log 323 | - 提供日志功能 324 | 325 | ``` 326 | ▼+Log : interface 327 | [methods] 328 | +Tail(ctx context.Context) : io.Reader 329 | ``` 330 | 331 | ## 11.10 miner 332 | - 创建矿工 333 | 334 | ``` 335 | ▼+Miner : interface 336 | [methods] 337 | +Create(ctx context.Context, fromAddr address.Address, gasPrice types.AttoFIL, gasLimit types.GasUnits, pledge uint64, pid peer.ID, collateral *types.AttoFIL) : address.Address, error 338 | ``` 339 | 340 | ## 11.11 mining 341 | - 挖矿控制 342 | - 启动 343 | - 停止 344 | 345 | ``` 346 | ▼+Mining : interface 347 | [methods] 348 | +Once(ctx context.Context) : *types.Block, error 349 | +Start(ctx context.Context) : error 350 | +Stop(ctx context.Context) : error 351 | ``` 352 | 353 | ## 11.12 ping 354 | - 提供ping接口 355 | 356 | ``` 357 | ▼+PingResult : struct 358 | [fields] 359 | +Success : bool 360 | +Text : string 361 | +Time : time.Duration 362 | 363 | ▼+Ping : interface 364 | [methods] 365 | +Ping(ctx context.Context, pid peer.ID, count uint, delay time.Duration) : chan *PingResult, error 366 | ``` 367 | 368 | ## 11.13 retrieval_client 369 | - 提供检索接口 370 | 371 | ``` 372 | ▼+RetrievalClient : interface 373 | [methods] 374 | +RetrievePiece(ctx context.Context, pieceCID cid.Cid, minerAddr address.Address) : io.ReadCloser, error 375 | ``` 376 | 377 | ## 11.14 swarm 378 | - 提供节点连接功能 379 | - 显示连接节点 380 | - 连接节点 381 | - 查找节点 382 | 383 | ``` 384 | ▼+SwarmConnInfo : struct 385 | [fields] 386 | +Addr : string 387 | +Latency : string 388 | +Muxer : string 389 | +Peer : string 390 | +Streams : []SwarmStreamInfo 391 | [methods] 392 | +Len() : int 393 | +Less(i, j int) : bool 394 | +Swap(i, j int) 395 | 396 | ▼+SwarmConnInfos : struct 397 | [fields] 398 | +Peers : []SwarmConnInfo 399 | [methods] 400 | +Len() : int 401 | +Less(i, j int) : bool 402 | +Swap(i, j int) 403 | 404 | ▼+SwarmConnectResult : struct 405 | [fields] 406 | +Peer : string 407 | +Success : bool 408 | 409 | ▼+SwarmStreamInfo : struct 410 | [fields] 411 | +Protocol : string 412 | 413 | ▼+Swarm : interface 414 | [methods] 415 | +Connect(ctx context.Context, addrs []string) : []SwarmConnectResult, error 416 | +FindPeer(ctx context.Context, peerID peer.ID) : peerstore.PeerInfo, error 417 | +Peers(ctx context.Context, verbose, latency, streams bool) : *SwarmConnInfos, error 418 | ``` 419 | -------------------------------------------------------------------------------- /12.md: -------------------------------------------------------------------------------- 1 | # filecoin技术架构分析之十二:filecoin源码分析之内部接口层plumbing&porcelain接口 2 | 3 | > 作者:杨尉(waynewyang),转载请注明出处 4 | 5 | 6 | ## 目录 7 | - 12.filecoin源码分析之内部接口层plumbing&porcelain接口 8 | - 12.1 说明 9 | - 12.2 plumbing&porcelain模式简述 10 | - 12.3 plumbing底层接口 11 | - 12.4 porcelain高层接口 12 | 13 | ## 12.1 说明 14 | - 目前官方正在将api包解耦,往plumbing、porcelain中迁移 15 | - 缘由: 原来的api包,依赖于node包,而node包应该属于api之上的,这导致代码耦合性大 16 | - node作为一个上帝对象,被api包依赖,对架构扩展性,其他类型节点扩展开发不利 17 | - 就在笔者写这篇文章的同时,官方应该还在继续迁移,后面api包会逐步都迁移完 18 | 19 | - porcelain主要依赖于plumbing接口 20 | 21 | - 上一章所述的api包将会被废除 22 | 23 | ## 12.2 plumbing&porcelain模式简述 24 | - 该模式是借鉴git的思路,提供两种接口,porcelain偏高层面对用户更加友好方便;plumbing偏底层,友好度弱于porcelain 25 | - porcelain是英文瓷器的意思,类似洗手盆之类;plumbing是水管装置的意思,类似下水管,用户当然直接用洗手盆省心,不用管水管的事情 26 | - 用户级更偏向用porcelain,协议级更偏向使用plumbing, 27 | 28 | ## 12.3 plumbing底层接口 29 | - 说明 30 | - plumbing底层接口是为实现协议以及面向网络的必须最小实现 31 | - 更应用级别的调用更多将会调用到porcelain高层接口 32 | 33 | - 提供的具体功能接口 34 | - 区块状态读取 35 | - 配置信息 36 | - 日志 37 | - 消息池操作 38 | - 消息预览,Gas计算 39 | - 消息查询 40 | - 消息发送 41 | - 消息等待 42 | - 网络操作 43 | - Chain状态获取(actor信息) 44 | - 钱包底层操作 45 | 46 | - 具体的方法如下 47 | 48 | ``` 49 | ▼ package 50 | plumbing 51 | 52 | ▶ imports 53 | 54 | ▼+API : struct 55 | [fields] 56 | -chain : chain.ReadStore 57 | -config : *cfg.Config 58 | -logger : logging.EventLogger 59 | -msgPool : *core.MessagePool 60 | -msgPreviewer : *msg.Previewer 61 | -msgQueryer : *msg.Queryer 62 | -msgSender : *msg.Sender 63 | -msgWaiter : *msg.Waiter 64 | -network : *ntwk.Network 65 | -sigGetter : *mthdsig.Getter 66 | -wallet : *wallet.Wallet 67 | [methods] 68 | +ActorGet(ctx context.Context, addr address.Address) : *actor.Actor, error 69 | +ActorGetSignature(ctx context.Context, actorAddr address.Address, method string) : *exec.FunctionSignature, error 70 | +BlockGet(ctx context.Context, id cid.Cid) : *types.Block, error 71 | +ChainHead(ctx context.Context) : types.TipSet 72 | +ChainLs(ctx context.Context) : chan interface{} 73 | +ConfigGet(dottedPath string) : interface{}, error 74 | +ConfigSet(dottedPath string, paramJSON string) : error 75 | +MessagePoolGet(cid cid.Cid) : *types.SignedMessage, bool 76 | +MessagePoolPending() : []*types.SignedMessage 77 | +MessagePoolRemove(cid cid.Cid) 78 | +MessagePreview(ctx context.Context, from, to address.Address, method string, params ...interface{}) : types.GasUnits, error 79 | +MessageQuery(ctx context.Context, optFrom, to address.Address, method string, params ...interface{}) : [][]byte, *exec.FunctionSignature, error 80 | +MessageSend(ctx context.Context, from, to address.Address, value *types.AttoFIL, gasPrice types.AttoFIL, gasLimit types.GasUnits, method string, params ...interface{}) : cid.Cid, error 81 | +MessageWait(ctx context.Context, msgCid cid.Cid, cb func(*types.Block, *types.SignedMessage, *types.MessageReceipt) error) : error 82 | +NetworkFindProvidersAsync(ctx context.Context, key cid.Cid, count int) : chan pstore.PeerInfo 83 | +NetworkGetPeerID() : peer.ID 84 | +PubSubPublish(topic string, data []byte) : error 85 | +PubSubSubscribe(topic string) : pubsub.Subscription, error 86 | +SignBytes(data []byte, addr address.Address) : types.Signature, error 87 | +WalletAddresses() : []address.Address 88 | +WalletFind(address address.Address) : wallet.Backend, error 89 | +WalletNewAddress() : address.Address, error 90 | [functions] 91 | +New(deps *APIDeps) : *API 92 | ``` 93 | 94 | ## 12.4 porcelain高层接口 95 | - 说明 96 | - porcelain主要依赖plumbing实现。 97 | - 主要是面向用户级操作 98 | 99 | - 提供功能 100 | - 获取区块高度 101 | - 建立支付通道/多支付通道 102 | - 获取默认地址 103 | - 消息池等待未被打包进区块的消息 104 | - 采用默认地址发送消息 105 | - 获取指定矿工报价单 106 | - 获取矿工Owner地址 107 | - 获取矿工节点ID 108 | - 创建矿工,预览Gas消耗 109 | - 矿工报价,预览Gas消耗 110 | - 矿工报价 111 | - 获取签名支付凭证 112 | - 钱包余额查询 113 | 114 | ``` 115 | ▼ package 116 | porcelain 117 | 118 | ▶ imports 119 | 120 | ▼+API : struct 121 | [embedded] 122 | +*plumbing.API : *plumbing.API 123 | [methods] 124 | +ChainBlockHeight(ctx context.Context) : *types.BlockHeight, error 125 | +CreatePayments(ctx context.Context, config CreatePaymentsParams) : *CreatePaymentsReturn, error 126 | +GetAndMaybeSetDefaultSenderAddress() : address.Address, error 127 | +MessagePoolWait(ctx context.Context, messageCount uint) : []*types.SignedMessage, error 128 | +MessageSendWithDefaultAddress(ctx context.Context, from, to address.Address, value *types.AttoFIL, gasPrice types.AttoFIL, gasLimit types.GasUnits, method string, params ...interface{}) : cid.Cid, error 129 | +MinerGetAsk(ctx context.Context, minerAddr address.Address, askID uint64) : minerActor.Ask, error 130 | +MinerGetOwnerAddress(ctx context.Context, minerAddr address.Address) : address.Address, error 131 | +MinerGetPeerID(ctx context.Context, minerAddr address.Address) : peer.ID, error 132 | +MinerPreviewCreate(ctx context.Context, fromAddr address.Address, pledge uint64, pid peer.ID, collateral *types.AttoFIL) : types.GasUnits, error 133 | +MinerPreviewSetPrice(ctx context.Context, from address.Address, miner address.Address, price *types.AttoFIL, expiry *big.Int) : types.GasUnits, error 134 | +MinerSetPrice(ctx context.Context, from address.Address, miner address.Address, gasPrice types.AttoFIL, gasLimit types.GasUnits, price *types.AttoFIL, expiry *big.Int) : MinerSetPriceResponse, error 135 | +PaymentChannelLs(ctx context.Context, fromAddr address.Address, payerAddr address.Address) : map[string]*paymentbroker.PaymentChannel, error 136 | +PaymentChannelVoucher(ctx context.Context, fromAddr address.Address, channel *types.ChannelID, amount *types.AttoFIL, validAt *types.BlockHeight) : *paymentbroker.PaymentVoucher, error 137 | +WalletBalance(ctx context.Context, address address.Address) : *types.AttoFIL, error 138 | [functions] 139 | +New(plumbing *plumbing.API) : *API 140 | ``` 141 | 142 | -------------------------------------------------------------------------------- /13.md: -------------------------------------------------------------------------------- 1 | # filecoin技术架构分析之十三:filecoin源码分析之服务层actor及vm 2 | 3 | > 作者:杨尉(waynewyang),转载请注明出处 4 | 5 | 6 | ## 目录 7 | - 13.filecoin源码分析之服务层actor及vm 8 | - 13.1 说明 9 | - 13.2 exec(actor及vm的接口定义) 10 | - 13.3 actor的类型及源码分析 11 | - 13.3.1 基础actor包 12 | - 13.3.2 storagemarket actor 13 | - 13.3.3 miner actor 14 | - 13.3.4 paymentbroker actor 15 | - 13.3.5 account actor 16 | - 13.4 vm(虚拟机运行环境) 17 | - 13.5 state包(actor状态) 18 | 19 | ## 13.1 说明 20 | > 分析源代码版本:master 2c87fd59(2019.3.7) 21 | 22 | > 回头看第三章开发网使用中创建矿工,提交订单,支付等操作实际上都是actor的新增及状态改变 23 | 24 | > 当前的实现vm还不具备通用abi数据的解释执行能力,未达到真正智能合约水平 25 | 26 | ## 13.2 exec(actor及vm的接口定义) 27 | - 说明 28 | - 提供可执行actor的最小接口要求 ExecutableActor (由actor及具体actor包实现) 29 | - 提供actor键值存取接口定义 Lookup (由actor包实现) 30 | - 提供状态临时存储的接口定义 Storage (由vm.Storage实现) 31 | - actor的执行环境接口定义 VMContext (由vm.context实现) 32 | 33 | - 具体源码注释如下 34 | 35 | ``` 36 | ▼ package 37 | exec 38 | 39 | ▶ imports 40 | 41 | ▼ constants 42 | +ErrDanglingPointer 43 | +ErrDecode 44 | +ErrInsufficientGas 45 | +ErrStaleHead 46 | 47 | ▼ variables 48 | +Errors 49 | 50 | ▼+Error : string 51 | [methods] 52 | +Error() : string 53 | 54 | +ExportedFunc : func(ctx VMContext) []byte, uint8, error 55 | 56 | // actor符号集合 57 | ▼+Exports : map[string]*FunctionSignature 58 | [methods] 59 | // 判断是否存在特定方法 60 | +Has(method string) : bool 61 | 62 | // 对于单个函数的符号表 63 | // todo中的事情:需要转换为非go类型 64 | ▼+FunctionSignature : struct 65 | [fields] 66 | +Params : []abi.Type 67 | +Return : []abi.Type 68 | 69 | // 可执行合约接口,这是每一类型的合约必须实现的最小接口 70 | // 包括account,miner,storagemarket,paymentbroker 71 | ▼+ExecutableActor : interface 72 | [methods] 73 | +Exports() : Exports 74 | +InitializeState(storage Storage, initializerData interface{}) : error 75 | 76 | // 由actor.lookup实现键值存储 (actor/storage.go) 77 | ▼+Lookup : interface 78 | [methods] 79 | +Commit(ctx context.Context) : cid.Cid, error 80 | +Delete(ctx context.Context, k string) : error 81 | +Find(ctx context.Context, k string) : interface{}, error 82 | +IsEmpty() : bool 83 | +Set(ctx context.Context, k string, v interface{}) : error 84 | +Values(ctx context.Context) : []*hamt.KV, error 85 | 86 | // 由vm.Storage实现 87 | // 解决持久化的问题,有副本防止回滚机制 88 | // 具体实现还有Flush持久化到datastore功能 89 | ▼+Storage : interface 90 | [methods] 91 | // 提交最新actor Head 92 | +Commit(cid.Cid, cid.Cid) : error 93 | // 如下都为内存中操作 94 | +Get(cid.Cid) : []byte, error 95 | +Head() : cid.Cid 96 | +Put(interface{}) : cid.Cid, error 97 | 98 | // actor的abi执行环境接口,由vm.context实现 99 | ▼+VMContext : interface 100 | [methods] 101 | // 创建新的合约地址 102 | +AddressForNewActor() : address.Address, error 103 | // 查询区块高度 104 | +BlockHeight() : *types.BlockHeight 105 | // Gas收费 106 | +Charge(cost types.GasUnits) : error 107 | // 创建合约 108 | +CreateNewActor(addr address.Address, code cid.Cid, initalizationParams interface{}) : error 109 | // 判断是否为account类型的Actor 110 | +IsFromAccountActor() : bool 111 | // 合约中交易信息 112 | +Message() : *types.Message 113 | // 执行合约函数 114 | +Send(to address.Address, method string, value *types.AttoFIL, params []interface{}) : [][]byte, uint8, error 115 | +Storage() : Storage 116 | // 当Storage接口完成会删除如下两项 117 | +ReadStorage() : []byte, error 118 | +WriteStorage(interface{}) : error 119 | ``` 120 | 121 | ## 13.3 actor的类型及源码分析 122 | - actor包定义及实现了基础actor,此外filecoin还定义了四种内置的actor类型 123 | - 存储市场actor,此类actor整个网络只有一个实例,用于创建存储矿工、更新功率表、获取总存储容量 124 | - Miner actor,此类actor整个网络只有多个实例(随用户数增加而增加),用于执行矿工相关的操作 125 | - paymentbroker actor,此类actor整个网络只有一个实例,用于创建支付通道以及支付相关信息 126 | - account actor,账户actor,此类actor整个网络只有多个实例(随用户数增加而增加),只能用于基本的转账操作 127 | 128 | ### 13.3.1 基础actor包 129 | - 说明 130 | - 定义了actor的基础结构,其中code如果使用内置的如上四种actor,他们的值都是固定的 131 | - 提供了actor的基础操作方法 132 | - 见笔者在代码中的注释 133 | 134 | ``` 135 | location: actor/actor.go 136 | 137 | ▼ package 138 | actor 139 | 140 | // Actor可以理解为合约或者账户,转账操作要检查code cid合法性 141 | ▼+Actor : struct 142 | [fields] 143 | //余额 144 | +Balance : *types.AttoFIL 145 | 146 | // 合约代码的cid,vm具体执行其对应的代码 147 | // 1 具体代码的cid 148 | // 2 在go语言实现的四种特定合约,这个字段是常量,比如account,miner,storagemarket,paymentbroker 149 | +Code : cid.Cid 150 | 151 | // 合约状态的最新状态 152 | +Head : cid.Cid 153 | 154 | // 防止重放攻击而设置的参数 155 | +Nonce : types.Uint64 156 | 157 | [methods] 158 | // 计算actor的cid 159 | +Cid() : cid.Cid, error 160 | 161 | // 打印合约信息 162 | +Format(f fmt.State, c rune) 163 | 164 | // 增加Nonce+1方法 165 | +IncNonce() 166 | 167 | // 编码 168 | +Marshal() : []byte, error 169 | 170 | // 解码 171 | +Unmarshal(b []byte) : error 172 | [functions] 173 | +NewActor(code cid.Cid, balance *types.AttoFIL) : *Actor 174 | 175 | ▼ functions 176 | // 只有account类型的actor使用 177 | +NextNonce(actor *Actor) : uint64, error 178 | -init() 179 | ``` 180 | 181 | ``` 182 | location: actor/export.go 183 | 184 | ▼ functions 185 | // 返回某个actor的方法执行函数 186 | +MakeTypedExport(actor exec.ExecutableActor, method string) : exec.ExportedFunc 187 | 188 | // 序列化成字节切片 189 | +MarshalValue(val interface{}) : []byte, error 190 | ``` 191 | 192 | ### 13.3.2 storagemarket actor 193 | - 主要功能 194 | - 创建存储矿工 195 | - 获取总存储量 196 | - 更新功率 197 | 198 | ``` 199 | ▼ package 200 | storagemarket 201 | 202 | ▶ imports 203 | 204 | ▼ constants 205 | +ErrInsufficientCollateral 206 | +ErrPledgeTooLow 207 | +ErrUnknownMiner 208 | 209 | ▼ variables 210 | +Errors 211 | +MinimumCollateralPerSector 212 | +MinimumPledge 213 | -storageMarketExports 214 | 215 | ▼+Actor : struct 216 | [methods] 217 | // 创建存储矿工 218 | // 会调用到miner actor的创建 219 | +CreateMiner(vmctx exec.VMContext, pledge *big.Int, publicKey []byte, pid peer.ID) : address.Address, uint8, error 220 | +Exports() : exec.Exports 221 | // 获取总存储 222 | +GetTotalStorage(vmctx exec.VMContext) : *big.Int, uint8, error 223 | +InitializeState(storage exec.Storage, _ interface{}) : error 224 | // 更新功率 225 | +UpdatePower(vmctx exec.VMContext, delta *big.Int) : uint8, error 226 | 227 | ▼+State : struct 228 | [fields] 229 | // miners合集的cid 230 | +Miners : cid.Cid 231 | +TotalCommittedStorage : *big.Int 232 | 233 | ▼ functions 234 | +MinimumCollateral(sectors *big.Int) : *types.AttoFIL 235 | // 实例化存储市场 236 | +NewActor() : *actor.Actor, error 237 | -init() 238 | ``` 239 | 240 | ### 13.3.3 miner actor 241 | - 提供功能 242 | - 有基本转账功能 243 | - 提供如下功能 244 | - filecoin网络中存在多个Miner Actor 245 | 246 | ``` 247 | ▼ package 248 | miner 249 | 250 | ▶ imports 251 | 252 | ▼ constants 253 | +ErrAskNotFound 254 | +ErrCallerUnauthorized 255 | +ErrInsufficientPledge 256 | +ErrInvalidPoSt 257 | +ErrInvalidSealProof 258 | +ErrInvalidSector 259 | +ErrPublicKeyTooBig 260 | +ErrSectorCommitted 261 | +ErrStoragemarketCallFailed 262 | +MaximumPublicKeySize 263 | 264 | ▼ variables 265 | +Errors 266 | +GracePeriodBlocks 267 | +ProvingPeriodBlocks 268 | -minerExports 269 | 270 | ▼+Actor : struct 271 | [fields] 272 | +Bootstrap : bool 273 | [methods] 274 | // 增加订单 275 | +AddAsk(ctx exec.VMContext, price *types.AttoFIL, expiry *big.Int) : *big.Int, uint8, error 276 | // 抵押承诺 277 | +CommitSector(ctx exec.VMContext, sectorID uint64, commD, commR, commRStar, proof []byte) : uint8, error 278 | +Exports() : exec.Exports 279 | // 获取存储矿工相关信息 280 | +GetAsk(ctx exec.VMContext, askid *big.Int) : []byte, uint8, error 281 | +GetAsks(ctx exec.VMContext) : []uint64, uint8, error 282 | +GetKey(ctx exec.VMContext) : []byte, uint8, error 283 | +GetLastUsedSectorID(ctx exec.VMContext) : uint64, uint8, error 284 | +GetOwner(ctx exec.VMContext) : address.Address, uint8, error 285 | +GetPeerID(ctx exec.VMContext) : peer.ID, uint8, error 286 | +GetPledge(ctx exec.VMContext) : *big.Int, uint8, error 287 | +GetPower(ctx exec.VMContext) : *big.Int, uint8, error 288 | +GetProvingPeriodStart(ctx exec.VMContext) : *types.BlockHeight, uint8, error 289 | +GetSectorCommitments(ctx exec.VMContext) : map[string]types.Commitments, uint8, error 290 | +InitializeState(storage exec.Storage, initializerData interface{}) : error 291 | // 提交时空证明 292 | +SubmitPoSt(ctx exec.VMContext, postProofs []proofs.PoStProof) : uint8, error 293 | // 更新节点Id 294 | +UpdatePeerID(ctx exec.VMContext, pid peer.ID) : uint8, error 295 | 296 | // 报价单:价格,时长,序号 297 | ▼+Ask : struct 298 | [fields] 299 | +Expiry : *types.BlockHeight 300 | +ID : *big.Int 301 | +Price : *types.AttoFIL 302 | 303 | // 矿工Actor状态 304 | ▼+State : struct 305 | [fields] 306 | +Asks : []*Ask 307 | +Collateral : *types.AttoFIL 308 | +LastPoSt : *types.BlockHeight 309 | +LastUsedSectorID : uint64 310 | +NextAskID : *big.Int 311 | +Owner : address.Address 312 | +PeerID : peer.ID 313 | +PledgeSectors : *big.Int 314 | +Power : *big.Int 315 | +ProvingPeriodStart : *types.BlockHeight 316 | +PublicKey : []byte 317 | +SectorCommitments : map[string]types.Commitments 318 | [functions] 319 | +NewState(owner address.Address, key []byte, pledge *big.Int, pid peer.ID, collateral *types.AttoFIL) : *State 320 | 321 | ▼ functions 322 | +NewActor() : *actor.Actor 323 | -init() 324 | ``` 325 | ### 13.3.4 paymentbroker actor 326 | - 说明 327 | - 全网只有一个paymentbroker 328 | - 几个概念的关系简图 329 | 330 | ![](img/payment.png) 331 | 332 | - 源码分析注释 333 | ``` 334 | ▼ package 335 | paymentbroker 336 | 337 | ▼+Actor : struct 338 | [methods] 339 | // 关闭支付通道 340 | +Close(vmctx exec.VMContext, payer address.Address, chid *types.ChannelID, amt *types.AttoFIL, validAt *types.BlockHeight, sig []byte) : uint8, error 341 | // 创建支付通道 342 | +CreateChannel(vmctx exec.VMContext, target address.Address, eol *types.BlockHeight) : *types.ChannelID, uint8, error 343 | +Exports() : exec.Exports 344 | // 增加资金 345 | +Extend(vmctx exec.VMContext, chid *types.ChannelID, eol *types.BlockHeight) : uint8, error 346 | +InitializeState(storage exec.Storage, initializerData interface{}) : error 347 | // 查询某个支付者的信息 348 | +Ls(vmctx exec.VMContext, payer address.Address) : []byte, uint8, error 349 | // 撤回资金 350 | +Reclaim(vmctx exec.VMContext, chid *types.ChannelID) : uint8, error 351 | // 赎回(或者收款)资金 352 | +Redeem(vmctx exec.VMContext, payer address.Address, chid *types.ChannelID, amt *types.AttoFIL, validAt *types.BlockHeight, sig []byte) : uint8, error 353 | // 收据,指明在特定区块高度之前都是有效的 354 | +Voucher(vmctx exec.VMContext, chid *types.ChannelID, amount *types.AttoFIL, validAt *types.BlockHeight) : []byte, uint8, error 355 | 356 | ▼+PaymentChannel : struct 357 | [fields] 358 | // 支付通道内金额 359 | +Amount : *types.AttoFIL 360 | // 已被赎回金额 361 | +AmountRedeemed : *types.AttoFIL 362 | +Eol : *types.BlockHeight 363 | // 收款人地址 364 | +Target : address.Address 365 | 366 | ▼ functions 367 | // 收据的签名及校验 368 | +SignVoucher(channelID *types.ChannelID, amount *types.AttoFIL, validAt *types.BlockHeight, addr address.Address, signer types.Signer) : types.Signature, error 369 | +VerifyVoucherSignature(payer address.Address, chid *types.ChannelID, amt *types.AttoFIL, validAt *types.BlockHeight, sig []byte) : bool 370 | -createVoucherSignatureData(channelID *types.ChannelID, amount *types.AttoFIL, validAt *types.BlockHeight) : []byte 371 | -findByChannelLookup(ctx context.Context, storage exec.Storage, byPayer exec.Lookup, payer address.Address) : exec.Lookup, error 372 | -init() 373 | -reclaim(ctx context.Context, vmctx exec.VMContext, byChannelID exec.Lookup, payer address.Address, chid *types.ChannelID, channel *PaymentChannel) : error 374 | -updateChannel(ctx exec.VMContext, target address.Address, channel *PaymentChannel, amt *types.AttoFIL, validAt *types.BlockHeight) : error 375 | -withPayerChannels(ctx context.Context, storage exec.Storage, payer address.Address, f func(exec.Lookup) error) : error 376 | -withPayerChannelsForReading(ctx context.Context, storage exec.Storage, payer address.Address, f func(exec.Lookup) error) : error 377 | ``` 378 | 379 | ### 13.3.5 account actor 380 | - 说明 381 | - 纯账户,记录nonce 382 | - 只有转帐功能 383 | - filecoin网络中存在多个account Actor 384 | 385 | ``` 386 | ▼ package 387 | account 388 | 389 | ▶ imports 390 | 391 | ▼ variables 392 | -accountExports 393 | 394 | ▼+Actor : struct 395 | [methods] 396 | +Exports() : exec.Exports 397 | +InitializeState(_ exec.Storage, _ interface{}) : error 398 | 399 | ▼ functions 400 | // 实例化account actor 集成actor包中Actor所实现的所有方法 401 | +NewActor(balance *types.AttoFIL) : *actor.Actor, error 402 | // 将其他actor类型转为account,保留余额 403 | +UpgradeActor(act *actor.Actor) : error 404 | ``` 405 | 406 | # 13.4 vm(虚拟机运行环境) 407 | 408 | - 虚拟机执行函数 409 | 410 | ``` 411 | ▼ package 412 | vm 413 | 414 | ▶ imports 415 | 416 | ▼-sendDeps : struct 417 | [fields] 418 | -transfer : func(*actor.Actor, *actor.Actor, *types.AttoFIL) error 419 | 420 | ▼ functions 421 | // 执行合约 422 | +Send(ctx context.Context, vmCtx *Context) : [][]byte, uint8, error 423 | // 转账 424 | +Transfer(fromActor, toActor *actor.Actor, value *types.AttoFIL) : error 425 | -send(ctx context.Context, deps sendDeps, vmCtx *Context) : [][]byte, uint8, error 426 | ``` 427 | - vm环境实现 428 | 429 | ``` 430 | ▼+Context : struct 431 | [fields] 432 | -ancestors : []types.TipSet 433 | -blockHeight : *types.BlockHeight 434 | -deps : *deps 435 | -from : *actor.Actor 436 | -gasTracker : *GasTracker 437 | -lookBack : int 438 | -message : *types.Message 439 | -state : *state.CachedTree 440 | -storageMap : StorageMap 441 | -to : *actor.Actor 442 | [methods] 443 | // 实现上述VMContext 接口,注释见上 444 | +AddressForNewActor() : address.Address, error 445 | +BlockHeight() : *types.BlockHeight 446 | +Charge(cost types.GasUnits) : error 447 | +CreateNewActor(addr address.Address, code cid.Cid, initializerData interface{}) : error 448 | +GasUnits() : types.GasUnits 449 | +IsFromAccountActor() : bool 450 | +Message() : *types.Message 451 | +Rand(sampleHeight *types.BlockHeight) : []byte, error 452 | +ReadStorage() : []byte, error 453 | +Send(to address.Address, method string, value *types.AttoFIL, params []interface{}) : [][]byte, uint8, error 454 | +Storage() : exec.Storage 455 | +WriteStorage(memory interface{}) : error 456 | [functions] 457 | +NewVMContext(params NewContextParams) : *Context 458 | 459 | ▼+NewContextParams : struct 460 | [fields] 461 | +Ancestors : []types.TipSet 462 | +BlockHeight : *types.BlockHeight 463 | +From : *actor.Actor 464 | +GasTracker : *GasTracker 465 | +LookBack : int 466 | +Message : *types.Message 467 | +State : *state.CachedTree 468 | +StorageMap : StorageMap 469 | +To : *actor.Actor 470 | 471 | ▼-deps : struct 472 | [fields] 473 | +EncodeValues : func([]*abi.Value) []byte, error 474 | +GetOrCreateActor : func(context.Context, address.Address, func() *actor.Actor, error) *actor.Actor, error 475 | +Send : func(context.Context, *Context) [][]byte, uint8, error 476 | +ToValues : func([]interface{}) []*abi.Value, error 477 | 478 | ▼ deps* : ctype 479 | [functions] 480 | -makeDeps(st *state.CachedTree) : *deps 481 | 482 | ▼ functions 483 | -computeActorAddress(creator address.Address, nonce uint64) : address.Address, error 484 | ``` 485 | 486 | - 合约状态存储 487 | 488 | ``` 489 | ▼+Storage : struct 490 | [fields] 491 | -actor : *actor.Actor 492 | -blockstore : blockstore.Blockstore 493 | -chunks : map[cid.Cid]ipld.Node 494 | [methods] 495 | +Commit(newCid cid.Cid, oldCid cid.Cid) : error 496 | +Flush() : error 497 | +Get(cid cid.Cid) : []byte, error 498 | +Head() : cid.Cid 499 | +Prune() : error 500 | +Put(v interface{}) : cid.Cid, error 501 | -liveDescendantIds(id cid.Cid) : *cid.Set, error 502 | [functions] 503 | +NewStorage(bs blockstore.Blockstore, act *actor.Actor) : Storage 504 | 505 | ▼-storageMap : struct 506 | [fields] 507 | -blockstore : blockstore.Blockstore 508 | -storageMap : map[address.Address]Storage 509 | [methods] 510 | +Flush() : error 511 | +NewStorage(addr address.Address, actor *actor.Actor) : Storage 512 | 513 | ▼+StorageMap : interface 514 | [methods] 515 | +Flush() : error 516 | +NewStorage(addr address.Address, actor *actor.Actor) : Storage 517 | 518 | ▼ functions 519 | +NewStorageMap(bs blockstore.Blockstore) : StorageMap 520 | ``` 521 | 522 | ## 13.5 state包(actor状态) 523 | - 表征actor的状态 524 | 525 | -------------------------------------------------------------------------------- /14.md: -------------------------------------------------------------------------------- 1 | # filecoin技术架构分析之十四:filecoin源码分析之服务层链同步、共识协议及挖矿 2 | 3 | > 作者:杨尉(waynewyang),转载请注明出处 4 | 5 | 6 | ## 目录 7 | - 14.filecoin源码分析之服务层链同步、共识协议及挖矿 8 | - 14.1 chain 9 | - 14.1.1 基础结构 10 | - 14.1.2 链同步 11 | - 14.1.3 链存储 12 | - 14.2 consensus 13 | - 14.3 mining 14 | - 14.3.1 挖矿的主要逻辑 15 | - 14.3.2 其他细节源码简析 16 | 17 | > 分析基于的源码版本:go-filecoin master a0598a54(2019年3月9日) 18 | 19 | ## 14.1 chain同步 20 | 21 | ### 14.1.1 基础结构 22 | 23 | - TipIndex 定义定义了tipset的基础结构及方法 24 | 25 | ``` 26 | ▼ package 27 | chain 28 | 29 | ▼+TipIndex : struct 30 | [fields] 31 | -mu : sync.Mutex 32 | // 根据id来获取tipset及其状态根 33 | -tsasByID : tsasByTipSetID 34 | // 根据父块来获取tipset及其状态根 35 | -tsasByParentsAndHeight : map[string]tsasByTipSetID 36 | 37 | [methods] 38 | // 根据id来获取tipset及其状态根 39 | +Get(tsKey string) : *TipSetAndState, error 40 | // 根据父块来获取tipset及其状态根 41 | +GetByParentsAndHeight(pKey string, h uint64) : []*TipSetAndState, error 42 | // 根据Id判断是否有此tipset 43 | +Has(tsKey string) : bool 44 | // 根据父块判断是否有此tipset 45 | +HasByParentsAndHeight(pKey string, h uint64) : bool 46 | // 设置tipset和状态根 47 | +Put(tsas *TipSetAndState) : error 48 | [functions] 49 | +NewTipIndex() : *TipIndex 50 | 51 | ▼+TipSetAndState : struct 52 | [fields] 53 | // tipset 54 | +TipSet : types.TipSet 55 | // 相当于区块的root cid 56 | +TipSetStateRoot : cid.Cid 57 | ``` 58 | 59 | ### 14.1.2 链同步 60 | 61 | - chain同步的接口定义 62 | 63 | ``` 64 | location: chain/syncer.go 65 | 66 | ▼ package 67 | chain 68 | 69 | ▼+Syncer : interface 70 | [methods] 71 | // 处理新区块的接口定义 72 | +HandleNewBlocks(ctx context.Context, blkCids []cid.Cid) : error 73 | 74 | 具体接口实现在location: chain/defalut_syncer.go中 75 | ``` 76 | 77 | - 特殊情况的错误 78 | 79 | ``` 80 | location: chain/reorg.go 81 | 82 | // 如果当前区块头不包含在最新的区块头之上时候,会报此错误 83 | ▼ functions 84 | +IsReorg(curHead types.TipSet, newChain []types.TipSet) : bool 85 | ``` 86 | 87 | ### 14.1.3 链存储 88 | 89 | - 其中 90 | - Readstore是一个通用接口 91 | - Store的设计基本是给ChainSync使用的 92 | 93 | ``` 94 | location: chain/store.go 95 | 96 | ▼ package 97 | chain 98 | 99 | ▼ constants 100 | // 用于发布新的区块头的主题"new-head" 101 | +NewHeadTopic 102 | 103 | ▼ variables 104 | // 创世块的key 105 | +GenesisKey 106 | 107 | ▼+ReadStore : interface 108 | [methods] 109 | // 获取历史区块,通过channel实现 110 | +BlockHistory(ctx context.Context, tips types.TipSet) : chan interface{} 111 | // 获取创世区块cid 112 | +GenesisCid() : cid.Cid 113 | // 通过cid获取具体的block 114 | +GetBlock(ctx context.Context, id cid.Cid) : *types.Block, error 115 | // 通过cid获取具体的block 116 | +GetTipSetAndState(ctx context.Context, tsKey string) : *TipSetAndState, error 117 | // 获取最新区块 118 | +Head() : types.TipSet 119 | // 最新区块变更事件 120 | +HeadEvents() : *pubsub.PubSub 121 | // 最新合约状态 122 | +LatestState(ctx context.Context) : state.Tree, error 123 | // 加载chain 124 | +Load(ctx context.Context) : error 125 | // 停止 126 | +Stop() 127 | 128 | // 这个接口只是chain同步使用 129 | ▼+Store : interface 130 | [embedded] 131 | +ReadStore 132 | [methods] 133 | +GetBlocks(ctx context.Context, ids types.SortedCidSet) : []*types.Block, error 134 | +GetTipSetAndStatesByParentsAndHeight(ctx context.Context, pTsKey string, h uint64) : []*TipSetAndState, error 135 | +HasAllBlocks(ctx context.Context, cs []cid.Cid) : bool 136 | +HasBlock(ctx context.Context, c cid.Cid) : bool 137 | +HasTipSetAndState(ctx context.Context, tsKey string) : bool 138 | +HasTipSetAndStatesWithParentsAndHeight(ctx context.Context, pTsKey string, h uint64) : bool 139 | // 存储并更新最新区块信息 140 | +PutTipSetAndState(ctx context.Context, tsas *TipSetAndState) : error 141 | +SetHead(ctx context.Context, s types.TipSet) : error 142 | ``` 143 | 144 | ## 14.2 consensus 145 | - 主要功能 146 | - 提供创建选票方法,验证中奖选票方法,确定最终的tipset 147 | - 将合法的tipset消息取出,生效actor状态 148 | 149 | ``` 150 | ▼ package 151 | consensus 152 | 153 | ▶ imports 154 | 155 | ▼ constants 156 | +ECPrM : uint64 157 | +ECV : uint64 158 | +LookBackParameter 159 | 160 | ▼ variables 161 | +AncestorRoundsNeeded 162 | +ErrInvalidBase 163 | +ErrStateRootMismatch 164 | +ErrUnorderedTipSets 165 | -log 166 | -ticketDomain : *big.Int 167 | 168 | // Expected实现EC共识 169 | ▼+Expected : struct 170 | [fields] 171 | // 全局功率表 172 | +PwrTableView : PowerTableView 173 | -bstore : blockstore.Blockstore 174 | -cstore : *hamt.CborIpldStore 175 | -genesisCid : cid.Cid 176 | -processor : Processor 177 | -verifier : proofs.Verifier 178 | [methods] 179 | // 比较两个tipset的权重 180 | +IsHeavier(ctx context.Context, a, b types.TipSet, aSt, bSt state.Tree) : bool, error 181 | // 建立新的tipset 182 | +NewValidTipSet(ctx context.Context, blks []*types.Block) : types.TipSet, error 183 | // 运行状态转换 184 | // 1 新区块到来的时候出发状态转换(chain sync逻辑) 185 | // 2 进入后判断tipset的有效性,包括验证选票是否中奖 186 | // 3 逐一执行消息,切换状态 187 | +RunStateTransition(ctx context.Context, ts types.TipSet, ancestors []types.TipSet, pSt state.Tree) : state.Tree, error 188 | // 计算tipset权重 189 | +Weight(ctx context.Context, ts types.TipSet, pSt state.Tree) : uint64, error 190 | -runMessages(ctx context.Context, st state.Tree, vms vm.StorageMap, ts types.TipSet, ancestors []types.TipSet) : state.Tree, error 191 | -validateBlockStructure(ctx context.Context, b *types.Block) : error 192 | -validateMining(ctx context.Context, st state.Tree, ts types.TipSet, parentTs types.TipSet) : error 193 | 194 | ▼+Processor : interface 195 | // 会被RunStateTransition间接掉用,进行状态切换(生效挖矿成功的tipset消息) 196 | [methods] 197 | // 从tipset中逐一取出block处理 198 | +ProcessBlock(ctx context.Context, st state.Tree, vms vm.StorageMap, blk *types.Block, ancestors []types.TipSet) : []*ApplicationResult, error 199 | +ProcessTipSet(ctx context.Context, st state.Tree, vms vm.StorageMap, ts types.TipSet, ancestors []types.TipSet) : *ProcessTipSetResponse, error 200 | 201 | ▼ functions 202 | // 与白皮书描述一致,按照存储功率出块,用以判断是否中奖 203 | +CompareTicketPower(ticket types.Signature, minerPower uint64, totalPower uint64) : bool 204 | // 产生随机挑战种子,针对时空证明 205 | +CreateChallengeSeed(parents types.TipSet, nullBlkCount uint64) : proofs.PoStChallengeSeed, error 206 | // 生成选票 207 | // 用上一个区块的时空证明+矿工地址(目前直接用的矿工地址,issue1054讨论中) 生成256bit哈希 208 | +CreateTicket(proof proofs.PoStProof, minerAddr address.Address) : []byte 209 | // 判断是否中奖,调用CompareTicketPower 210 | +IsWinningTicket(ctx context.Context, bs blockstore.Blockstore, ptv PowerTableView, st state.Tree, ticket types.Signature, miner address.Address) : bool, error 211 | // 实例化Expected 212 | +NewExpected(cs *hamt.CborIpldStore, bs blockstore.Blockstore, processor Processor, pt PowerTableView, gCid cid.Cid, verifier proofs.Verifier) : Protocol 213 | -init() 214 | 215 | ``` 216 | 217 | ## 14.3 mining 218 | 219 | ### 14.3.1 挖矿的主要逻辑 220 | 221 | - 1 不能将空块最为基准块 222 | - 2 基于上一个Tipset信息(如果上一个为空块,必须找到空块之前高度最高的Tipset,并记录中间空块数据)和空块数母生成合法的时空证明挑战参数 223 | - 3 生成时空证明 224 | - 4 时空证明成功,调用共识协议创建奖票 225 | - 5 如果奖票中奖,将未打包的消息打包区块 226 | 227 | ``` 228 | location: mining/working 229 | 230 | //这里是挖矿逻辑的真正入口 231 | 232 | // Mine implements the DefaultWorkers main mining function.. 233 | // The returned bool indicates if this miner created a new block or not. 234 | func (w *DefaultWorker) Mine(ctx context.Context, base types.TipSet, nullBlkCount int, outCh chan<- Output) bool { 235 | log.Info("Worker.Mine") 236 | ctx = log.Start(ctx, "Worker.Mine") 237 | defer log.Finish(ctx) 238 | // 不能将空块作为基准块挖矿 239 | if len(base) == 0 { 240 | log.Warning("Worker.Mine returning because it can't mine on an empty tipset") 241 | outCh <- Output{Err: errors.New("bad input tipset with no blocks sent to Mine()")} 242 | return false 243 | } 244 | 245 | st, err := w.getStateTree(ctx, base) 246 | if err != nil { 247 | log.Errorf("Worker.Mine couldn't get state tree for tipset: %s", err.Error()) 248 | outCh <- Output{Err: err} 249 | return false 250 | } 251 | 252 | log.Debugf("Mining on tipset: %s, with %d null blocks.", base.String(), nullBlkCount) 253 | if ctx.Err() != nil { 254 | log.Warningf("Worker.Mine returning with ctx error %s", ctx.Err().Error()) 255 | return false 256 | } 257 | 258 | // 基于上一个基准Tipset以及空块数目生成Post随机挑战参数 259 | challenge, err := consensus.CreateChallengeSeed(base, uint64(nullBlkCount)) 260 | if err != nil { 261 | outCh <- Output{Err: err} 262 | return false 263 | } 264 | 265 | // 生成时空证明 266 | prCh := createProof(challenge, w.createPoSTFunc) 267 | 268 | var proof proofs.PoStProof 269 | var ticket []byte 270 | select { 271 | case <-ctx.Done(): 272 | log.Infof("Mining run on base %s with %d null blocks canceled.", base.String(), nullBlkCount) 273 | return false 274 | case prChRead, more := <-prCh: 275 | if !more { 276 | log.Errorf("Worker.Mine got zero value from channel prChRead") 277 | return false 278 | } 279 | copy(proof[:], prChRead[:]) 280 | // 时空证明成功,调用共识协议创建奖票 281 | ticket = consensus.CreateTicket(proof, w.minerAddr) 282 | } 283 | 284 | // TODO: Test the interplay of isWinningTicket() and createPoSTFunc() 285 | // https://github.com/filecoin-project/go-filecoin/issues/1791 286 | // 调用共识协议确认是否中奖 287 | weHaveAWinner, err := consensus.IsWinningTicket(ctx, w.blockstore, w.powerTable, st, ticket, w.minerAddr) 288 | 289 | if err != nil { 290 | log.Errorf("Worker.Mine couldn't compute ticket: %s", err.Error()) 291 | outCh <- Output{Err: err} 292 | return false 293 | } 294 | 295 | if weHaveAWinner { 296 | // 如果中奖将打包消息,生成区块 297 | next, err := w.Generate(ctx, base, ticket, proof, uint64(nullBlkCount)) 298 | if err == nil { 299 | log.SetTag(ctx, "block", next) 300 | log.Debugf("Worker.Mine generates new winning block! %s", next.Cid().String()) 301 | } 302 | outCh <- NewOutput(next, err) 303 | return true 304 | } 305 | 306 | return false 307 | } 308 | ``` 309 | 310 | ### 14.3.2 其他细节源码简析 311 | 312 | - 消息队列(交易消息集)的处理 313 | 314 | ``` 315 | location: mining/mqueue.go 316 | 317 | ▼ package 318 | mining 319 | 320 | ▶ imports 321 | 322 | ▼+MessageQueue : struct 323 | [fields] 324 | -senderQueues : queueHeap 325 | [methods] 326 | // 取出消息切片,即多条消息 327 | +Drain() : []*types.SignedMessage 328 | +Empty() : bool 329 | // 从队列取出一条消息 330 | +Pop() : *types.SignedMessage, bool 331 | [functions] 332 | // 实例化消息队列 333 | +NewMessageQueue(msgs []*types.SignedMessage) : MessageQueue 334 | 335 | -nonceQueue : []*types.SignedMessage 336 | 337 | // 一些队列的基本操作 338 | // 1 长度、push、pop功能 339 | // 2 Less主要是比较两条交易中的Gas价格,大家可以回头看看type中的消息定义,这里不赘述了 340 | // 3 为什么要提供Less接口,留给大家思索一下,熟悉以太坊的可能一眼就看出了 341 | ▼-queueHeap : []nonceQueue 342 | [methods] 343 | +Len() : int 344 | +Less(i, j int) : bool 345 | +Pop() : interface{} 346 | +Push(x interface{}) 347 | +Swap(i, j int) 348 | ``` 349 | 350 | - 调度器 351 | - 入口 352 | - node实例会调用NewScheduler创建相关实例并启动挖矿 353 | 354 | ``` 355 | ▼ package 356 | mining 357 | 358 | ▶ imports 359 | 360 | ▼ constants 361 | +MineDelayConversionFactor 362 | 363 | ▼-timingScheduler : struct 364 | [fields] 365 | -isStarted : bool 366 | -mineDelay : time.Duration 367 | // 查找权重最高的Tipset 368 | -pollHeadFunc : func() types.TipSet 369 | // 底层的挖矿逻辑,在下面会分析Worker 370 | -worker : Worker 371 | [methods] 372 | // 判断是否启动挖矿 373 | +IsStarted() : bool 374 | // 启动挖矿 375 | +Start(miningCtx context.Context) : chan Output, *sync.WaitGroup 376 | 377 | ▼+Scheduler : interface 378 | [methods] 379 | +IsStarted() : bool 380 | +Start(miningCtx context.Context) : chan Output, *sync.WaitGroup 381 | 382 | ▼ functions 383 | +MineOnce(ctx context.Context, w Worker, md time.Duration, ts types.TipSet) : Output, error 384 | // 实例化timingScheduler 385 | +NewScheduler(w Worker, md time.Duration, f func() types.TipSet) : Scheduler 386 | -nextNullBlkCount(prevNullBlkCount int, prevBase, currBase types.TipSet) : int 387 | ``` 388 | 389 | - 打包区块 390 | - 具体见如下注释,可对应此查阅源码。 391 | 392 | ``` 393 | location: mining/block_generate.go 394 | 395 | ▼ package 396 | mining 397 | 398 | ▶ imports 399 | 400 | ▼ DefaultWorker* : ctype 401 | [methods] 402 | // 1 如果节点没有产生过有效存储,无法参与挖矿 403 | // 2 计算区块高度= 基准Tipset高度+空块数目 404 | // 3 取出未打包消息,调用vm执行,生成收据,并更新状态 405 | // 4 打包区块信息,返回 406 | +Generate(ctx context.Context, baseTipSet types.TipSet, ticket types.Signature, proof proofs.PoStProof, nullBlockCount uint64) : *types.Block, error 407 | ``` 408 | 409 | -------------------------------------------------------------------------------- /15.md: -------------------------------------------------------------------------------- 1 | # filecoin技术架构分析之十五:filecoin源码分析之节点运行逻辑 2 | 3 | > 作者:杨尉(waynewyang),转载请注明出处 4 | 5 | 6 | ## 目录 7 | - 15.filecoin源码分析之节点运行逻辑 8 | - 15.1 前提 9 | - 15.2 filecoin节点运行逻辑简析 10 | - 15.2.1 基本数据结构 11 | - 15.2.2 创建filecoin节点实例 12 | - 15.2.3 启动及停止filecoin节点 13 | - 15.2.4 启动及停止挖矿 14 | - 15.3 阶段性分析结束说明 15 | 16 | 17 | > 分析基于的源码版本:go-filecoin master a0598a54(2019年3月9日) 18 | 19 | ## 15.1 前提 20 | 21 | - 我们在前面的章节已经经过了三个阶段的梳理分析 22 | - 概念阶段,包括概念、通用语言理解、开发网络使用 23 | - 顶层架构与概念的结合理解 24 | - 具体源码的简析,包括协议层、支撑包、内部api层、服务层 25 | 26 | - 源码部分的command部分比较容易理解,就不单独文章赘述了,基本与内部api层都可以对应起来 27 | 28 | - 现在再来看节点的运行逻辑应该会更加清晰了 29 | 30 | ## 15.2 filecoin节点运行逻辑简析 31 | 32 | ### 15.2.1 基本数据结构 33 | 34 | ``` 35 | ▼ package 36 | node 37 | 38 | ▶ imports 39 | 40 | ▼ variables 41 | +ErrNoMinerAddress 42 | -filecoinDHTProtocol : dhtprotocol.ID 43 | -log 44 | 45 | // 创建具体的filecoin节点实例 46 | ▼+Config : struct 47 | [fields] 48 | // 设置区块时间 49 | +BlockTime : time.Duration 50 | // 配置节点是否转发 51 | +IsRelay : bool 52 | // libp2p选项 53 | +Libp2pOpts : []libp2p.Option 54 | // 在离线模式下,会关闭libp2p 55 | +OfflineMode : bool 56 | // 配置资源 57 | +Repo : repo.Repo 58 | // 配置区块奖励方法 59 | +Rewarder : consensus.BlockRewarder 60 | // 配置节点时空证明校验函数 61 | +Verifier : proofs.Verifier 62 | [methods] 63 | // 创建node实例 64 | +Build(ctx context.Context) : *Node, error 65 | -buildHost(ctx context.Context, makeDHT func(host host.Host) routing.IpfsRouting, error) : host.Host, error 66 | 67 | +ConfigOpt : func(*Config) error 68 | 69 | ▼+Node : struct 70 | [fields] 71 | // 确认最新区块,本地持久化并广播 72 | +AddNewlyMinedBlock : newBlockFunc 73 | // 订阅主题"/fil/blocks" 74 | +BlockSub : pubsub.Subscription 75 | // 块服务接口 76 | +Blockstore : bstore.Blockstore 77 | // 维持相关节点连接 78 | +Bootstrapper : *net.Bootstrapper 79 | // 读取区块信息 80 | +ChainReader : chain.ReadStore 81 | // 同时协议 82 | +Consensus : consensus.Protocol 83 | // 块交换,节点间的数据交换 84 | +Exchange : exchange.Interface 85 | // new-head 主题 86 | +HeaviestTipSetCh : chan interface{} 87 | // 新区块处理请求 88 | +HeaviestTipSetHandled : func() 89 | // hello服务 90 | +HelloSvc : *hello.Handler 91 | // 消息订阅 92 | +MessageSub : pubsub.Subscription 93 | // 挖矿调度 94 | +MiningScheduler : mining.Scheduler 95 | // 消息池操作 96 | +MsgPool : *core.MessagePool 97 | // 离线模式 98 | +OfflineMode : bool 99 | +OnlineStore : *hamt.CborIpldStore 100 | // 对应libp2p中的host 101 | +PeerHost : host.Host 102 | // libp2p中的ping service 103 | +Ping : *ping.PingService 104 | // 高层api 105 | +PorcelainAPI : *porcelain.API 106 | // 功率表 107 | +PowerTable : consensus.PowerTableView 108 | // 配置资源 109 | +Repo : repo.Repo 110 | // 检索客户端 111 | +RetrievalClient : *retrieval.Client 112 | // 检索矿工 113 | +RetrievalMiner : *retrieval.Miner 114 | // 路由,libp2p 115 | +Router : routing.IpfsRouting 116 | // 存储矿工 117 | +StorageMiner : *storage.Miner 118 | // 存储客户 119 | +StorageMinerClient : *storage.Client 120 | // 链同步 121 | +Syncer : chain.Syncer 122 | // 钱包管理 123 | +Wallet : *wallet.Wallet 124 | -blockTime : time.Duration 125 | -blockservice : bserv.BlockService 126 | -cancelMining : context.CancelFunc 127 | -cancelSubscriptionsCtx : context.CancelFunc 128 | -cborStore : *hamt.CborIpldStore 129 | -host : host.Host 130 | -lookup : lookup.PeerLookupService 131 | -mining 132 | -miningCtx : context.Context 133 | -miningDoneWg : *sync.WaitGroup 134 | -sectorBuilder : sectorbuilder.SectorBuilder 135 | 136 | [methods] 137 | +BlockHeight() : *types.BlockHeight, error 138 | +BlockService() : bserv.BlockService 139 | +CborStore() : *hamt.CborIpldStore 140 | +ChainReadStore() : chain.ReadStore 141 | // 创建矿工方法 142 | +CreateMiner(ctx context.Context, accountAddr address.Address, gasPrice types.AttoFIL, gasLimit types.GasUnits, pledge uint64, pid libp2ppeer.ID, collateral *types.AttoFIL) : *address.Address, error 143 | +GetBlockTime() : time.Duration 144 | +Host() : host.Host 145 | // 节点查找方法 146 | +Lookup() : lookup.PeerLookupService 147 | +MiningSignerAddress() : address.Address 148 | +MiningTimes() : time.Duration, time.Duration 149 | // 创建新的account地址,钱包地址 150 | +NewAddress() : address.Address, error 151 | +SectorBuilder() : sectorbuilder.SectorBuilder 152 | +SetBlockTime(blockTime time.Duration) 153 | // 启动节点 154 | +Start(ctx context.Context) : error 155 | // 启动挖矿 156 | +StartMining(ctx context.Context) : error 157 | // 停止节点 158 | +Stop(ctx context.Context) 159 | // 停止挖矿 160 | +StopMining(ctx context.Context) 161 | -addNewlyMinedBlock(ctx context.Context, b *types.Block) 162 | -cancelSubscriptions() 163 | -getLastUsedSectorID(ctx context.Context, minerAddr address.Address) : uint64, error 164 | -getMinerActorPubKey() : []byte, error 165 | -handleNewHeaviestTipSet(ctx context.Context, head types.TipSet) 166 | -handleNewMiningOutput(miningOutCh chan mining.Output) 167 | -handleSubscription(ctx context.Context, f pubSubProcessorFunc, fname string, s pubsub.Subscription, sname string) 168 | -isMining() : bool 169 | -miningAddress() : address.Address, error 170 | -miningOwnerAddress(ctx context.Context, miningAddr address.Address) : address.Address, error 171 | -saveMinerConfig(minerAddr address.Address, signerAddr address.Address) : error 172 | -setIsMining(isMining bool) 173 | -setupHeartbeatServices(ctx context.Context) : error 174 | -setupMining(ctx context.Context) : error 175 | [functions] 176 | // 调用Build创建node实例 177 | +New(ctx context.Context, opts ...ConfigOpt) : *Node, error 178 | 179 | ▼-blankValidator : struct 180 | [methods] 181 | +Select(_ string, _ [][]byte) : int, error 182 | +Validate(_ string, _ []byte) : error 183 | 184 | -newBlockFunc : func(context.Context, *types.Block) 185 | 186 | -pubSubProcessorFunc : func(ctx context.Context, msg pubsub.Message) error 187 | 188 | ▼ functions 189 | +BlockTime(blockTime time.Duration) : ConfigOpt 190 | +IsRelay() : ConfigOpt 191 | +Libp2pOptions(opts ...libp2p.Option) : ConfigOpt 192 | +OfflineMode(offlineMode bool) : ConfigOpt 193 | +RewarderConfigOption(rewarder consensus.BlockRewarder) : ConfigOpt 194 | +StartMining(ctx context.Context, node *Node) : error 195 | +VerifierConfigOption(verifier proofs.Verifier) : ConfigOpt 196 | -initSectorBuilderForNode(ctx context.Context, node *Node, sectorStoreType proofs.SectorStoreType) : sectorbuilder.SectorBuilder, error 197 | -initStorageMinerForNode(ctx context.Context, node *Node) : *storage.Miner, error 198 | -readGenesisCid(ds datastore.Datastore) : cid.Cid, error 199 | ``` 200 | 201 | ### 15.2.2 创建filecoin节点实例 202 | 203 | - 实例化filecoin节点,简析见如下添加的注释 204 | 205 | ``` 206 | // Build instantiates a filecoin Node from the settings specified in the config. 207 | func (nc *Config) Build(ctx context.Context) (*Node, error) { 208 | // 创建内存资源实例 209 | if nc.Repo == nil { 210 | nc.Repo = repo.NewInMemoryRepo() 211 | } 212 | 213 | // 创建块服务实例 214 | bs := bstore.NewBlockstore(nc.Repo.Datastore()) 215 | 216 | validator := blankValidator{} 217 | 218 | var peerHost host.Host 219 | var router routing.IpfsRouting 220 | 221 | // 带宽统计实例,加入libp2popts 222 | bandwidthTracker := p2pmetrics.NewBandwidthCounter() 223 | nc.Libp2pOpts = append(nc.Libp2pOpts, libp2p.BandwidthReporter(bandwidthTracker)) 224 | 225 | // 非离线模式才启用libp2p 226 | if !nc.OfflineMode { 227 | makeDHT := func(h host.Host) (routing.IpfsRouting, error) { 228 | r, err := dht.New( 229 | ctx, 230 | h, 231 | dhtopts.Datastore(nc.Repo.Datastore()), 232 | dhtopts.NamespacedValidator("v", validator), 233 | dhtopts.Protocols(filecoinDHTProtocol), 234 | ) 235 | if err != nil { 236 | return nil, errors.Wrap(err, "failed to setup routing") 237 | } 238 | router = r 239 | return r, err 240 | } 241 | 242 | var err error 243 | // 实例化非离线模式libp2p host 244 | peerHost, err = nc.buildHost(ctx, makeDHT) 245 | if err != nil { 246 | return nil, err 247 | } 248 | } else { 249 | // 离线模式处理 250 | router = offroute.NewOfflineRouter(nc.Repo.Datastore(), validator) 251 | peerHost = rhost.Wrap(noopLibP2PHost{}, router) 252 | } 253 | 254 | // ping服务实例 255 | // set up pinger 256 | pinger := ping.NewPingService(peerHost) 257 | 258 | // bitswap实例 259 | // set up bitswap 260 | nwork := bsnet.NewFromIpfsHost(peerHost, router) 261 | //nwork := bsnet.NewFromIpfsHost(innerHost, router) 262 | bswap := bitswap.New(ctx, nwork, bs) 263 | bservice := bserv.New(bs, bswap) 264 | 265 | cstOnline := hamt.CborIpldStore{Blocks: bservice} 266 | cstOffline := hamt.CborIpldStore{Blocks: bserv.New(bs, offline.Exchange(bs))} 267 | // 获取创世块cid 268 | genCid, err := readGenesisCid(nc.Repo.Datastore()) 269 | if err != nil { 270 | return nil, err 271 | } 272 | 273 | // chain.Store实例以及功率表 274 | var chainStore chain.Store = chain.NewDefaultStore(nc.Repo.ChainDatastore(), &cstOffline, genCid) 275 | powerTable := &consensus.MarketView{} 276 | 277 | // 共识协议processor实例 278 | var processor consensus.Processor 279 | if nc.Rewarder == nil { 280 | processor = consensus.NewDefaultProcessor() 281 | } else { 282 | processor = consensus.NewConfiguredProcessor(consensus.NewDefaultMessageValidator(), nc.Rewarder) 283 | } 284 | 285 | // 共识协议实例 286 | var nodeConsensus consensus.Protocol 287 | if nc.Verifier == nil { 288 | nodeConsensus = consensus.NewExpected(&cstOffline, bs, processor, powerTable, genCid, &proofs.RustVerifier{}) 289 | } else { 290 | nodeConsensus = consensus.NewExpected(&cstOffline, bs, processor, powerTable, genCid, nc.Verifier) 291 | } 292 | 293 | // 链同步,链读取,消息池实例 294 | // only the syncer gets the storage which is online connected 295 | chainSyncer := chain.NewDefaultSyncer(&cstOnline, &cstOffline, nodeConsensus, chainStore) 296 | chainReader, ok := chainStore.(chain.ReadStore) 297 | if !ok { 298 | return nil, errors.New("failed to cast chain.Store to chain.ReadStore") 299 | } 300 | msgPool := core.NewMessagePool() 301 | 302 | // Set up libp2p pubsub 303 | fsub, err := libp2pps.NewFloodSub(ctx, peerHost) 304 | if err != nil { 305 | return nil, errors.Wrap(err, "failed to set up pubsub") 306 | } 307 | 308 | // 钱包服务实例 309 | backend, err := wallet.NewDSBackend(nc.Repo.WalletDatastore()) 310 | if err != nil { 311 | return nil, errors.Wrap(err, "failed to set up wallet backend") 312 | } 313 | fcWallet := wallet.New(backend) 314 | 315 | // 实例化高层api 316 | PorcelainAPI := porcelain.New(plumbing.New(&plumbing.APIDeps{ 317 | Chain: chainReader, 318 | Config: cfg.NewConfig(nc.Repo), 319 | Deals: strgdls.New(nc.Repo.DealsDatastore()), 320 | MsgPool: msgPool, 321 | MsgPreviewer: msg.NewPreviewer(fcWallet, chainReader, &cstOffline, bs), 322 | MsgQueryer: msg.NewQueryer(nc.Repo, fcWallet, chainReader, &cstOffline, bs), 323 | MsgSender: msg.NewSender(fcWallet, chainReader, msgPool, consensus.NewOutboundMessageValidator(), fsub.Publish), 324 | MsgWaiter: msg.NewWaiter(chainReader, bs, &cstOffline), 325 | Network: net.New(peerHost, pubsub.NewPublisher(fsub), pubsub.NewSubscriber(fsub), net.NewRouter(router), bandwidthTracker), 326 | SigGetter: mthdsig.NewGetter(chainReader), 327 | Wallet: fcWallet, 328 | })) 329 | 330 | // 实例化node 331 | nd := &Node{ 332 | blockservice: bservice, 333 | Blockstore: bs, 334 | cborStore: &cstOffline, 335 | OnlineStore: &cstOnline, 336 | Consensus: nodeConsensus, 337 | ChainReader: chainReader, 338 | Syncer: chainSyncer, 339 | PowerTable: powerTable, 340 | PorcelainAPI: PorcelainAPI, 341 | Exchange: bswap, 342 | host: peerHost, 343 | MsgPool: msgPool, 344 | OfflineMode: nc.OfflineMode, 345 | PeerHost: peerHost, 346 | Ping: pinger, 347 | Repo: nc.Repo, 348 | Wallet: fcWallet, 349 | blockTime: nc.BlockTime, 350 | Router: router, 351 | } 352 | 353 | // Bootstrapping network peers. 354 | periodStr := nd.Repo.Config().Bootstrap.Period 355 | period, err := time.ParseDuration(periodStr) 356 | if err != nil { 357 | return nil, errors.Wrapf(err, "couldn't parse bootstrap period %s", periodStr) 358 | } 359 | 360 | // 实例化Bootstrapper,指定node的该方法 361 | // Bootstrapper maintains connections to some subset of addresses 362 | ba := nd.Repo.Config().Bootstrap.Addresses 363 | bpi, err := net.PeerAddrsToPeerInfos(ba) 364 | if err != nil { 365 | return nil, errors.Wrapf(err, "couldn't parse bootstrap addresses [%s]", ba) 366 | } 367 | minPeerThreshold := nd.Repo.Config().Bootstrap.MinPeerThreshold 368 | nd.Bootstrapper = net.NewBootstrapper(bpi, nd.Host(), nd.Host().Network(), nd.Router, minPeerThreshold, period) 369 | 370 | // 实例化链查找服务,指定node的该方法 371 | // On-chain lookup service 372 | defaultAddressGetter := func() (address.Address, error) { 373 | return nd.PorcelainAPI.GetAndMaybeSetDefaultSenderAddress() 374 | } 375 | nd.lookup = lookup.NewChainLookupService(nd.ChainReader, defaultAddressGetter, bs) 376 | 377 | return nd, nil 378 | } 379 | ``` 380 | 381 | ### 15.2.3 启动及停止filecoin节点 382 | 383 | - 启动filecoin节点的流程概览 384 | 385 | ``` 386 | // Start boots up the node. 387 | func (node *Node) Start(ctx context.Context) error { 388 | // 加载本地chain信息 389 | if err := node.ChainReader.Load(ctx); err != nil { 390 | return err 391 | } 392 | 393 | // 如果存在存储矿工,配置挖矿功能 394 | // Only set these up if there is a miner configured. 395 | if _, err := node.miningAddress(); err == nil { 396 | if err := node.setupMining(ctx); err != nil { 397 | log.Errorf("setup mining failed: %v", err) 398 | return err 399 | } 400 | } 401 | 402 | // 设置链同步回调函数 403 | // Start up 'hello' handshake service 404 | syncCallBack := func(pid libp2ppeer.ID, cids []cid.Cid, height uint64) { 405 | // TODO it is possible the syncer interface should be modified to 406 | // make use of the additional context not used here (from addr + height). 407 | // To keep things simple for now this info is not used. 408 | err := node.Syncer.HandleNewBlocks(context.Background(), cids) 409 | if err != nil { 410 | log.Infof("error handling blocks: %s", types.NewSortedCidSet(cids...).String()) 411 | } 412 | } 413 | // 实例化hello握手协议 414 | node.HelloSvc = hello.New(node.Host(), node.ChainReader.GenesisCid(), syncCallBack, node.ChainReader.Head) 415 | 416 | // 实例化存储矿工协议 417 | cni := storage.NewClientNodeImpl(dag.NewDAGService(node.BlockService()), node.Host(), node.GetBlockTime()) 418 | var err error 419 | node.StorageMinerClient, err = storage.NewClient(cni, node.PorcelainAPI) 420 | if err != nil { 421 | return errors.Wrap(err, "Could not make new storage client") 422 | } 423 | 424 | // 实例化检索客户及检索矿工协议 425 | node.RetrievalClient = retrieval.NewClient(node) 426 | node.RetrievalMiner = retrieval.NewMiner(node) 427 | 428 | // 订阅区块通知 429 | // subscribe to block notifications 430 | blkSub, err := node.PorcelainAPI.PubSubSubscribe(BlockTopic) 431 | if err != nil { 432 | return errors.Wrap(err, "failed to subscribe to blocks topic") 433 | } 434 | node.BlockSub = blkSub 435 | 436 | // 订阅消息通知 437 | // subscribe to message notifications 438 | msgSub, err := node.PorcelainAPI.PubSubSubscribe(msg.Topic) 439 | if err != nil { 440 | return errors.Wrap(err, "failed to subscribe to message topic") 441 | } 442 | node.MessageSub = msgSub 443 | 444 | cctx, cancel := context.WithCancel(context.Background()) 445 | node.cancelSubscriptionsCtx = cancel 446 | 447 | // 启用新线程订阅区块及消息主题,设置handle回调 448 | go node.handleSubscription(cctx, node.processBlock, "processBlock", node.BlockSub, "BlockSub") 449 | go node.handleSubscription(cctx, node.processMessage, "processMessage", node.MessageSub, "MessageSub") 450 | 451 | // 启用新线程处理新的tipset事件 452 | node.HeaviestTipSetHandled = func() {} 453 | node.HeaviestTipSetCh = node.ChainReader.HeadEvents().Sub(chain.NewHeadTopic) 454 | go node.handleNewHeaviestTipSet(cctx, node.ChainReader.Head()) 455 | 456 | // 非离线模式启动bootstapper服务 457 | if !node.OfflineMode { 458 | node.Bootstrapper.Start(context.Background()) 459 | } 460 | 461 | // 启动心跳服务 462 | if err := node.setupHeartbeatServices(ctx); err != nil { 463 | return errors.Wrap(err, "failed to start heartbeat services") 464 | } 465 | 466 | return nil 467 | } 468 | ``` 469 | 470 | - 停止filecoin节点的流程概览 471 | 472 | > 释放资源,停止相关服务 473 | 474 | ``` 475 | // Stop initiates the shutdown of the node. 476 | func (node *Node) Stop(ctx context.Context) { 477 | node.ChainReader.HeadEvents().Unsub(node.HeaviestTipSetCh) 478 | // 停止挖矿 479 | node.StopMining(ctx) 480 | 481 | // 取消订阅 482 | node.cancelSubscriptions() 483 | // 停止链读取服务 484 | node.ChainReader.Stop() 485 | 486 | // 停止密封服务 487 | if node.SectorBuilder() != nil { 488 | if err := node.SectorBuilder().Close(); err != nil { 489 | fmt.Printf("error closing sector builder: %s\n", err) 490 | } 491 | node.sectorBuilder = nil 492 | } 493 | 494 | // 关闭host实例 495 | if err := node.Host().Close(); err != nil { 496 | fmt.Printf("error closing host: %s\n", err) 497 | } 498 | 499 | // 关闭资源实例 500 | if err := node.Repo.Close(); err != nil { 501 | fmt.Printf("error closing repo: %s\n", err) 502 | } 503 | 504 | // 关闭bootstqpper实例 505 | node.Bootstrapper.Stop() 506 | 507 | fmt.Println("stopping filecoin :(") 508 | } 509 | ``` 510 | 511 | 512 | ### 15.2.4 启动及停止挖矿 513 | 514 | - 启动挖矿 515 | 516 | ``` 517 | // StartMining causes the node to start feeding blocks to the mining worker and initializes 518 | // the SectorBuilder for the mining address. 519 | func (node *Node) StartMining(ctx context.Context) error { 520 | // 如果在挖矿中,退出 521 | if node.isMining() { 522 | return errors.New("Node is already mining") 523 | } 524 | // 获取矿工地址 525 | minerAddr, err := node.miningAddress() 526 | if err != nil { 527 | return errors.Wrap(err, "failed to get mining address") 528 | } 529 | 530 | // 确保密封服务实例存在 531 | // ensure we have a sector builder 532 | if node.SectorBuilder() == nil { 533 | if err := node.setupMining(ctx); err != nil { 534 | return err 535 | } 536 | } 537 | 538 | // 获取地址 539 | minerOwnerAddr, err := node.miningOwnerAddress(ctx, minerAddr) 540 | minerSigningAddress := node.MiningSignerAddress() 541 | if err != nil { 542 | return errors.Wrapf(err, "failed to get mining owner address for miner %s", minerAddr) 543 | } 544 | 545 | blockTime, mineDelay := node.MiningTimes() 546 | 547 | // 实例化挖矿调度服务 548 | if node.MiningScheduler == nil { 549 | getStateFromKey := func(ctx context.Context, tsKey string) (state.Tree, error) { 550 | tsas, err := node.ChainReader.GetTipSetAndState(ctx, tsKey) 551 | if err != nil { 552 | return nil, err 553 | } 554 | return state.LoadStateTree(ctx, node.CborStore(), tsas.TipSetStateRoot, builtin.Actors) 555 | } 556 | getState := func(ctx context.Context, ts types.TipSet) (state.Tree, error) { 557 | return getStateFromKey(ctx, ts.String()) 558 | } 559 | getWeight := func(ctx context.Context, ts types.TipSet) (uint64, error) { 560 | parent, err := ts.Parents() 561 | if err != nil { 562 | return uint64(0), err 563 | } 564 | // TODO handle genesis cid more gracefully 565 | if parent.Len() == 0 { 566 | return node.Consensus.Weight(ctx, ts, nil) 567 | } 568 | pSt, err := getStateFromKey(ctx, parent.String()) 569 | if err != nil { 570 | return uint64(0), err 571 | } 572 | return node.Consensus.Weight(ctx, ts, pSt) 573 | } 574 | getAncestors := func(ctx context.Context, ts types.TipSet, newBlockHeight *types.BlockHeight) ([]types.TipSet, error) { 575 | return chain.GetRecentAncestors(ctx, ts, node.ChainReader, newBlockHeight, consensus.AncestorRoundsNeeded, consensus.LookBackParameter) 576 | } 577 | processor := consensus.NewDefaultProcessor() 578 | worker := mining.NewDefaultWorker(node.MsgPool, getState, getWeight, getAncestors, processor, node.PowerTable, 579 | node.Blockstore, node.CborStore(), minerAddr, minerOwnerAddr, minerSigningAddress, node.Wallet, blockTime) 580 | node.MiningScheduler = mining.NewScheduler(worker, mineDelay, node.ChainReader.Head) 581 | } 582 | 583 | // paranoid check 584 | // 启动挖矿服务 585 | if !node.MiningScheduler.IsStarted() { 586 | node.miningCtx, node.cancelMining = context.WithCancel(context.Background()) 587 | outCh, doneWg := node.MiningScheduler.Start(node.miningCtx) 588 | 589 | node.miningDoneWg = doneWg 590 | node.AddNewlyMinedBlock = node.addNewlyMinedBlock 591 | node.miningDoneWg.Add(1) 592 | go node.handleNewMiningOutput(outCh) 593 | } 594 | 595 | // initialize a storage miner 596 | // 初始化存储矿工 597 | storageMiner, err := initStorageMinerForNode(ctx, node) 598 | if err != nil { 599 | return errors.Wrap(err, "failed to initialize storage miner") 600 | } 601 | node.StorageMiner = storageMiner 602 | 603 | // loop, turning sealing-results into commitSector messages to be included 604 | // in the chain 605 | // 新开线程处理,1 密封完成处理;2 接受停止挖矿消息 606 | go func() { 607 | for { 608 | select { 609 | // 密封完成处理 610 | case result := <-node.SectorBuilder().SectorSealResults(): 611 | if result.SealingErr != nil { 612 | log.Errorf("failed to seal sector with id %d: %s", result.SectorID, result.SealingErr.Error()) 613 | } else if result.SealingResult != nil { 614 | 615 | // TODO: determine these algorithmically by simulating call and querying historical prices 616 | gasPrice := types.NewGasPrice(0) 617 | gasUnits := types.NewGasUnits(300) 618 | 619 | val := result.SealingResult 620 | // This call can fail due to, e.g. nonce collisions. Our miners existence depends on this. 621 | // We should deal with this, but MessageSendWithRetry is problematic. 622 | _, err := node.PorcelainAPI.MessageSend( 623 | node.miningCtx, 624 | minerOwnerAddr, 625 | minerAddr, 626 | nil, 627 | gasPrice, 628 | gasUnits, 629 | "commitSector", 630 | val.SectorID, 631 | val.CommD[:], 632 | val.CommR[:], 633 | val.CommRStar[:], 634 | val.Proof[:], 635 | ) 636 | if err != nil { 637 | log.Errorf("failed to send commitSector message from %s to %s for sector with id %d: %s", minerOwnerAddr, minerAddr, val.SectorID, err) 638 | continue 639 | } 640 | 641 | node.StorageMiner.OnCommitmentAddedToChain(val, nil) 642 | } 643 | // 挖矿取消 644 | case <-node.miningCtx.Done(): 645 | return 646 | } 647 | } 648 | }() 649 | 650 | // schedules sealing of staged piece-data 651 | // 定时密封阶段性的碎片数据 652 | if node.Repo.Config().Mining.AutoSealIntervalSeconds > 0 { 653 | go func() { 654 | for { 655 | select { 656 | // 取消 657 | case <-node.miningCtx.Done(): 658 | return 659 | // 定时密封 660 | case <-time.After(time.Duration(node.Repo.Config().Mining.AutoSealIntervalSeconds) * time.Second): 661 | log.Info("auto-seal has been triggered") 662 | if err := node.SectorBuilder().SealAllStagedSectors(node.miningCtx); err != nil { 663 | log.Errorf("scheduler received error from node.SectorBuilder.SealAllStagedSectors (%s) - exiting", err.Error()) 664 | return 665 | } 666 | } 667 | } 668 | }() 669 | } else { 670 | log.Debug("auto-seal is disabled") 671 | } 672 | // 设置微挖矿状态 673 | node.setIsMining(true) 674 | 675 | return nil 676 | } 677 | ``` 678 | 679 | - 停止挖矿 680 | 681 | ``` 682 | // StopMining stops mining on new blocks. 683 | func (node *Node) StopMining(ctx context.Context) { 684 | node.setIsMining(false) 685 | 686 | // 取消挖矿 687 | if node.cancelMining != nil { 688 | node.cancelMining() 689 | } 690 | 691 | // 等待执行中的挖矿任务完成后结束 692 | if node.miningDoneWg != nil { 693 | node.miningDoneWg.Wait() 694 | } 695 | 696 | // TODO: stop node.StorageMiner 697 | } 698 | ``` 699 | 700 | ## 15.3 阶段性分析结束说明 701 | 702 | > 至此笔者针对go-filecoin部分的分析快告一个小的段落了 703 | 704 | > 文章因为时间的关系,书面出来只是将关键部分书面表达出来,更多的像是笔者的一个分析笔记,但是我相信对于想分析源码的朋友有一定帮助 705 | 706 | > 后面会抽空补充一章总结,笔者在第4章中有提到过,薄读->厚读->再薄读,我们还需要一次薄读,来加深我们对filecoin的认识。 707 | 708 | -------------------------------------------------------------------------------- /2.md: -------------------------------------------------------------------------------- 1 | # [先河系统杨尉] filecoin技术架构分析之二:filecoin通用语言理解 2 | 3 | > 作者:杨尉(waynewyang),转载请注明出处 4 | 5 | ## 目录 6 | - [2 filecoin通用语言理解](#filecoin通用语言理解) 7 | - [2.1 为什么要把filecoin通用语言单独列为一讲](#为什么要把filecoin通用语言单独列为一讲) 8 | - [2.2 存储证明](#存储证明) 9 | - [2.2.1 为什么使用存储证明](#为什么使用存储证明) 10 | - [2.2.2 复制证明](#复制证明) 11 | - [2.2.3 时空证明](#时空证明) 12 | - [2.3 预期共识](#预期共识) 13 | - [2.3.1 基础前提](#基础前提) 14 | - [2.3.2 使用power达成共识](#使用power达成共识) 15 | - [2.3.3 选举方案](#选举方案) 16 | - [2.4 filecoin智能合约](#filecoin智能合约) 17 | - [2.4.1 文件合约](#文件合约) 18 | - [2.4.2 智能合约](#智能合约) 19 | - [2.4.3 与其他系统的兼容](#与其他系统的兼容) 20 | - [2.5 交易市场](#交易市场) 21 | - [2.4.1 存储市场](#存储市场) 22 | - [2.4.2 检索市场](#检索市场) 23 | - [2.6 filecoin节点](#filecoin节点) 24 | 25 | ## 2.1 为什么要把filecoin通用语言单独列为一讲 26 | 27 | - 笔者认为一位优秀的软件从业人员,必须具备两种必备能力 28 | - 架构设计能力 29 | - 架构剖析能力 30 | 31 | > 这两者是相辅相成的,架构设计师所设计之架构不可能超过自己的认知范畴,故架构设计师必须有效高效地拓展自己的技术认知视图,以适应当代软件架构高速发展的现实。而拓展的途径,一方面就是相关理论体系的快速学习跟进;另一方面,就是实战,对有显著价值的优秀软件项目进行架构剖析。有理论、有实战方是王道。理解具体架构的通用语言就是分析他人架构设计思维的一条捷径。 32 | 33 | 34 | - 理解具体技术架构的通用语言是分析架构的一条捷径 35 | - 通用语言是架构设计人员为实现某个具体技术架构,所高度抽象出来的名词或者称谓,通过理解通用语言,可以快速理解架构设计人员的思维和设计目的。 36 | - 与读书方法类似(薄读->厚读>再薄读),理解通用语言就是第一次的薄读过程,非常重要。 37 | 38 | 39 | - 对业务的理解是非常有必要的,所以在[第一讲](https://www.jianshu.com/p/97b454d81b6e)中,笔者铺垫了filecoin的一些基本概念,任何架构的设计不能脱离业务而行,业务驱动开发仍是非常实用的架构模式;filecoin 技术架构从业务来划分,可划分为两个大的范畴。 40 | - 分布式存储解决方案 41 | - 存储矿工 42 | - 检索矿工 43 | - 存储客户端 44 | - 检索客户端 45 | - 区块链项目 46 | - filecoin公链 47 | - filecoin actors 智能合约 48 | 49 | - filecoin核心通用业务组件 50 | 51 | |组件名称| 目的 | 52 | |:-------:|:--------:| 53 | |DSN |保障数据安全、包括故障容错、数据完整性、数据可恢复等| 54 | |新型存储证明 | 证明矿工按照协议规范存储了客户指定的数据,数据有效性| 55 | |可验证市场 |对矿工与客户组成的交易市场进行了建模,保证交易的有效性 | 56 | |有效工作量证明 |出块的共识机制,很重要,做到激励兼容 | 57 | 58 | **下面各节将会对filecoin技术架构中的核心通用语言进行解释。** 59 | 60 | - [回到目录](#目录) 61 | 62 | ## 2.2 存储证明 63 | Proof-of-Storage包含复制证明(PoR)和时空证明(PoSt),其作用主要有两点: 64 | - 证明矿工做了有效存储 65 | 66 | - 竞争区块打包出块,获取区块奖励 67 | 68 | - [回到目录](#目录) 69 | 70 | ### 2.2.1 为什么使用存储证明 71 | - 相对于PoW(Proof-of-Work)或者PoC 72 | - PoW耗能严重;PoC以空间换时间,同样存在耗能严重问题 73 | - 而filecoin网络的耗能必须远低于类似比特币的PoW,参见[第一讲](https://www.jianshu.com/p/97b454d81b6e)filecoin的对标对象,filecoin必须实现以更低的成对去应对商业竞争,同时提供相同级别的安全性,以及文件存储的效用 74 | - 存储证明需要做要与实体经济挂钩,减少无谓浪费 75 | 76 | - 相对于PoS(Proof-of-Stake)或者PoC 77 | 78 | - Proof-of-Storage在定向领域(分布式存储)以更简单方式,协调激励,并驱使矿工以有竞争力的价格提供真实的新存储,它促使矿工积极保证filecoin网络的效用 79 | - 当然Proof-of-Stake是区块链领域的热点研究问题 80 | 81 | - Proof-of-Storage阻止网络攻击 82 | 83 | |攻击类型| 说明 |阻止攻击原理| 84 | |:-------:|:--------:|----------| 85 | |女巫攻击Sybil attack| 作恶节点创造多个女巫身份,谎称存储了多个副本|每个节点的副本都是有签名的,想通过复制证明,就相当于真实做了有效存储| 86 | |外包攻击outsourcing attacks| 作恶节点快速从其他节点获取内容,谎称他们存储了比他们实际存储更多的内容|针对外包攻击,从其他节点获取的整个过程,满足不了证明人随机挑战的要求,依然需要重新生成副本(重新seal需要时间),从而阻止外包攻击| 87 | |生成攻击generation attacks|作恶节点宣称将要存储超过其实际容量的内容但并未存储内容,以此增加出块的概率 |宣称无用,存储证明一定要确认密封动作并能应对随机挑战才能OK,如果重新密封就来不及证明,每次挑战是有时间要求的| 88 | 89 | - [回到目录](#目录) 90 | 91 | ### 2.2.2 复制证明 92 | #### 2.2.2.1 基础 93 | - 复制证明本质上可以理解为一种零知识证明,既然是零知识证明,我们在后面需要理解filecoin复制证明的题目和答案 94 | 95 | > zk-SNARK zero knowledge Succinct Non-interactive ARgument of Knowledge 96 | > zero knowledge:零知识,即在证明的过程中不透露任何内情 97 | > succinct:简洁的,主要是指验证过程不涉及大量数据传输以及验证算法简单 98 | > non-interactive:无交互。 99 | 100 | 101 | 102 | - 生成证明的方法在filecoin架构中称之为seal密封 103 | 104 | > 密封过程是需要时间的,Seal过程串行加密的过程,无法并行操作,seal密封过程是有意设计慢的,主要目的是为了防攻击。 105 | 106 | 107 | - [回到目录](#目录) 108 | 109 | 110 | #### 2.2.2.2 filecoin复制证明的题目和答案 111 | - 公开的信息 112 | 113 | - 矿工的节点公钥、密封公钥、存储公钥、原始Data哈希、该矿工存储的副本根哈希 114 | 115 | - 隐含因素理解: 116 | - 特有节点的副本哈希是由哪些哈希组成(DAG),任意挑战者或者攻击者是不知情的 117 | - 挑战随机参数,通过CRH(防碰撞的哈希散列Collision-resistant hashing)生成哈希之后传递给证明者,作用是确定特定的叶子节点的哈希,比如让证明者自行计算离H(c))最近的叶子节点哈希。 118 | 119 | - 复制证明的题目与答案 120 | - 挑战参数:副本哈希rt,挑战随机参数c -> H(c) 121 | - 证明者输入(题目): 122 | - H(c)(每一次挑战都会变) 123 | - 隐含信息比喻:该叶子节点是与H(c)最近的节点 124 | - 证明者输出(答案): 125 | - H(c)对应的叶子节点 ——> rt的路径(攻击者是很难反推的) 126 | 127 | 128 | - [回到目录](#目录) 129 | 130 | ### 2.2.3 时空证明 131 | 132 | - 时空证明可以理解为矿工持续性地生成复制证明 133 | - 挑战者输入一个随机参数c,后面的随机参数由证明者基于上一个的挑战答案去生成。(不用与挑战者持续交互) 134 | - 下图中变量i会轮询生成新的时间变量产生随机挑战。 135 | 136 | 137 | ![](img/post.png) 138 | 139 | 140 | - [回到目录](#目录) 141 | 142 | ## 2.3 预期共识 143 | ### 2.3.1 基础前提 144 | - filecoin基于存储证明(有效存储量)来作为矿工在整个网络中的power 145 | 146 | |power属性|说明| 147 | |:------:|:------:| 148 | |公开|1 某一时刻,整个网络存储总量是公开的
2 单个矿工某一时刻,有效存储总量是公开的| 149 | |可公开验证的|对于每个存储任务,矿工都需要生成”时空证明“,证明持续提供服务。通过读取区块链,任何人都可以验证矿工的power声明是否是正确的。| 150 | |变化|在任意时间点,矿工都可以通过增加新增扇区和扇区补充的抵押来增加新的存储。这样矿工就能变更他们能提供的power。| 151 | 152 | - [回到目录](#目录) 153 | 154 | #### 2.3.2 使用power达成共识 155 | - 目的: 156 | > 每一轮选举一个(或多个)矿工,**使得赢得选举的概率与每个矿工分配的存储成比例** 157 | 158 | - filecoin预期共识(Expected Consensus,EC) 159 | - 预期共识的基本直觉是确定性的,不可预测的 160 | - 预期的期望是每个周期内当选的Leader是1,但一些周期内可能有0个或者许多的Leader。 161 | - 在每个周期,每个区块链被延伸一个或多个区块,见下图 162 | - 区块线性扩展,但是数据结构是DAG 163 | - EC是一个概率共识,每个周期都使得比前面的区块更加确定,最终达到了足够的确定性 164 | 165 | 166 | - [回到目录](#目录) 167 | 168 | #### 2.3.3 选举方案 169 | 170 | > 预期共识通过选举方案产生 171 | 172 | ![](img/sle.png) 173 | 174 | ![](img/sle2.png) 175 | 176 | 177 | |选举方案属性|说明| 178 | |:------:|:------:| 179 | |公平|每个参与者每次选举只有一次试验,因为签名是确定性的,而且t和rand(t)是固定的。随机值rand(t)在时刻t之前是未知的
| 180 | |保密|由于有能力的攻击者不拥有Mi用来计算签名的秘钥
| 181 | |公开可验证|当选Leader i ∈ Lt 可以通过给出t,rand(t),H(i)/2L,来说服一个有效的验证者。鉴于前面的观点(复制证明与时间证明),有能力的攻击者在不拥有获胜秘密秘钥的情况下不能生成证明。| 182 | 183 | - [回到目录](#目录) 184 | 185 | ## 2.4 filecoin智能合约 186 | ### 2.4.1 文件合约 187 | 188 | > 允许用户对他们提供的存储服务进行条件编程,**会形成一个多样化市场**。 189 | 190 | - 承包矿工:客户可以提前指定矿工提供服务而不参与市场 191 | - 付款策略:客户可以为矿工设计不同的奖励策略,例如合约可以给矿工支付随着时间的推移越来高的费用 192 | - 票务服务:合约可以允许矿工存放token和用于代表用户的存储/检索的支付 193 | - 更复杂的操作:客户可以创建合约来运行数据更新 194 | 195 | - [回到目录](#目录) 196 | 197 | ### 2.4.2 智能合约 198 | > 用户可以将程序关联到其他系统(如以太坊)的交易上,他们不直接依赖存储的使用。 199 | 200 | - 回到[目录](#目录) 201 | 202 | ### 2.4.3 与其他系统的兼容 203 | > 规格支持跨链交互,以便能将filecoin存储带入其他基于区块链的平台,同时也将其他平台的功能带入filecoin。 204 | 205 | - [回到目录](#目录) 206 | 207 | ## 2.5 交易市场 208 | - 存储需求和供给组成了两个Filecoin市场:存储市场和检索市场。这两个市场是两个去中心化交易所,简而言之,客户和矿工们通过向各自的市场提交订单来设定他们请求服务或者提供服务的订单的价格。交易所为客户和矿工们提供了一种方式来查看匹配出价并执行订单。如果服务请求被成功满足,通过运行管理协议,网络保证了矿工得到报酬,客户将被收取费用。 209 | - 可以类比为淘宝商城 210 | 211 | 212 | 213 | - [回到目录](#目录) 214 | 215 | ### 2.5.1 存储市场 216 | 217 | - 交易数据会上链,包含于区块之中。 218 | - 本质上也属于filecoin智能合约中的文件合约。 219 | - 20190214上线的开发网络已支持 220 | 221 | 222 | - [回到目录](#目录) 223 | 224 | ### 2.5.2 检索市场 225 | 226 | - 交易数据不会上链,属于offchain的方式。 227 | - 本质上也属于filecoin智能合约中的文件合约。 228 | 229 | 230 | - [回到目录](#目录) 231 | 232 | ## 2.6 filecoin节点 233 | - filecoin节点相关 234 | - node id表示filecoin网络节点 235 | - account id并表示账号,默认与钱包地址一致 236 | - wallet addr表示钱包地址 237 | - miner id表示矿工id 238 | 239 | ![](img/filecoin-1.png) 240 | 241 | - [回到目录](#目录) 242 | -------------------------------------------------------------------------------- /3.md: -------------------------------------------------------------------------------- 1 | # filecoin技术架构分析之三:filecoin开发网络使用 2 | 3 | > 作者:杨尉(waynewyang),转载请注明出处 4 | 5 | ## 目录 6 | - [3 filecoin开发网使用](#filecoin开发网使用) 7 | - [3.1 辅助资源](#辅助资源) 8 | - [3.2 使用](#使用) 9 | - [3.2.1 接入filecoin开发网络](#接入filecoin开发网络) 10 | - [3.2.2 获取Mock FIL用于测试](#从faucet中获取mock代币) 11 | - [3.2.3 矿工操作](#矿工操作) 12 | - [3.2.3.1 存储矿工](#存储矿工) 13 | - [3.2.3.2 检索矿工](#检索矿工) 14 | - [3.2.3.3 修复矿工](#修复矿工) 15 | - [3.2.4 客户操作](#客户操作) 16 | - [3.2.4.1 存储客户](#存储客户) 17 | - [3.2.4.2 检索客户](#检索客户) 18 | - [3.2.5 filecoin合约](#filecoin合约) 19 | - [3.2.5.1 文件合约](#文件合约) 20 | - [3.2.5.2 智能合约](#智能合约) 21 | - [3.2.6 单机运行多个filecoin节点](#单机运行多个filecoin节点) 22 | - [3.2.6.1 修改资源目录和服务端口的方式](#修改资源目录和服务端口的方式) 23 | - [3.2.6.2 容器部署方式](#容器部署方式) 24 | 25 | ## 3 filecoin开发网络使用 26 | ### 3.1 辅助资源 27 | 28 | - Filecoin状态: https://stats.kittyhawk.wtf 29 | - 网络 30 | - 存储实时价格 FIL/GB/Month 31 | - 当前存储容量 GB 32 | - 当前网络利用率 33 | - 检索平均价格 34 | - 激活节点以及分布图 35 | - 存储平均价格曲线 36 | - best tipset 37 | - 存储矿工 38 | - 存储矿工数量变化曲线 39 | - 存储矿工共识结果 40 | - 近30天的矿工top图 41 | - 检索矿工 42 | - 平均检索价格 43 | - 平均检索时间 44 | - 平均检索容量 45 | - FIL指数 46 | - 流通FIL及抵押FIL变化图 47 | - FIL地址总数 48 | - FIL总抵押数及对应存储空间 49 | - FIL总数上升曲线图 50 | - FIL区块奖励下降曲线图 51 | 52 | - Filecoin区块浏览器: http://user.kittyhawk.wtf:8000 53 | - Chain信息 54 | - BestBlock信息 55 | - Actor合约信息 56 | 57 | - 获取FIL用于抵押或支付:http://user.kittyhawk.wtf:9797 58 | 59 | - 获取mock FIL代币 60 | 61 | - Dashboard: http://user.kittyhawk.wtf:8010 62 | 63 | - Network概览,最新区块信息 64 | - 区块浏览器链接 65 | 66 | - Genesis File: http://user.kittyhawk.wtf:8020/genesis.car 67 | 68 | - 创始文件,用于初始化filecoin资源 69 | 70 | - Prometheus Endpoint: http://user.kittyhawk.wtf:9082/metrics 71 | 72 | - 一些技术指标,比如内存、进程、线程等 73 | 74 | - Connected Nodes PeerID's: http://user.kittyhawk.wtf:9082/nodes 75 | 76 | - 连接的节点信息 77 | 78 | 79 | ### 3.2 使用 80 | #### 3.2.1 接入filecoin开发网络 81 | - 初始化filecoin资源目录 82 | 83 | >如果之前有运行过filecoin,想重新开始,需要删除filecoin资源,同时重新初始化是需要重新花时间同步开发网区块信息的。 84 | 85 | ``` 86 | rm -rf ~/.filecoin 87 | ``` 88 | 89 | > 初始化资源目录,使用--devnet-user表示连接至开发网 90 | 91 | ``` 92 | waynewyang:Downloads waynewyang$ go-filecoin init --devnet-user --genesisfile=http://user.kittyhawk.wtf:8020/genesis.car 93 | initializing filecoin node at ~/.filecoin 94 | waynewyang:Downloads waynewyang$ 95 | ``` 96 | 97 | - 启动filecoin进程,接入开发网 98 | ``` 99 | go-filecoin daemon 100 | 101 | //如果开发者,需要接入nightly devnet,请设置环境变量后启动filecoin 102 | env FIL_USE_SMALL_SECTORS=true go-filecoin daemon 103 | ``` 104 | 105 | - 检查连接性 106 | 107 | > go-filecoin swarm peers 查看已经连接的节点 108 | 109 | ``` 110 | waynewyang:filecoin waynewyang$ go-filecoin swarm peers 111 | /ip4/115.238.154.84/tcp/19109/ipfs/Qmb6ZYi7GLFAje3UekGZ2LZymck7RVHKSKb1bhPzzPTQkm 112 | /ip4/115.238.154.84/tcp/41187/ipfs/QmZ9UHdU2fwDN7emWW8AeaUdkF9fT7RwJrnbbdcQFUq9X6 113 | /ip4/123.134.67.81/tcp/6000/ipfs/QmccrEQsauwge4BZQeN1jBtFyd7dnTi4pSDvkikMWaFccw 114 | /ip4/123.134.67.82/tcp/6000/ipfs/QmWuA1AW4qDqztDrwo2pBgT2au67BJbGtEzWRufbc8isgn 115 | /ip4/123.134.67.83/tcp/6000/ipfs/QmbPCabGcngs3bCgMK8dC3w9pjoyPd1NFyDhbkgLyT2eJ7 116 | /ip4/123.134.67.85/tcp/6000/ipfs/QmUqSSZrwfSUU3vfw7D1UyKaLvEv1Ykcvx3ntvSXWaA7kj 117 | /ip4/123.134.67.86/tcp/6000/ipfs/QmPrz2z764AVaHivM7iX2JqRw5EdE3jcZTrjwVxS4VukyK 118 | /ip4/123.134.67.87/tcp/6000/ipfs/QmTxVFq3u7qPxsXFQdoyqPrdh6meW6JBGkSJ8HJXAiMUfh 119 | /ip4/123.134.67.88/tcp/6000/ipfs/QmXAVRPYu57XDwJHszn9U9x1KtTwPsJBaS1mTdNZzAQVyQ 120 | /ip4/123.134.67.89/tcp/6000/ipfs/Qmc5umx9R3bpD5VxvUmfyLoDz5wtVT43p5xjSEmTe26qTD 121 | ``` 122 | 123 | > go-filecoin ping peerID 确认连通性 124 | 125 | ``` 126 | waynewyang:filecoin waynewyang$ go-filecoin ping QmW4Z8p7FCspLV1FeTRW6uCNApUXqkm8xYYw4yuBnqBGeB 127 | PING 128 | Pong received: time=245.12 ms 129 | Pong received: time=245.61 ms 130 | Pong received: time=251.98 ms 131 | Pong received: time=245.69 ms 132 | Pong received: time=255.64 ms 133 | ``` 134 | 135 | - 给你的filecoin Node设置昵称 136 | ``` 137 | waynewyang:filecoin waynewyang$ go-filecoin config heartbeat.nickname "wwwarsyuncom" 138 | "wwwarsyuncom" 139 | waynewyang:filecoin waynewyang$ go-filecoin config heartbeat.nickname 140 | "wwwarsyuncom" 141 | ``` 142 | 143 | - 激活节点 144 | 145 | ``` 146 | go-filecoin config heartbeat.beatTarget "/dns4/stats-infra.kittyhawk.wtf/tcp/8080/ipfs/QmUWmZnpZb6xFryNDeNU7KcJ1Af5oHy7fB9npU67sseEjR" 147 | ``` 148 | 149 | > 在 https://stats.kittyhawk.wtf/ 查看filecoin网络,节点已经激活 150 | 151 | ![](img/active.png) 152 | 153 | #### 3.2.2 获取Mock FIL用于测试 154 | 155 | > FIL用于矿工抵押;或者作为客户进行交易需要 156 | 157 | - 注意:开发网目前运行的都是全节点,获取mock FIL需要建立在本地区块数据同步完成的基础上进行,必须同步完区块之后才能生效,根据个人机器配置情况,这需要较长一段时间。 158 | - go-filecoin message wait ${MESSAGE_CID} 本质上是转账交易,wiki上说明的是等待30s,但是这是在本地区块数据同步完成的基础上才行的。 159 | - 笔者已提交建议给官方,在wiki上更为清晰地表述。 160 | 161 | ![](img/wikiissue1.png) 162 | 163 | 164 | ``` 165 | waynewyang:filecoin waynewyang$ go-filecoin wallet addrs ls 166 | fcq09qtmrxgq5sdr95gs93tx79u9uymdwfdsaphpa 167 | 168 | waynewyang:filecoin waynewyang$ export WALLET_ADDR=`go-filecoin wallet addrs ls` 169 | 170 | waynewyang:filecoin waynewyang$ MESSAGE_CID=`curl -X POST -F "target=${WALLET_ADDR}" "http://user.kittyhawk.wtf:9797/tap" | cut -d" " -f4` 171 | % Total % Received % Xferd Average Speed Time Time Time Current 172 | Dload Upload Total Spent Left Speed 173 | 100 232 100 50 100 182 48 177 0:00:01 0:00:01 --:--:-- 177 174 | 175 | waynewyang:go-filecoin waynewyang$ go-filecoin message wait ${MESSAGE_CID} 176 | { 177 | "meteredMessage": { 178 | "message": { 179 | "to": "fcqm0u932ja5thlsy4dgpz5urlapk8qhtd0clqv5e", 180 | "from": "fcq09sqhrd4gls86muuenzvqdc37mzscagapjveal", 181 | "nonce": "rQQ=", 182 | "value": "1000", 183 | "method": "", 184 | "params": null 185 | }, 186 | "gasPrice": "0", 187 | "gasLimit": "AA==" 188 | }, 189 | "signature": "WKA+eRY7XCQlSmallzoFu8Tps7NZ2AOAKLRFo21rTERFYJqXJT2qEWZ8sFvm6ZShR5syb7RSAJnDp4Am2Vzp0gE=" 190 | } 191 | { 192 | "exitCode": 0, 193 | "return": null, 194 | "gasAttoFIL": "0" 195 | } 196 | waynewyang:filecoin waynewyang$ go-filecoin wallet balance fcq09qtmrxgq5sdr95gs93tx79u9uymdwfdsaphpa 197 | 1000 198 | waynewyang:filecoin waynewyang$ go-filecoin wallet balance ${WALLET_ADDR} 199 | 1000 200 | ``` 201 | 202 | #### 3.2.3 矿工操作 203 | #### 3.2.3.1 存储矿工 204 | - 创建存储矿工示例,需要等待1分钟左右 205 | - 抵押10个扇区的存储空间(当前默认每个扇区256MiB) 206 | - 支付100个FIL为担保 207 | - gas价格为0 208 | - 限制gas消耗最大为1000个FIL 209 | 210 | 211 | 212 | ``` 213 | waynewyang:filecoin waynewyang$ go-filecoin miner create 10 100 --price=0 --limit=1000 --peerid `go-filecoin id | jq -r '.ID'` 214 | fcqjge872spqrgtm8dhlndjgfhhuxzx0y3ujvxxsl //所返回的就是矿工地址minerAddress 215 | 216 | ``` 217 | 218 | - 启动挖矿 219 | ``` 220 | waynewyang:filecoin waynewyang$ go-filecoin mining start 221 | Started mining 222 | ``` 223 | 224 | - 收益之一: 启动挖矿之后就可以参与挖区块奖励 225 | > 查询区块头 226 | 227 | ``` 228 | waynewyang:go-filecoin waynewyang$ go-filecoin chain head 229 | [{"/":"zDPWYqFD2mBqLx7bwQNdeVoMxj6SC5HxzorZAoXpT6xjaythnENw"}] 230 | ``` 231 | 232 | > 查询具体区块信息 233 | 234 | ``` 235 | go-filecoin show block 236 | 237 | waynewyang:go-filecoin waynewyang$ go-filecoin show block zDPWYqFD2mBqLx7bwQNdeVoMxj6SC5HxzorZAoXpT6xjaythnENw 238 | Block Details 239 | Miner: fcq0y72meekgwnvchwml0uzx759q25nk0rqc47ret 240 | Weight: 293567.552 241 | Height: 10787 242 | Nonce: 0 243 | ``` 244 | 245 | - 收益之二:创建报价单ask 246 | 247 | ``` 248 | 1) 获取矿工地址 249 | export MINER_ADDR=`go-filecoin config mining.minerAddress | tr -d \"` 250 | 251 | 2) 设置矿机Owner 252 | export MINER_OWNER_ADDR=`go-filecoin miner owner $MINER_ADDR` 253 | 254 | 3) 创建报价单,价格0.000000001 FIL/byte/block, 交易费0,gas限制1000,提供2880个block空间存储 255 | 256 | go-filecoin miner set-price --from=$MINER_OWNER_ADDR --miner=$MINER_ADDR --price=0 --limit=1000 0.000000001 2880 # output: CID of the ask 257 | 258 | 发布报价单,需要打包进去区块 259 | 260 | waynewyang:filecoin waynewyang$ go-filecoin miner set-price --from=$MINER_OWNER_ADDR --miner=$MINER_ADDR --price=0 --limit=1000 0.000000001 15315 261 | Set price for miner fcqjge872spqrgtm8dhlndjgfhhuxzx0y3ujvxxsl to 0.000000001. 262 | Published ask, cid: zDPWYqFCxL3VW3xzmHhCBqPTvhoQa53pn6DzV3uY23jNL76za1Vt. 263 | Ask confirmed on chain in block: zDPWYqFD7wjnj74sdB9HqupDmWmpPPEvygB14Pbo6rQC7ho2687D. 264 | 265 | 4) 查询区块信息(第三步中是zDPWYqFD7wjnj74sdB9HqupDmWmpPPEvygB14Pbo6rQC7ho2687D)可以找到对应报价单信息 266 | 267 | waynewyang:filecoin waynewyang$ go-filecoin show block zDPWYqFD7wjnj74sdB9HqupDmWmpPPEvygB14Pbo6rQC7ho2687D --enc=json 268 | {"miner":"fcqnam6n2qml2eyngws25srzvhcdf0t8gcgrsvnrk","ticket":"AM0p5IC9ph+o9dTwd/MXYdeOJW25PfDwhTgonNRkSP4=","parents":[{"/":"zDPWYqFCwNWHJXdeXcjx7ipUvRKFq5WhLbtSm6ESuNufkLuGiAgW"}],"parentWeight":"kujBrQE=","height":"2Hc=","nonce":"AA==","messages":[{"meteredMessage":{"message":{"to":"fcqp606qfk5gwmq6ac24g4mhv3cr8zzf67vqkpulh","from":"fcqr89lj0lvduj475zw002j6q5yrl30ks7uep2p5e","nonce":"Ag==","value":"215.6046624","method":"createChannel","params":"glYAABXMs4C0hXqcW94YGyVKxii6SLQhQ6HoAg=="},"gasPrice":"0","gasLimit":"rAI="},"signature":"vHzwO73TvM8MW1FKg8Qgfy/IP+wfJIQkEK0ExBB75gBbPMhv6GiU4aBq1T2Gb2OeMfrch8Zg3EFOJd0uUJltwAE="},{"meteredMessage":{"message":{"to":"fcqafmqgvzkzpvc6wjxecm7gsweuawjv8t6falk6r","from":"fcqr89lj0lvduj475zw002j6q5yrl30ks7uep2p5e","nonce":"Aw==","value":"100","method":"createMiner","params":"g0EKWEEEiA8ArEoyzhjWwijpTWYqDsOFfwxa2F0pUfOyRI/6yY28OD4QHcwUdb3a9omX9DNxVzdS2a8pWgiLNowe9wYVcFgiEiD3rKfg/NyDnrLF9IGxfp6U72jZxuniXlPcv5SG5OZHrA=="},"gasPrice":"0","gasLimit":"6Ac="},"signature":"/X/87zil8InOeLeQ6kqkqnpg7mP/e5jMaaVS4LRMIdYN84HTbABBpvt6quRqVQsadJnqOW7mn+6NA+2d9FDjPAA="},{"meteredMessage":{"message":{"to":"fcqp606qfk5gwmq6ac24g4mhv3cr8zzf67vqkpulh","from":"fcqmqr5f2a5qnwnvftpuzd6sjfy5tcq5dd0k24h85","nonce":"Ug==","value":"0.008596","method":"createChannel","params":"glYAAIVV3axUhfe7OGwwSH/IIONRcbinQ/OWAQ=="},"gasPrice":"0","gasLimit":"rAI="},"signature":"dxhSaVRvFBtdrbnEByza7a5JqLzm6n6rVZYGuFN6zegCTMDKbGGh++EvVmWo0WSbdcUo2vB/jFTgqzATh9+1NQA="},{"meteredMessage":{"message":{"to":"fcqugc6nql2eqglfwq0dw7ep7l9a07jacqgstely7","from":"fcq0nmdcq7updgwc3uh2lz2rnjms7gprdggcvxjqj","nonce":"BA==","value":null,"method":"commitSector","params":"hUECWCCFzRGqHyJ3VWk3GueHzfkcWF218hOqRGtLxsJ0oJ3pXVgg6qrxCGo6SSjyUSbJWVKPKGaY/wrymC21t5LSScCNpQBYIOeZkdzp7lPt8Fh/Sdl9YqJ8BCaJ7etWEnDnLzRnV/geWQGAlTMp95t1Hh61eFBmzy6Ex/Ee1cso7Cethz+Z2EHCfhi5UzOMeLqeA/Wfypcnrw15mF4OrYR8648RXx6jp8svbgZ6Jg9fP/q0RukszZ/SD9f0pCMg2N/xt5hVIPG7jowSCfkj//CpdRj1GRPLzXvzyWmW4SgKR4lNpJNnmuiXSe8nYLsZgY2v8xy4NB448e/slxh7D4NQPanCoN3WO10oBR42ZxeCZY6stq+JfwucGr5OajgXSK2rGwz/Sj+GYpMbtgpfxSd4Z+jZ6mnoY03NaIHvwnDKchRz797lFL3so6AQRRnctN3Pl7LSn52YA0EOkmhJLMev6DKBWEqSfjXTY4AJSJ7RmGq88BXoHzwGjndRj0QHFtSTjHIoxF9uN86zB6gfSS+A7ZviuTvfturtKee243b9OIojIf2ne2hF8+7PSIwCp5FPLXEqR/UtXnJ6pxjBKhF36k3FRdzsxo52DMImgPhluGWI3xRhpIMmFNDMNynOBQ9F6mnR8fRBdPKB"},"gasPrice":"0","gasLimit":"rAI="},"signature":"hEluqbFG8TTSeeJyfOs10fZD/gOsrnFV8QgRAb4mhSFlzYpcijT9ye1yUYam5hcsW1eq1MFRfVGHhqYYUZ4LYwA="},{"meteredMessage":{"message":{"to":"fcqjge872spqrgtm8dhlndjgfhhuxzx0y3ujvxxsl","from":"fcqm0u932ja5thlsy4dgpz5urlapk8qhtd0clqv5e","nonce":"AQ==","value":"0","method":"addAsk","params":"gkWAlOvcA0I70w=="},"gasPrice":"0","gasLimit":"6Ac="},"signature":"IGUvf7CZ8lKDSTyzbg5kyArIPv8TIoFEWpA3ihRC7I86NFvgebCdEKQ6PqYhTJj1GQK/+JF28kCinXFN/9G8FgE="},{"meteredMessage":{"message":{"to":"fcqp606qfk5gwmq6ac24g4mhv3cr8zzf67vqkpulh","from":"fcqsvmdzpy5mjc9m0cuh6uhmprr6gk5w2zcrnjy02","nonce":"Aw==","value":"0.0000301989888","method":"createChannel","params":"glYAAGsLRe4dsJwsvfE2+dkEh1DPX11jQ+OdAQ=="},"gasPrice":"0","gasLimit":"rAI="},"signature":"Zg5kUOtEZ9Um+mGKicHaqTCaORppGv5KAaSlTpN/qTcoTptRUP3ZbQ5YOL7zjTG6aF7Y4r0Ck0NsnG0J/i2B0AA="},{"meteredMessage":{"message":{"to":"fcqp606qfk5gwmq6ac24g4mhv3cr8zzf67vqkpulh","from":"fcqrqtfcug5hlx7gugvwj0f2dyx6j9cxdn0ynmpu3","nonce":"Ag==","value":"0","method":"createChannel","params":"glYAAJsQ6e5i/M0X04YZwzl3VWckPu4RQ62HAQ=="},"gasPrice":"0","gasLimit":"rAI="},"signature":"oisXV83sTFkJw7y5KbO5fLhx2oa48qZKAVwX+1fIvWUDgm5PQNDddPeCkklPg2L+fmp4wL2fLF9R2qPRciLUfAA="}],"stateRoot":{"/":"zdpuAvwpuqNR4J6PJDqGfF5GbyjWarD1BhujTUfyREMHSY1eF"},"messageReceipts":[{"exitCode":0,"return":["Ag=="],"gasAttoFIL":"0"},{"exitCode":0,"return":["AAA2Qrupiubk6ljOMnMUrnnHKaVXmQ=="],"gasAttoFIL":"0"},{"exitCode":0,"return":["0gA="],"gasAttoFIL":"0"},{"exitCode":0,"return":null,"gasAttoFIL":"0"},{"exitCode":0,"return":[""],"gasAttoFIL":"0"},{"exitCode":0,"return":["Aw=="],"gasAttoFIL":"0"},{"exitCode":0,"return":["Ag=="],"gasAttoFIL":"0"}],"proof":[177,165,90,219,1,18,240,190,113,56,243,22,167,201,232,75,124,152,130,111,74,132,5,192,33,191,102,220,102,9,99,109,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]} 269 | 270 | //查询最新区块信息 271 | waynewyang:go-filecoin waynewyang$ go-filecoin show block `go-filecoin chain head --repodir=~/.filecoin2/ |jq -r '.[0]'|jq -r '.["/"]'` 272 | Block Details 273 | Miner: fcq973y2y7hvcce8zkwds7r2847xmfjvdecn98lws 274 | Weight: 134386.836 275 | Height: 5013 276 | Nonce: 0 277 | 278 | 5) 获取所有矿工的报价信息 279 | go-filecoin client list-asks --enc=json | jq 280 | ``` 281 | 282 | > 现在默认是只要客户出价高于矿工报价,默认接受交易。 283 | 284 | - 停止挖矿 285 | 286 | ``` 287 | go-filecoin mining stop 288 | rm -rf ~/.filecoin //删除filecoin矿工实例,区块同步也被删除,再次实例化,需要再次同步区块 289 | ``` 290 | 291 | #### 3.2.3.2 检索矿工 292 | 293 | > 暂未发现支持,目前可以自己的供应商(具体矿工)处获取;后面通过更深入的分析之后另行补充。 294 | 295 | #### 3.2.3.3 修复矿工 296 | 297 | > 修复矿工的概念是白皮书之后提出的,后面继续深入分析之后再另行补充。 298 | 299 | #### 3.2.4 客户操作 300 | #### 3.2.4.1 存储客户 301 | 302 | - filecoin 与IPFS数据结构是兼容的 303 | 304 | ``` 305 | //创建测试文件 306 | waynewyang:test waynewyang$ echo "Hi my name is $USER"> hello.txt 307 | waynewyang:test waynewyang$ cat hello.txt 308 | Hi my name is waynewyang 309 | 310 | //导入filecoin本地资源库 311 | waynewyang:test waynewyang$ export CID=`go-filecoin client import ./hello.txt` 312 | waynewyang:test waynewyang$ echo $CID 313 | Qmchgh3N3kxWiaZ2cp9PbV93i77H3K8KtQCBTeVR5Q7wzs 314 | 315 | //这里会发现用IPFS上传得到的CID也是一样 316 | waynewyang:test waynewyang$ ipfs add hello.txt 317 | added Qmchgh3N3kxWiaZ2cp9PbV93i77H3K8KtQCBTeVR5Q7wzs hello.txt 318 | 25 B / 25 B [========================================================================] 100.00% 319 | 320 | //用go-filecoin或者IPFS命令获取数据,结果一致 321 | waynewyang:test waynewyang$ go-filecoin client cat $CID 322 | Hi my name is waynewyang 323 | waynewyang:test waynewyang$ ipfs block get $CID 324 | Hi my name is waynewyang 325 | ``` 326 | 327 | - 导入测试数据 328 | ``` 329 | waynewyang:sample-data waynewyang$ export CID=`go-filecoin client import camel.jpg` 330 | waynewyang:sample-data waynewyang$ go-filecoin client cat $CID > image.png && open image.png 331 | waynewyang:sample-data waynewyang$ echo $CID 332 | QmeubcGKFXpafFT4xRFGf3NqDRzJUVoAqe5sh1ugbRPZ7u 333 | ``` 334 | 335 | - 查询矿工的报价单 336 | 337 | ``` 338 | waynewyang:sample-data waynewyang$ go-filecoin client list-asks --enc=json | jq 339 | { 340 | "Miner": "fcqvnwlanfu7ecflnp3rc5gm0ecdamvxgvlawref4", 341 | "Price": "0.000000001", 342 | "Expiry": 7079, 343 | "ID": 0, 344 | "Error": null 345 | } 346 | { 347 | "Miner": "fcqsmut6jnwchq0qlc3t6v44pzgf8l49lg6r8wl4a", 348 | "Price": "0.000000001", 349 | "Expiry": 16522, 350 | "ID": 0, 351 | "Error": null 352 | } 353 | { 354 | "Miner": "fcqsmut6jnwchq0qlc3t6v44pzgf8l49lg6r8wl4a", 355 | "Price": "0.000000000000000001", 356 | "Expiry": 18753, 357 | "ID": 1, 358 | "Error": null 359 | } 360 | { 361 | "Miner": "fcqghrce7vaf6czj54x5qke0mn2uzzg8ckvgvcjpe", 362 | "Price": "0.000000001", 363 | "Expiry": 14404, 364 | "ID": 0, 365 | "Error": null 366 | } 367 | ...... 368 | ``` 369 | 370 | - 下单 371 | 372 | ``` 373 | go-filecoin client propose-storage-deal 374 | 375 | address of the miner from list-asks 376 | CID of the imported data that you want to store 377 | ID of the ask, also from list-asks (usually 0) 378 | how long you want to store (in # of ~30sec blocks). For example, storing for 1 day (2 blocks/min * 60 min/hr * 24 hr/day) = 2880 blocks. 379 | ``` 380 | 381 | - 发送数据和支付 382 | 383 | ``` 384 | 1 支付 385 | 386 | 1)支付到paych中 387 | 2)定期向矿工付款 388 | 389 | 2 数据 390 | 391 | 1)未密封完的数据称之为暂存区 392 | 2)密封完成后阶段性支付 393 | ``` 394 | 395 | #### 3.2.4.2 检索客户 396 | 397 | > 现在是指定所对应的存储矿工进行检索,暂未发现更多支持,在后面的深入分析中会继续跟进。 398 | 399 | - 查询订单状态,必须是密封,posted交易结束后才能查询 400 | 401 | ``` 402 | go-filecoin client query-storage-deal < dealID > 403 | ``` 404 | 405 | - 检索 406 | 407 | ``` 408 | go-filecoin retrieval-client retrieve-piece < minerAddress > < CID > 409 | ``` 410 | 411 | - [回到目录](#目录) 412 | 413 | ### 3.2.5 filecoin合约 414 | #### 3.2.5.1 文件合约 415 | 416 | > 其实现在的创建存储矿工,以及矿工创建报价、存储客户提交订单存储,这些笔者认为属于filecoin文件合约的范畴。 417 | 418 | > 与以太坊类似,以太坊抽象出了代币合约以及通用智能合约; 而filecoin则是抽象出了文件合约和通用智能合约。 419 | 420 | - [回到目录](#目录) 421 | 422 | #### 3.2.5.2 智能合约 423 | 424 | > 暂未发现支持,在后面的深入分析中会继续跟进。 425 | 426 | - [回到目录](#目录) 427 | 428 | ### 3.2.6 单机运行多个filecoin节点 429 | #### 3.2.6.1 修改资源目录和服务端口的方式 430 | 431 | - go-filecoin init的时候,通过 ‘--repodir=所指定资源目录路径’ 命令进行初始化目录资源,后面的其他命令同样需要所指定资源目录路径进行操作。 432 | 433 | - 修改资源目录下的config.json文件,将默认的端口予以修改,避免与另外的本机实例相冲突。 434 | 435 | - [回到目录](#目录) 436 | 437 | #### 3.2.6.2 容器部署方式 438 | 439 | - 可以打包成docker镜像,有兴趣的朋友可以自行尝试。 440 | 441 | 442 | - [回到目录](#目录) 443 | -------------------------------------------------------------------------------- /4.md: -------------------------------------------------------------------------------- 1 | # filecoin技术架构分析之四:filecoin源码顶层架构分析 2 | 3 | > 作者:杨尉(waynewyang),转载请注明出处 4 | 5 | 6 | ## 目录 7 | - [4 filecoin源码顶层架构分析](#filecoin源码顶层架构分析) 8 | - [4.1 题外话——关于竞争力](#题外话关于竞争力) 9 | - [4.2 filecoin顶层架构概览及分析思路](#filecoin顶层架构概览及分析思路) 10 | - [4.2.1 分析思路](#分析思路) 11 | - [4.2.2 filecoin顶层架构概览](#filecoin顶层架构概览) 12 | - [4.3 网络层](#网络层) 13 | - [4.4 协议层](#协议层) 14 | - [4.5 REST/CMD](#restcmd) 15 | - [4.6 内部api层](#内部api层) 16 | - [4.7 core服务层](#core服务层) 17 | 18 | 19 | 20 | ## 4 filecoin源码顶层架构分析 21 | 22 | ## 4.1 题外话——关于竞争力 23 | 24 | > 网络技术的高速发展带领我们进入了知识大爆炸、技术快速跃迁的时代,5G已经开始走向商业落地,网络速率的再次跃迁给我们带来了无限的想象空间,全息投影、即时翻译、远程医疗、人工智能等等会更加成熟落地?路由器在个人家庭中的角色可能会发生变化?IOT万物互联的时代将会真正到来?区块链的TPS提升?高速网络下的云应用、大数据会出现什么新的玩法? 25 | 26 | >笔者想说的是,整个世界都在急速变化,在波涛汹涌的竞争浪潮之中,如何保持自己的竞争力。我偶尔会问同事、朋友,你与刚毕业的大学生相比,优势在哪里? 27 | 28 | 笔者认为如下两点才是在这个高速时代的真正竞争力,个人如此,公司团队亦如此。 29 | 30 | - 高效的学习能力 31 | - 高维的思维能力 32 | 33 | 34 | 35 | 以上为笔者观点,也欢迎大家探讨。在分析具体架构之前,笔者在4.2.1中分享自己的分析思路,我认为这也许也值得分享。 36 | 37 | 38 | 39 | 40 | 41 | ## 4.2 filecoin源码顶层架构概览及分析思路 42 | ### 4.2.1 分析思路 43 | 44 | - 终于进入到源码分析环节了,其实回顾一下前面三章,filecoin的概念及通用语言可以总结为filecoin的本质,分析源码的过程归根接底还是理解设计者的意图,第三章filecoin开发网络的实战使用对于笔者来说也是为了更清晰地对filecoin本质及设计意图进行深入理解。 45 | 46 | - 分析总思路为:抓住本质分析,理解设计者意图 47 | 48 | - 自上而下逐层分析,从抽象到具体 49 | - 自下而上反向总结,从具体到抽象 50 | 51 | - 分析过程分为三大步骤 52 | 53 | - 第一步,理解filecoin本质及设计目的(前面三章) 54 | - 第二步,理解filecoin的顶层架构设计(本章),反向加深对filecoin本质的理解 55 | - 第三步,各层的具体源码分析(后面章节),反向加深对filecoin本质的理解 56 | 57 | > 详细参见下图 58 | 59 | 60 | ![](img/filecoin_arch.png) 61 | 62 | - 在顶层源码中分为go-filecon和rust-fil-proofs。分别为主框架和存储证明部分,本文主要分析go-filecoin源码的顶层框架。 63 | 64 | 65 | 66 | 67 | 68 | ### 4.2.2 filecoin顶层架构概览 69 | #### 4.2.2.1 架构图 70 | 71 | ``` 72 | ┌─────────────────────────────────────┐ 73 | │ │ 74 | Network │ network (gossipsub, bitswap, etc.) │ | | \/ 75 | │ │ |_| /\ 76 | └─────▲────────────▲────────────▲─────┘ 77 | │ │ │ ┌────────────────────────────┐ 78 | ┌─────▼────┐ ┌─────▼─────┐ ┌────▼─────┐ │ │ 79 | │ │ │ │ │ │ │ Commands / REST API │ 80 | Protocols │ Storage │ │ Mining │ │Retrieval │ │ │ 81 | │ Protocol │ │ Protocol │ │ Protocol │ └────────────────────────────┘ 82 | │ │ │ │ │ │ │ 83 | └──────────┘ └───────────┘ └──────────┘ │ 84 | │ │ │ │ 85 | └──────────┬─┴─────────────┴───────────┐ │ 86 | ▼ ▼ ▼ 87 | ┌────────────────────────────────┐ ┌───────────────────┬─────────────────┐ 88 | Internal │ Core API │ │ Porcelain │ Plumbing │ 89 | API │ │ ├───────────────────┘ │ 90 | └────────────────────────────────┘ └─────────────────────────────────────┘ 91 | │ │ 92 | ┌─────────┴────┬──────────────┬──────────────┬─┴────────────┐ 93 | ▼ ▼ ▼ ▼ ▼ 94 | ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ 95 | │ │ │ │ │ │ │ │ │ │ 96 | Core │ Message │ │ Chain │ │ Processor │ │ Block │ │ Wallet │ 97 | │ Pool │ │ Store │ │ │ │ Service │ │ │ 98 | │ │ │ │ │ │ │ │ │ │ 99 | └────────────┘ └────────────┘ └────────────┘ └────────────┘ └────────────┘ 100 | ``` 101 | 102 | 103 | 104 | - 官方给出的如上架构概览图是小于实际源码的,但是不影响理解。 105 | - 官方的spec项目中,有较多文档说明已经滞后于源码,其引用的源码有些已经从go-filecoin源码中消失了,想深入分析的朋友建议可以结合源码和文档同步进行看。 106 | - 本文后面的章节中,只会简述各个层的设计目的,每一层的具体源码分析,将放到后面章节分享给大家。 107 | 108 | 109 | 110 | 111 | 112 | #### 4.2.2.2 IPFS与filecoin在技术架构层面的关系 113 | 114 | ![](img/ipfsandfilecoin.png) 115 | 116 | - IPFS与filecoin同样采用IPLD结构,数据结构是互通的,简而言之,在IPFS之上存储的数据,filecoin可以读取。filecoin存储的未密封数据,IPFS也是可以读取的。 117 | - IPFS与filecoin网络部分均复用libp2p部分。 118 | - filecoin复用了大量IPFS组件,比如CID、IPLD、bitswap等等。 119 | 120 | 121 | 122 | 123 | 124 | ## 4.3 网络层 125 | 126 | - 网络层的实现依赖协议实验室的libp2p项目,如果不熟悉的可以先简单记住如下要点,后面笔者考虑视情况补充IPFS/libp2p的相关分享。 127 | - libp2p的网络层实现了节点之间的联通性问题,包括节点发现、NAT穿透、pubsub、relay等。 128 | 129 | - libp2p的路由层的主要目的,包括节点路由、内容路由、DHT键值存储。 130 | 131 | - multistream需要理解,filecoin的协议层之协议定义就是基于mulitistream的。 132 | 133 | - filecoin网络层的目的 134 | 135 | - 处理请求信息、回复响应信息,包括存储订单处理、检索请求处理、区块同步等等。 136 | 137 | 138 | 139 | 140 | 141 | ## 4.4 协议层 142 | 143 | 协议层主要处理应用级的逻辑,状态切换等,具体会通过api层调用具体的core服务进行处理。 144 | 145 | 146 | ### 4.4.1 hello握手协议 147 | - 协议名称: /fil/hello/1.0.0 148 | - 目的: 149 | - 本节点上线,向其他节点发起hello握手请求,进而进行区块同步。 150 | - 响应其他新上线的节点hello握手请求,触发其进行区块同步。 151 | 152 | 153 | 154 | ### 4.4.2 存储协议 155 | #### 4.4.2.1 存储矿工 156 | 157 | - 协议名称:/fil/storage/mk/1.0.0、 /fil/storage/qry/1.0.0 158 | - 目的: 159 | - 接受客户发起的订单交易请求、查询订单请求,会提交对应处理状态到区块链上(包括清单处理成功或失败;密封成功或者失败等等)。 160 | - 更新本地的密封或者订单状态。 161 | 162 | 163 | 164 | #### 4.4.2.2 存储客户 165 | 166 | - 采用上述的/fil/storage/mk/1.0.0、 /fil/storage/qry/1.0.0协议,建立multistream,向矿工发起交易或者查询交易状态。 167 | 168 | 169 | 170 | ### 4.4.3 检索协议 171 | 172 | #### 4.4.3.1 检索矿工 173 | - 协议名称:/fil/retrieval/free/0.0.0 174 | - 目的: 175 | 176 | - 接受客户的检索请求,并响应处理 177 | 178 | - 注意:目前仅仅支持free检索,白皮书所描述的完整检索功能尚未实现。 179 | 180 | 181 | #### 4.4.3.2 检索客户 182 | 183 | - 采用上述的/fil/retrieval/free/0.0.0协议,建立multistream,向矿工发起检索请求。 184 | 185 | 186 | ### 4.4.4 心跳协议 187 | 188 | - 协议名称:fil/heartbeat/1.0.0 189 | - 目的: 190 | - 启动之后向指定节点发起心跳。 191 | - 如前面3.2.1章节中的设置Nick Name,以及激活极点,都属于心跳协议实现的。 192 | 193 | 194 | 195 | ## 4.5 REST/CMD 196 | 197 | - 这个应该不用多解释,提供cmd或者REST接口供用户操作具体节点。第三章中的开发网络使用基本都是使用的CMD方式。 198 | 199 | 200 | 201 | ## 4.6 内部api层 202 | ### 4.6.1 node对象 203 | - filecoin的node节点是一个上帝对象,从下面Node的结构可以看出,基本贯穿了filecoin的整个业务。 204 | 205 | - 为了解决耦合性问题,尤其是后续轻节点的实现,官方已经开始将原有的api包,往plumbing包以及porcelain包迁移,这样做的目的是让系统具备更好的解耦性,以满足更灵活的需求。 206 | 207 | - plumbing和 porcelain模式也是借鉴git的思维。 208 | 209 | 210 | ``` 211 | ▼+Node : struct 212 | [fields] 213 | +AddNewlyMinedBlock : newBlockFunc 214 | +BlockSub : ps.Subscription 215 | +Blockstore : bstore.Blockstore 216 | +Bootstrapper : *filnet.Bootstrapper 217 | +ChainReader : chain.ReadStore 218 | +Consensus : consensus.Protocol 219 | +Exchange : exchange.Interface 220 | +HeaviestTipSetCh : chan interface{} 221 | +HeaviestTipSetHandled : func() 222 | +HelloSvc : *hello.Handler 223 | +MessageSub : ps.Subscription 224 | +MiningScheduler : mining.Scheduler 225 | +MsgPool : *core.MessagePool 226 | +OfflineMode : bool 227 | +OnlineStore : *hamt.CborIpldStore 228 | +PeerHost : host.Host 229 | +Ping : *ping.PingService 230 | +PorcelainAPI : *porcelain.API 231 | +PowerTable : consensus.PowerTableView 232 | +Repo : repo.Repo 233 | +RetrievalClient : *retrieval.Client 234 | +RetrievalMiner : *retrieval.Miner 235 | +Router : routing.IpfsRouting 236 | +StorageMiner : *storage.Miner 237 | +StorageMinerClient : *storage.Client 238 | +Syncer : chain.Syncer 239 | +Wallet : *wallet.Wallet 240 | -blockTime : time.Duration 241 | -blockservice : bserv.BlockService 242 | -cancelMining : context.CancelFunc 243 | -cancelSubscriptionsCtx : context.CancelFunc 244 | -cborStore : *hamt.CborIpldStore 245 | -host : host.Host 246 | -lookup : lookup.PeerLookupService 247 | -mining 248 | -miningCtx : context.Context 249 | -miningDoneWg : *sync.WaitGroup 250 | -sectorBuilder : sectorbuilder.SectorBuilder 251 | [methods] 252 | +BlockHeight() : *types.BlockHeight, error 253 | +BlockService() : bserv.BlockService 254 | +CborStore() : *hamt.CborIpldStore 255 | +ChainReadStore() : chain.ReadStore 256 | +CreateMiner(ctx context.Context, accountAddr address.Address, gasPrice types.AttoFIL, gasLimit types.GasUnits, pledge uint64, pid libp2ppeer.ID, collateral *types.AttoFIL) : *address.Address, error 257 | +GetBlockTime() : time.Duration 258 | +Host() : host.Host 259 | +Lookup() : lookup.PeerLookupService 260 | +MiningSignerAddress() : address.Address 261 | +MiningTimes() : time.Duration, time.Duration 262 | +NewAddress() : address.Address, error 263 | +SectorBuilder() : sectorbuilder.SectorBuilder 264 | +SetBlockTime(blockTime time.Duration) 265 | +Start(ctx context.Context) : error 266 | +StartMining(ctx context.Context) : error 267 | +Stop(ctx context.Context) 268 | +StopMining(ctx context.Context) 269 | -addNewlyMinedBlock(ctx context.Context, b *types.Block) 270 | -cancelSubscriptions() 271 | -getLastUsedSectorID(ctx context.Context, minerAddr address.Address) : uint64, error 272 | -getMinerActorPubKey() : []byte, error 273 | -handleNewHeaviestTipSet(ctx context.Context, head types.TipSet) 274 | -handleNewMiningOutput(miningOutCh chan mining.Output) 275 | -handleSubscription(ctx context.Context, f pubSubProcessorFunc, fname string, s ps.Subscription, sname string) 276 | -isMining() : bool 277 | -miningAddress() : address.Address, error 278 | -miningOwnerAddress(ctx context.Context, miningAddr address.Address) : address.Address, error 279 | -saveMinerConfig(minerAddr address.Address, signerAddr address.Address) : error 280 | -setIsMining(isMining bool) 281 | -setupMining(ctx context.Context) : error 282 | [functions] 283 | +New(ctx context.Context, opts ...ConfigOpt) : *Node, error 284 | ``` 285 | 286 | 287 | 288 | ### 4.6.2 api包 289 | - 这基本上是早期实现的api接口,对应4.2.2.1中的Core API,严重依赖于Node,耦合性大。现在在逐步迁移。感兴趣可以参照如下源码逐层深入去看。 290 | 291 | ``` 292 | package: api 293 | location: api/api.go 294 | 295 | type API interface { 296 | Actor() Actor 297 | Address() Address 298 | Client() Client 299 | Daemon() Daemon 300 | Dag() Dag 301 | ID() ID 302 | Log() Log 303 | Miner() Miner 304 | Mining() Mining 305 | Paych() Paych 306 | Ping() Ping 307 | RetrievalClient() RetrievalClient 308 | Swarm() Swarm 309 | Version() Version 310 | } 311 | ``` 312 | 313 | 314 | 315 | ### 4.6.3 plumbing和porcelain包 316 | 317 | - plumbing api简而言之,是实现底层的公共api,其不依赖于Node的实现。 318 | - 而porcelain api则是在plumbing api之上,更偏应用级的调用,同样不依赖于Node实现。 319 | 320 | ``` 321 | 源码分别参见go-filecoin目录下,./plumbing和./porcelain。 322 | ``` 323 | 324 | 325 | 326 | 327 | ## 4.7 core服务层 328 | 329 | > 关于核心业务调度以及业务持久化的底层处理基本都在这一层,包含但不限于如下服务。 330 | 331 | ### 4.7.1 Message pool 332 | 333 | > 消息池主要保存还未上链的消息。 334 | 335 | ### 4.7.2 Chain store 336 | 337 | > 链存储主要持久化链信息,注意同步区块的逻辑是在协议层的hello协议所出发的。 338 | 339 | ### 4.7.3 Processor 340 | 341 | > 处理事务消息如何驱动状态转换。 342 | 343 | ### 4.7.4 Block service 344 | 345 | > 负责IPLD数据的内容寻址,包括区块链等。 346 | 347 | ### 4.7.5 Wallet 348 | 349 | > 钱包管理。 350 | -------------------------------------------------------------------------------- /5.md: -------------------------------------------------------------------------------- 1 | # filecoin技术架构分析之五:filecoin源码分析之协议层心跳协议 2 | 3 | > 作者:杨尉(waynewyang),转载请注明出处 4 | 5 | 6 | ## 目录 7 | - 5 filecoin源码协议层分析之心跳协议 8 | - 5.1 源码信息 9 | - 5.2 源码分析 10 | - 5.2.1 数据结构 11 | - 5.2.2 方法 12 | - 5.2.3 函数 13 | - 5.2.4 实例化及业务逻辑 14 | 15 | 16 | ## 5.1 源码信息 17 | 18 | - version 19 | - master分支 619b0eb1(2019年3月2日) 20 | - package 21 | - metrics 22 | - location 23 | - metrics/heartbeat.go 24 | - node/node.go 25 | 26 | ## 5.2 源码分析 27 | ### 5.2.1 数据结构 28 | 29 | - 定义心跳协议名称以及连接超时时间 30 | 31 | ``` 32 | // HeartbeatProtocol is the libp2p protocol used for the heartbeat service 33 | const ( 34 | HeartbeatProtocol = "fil/heartbeat/1.0.0" 35 | // Minutes to wait before logging connection failure at ERROR level 36 | connectionFailureErrorLogPeriodMinutes = 10 * time.Minute 37 | ) 38 | ``` 39 | 40 | - 定义心跳信息结构 41 | - 节点的区块头 42 | - 节点的区块高度 43 | - 节点的昵称 44 | - 是否在区块同步中(TODO) 45 | - 矿工地址(如果没有挖矿,这里为零地址) 46 | 47 | ``` 48 | // Heartbeat contains the information required to determine the current state of a node. 49 | // Heartbeats are used for aggregating information about nodes in a log aggregator 50 | // to support alerting and devnet visualization. 51 | type Heartbeat struct { 52 | // Head represents the heaviest tipset the nodes is mining on 53 | Head string 54 | // Height represents the current height of the Tipset 55 | Height uint64 56 | // Nickname is the nickname given to the filecoin node by the user 57 | Nickname string 58 | // TODO: add when implemented 59 | // Syncing is `true` iff the node is currently syncing its chain with the network. 60 | // Syncing bool 61 | 62 | // Address of this node's active miner. Can be empty - will return the zero address 63 | MinerAddress address.Address 64 | } 65 | ``` 66 | 67 | - 心跳服务结构体 68 | - 主机结构体:对应libp2p主机 69 | - 心跳配置 70 | - 区块头获取 71 | - 挖矿地址获取 72 | - stream锁 73 | - stream 74 | 75 | ``` 76 | // HeartbeatService is responsible for sending heartbeats. 77 | type HeartbeatService struct { 78 | Host host.Host 79 | Config *config.HeartbeatConfig 80 | 81 | // A function that returns the heaviest tipset 82 | HeadGetter func() types.TipSet 83 | 84 | // A function that returns the miner's address 85 | MinerAddressGetter func() address.Address 86 | 87 | streamMu sync.Mutex 88 | stream net.Stream 89 | } 90 | ``` 91 | 92 | - 定义心跳服务Option函数 93 | - 函数入参为心跳服务结构体,主要用于对心跳服务结构体传参或者解析 94 | ``` 95 | // HeartbeatServiceOption is the type of the heartbeat service's functional options. 96 | type HeartbeatServiceOption func(service *HeartbeatService) 97 | ``` 98 | 99 | ### 5.2.2 方法 100 | - 获取心跳服务的stream实例 101 | 102 | ``` 103 | // Stream returns the HeartbeatService stream. Safe for concurrent access. 104 | // Stream is a libp2p connection that heartbeat messages are sent over to an aggregator. 105 | func (hbs *HeartbeatService) Stream() net.Stream { 106 | hbs.streamMu.Lock() 107 | defer hbs.streamMu.Unlock() 108 | return hbs.stream 109 | } 110 | ``` 111 | 112 | - 设置心跳服务的stream实例 113 | 114 | ``` 115 | // SetStream sets the stream on the HeartbeatService. Safe for concurrent access. 116 | func (hbs *HeartbeatService) SetStream(s net.Stream) { 117 | hbs.streamMu.Lock() 118 | defer hbs.streamMu.Unlock() 119 | hbs.stream = s 120 | } 121 | ``` 122 | 123 | - 定时确认连接性,并调用运行心跳服务 124 | 125 | ``` 126 | // Start starts the heartbeat service by, starting the connection loop. The connection 127 | // loop will attempt to connected to the aggregator service, once a successful 128 | // connection is made with the aggregator service hearbeats will be sent to it. 129 | // If the connection is broken the heartbeat service will attempt to reconnect via 130 | // the connection loop. Start will not return until context `ctx` is 'Done'. 131 | func (hbs *HeartbeatService) Start(ctx context.Context) { 132 | log.Debug("starting heartbeat service") 133 | 134 | rd, err := time.ParseDuration(hbs.Config.ReconnectPeriod) 135 | if err != nil { 136 | log.Errorf("invalid heartbeat reconnectPeriod: %s", err) 137 | return 138 | } 139 | 140 | //启动重连定时器 141 | reconTicker := time.NewTicker(rd) 142 | defer reconTicker.Stop() 143 | // Timestamp of the first connection failure since the last successful connection. 144 | // Zero initially and while connected. 145 | var failedAt time.Time 146 | // Timestamp of the last ERROR log (or of failure, before the first ERROR log). 147 | var erroredAt time.Time 148 | for { 149 | select { 150 | case <-ctx.Done(): 151 | return 152 | case <-reconTicker.C: 153 | //重连定时周期到,重新连接 154 | if err := hbs.Connect(ctx); err != nil { 155 | // Logs once as a warning immediately on failure, then as error every 10 minutes. 156 | now := time.Now() 157 | logfn := log.Debugf 158 | if failedAt.IsZero() { // First failure since connection 159 | failedAt = now 160 | erroredAt = failedAt // Start the timer on raising to ERROR level 161 | logfn = log.Warningf 162 | } else if now.Sub(erroredAt) > connectionFailureErrorLogPeriodMinutes { 163 | logfn = log.Errorf 164 | erroredAt = now // Reset the timer 165 | } 166 | failureDuration := now.Sub(failedAt) 167 | logfn("Heartbeat service failed to connect for %s: %s", failureDuration, err) 168 | // failed to connect, continue reconnect loop 169 | continue 170 | } 171 | failedAt = time.Time{} 172 | 173 | // we connected, send heartbeats! 174 | // Run will block until it fails to send a heartbeat. 175 | //如果连接成功,运行心跳服务 176 | if err := hbs.Run(ctx); err != nil { 177 | log.Warning("disconnecting from aggregator, failed to send heartbeat") 178 | continue 179 | } 180 | } 181 | } 182 | } 183 | ``` 184 | 185 | 186 | - 运行心跳服务 187 | 188 | ``` 189 | // Run is called once the heartbeat service connects to the aggregator. Run 190 | // send the actual heartbeat. Run will block until `ctx` is 'Done`. An error will 191 | // be returned if Run encounters an error when sending the heartbeat and the connection 192 | // to the aggregator will be closed. 193 | func (hbs *HeartbeatService) Run(ctx context.Context) error { 194 | bd, err := time.ParseDuration(hbs.Config.BeatPeriod) 195 | if err != nil { 196 | log.Errorf("invalid heartbeat beatPeriod: %s", err) 197 | return err 198 | } 199 | 200 | //启动心跳定时器 201 | beatTicker := time.NewTicker(bd) 202 | defer beatTicker.Stop() 203 | 204 | //通过encoder进行流写入 205 | // TODO use cbor instead of json 206 | encoder := json.NewEncoder(hbs.stream) 207 | for { 208 | select { 209 | case <-ctx.Done(): 210 | return nil 211 | case <-beatTicker.C: 212 | //心跳定时周期到,调用Beat方法获取心跳参数 213 | hb := hbs.Beat() 214 | //写入流,发起心跳 215 | if err := encoder.Encode(hb); err != nil { 216 | //发生错误会关闭流连接 217 | hbs.stream.Conn().Close() // nolint: errcheck 218 | return err 219 | } 220 | } 221 | } 222 | } 223 | ``` 224 | 225 | - 获取心跳参数 226 | 227 | ``` 228 | // Beat will create a heartbeat. 229 | func (hbs *HeartbeatService) Beat() Heartbeat { 230 | nick := hbs.Config.Nickname 231 | ts := hbs.HeadGetter() 232 | tipset := ts.ToSortedCidSet().String() 233 | height, err := ts.Height() 234 | if err != nil { 235 | log.Warningf("heartbeat service failed to get chain height: %s", err) 236 | } 237 | addr := hbs.MinerAddressGetter() 238 | return Heartbeat{ 239 | Head: tipset, 240 | Height: height, 241 | Nickname: nick, 242 | MinerAddress: addr, 243 | } 244 | } 245 | ``` 246 | 247 | - 心跳流连接 248 | ``` 249 | // Connect will connects to `hbs.Config.BeatTarget` or returns an error 250 | func (hbs *HeartbeatService) Connect(ctx context.Context) error { 251 | log.Debugf("Heartbeat service attempting to connect, targetAddress: %s", hbs.Config.BeatTarget) 252 | targetMaddr, err := ma.NewMultiaddr(hbs.Config.BeatTarget) 253 | if err != nil { 254 | return err 255 | } 256 | 257 | pid, err := targetMaddr.ValueForProtocol(ma.P_P2P) 258 | if err != nil { 259 | return err 260 | } 261 | 262 | peerid, err := peer.IDB58Decode(pid) 263 | if err != nil { 264 | return err 265 | } 266 | 267 | // Decapsulate the /p2p/ part from the target 268 | // /ip4//p2p/ becomes /ip4/ 269 | targetPeerAddr, _ := ma.NewMultiaddr( 270 | fmt.Sprintf("/p2p/%s", peer.IDB58Encode(peerid))) 271 | targetAddr := targetMaddr.Decapsulate(targetPeerAddr) 272 | 273 | hbs.Host.Peerstore().AddAddr(peerid, targetAddr, pstore.PermanentAddrTTL) 274 | 275 | // 建立心跳服务流 276 | s, err := hbs.Host.NewStream(ctx, peerid, HeartbeatProtocol) 277 | if err != nil { 278 | log.Debugf("failed to open stream, peerID: %s, targetAddr: %s %s", peerid, targetAddr, err) 279 | return err 280 | } 281 | log.Infof("successfully to open stream, peerID: %s, targetAddr: %s", peerid, targetAddr) 282 | 283 | //设置流函数 284 | hbs.SetStream(s) 285 | return nil 286 | } 287 | ``` 288 | 289 | ### 5.2.3 函数 290 | - 向心跳服务结构体传参,用于设置获取矿工地址函数 291 | 292 | ``` 293 | // WithMinerAddressGetter returns an option that can be used to set the miner address getter. 294 | func WithMinerAddressGetter(ag func() address.Address) HeartbeatServiceOption { 295 | return func(service *HeartbeatService) { 296 | service.MinerAddressGetter = ag 297 | } 298 | } 299 | ``` 300 | - 获取默认的矿工地址 301 | 302 | ``` 303 | func defaultMinerAddressGetter() address.Address { 304 | return address.Address{} 305 | } 306 | ``` 307 | 308 | - 实例化心跳服务,具体的实例化在node包中实现。 309 | 310 | ``` 311 | // NewHeartbeatService returns a HeartbeatService 312 | func NewHeartbeatService(h host.Host, hbc *config.HeartbeatConfig, hg func() types.TipSet, options ...HeartbeatServiceOption) *HeartbeatService { 313 | srv := &HeartbeatService{ 314 | Host: h, 315 | Config: hbc, 316 | HeadGetter: hg, 317 | MinerAddressGetter: defaultMinerAddressGetter, 318 | } 319 | 320 | // 设置心跳服务的获取矿工属性,这会覆盖到上面设置的默认矿工地址 321 | for _, option := range options { 322 | option(srv) 323 | } 324 | 325 | return srv 326 | } 327 | ``` 328 | 329 | ### 5.2.4 实例化及业务逻辑 330 | - 主要由node调用,location:node/node.go,主要逻辑如下 331 | 332 | - 在node的启动方法中,调用node.setupHeartbeatServices方法,建立心跳服务 333 | 334 | ``` 335 | // Start boots up the node. 336 | func (node *Node) Start(ctx context.Context) error { 337 | ...... 338 | 339 | if err := node.setupHeartbeatServices(ctx); err != nil { 340 | return errors.Wrap(err, "failed to start heartbeat services") 341 | } 342 | 343 | return nil 344 | } 345 | ``` 346 | 347 | - 建立心跳服务,具体见如下注释 348 | 349 | ``` 350 | func (node *Node) setupHeartbeatServices(ctx context.Context) error { 351 | // 设置“矿工地址获取函数” 352 | mag := func() address.Address { 353 | addr, err := node.miningAddress() 354 | // the only error miningAddress() returns is ErrNoMinerAddress. 355 | // if there is no configured miner address, simply send a zero 356 | // address across the wire. 357 | if err != nil { 358 | return address.Address{} 359 | } 360 | return addr 361 | } 362 | 363 | // 存在心跳目标的时候,实例化心跳服务实例 364 | // start the primary heartbeat service 365 | if len(node.Repo.Config().Heartbeat.BeatTarget) > 0 { 366 | //调用metrics包中的建立心跳服务实例、以及启动心跳服务实例方法 367 | hbs := metrics.NewHeartbeatService(node.Host(), node.Repo.Config().Heartbeat, node.ChainReader.Head, metrics.WithMinerAddressGetter(mag)) 368 | go hbs.Start(ctx) 369 | } 370 | 371 | // 确认是否用户有通过环境变量配置额外的心跳告警服务(自定义指向其他节点),根据用户配置的数目,拉起对应的多线程心跳服务。 372 | // check if we want to connect to an alert service. An alerting service is a heartbeat 373 | // service that can trigger alerts based on the contents of heatbeats. 374 | if alertTarget := os.Getenv("FIL_HEARTBEAT_ALERTS"); len(alertTarget) > 0 { 375 | ahbs := metrics.NewHeartbeatService(node.Host(), &config.HeartbeatConfig{ 376 | BeatTarget: alertTarget, 377 | BeatPeriod: "10s", 378 | ReconnectPeriod: "10s", 379 | Nickname: node.Repo.Config().Heartbeat.Nickname, 380 | }, node.ChainReader.Head, metrics.WithMinerAddressGetter(mag)) 381 | go ahbs.Start(ctx) 382 | } 383 | return nil 384 | } 385 | ``` 386 | -------------------------------------------------------------------------------- /6.md: -------------------------------------------------------------------------------- 1 | # filecoin技术架构分析之六:filecoin源码协议层分析之hello握手协议 2 | 3 | > 作者:杨尉(waynewyang),转载请注明出处 4 | 5 | 6 | ## 目录 7 | - 6 filecoin源码协议层分析之hello握手协议 8 | - 6.1 目的 9 | - 6.2 源码信息 10 | - 6.3 源码分析 11 | - 6.3.1 数据结构 12 | - 6.3.2 方法 13 | - 6.3.3 函数 14 | - 6.3.4 实例化及业务逻辑 15 | 16 | ## 6.1 目的 17 | - 处理节点上线后的区块同步握手。 18 | 19 | ## 6.2 源码信息 20 | 21 | - version 22 | - master分支 619b0eb1(2019年3月2日) 23 | - package 24 | - hello 25 | - location 26 | - protocol/hello 27 | - node/node.go 28 | 29 | ## 6.3 源码分析 30 | ### 6.3.1 数据结构 31 | 32 | - 定义协议名称 33 | 34 | ``` 35 | // Protocol is the libp2p protocol identifier for the hello protocol. 36 | const protocol = "/fil/hello/1.0.0" 37 | ``` 38 | 39 | - 定义hello协议消息体结构 40 | - TipSet切片 41 | - TipSet高度 42 | - 创世区块cid 43 | 44 | ``` 45 | // Message is the data structure of a single message in the hello protocol. 46 | type Message struct { 47 | HeaviestTipSetCids []cid.Cid 48 | HeaviestTipSetHeight uint64 49 | GenesisHash cid.Cid 50 | } 51 | ``` 52 | 53 | - 同步回调函数类型定义 54 | 55 | ``` 56 | type syncCallback func(from peer.ID, cids []cid.Cid, height uint64) 57 | ``` 58 | 59 | - 获取Tipset函数类型定义 60 | 61 | ``` 62 | type getTipSetFunc func() types.TipSet 63 | ``` 64 | 65 | - Handler结构体,当连接到其他节点的时候,其一,会发送包含本节点信息的hello 消息给对端节点; 其二, 对端也会回复一个包含对端节点信息的消息体过来。 66 | - host 对应libp2p上的主机 67 | - 创世区块cid 68 | - 区块同步回调函数 69 | - 获取TipSet的函数 70 | 71 | ``` 72 | // Handler implements the 'Hello' protocol handler. Upon connecting to a new 73 | // node, we send them a message containing some information about the state of 74 | // our chain, and receive the same information from them. This is used to 75 | // initiate a chainsync and detect connections to forks. 76 | type Handler struct { 77 | host host.Host 78 | 79 | genesis cid.Cid 80 | 81 | // chainSyncCB is called when new peers tell us about their chain 82 | chainSyncCB syncCallback 83 | 84 | // getHeaviestTipSet is used to retrieve the current heaviest tipset 85 | // for filling out our hello messages. 86 | getHeaviestTipSet getTipSetFunc 87 | } 88 | ``` 89 | 90 | - 错误的创世区块 91 | 92 | ``` 93 | // ErrBadGenesis is the error returned when a missmatch in genesis blocks happens. 94 | var ErrBadGenesis = fmt.Errorf("bad genesis block") 95 | ``` 96 | 97 | - 以上基本是作为hello客户端的一些定义,以下作为hello服务端的一些定义 98 | 99 | ``` 100 | // New peer connection notifications 101 | type helloNotify Handler 102 | 103 | // 连接超时时间 104 | const helloTimeout = time.Second * 10 105 | ``` 106 | 107 | ### 6.3.2 方法 108 | #### 6.3.2.1 Handler 方法 109 | 110 | - 流函数处理,接收远端节点的hello消息 111 | 112 | 113 | ``` 114 | func (h *Handler) handleNewStream(s net.Stream) { 115 | defer s.Close() // nolint: errcheck 116 | 117 | //获取远端节点实例 118 | from := s.Conn().RemotePeer() 119 | 120 | var hello Message 121 | // 读取流信息到hello结构体中 122 | if err := cbu.NewMsgReader(s).ReadMsg(&hello); err != nil { 123 | log.Warningf("bad hello message from peer %s: %s", from, err) 124 | return 125 | } 126 | 127 | // 调用processHelloMessage方法对接收到的消息进行处理 128 | switch err := h.processHelloMessage(from, &hello); err { 129 | // 如果创世区块不一样,关闭流连接退出,不予处理 130 | case ErrBadGenesis: 131 | log.Warningf("genesis cid: %s does not match: %s, disconnecting from peer: %s", &hello.GenesisHash, h.genesis, from) 132 | s.Conn().Close() // nolint: errcheck 133 | return 134 | case nil: // ok, noop 135 | default: 136 | log.Error(err) 137 | } 138 | } 139 | ``` 140 | 141 | - 处理hello消息 142 | 143 | ``` 144 | func (h *Handler) processHelloMessage(from peer.ID, msg *Message) error { 145 | // 如果创世区块不一样,报错 146 | if !msg.GenesisHash.Equals(h.genesis) { 147 | return ErrBadGenesis 148 | } 149 | 150 | // 调用区块同步方法 151 | // 此回调函数实在node包实例化hello协议的时候中定义的 152 | h.chainSyncCB(from, msg.HeaviestTipSetCids, msg.HeaviestTipSetHeight) 153 | return nil 154 | } 155 | ``` 156 | 157 | - 响应远端节点的连接,回复hello消息体 158 | 159 | ``` 160 | func (h *Handler) getOurHelloMessage() *Message { 161 | heaviest := h.getHeaviestTipSet() 162 | height, err := heaviest.Height() 163 | if err != nil { 164 | panic("somehow heaviest tipset is empty") 165 | } 166 | 167 | return &Message{ 168 | GenesisHash: h.genesis, 169 | HeaviestTipSetCids: heaviest.ToSortedCidSet().ToSlice(), 170 | HeaviestTipSetHeight: height, 171 | } 172 | } 173 | 174 | func (h *Handler) sayHello(ctx context.Context, p peer.ID) error { 175 | s, err := h.host.NewStream(ctx, p, protocol) 176 | if err != nil { 177 | return err 178 | } 179 | defer s.Close() // nolint: errcheck 180 | 181 | //获取本节点的hello消息体 182 | msg := h.getOurHelloMessage() 183 | 184 | //向远端节点发送消息体 185 | return cbu.NewMsgWriter(s).WriteMsg(&msg) 186 | } 187 | ``` 188 | 189 | #### 6.3.2.2 helloNotify方法 190 | 191 | - hello方法,返回一个handler实例 192 | 193 | ``` 194 | func (hn *helloNotify) hello() *Handler { 195 | return (*Handler)(hn) 196 | } 197 | ``` 198 | 199 | - helloNotify实现了libp2p-net/interface.go中的Notifiee接口 200 | 201 | ``` 202 | func (hn *helloNotify) Connected(n net.Network, c net.Conn) { 203 | go func() { 204 | ctx, cancel := context.WithTimeout(context.Background(), helloTimeout) 205 | defer cancel() 206 | p := c.RemotePeer() 207 | // 有其他节点连接的时候调用sayHello,发送hello消息体 208 | if err := hn.hello().sayHello(ctx, p); err != nil { 209 | log.Warningf("failed to send hello handshake to peer %s: %s", p, err) 210 | } 211 | }() 212 | } 213 | 214 | func (hn *helloNotify) Listen(n net.Network, a ma.Multiaddr) {} 215 | func (hn *helloNotify) ListenClose(n net.Network, a ma.Multiaddr) {} 216 | func (hn *helloNotify) Disconnected(n net.Network, c net.Conn) {} 217 | func (hn *helloNotify) OpenedStream(n net.Network, s net.Stream) {} 218 | func (hn *helloNotify) ClosedStream(n net.Network, s net.Stream) {} 219 | ``` 220 | 221 | ### 6.3.3 函数 222 | 223 | - 创建hello实例 224 | 225 | ``` 226 | // New creates a new instance of the hello protocol and registers it to 227 | // the given host, with the provided callbacks. 228 | func New(h host.Host, gen cid.Cid, syncCallback syncCallback, getHeaviestTipSet getTipSetFunc) *Handler { 229 | hello := &Handler{ 230 | host: h, 231 | genesis: gen, 232 | chainSyncCB: syncCallback, 233 | getHeaviestTipSet: getHeaviestTipSet, 234 | } 235 | 236 | //设置流处理回调函数 237 | h.SetStreamHandler(protocol, hello.handleNewStream) 238 | 239 | //注册网络状态改变通知回调函数 240 | // register for connection notifications 241 | h.Network().Notify((*helloNotify)(hello)) 242 | 243 | return hello 244 | } 245 | 246 | 247 | 248 | //上文中的helloNotify 实现了libp2p-net/interface.go中的Notifiee接口 249 | // Notifiee is an interface for an object wishing to receive 250 | // notifications from a Network. 251 | type Notifiee interface { 252 | Listen(Network, ma.Multiaddr) // called when network starts listening on an addr 253 | ListenClose(Network, ma.Multiaddr) // called when network stops listening on an addr 254 | Connected(Network, Conn) // called when a connection opened 255 | Disconnected(Network, Conn) // called when a connection closed 256 | OpenedStream(Network, Stream) // called when a stream opened 257 | ClosedStream(Network, Stream) // called when a stream closed 258 | 259 | // TODO 260 | // PeerConnected(Network, peer.ID) // called when a peer connected 261 | // PeerDisconnected(Network, peer.ID) // called when a peer disconnected 262 | } 263 | ``` 264 | 265 | ### 6.3.4 实例化及业务逻辑 266 | 267 | - location: node/node.go 268 | 269 | - Node节点中定义了hello服务 270 | 271 | ``` 272 | type Node struct { 273 | ...... 274 | 275 | HelloSvc *hello.Handler 276 | ...... 277 | } 278 | ``` 279 | 280 | - 启动hello服务 281 | 282 | ``` 283 | // Start boots up the node. 284 | func (node *Node) Start(ctx context.Context) error { 285 | ...... 286 | 287 | // Start up 'hello' handshake service 288 | // 定义区块同步的回调函数 289 | syncCallBack := func(pid libp2ppeer.ID, cids []cid.Cid, height uint64) { 290 | // TODO it is possible the syncer interface should be modified to 291 | // make use of the additional context not used here (from addr + height). 292 | // To keep things simple for now this info is not used. 293 | // 触发调用会启动同步区块的动作 294 | err := node.Syncer.HandleNewBlocks(context.Background(), cids) 295 | if err != nil { 296 | log.Infof("error handling blocks: %s", types.NewSortedCidSet(cids...).String()) 297 | } 298 | } 299 | //实例化hello服务 300 | node.HelloSvc = hello.New(node.Host(), node.ChainReader.GenesisCid(), syncCallBack, node.ChainReader.Head) 301 | 302 | ...... 303 | } 304 | ``` 305 | -------------------------------------------------------------------------------- /7.md: -------------------------------------------------------------------------------- 1 | # filecoin技术架构分析之七:filecoin源码协议层分析之存储协议 2 | 3 | > 作者:杨尉(waynewyang),转载请注明出处 4 | 5 | 6 | ## 目录 7 | - 7 filecoin源码协议层分析之存储协议 8 | - 7.1 协议概览图 9 | - 7.2 源码信息 10 | - 7.3 源码分析 11 | - 7.3.1 存储矿工 12 | - 7.3.2 存储客户 13 | 14 | ## 7.1 协议概览图 15 | ![](img/storage_protocol.png) 16 | 17 | ## 7.2 源码信息 18 | 19 | - version 20 | - master分支 619b0eb1(2019年3月2日) 21 | - package 22 | - storage 23 | - location 24 | - protocol/storage 25 | 26 | ## 7.3 源码分析 27 | 28 | ### 7.3.1 存储矿工 29 | 30 | ``` 31 | ▼ package 32 | storage 33 | 34 | ▶ imports 35 | 36 | ▼ constants 37 | //等待密封数据前缀 38 | -dealsAwatingSealDatastorePrefix 39 | // 存储交易协议名称:"/fil/storage/mk/1.0.0" 40 | -makeDealProtocol 41 | // 矿工数据存储前缀 42 | -minerDatastorePrefix 43 | // 存储查询协议名称:"/fil/storage/qry/1.0.0" 44 | -queryDealProtocol 45 | // Gas及Gas限制 46 | -submitPostGasLimit 47 | -submitPostGasPrice 48 | // 支付通道建立等待时间 49 | -waitForPaymentChannelDuration 50 | 51 | ▼ variables 52 | -log 53 | 54 | ▼+Miner : struct 55 | [fields] 56 | // 交易集合 57 | -deals : map[cid.Cid]*storageDeal 58 | // 等待密封结构体 59 | -dealsAwaitingSeal : *dealsAwaitingSealStruct 60 | // 交易的资源对象 61 | -dealsDs : repo.Datastore 62 | // 交易锁 63 | -dealsLk : sync.Mutex 64 | // 存储矿工地址 65 | -minerAddr : address.Address 66 | // 节点的Owner地址 67 | -minerOwnerAddr : address.Address 68 | // 节点对象,有定义存储矿工必须实现的接口 69 | -node : node 70 | // 存储矿工的高层API 71 | -porcelainAPI : minerPorcelain 72 | // 是否在生成时空证明中,以及对应的锁 73 | -postInProcess : *types.BlockHeight 74 | -postInProcessLk : sync.Mutex 75 | // 接受交易以及拒绝交易 76 | -proposalAcceptor : func(ctx context.Context, m *Miner, p *DealProposal) *DealResponse, error 77 | -proposalRejector : func(ctx context.Context, m *Miner, p *DealProposal, reason string) *DealResponse, error 78 | 79 | [methods] 80 | // 密封消息提交到区块链时候,所执行的回调函数,在node中执行 81 | // 1 失败,则调用dealsAwaitingSeal.fail 82 | // 2 成功,则调用dealsAwaitingSeal.success 83 | // 3 成功之后,需要保存密封扇区信息,如果失败调用dealsAwaitingSeal.fail 84 | +OnCommitmentAddedToChain(sector *sectorbuilder.SealedSectorMetadata, err error) 85 | // 新区块产生的回调,由node调用,它将会触发新的存储证明 86 | // 如果时空证明过期,将会在新的周期重新出发时空证明 87 | +OnNewHeaviestTipSet(ts types.TipSet) 88 | // 由handleQueryDeal调用,返回查询结果 89 | +Query(ctx context.Context, c cid.Cid) : *DealResponse 90 | // 生成时空证明 91 | -generatePoSt(commRs []proofs.CommR, challenge proofs.PoStChallengeSeed) : proofs.PoStProof, []uint64, error 92 | // 获取支付通道信息 93 | // 1 等待支付通道建立完成 94 | // 2 获取支付通道信息并返回 95 | // 3 支付信息包括:合约地址、支付者地址、通道信息、支付通道消息cid、支付凭证合集 96 | -getPaymentChannel(ctx context.Context, p *DealProposal) : *paymentbroker.PaymentChannel, error 97 | // 获取新的时空证明时间 98 | -getProvingPeriodStart() : *types.BlockHeight, error 99 | // 获取存储矿工的特定交易 100 | -getStorageDeal(c cid.Cid) : *storageDeal 101 | // 获取存储矿工报价 102 | -getStoragePrice() : *types.AttoFIL, error 103 | // 存储交易请求的入口方法,交易请求流的handle函数 104 | // 1 读取流中交易请求信息 105 | // 2 调用receiveStorageProposal处理交易请求 106 | // 3 回复处理回复 107 | -handleMakeDeal(s inet.Stream) 108 | //解析具体流信息,处理查询请求,会调用Query请求 109 | -handleQueryDeal(s inet.Stream) 110 | // 从资源目录中加载交易信息到Miner实例中 111 | -loadDeals() : error 112 | // 加载待密封的信息 113 | -loadDealsAwaitingSeal() : error 114 | // 密封失败,更新响应信息 115 | -onCommitFail(dealCid cid.Cid, message string) 116 | // 密封成功,更新响应信息 117 | // 1 切换状态至Posted 118 | // 2 更新证明信息:扇区ID,副本信息,原始数据信息 119 | -onCommitSuccess(dealCid cid.Cid, sector *sectorbuilder.SealedSectorMetadata) 120 | // 处理存储交易 121 | // 1,获取存储交易信息 122 | // 2,数据处理,密封 123 | -processStorageDeal(c cid.Cid) 124 | // 处理交易请求 125 | // 1 检查签名的正确性 126 | // 2 检查支付信息正确性,调用validateDealPayment方法 127 | // 3 不合法调用proposalRejector(rejectProposal)拒绝请求;合法调用proposalAcceptor(acceptProposal)回复 128 | -receiveStorageProposal(ctx context.Context, sp *SignedDealProposal) : *DealResponse, error 129 | // 从Miner对象中存储交易信息到资源目录中 130 | -saveDeal(proposalCid cid.Cid) : error 131 | // 存储待密封信息至资源目录 132 | -saveDealsAwaitingSeal() : error 133 | // 提交时空证明 134 | // 1 产生随机种子 135 | // 2 根据时空证明输入长度,生成副本切片 136 | // 3 随机种子+副本切片作为输入生成时空证明 137 | // 4 调用高层接口发送消息 138 | -submitPoSt(start, end *types.BlockHeight, inputs []generatePostInput) 139 | // 更新交易响应消息 140 | -updateDealResponse(proposalCid cid.Cid, f func(*DealResponse)) : error 141 | // 检查支付信息的正确性 142 | // 1 客户出价必须高于矿工报价 143 | // 2 收款人必须为本节点矿工 144 | // 3 支付通道总资金必须大于矿工报价 145 | // 4 必须有交易凭证,且交易凭证总金额必须大于矿工报价 146 | -validateDealPayment(ctx context.Context, p *DealProposal) : error 147 | 148 | [functions] 149 | // 实例化存储矿工 150 | // 1 通过node传参赋值 151 | // 2 指定密封成功失败的回调函数 152 | // 3 设置交易请求以及交易查询的流handle方法 153 | +NewMiner(ctx context.Context, minerAddr, minerOwnerAddr address.Address, nd node, dealsDs repo.Datastore, porcelainAPI minerPorcelain) : *Miner, error 154 | 155 | ▼-dealsAwaitingSealStruct : struct 156 | [fields] 157 | // 从扇区id获取失败信息 158 | +FailedSectors : map[uint64]string 159 | // 从扇区id获取交易的cid 160 | +SectorsToDeals : map[uint64][]cid.Cid 161 | // 从扇区id获取sector元数据 162 | +SuccessfulSectors : map[uint64]*sectorbuilder.SealedSectorMetadata 163 | -l : sync.Mutex 164 | // 失败处理回调,在实例化Miner指向onCommitFail 165 | -onFail : func(dealCid cid.Cid, message string) 166 | // 成功处理回调,在实例化Miner指向onCommitSuccess 167 | -onSuccess : func(dealCid cid.Cid, sector *sectorbuilder.SealedSectorMetadata) 168 | 169 | [methods] 170 | // 对数据进行密封 171 | -add(sectorID uint64, dealCid cid.Cid) 172 | // 密封失败处理dealsAwaitingSeal.onFail 173 | -fail(sectorID uint64, message string) 174 | // 密封成功处理dealsAwaitingSeal.onSuccess 175 | -success(sector *sectorbuilder.SealedSectorMetadata) 176 | 177 | ▼-generatePostInput : struct 178 | [fields] 179 | // 副本merkle根 180 | -commD : proofs.CommD 181 | // 原始数据merkle根 182 | -commR : proofs.CommR 183 | // 中间数据merkle根 184 | -commRStar : proofs.CommRStar 185 | // 扇区ID 186 | -sectorID : uint64 187 | 188 | ▼-storageDeal : struct 189 | [fields] 190 | // 交易请求结构体 191 | +Proposal : *DealProposal 192 | // 交易请求响应结构体 193 | +Response : *DealResponse 194 | 195 | // 存储矿工高层API 196 | ▼-minerPorcelain : interface 197 | [methods] 198 | // 区块高度 199 | +ChainBlockHeight(ctx context.Context) : *types.BlockHeight, error 200 | // 获取配置 201 | +ConfigGet(dottedPath string) : interface{}, error 202 | // 发送、查询、等待消息 203 | +MessageQuery(ctx context.Context, optFrom, to address.Address, method string, params ...interface{}) : [][]byte, *exec.FunctionSignature, error 204 | +MessageSend(ctx context.Context, from, to address.Address, value *types.AttoFIL, gasPrice types.AttoFIL, gasLimit types.GasUnits, method string, params ...interface{}) : cid.Cid, error 205 | +MessageWait(ctx context.Context, msgCid cid.Cid, cb func(*types.Block, *types.SignedMessage, *types.MessageReceipt) error) : error 206 | 207 | ▼-node : interface 208 | [methods] 209 | // 区块高度 210 | +BlockHeight() : *types.BlockHeight, error 211 | // 区块服务,存储/查询服务 212 | +BlockService() : bserv.BlockService 213 | // 区块时间 214 | +GetBlockTime() : time.Duration 215 | // 主机信息 216 | +Host() : host.Host 217 | // 扇区创建,具体包含 218 | // 1 增加、读取piece; 219 | // 2 密封所有非空分期扇区 220 | // 3 密封结果通过返回,通过通道channel的方式 221 | // 4 获取扇区中最大的piece字节大小 222 | // 5 生成时空证明 223 | +SectorBuilder() : sectorbuilder.SectorBuilder 224 | 225 | ▼ functions 226 | // 存储交易信息之后,调用processStorageDeal处理交易信息 227 | -acceptProposal(ctx context.Context, sm *Miner, p *DealProposal) : *DealResponse, error 228 | // 获取具体文件大小 229 | -getFileSize(ctx context.Context, c cid.Cid, dserv ipld.DAGService) : uint64, error 230 | -init() 231 | // 存储交易信息,更新响应消息,并返回 232 | -rejectProposal(ctx context.Context, sm *Miner, p *DealProposal, reason string) : *DealResponse, error 233 | ``` 234 | 235 | ### 7.3.2 存储客户 236 | 237 | ``` 238 | ▼ package 239 | storage 240 | 241 | ▼ imports 242 | 243 | ▼ constants 244 | +ChannelExpiryInterval 245 | // Gas及Gas限制 246 | +CreateChannelGasLimit 247 | +CreateChannelGasPrice 248 | +ErrDupicateDeal 249 | // 建立Voucher的周期 250 | +VoucherInterval 251 | // 存储前缀 252 | -clientDatastorePrefix 253 | 254 | ▼ variables 255 | +Errors 256 | 257 | ▼+Client : struct 258 | [fields] 259 | 260 | // 存储客户高层API 261 | -api : clientPorcelainAPI 262 | // 交易集合 263 | -deals : map[cid.Cid]*clientDeal 264 | // 交易资源目录对象及锁 265 | -dealsDs : repo.Datastore 266 | -dealsLk : sync.Mutex 267 | // 存储客户节点 268 | -node : clientNode 269 | 270 | [methods] 271 | // 加载特定交易的凭证 272 | +LoadVouchersForDeal(dealCid cid.Cid) : []*paymentbroker.PaymentVoucher, error 273 | 274 | // 发起存储交易 275 | // 1 获取文件大小、矿工报价、区块高度、目的地址 276 | // 2 建立支付通道 277 | // 3 调用MakeProtocolRequest发起交易请求 278 | // 4 检查交易响应 279 | // 5 持久化交易响应并回复 280 | +ProposeDeal(ctx context.Context, miner address.Address, data cid.Cid, askID uint64, duration uint64, allowDuplicates bool) : *DealResponse, error 281 | 282 | // 查询交易 283 | // 1 获取矿工信息,地址、节点ID 284 | // 2 调用MakeProtocolRequest发起请求 285 | +QueryDeal(ctx context.Context, proposalCid cid.Cid) : *DealResponse, error 286 | 287 | // 检查交易响应 288 | -checkDealResponse(ctx context.Context, resp *DealResponse) : error 289 | 290 | // 判断是否为重复交易 291 | -isMaybeDupDeal(p *DealProposal) : bool 292 | 293 | // 加载交易信息 294 | -loadDeals() : error 295 | 296 | // 返回目标矿工地址 297 | -minerForProposal(c cid.Cid) : address.Address, error 298 | 299 | // 持久化交易响应 300 | -recordResponse(resp *DealResponse, miner address.Address, p *DealProposal) : error 301 | 302 | // 保存交易信息 303 | -saveDeal(cid cid.Cid) : error 304 | 305 | [functions] 306 | // 实例化存储客户 307 | +NewClient(nd clientNode, api clientPorcelainAPI, dealsDs repo.Datastore) : *Client, error 308 | 309 | ▼+ClientNodeImpl : struct 310 | [fields] 311 | -blockTime : time.Duration 312 | -dserv : ipld.DAGService 313 | -host : host.Host 314 | 315 | [methods] 316 | //实现clientNode接口 317 | +GetBlockTime() : time.Duration 318 | 319 | // 获取文件大小 320 | +GetFileSize(ctx context.Context, c cid.Cid) : uint64, error 321 | 322 | // 发起协议请求 323 | // 1 建立对应的存储交易或者请求的协议流 324 | // 2 发起请求 325 | +MakeProtocolRequest(ctx context.Context, protocol protocol.ID, peer peer.ID, request interface{}, response interface{}) : error 326 | 327 | [functions] 328 | // 实例化客户节点 329 | +NewClientNodeImpl(ds ipld.DAGService, host host.Host, bt time.Duration) : *ClientNodeImpl 330 | 331 | ▼-clientDeal : struct 332 | [fields] 333 | // 目标矿工,请求及响应 334 | +Miner : address.Address 335 | +Proposal : *DealProposal 336 | +Response : *DealResponse 337 | 338 | ▼-clientNode : interface 339 | // 由ClientNodeImpl实现 340 | [methods] 341 | +GetBlockTime() : time.Duration 342 | +GetFileSize(context.Context, cid.Cid) : uint64, error 343 | +MakeProtocolRequest(ctx context.Context, protocol protocol.ID, peer peer.ID, request interface{}, response interface{}) : error 344 | 345 | ▼-clientPorcelainAPI : interface 346 | [embedded] 347 | +types.Signer 348 | 349 | [methods] 350 | // 获取区块高度 351 | +ChainBlockHeight(ctx context.Context) : *types.BlockHeight, error 352 | // 创建支付通道 353 | // 包括源及目的地址,价格,时间,支付间隔,通道超时时间,Gas及限制 354 | +CreatePayments(ctx context.Context, config porcelain.CreatePaymentsParams) : *porcelain.CreatePaymentsReturn, error 355 | // 获取目标地址 356 | +GetAndMaybeSetDefaultSenderAddress() : address.Address, error 357 | // 获取矿工报价 358 | +MinerGetAsk(ctx context.Context, minerAddr address.Address, askID uint64) : miner.Ask, error 359 | // 获取矿工Owner地址 360 | +MinerGetOwnerAddress(ctx context.Context, minerAddr address.Address) : address.Address, error 361 | // 获取矿工节点ID 362 | +MinerGetPeerID(ctx context.Context, minerAddr address.Address) : peer.ID, error 363 | 364 | ▼ functions 365 | -init() 366 | ``` 367 | -------------------------------------------------------------------------------- /8.md: -------------------------------------------------------------------------------- 1 | # filecoin技术架构分析之八:filecoin源码分析之协议层检索协议 2 | 3 | > 作者:杨尉(waynewyang),转载请注明出处 4 | 5 | 6 | ## 目录 7 | - 8 filecoin源码协议层分析之检索协议 8 | - 8.1 协议概览图 9 | - 8.2 源码信息 10 | - 8.3 源码分析 11 | - 8.3.1 检索矿工 12 | - 8.3.2 检索客户 13 | 14 | ## 8.1 协议概览图 15 | ![](img/retrieval_protocol.png) 16 | 17 | - 此概览图为当前的实现,整个检索的代码还没有完善 18 | - 目前的逻辑比较简单,需要指定矿工、内容cid即可进行免费检索 19 | 20 | ## 8.2 源码信息 21 | 22 | - version 23 | - master分支 619b0eb1(2019年3月2日) 24 | - package 25 | - retrieval 26 | - location 27 | - protocol/retrieval 28 | 29 | ## 8.3 源码分析 30 | 31 | ### 8.3.1 检索矿工 32 | 33 | ``` 34 | ▼ package 35 | retrieval 36 | 37 | ▼ imports 38 | github.com/filecoin-project/go-filecoin/cborutil 39 | github.com/filecoin-project/go-filecoin/proofs/sectorbuilder 40 | gx/ipfs/QmTGxDz2CjBucFzPNTiWwzQmTWdrBnzqbqrMucDYMsjuPb/go-libp2p-net 41 | gx/ipfs/QmZNkThpqfVXs9GNbexPrfBbXSLNYeKrE7jwFM2oqHbyqN/go-libp2p-protocol 42 | gx/ipfs/QmbkT7eMTyXfpeyB3ZMxxcxg7XH8t6uXp49jqzz4HB7BGF/go-log 43 | gx/ipfs/Qmd52WKRSwrBK5gUaJKawryZQ5by6UbNB8KVW2Zy6JtbyW/go-libp2p-host 44 | io/ioutil 45 | 46 | ▼ constants 47 | // 定义检索协议: "/fil/retrieval/free/0.0.0" 48 | -retrievalFreeProtocol 49 | 50 | ▼ variables 51 | -log 52 | 53 | ▼+Miner : struct 54 | [fields] 55 | // 矿工节点,参见minerNode 56 | -node : minerNode 57 | 58 | [methods] 59 | // 执行具体的检索服务 60 | // 通过解析协议流数据,执行检索动作并返回 61 | -handleRetrievePieceForFree(s inet.Stream) 62 | 63 | [functions] 64 | // 实例化检索矿工 65 | // 设置处理免费检索的handle方法:handleRetrievePieceForFree 66 | +NewMiner(nd minerNode) : *Miner 67 | 68 | ▼-minerNode : interface 69 | [methods] 70 | +Host() : host.Host 71 | +SectorBuilder() : sectorbuilder.SectorBuilder 72 | ``` 73 | 74 | ### 8.3.2 检索客户 75 | 76 | ``` 77 | ▼ package 78 | retrieval 79 | 80 | ▶ imports 81 | 82 | ▼ constants 83 | // 检索内容大小限制 84 | +RetrievePieceChunkSize 85 | 86 | ▼+Client : struct 87 | [fields] 88 | -node : clientNode 89 | 90 | [methods] 91 | // 通过cid进行检索 92 | // 通过协议流,发送检索请求以及接受检索回复和数据 93 | +RetrievePiece(ctx context.Context, minerPeerID peer.ID, pieceCID cid.Cid) : io.ReadCloser, error 94 | 95 | [functions] 96 | // 实例化检索客户 97 | +NewClient(nd clientNode) : *Client 98 | 99 | ▼-clientNode : interface 100 | [methods] 101 | +Host() : host.Host 102 | ``` 103 | -------------------------------------------------------------------------------- /9.md: -------------------------------------------------------------------------------- 1 | # filecoin技术架构分析之九:filecoin源码分析之支撑包分析(1) 2 | 3 | > 作者:杨尉(waynewyang),转载请注明出处 4 | 5 | 6 | ## 目录 7 | - 9 filecoin源码分析之支撑包分析(1) 8 | - 9.1 目的 9 | - 9.2 编译相关 10 | - 9.3 cborutil 11 | - 9.4 address 12 | - 9.5 config 13 | - 9.6 crypto 14 | - 9.7 util/convert 15 | - 9.8 functional-tests 16 | - 9.9 flags 17 | - 9.10 fixtures 18 | - 9.11 filnet 19 | 20 | ## 9.1 目的 21 | > 简析一些支撑包,便于后面分析的理解 22 | 23 | ## 9.2 编译相关 24 | - bin目录:主要为编译用shell脚本 25 | - bls-signatures: 通过cgo编译,导出库及头文件 26 | - build: 编译相关 27 | - util/version:版本检查 28 | - scripts:相关脚本 29 | 30 | ## 9.3 cborutil 31 | 32 | - 对外提供功能 33 | - 读取流消息 34 | - 写入流消息 35 | - 主要被协议层使用 36 | 37 | ``` 38 | ▼ package 39 | cborutil 40 | 41 | ▶ imports 42 | 43 | ▼ constants 44 | +MaxMessageSize 45 | 46 | ▼ variables 47 | +ErrMessageTooLarge 48 | 49 | ▼+MsgReader : struct 50 | [fields] 51 | -br : *bufio.Reader 52 | [methods] 53 | +ReadMsg(i interface{}) : error 54 | [functions] 55 | +NewMsgReader(r io.Reader) : *MsgReader 56 | ``` 57 | 58 | ``` 59 | ▼ package 60 | cborutil 61 | 62 | ▶ imports 63 | 64 | ▼+MsgWriter : struct 65 | [fields] 66 | -w : *bufio.Writer 67 | [methods] 68 | +WriteMsg(i interface{}) : error 69 | [functions] 70 | +NewMsgWriter(w io.Writer) : *MsgWriter 71 | ``` 72 | 73 | ## 9.4 address 74 | 75 | - 对外提供功能 76 | - 地址相关操作功能 77 | - 实例化铸币地址、存储市场地址、支付通道地址 78 | - 实例化两个测试地址 79 | - 提供主网地址、测试网地址创建接口 80 | - 提供地址格式转换功能,包含22bytes与41bytes、切片字符串转换、打印。 81 | - 提供地址的合法性检查功能 82 | - 地址格式 83 | - 要与id区分开,id用的是ipfs中的cid,而地址则是filecoin独立定义的。 84 | - 22 bytes地址:包含1byte网络类型、1byte地址版本、20bytes哈希 85 | - 41 bytes地址:包含2bytes网络类型、1byte地址版本、32bytes编码值、6bytes校验和 86 | - 用命令显示的是41bytes格式的地址,address包提供了22bytes与41bytes地址的转换接口 87 | 88 | ``` 89 | location: address/constants.go 90 | 91 | ▼ package 92 | address 93 | 94 | ▶ imports 95 | 96 | ▼ constants 97 | // Base32编码的字符集 98 | +Base32Charset 99 | // 地址的哈希部分,目前为20 bytes 100 | +HashLength, 20bytes,160bit 101 | // 地址长度,为HashLength+1+1= 22 bytes 102 | +Length 103 | // 地址格式的版本定义:当前为0 104 | +Version : byte 105 | 106 | ▼ variables 107 | // 基于Base32Charset的Base32实例,用于编解码 108 | +Base32 109 | // Base32 Reverse集合 110 | +Base32CharsetReverse 111 | // 铸币地址,基于"filecoin"哈希生成 112 | +NetworkAddress : Address 113 | // 支付通道地址 114 | +PaymentBrokerAddress : Address 115 | // 存储市场地址 116 | +StorageMarketAddress : Address 117 | // 测试地址 118 | +TestAddress : Address 119 | // 测试地址 120 | +TestAddress2 : Address 121 | 122 | ▼ functions 123 | -init() 124 | ``` 125 | 126 | ``` 127 | location: address/address.go 128 | 129 | ▼ package 130 | address 131 | 132 | ▶ imports 133 | 134 | ▼ constants 135 | +Mainnet : Network 136 | +Testnet 137 | 138 | ▼ variables 139 | // 错误提示 140 | +ErrInvalidBytes 141 | +ErrUnknownNetwork 142 | +ErrUnknownVersion 143 | -generator 144 | // 配置输入哈希长度20bytes 145 | -hashConfig 146 | 147 | // Address为22字节字符串 148 | ▼+Address : []byte 149 | [methods] 150 | // 转换为编码前地址切片输出 151 | +Bytes() : []byte 152 | // 判断地址是否为空 153 | +Empty() : bool 154 | // 打印地址信息 155 | +Format(f fmt.State, c rune) 156 | // 输出地址中的20bytes哈希值 157 | +Hash() : []byte 158 | // 转换为编码后地址切片输出 159 | +MarshalText() : []byte, error 160 | // 输出地址的网络类型 161 | +Network() : Network 162 | // 转换为41bytes的编码输出 163 | // 2(网络类型)+1(地址版本)+32(base32编码)+6(base32校验位) 164 | +String() : string 165 | // 编码后地址切片输出转换为字符 166 | +UnmarshalText(in []byte) : error 167 | // 获取地址版本号 168 | +Version() : byte 169 | 170 | // 类型定义 171 | +Network : byte 172 | 173 | ▼ functions 174 | // 采用blake2b-160再次哈希 175 | +Hash(input []byte) : []byte 176 | // 生成测试网络地址,输入为原始哈希,会执行blake2b-160再次哈希 177 | +MakeTestAddress(input string) : Address 178 | // 通过字符串网络类型转换为byte网络类型 179 | // fc:主网转化为0 180 | // tf:测试网化为1 181 | +NetworkFromString(input string) : Network, error 182 | // 通过byte网络类型转换为字符串网络类型 183 | // 0:主网转化为fc 184 | // 1:测试网化为tf 185 | +NetworkToString(n Network) : string 186 | // 构建新地址:输入为原始20bytes哈希+网络类型+地址版本 187 | +New(network Network, hash []byte) : Address 188 | // 构建新地址:输入为22bytes的原始切片 189 | +NewFromBytes(raw []byte) : Address, error 190 | // 通过41bytes的字串串生成22bytes的原始地址 191 | +NewFromString(s string) : Address, error 192 | // 构建新地址:输入为原始20bytes哈希,调用New 193 | +NewMainnet(hash []byte) : Address 194 | // 生成测试网络地址,输入为原始哈希再次哈希,被MakeTestAddress调用 195 | +NewTestnet(hash []byte) : Address 196 | // 校验41bytes地址的合法性 197 | +ParseError(addr string) : error 198 | 199 | // base32编码校验码生成,结果为6bytes 200 | -createChecksum(hrp string, data []byte) : []byte 201 | // 解码 202 | -decode(addr string) : string, byte, []byte, error 203 | // 编码 204 | -encode(hrp string, version byte, data []byte) : string, error 205 | -hrpExpand(hrp string) : []byte 206 | -init() 207 | -polymod(values []byte) : uint32 208 | // 校验和验证 209 | -verifyChecksum(hrp string, data []byte) : bool 210 | 211 | ``` 212 | 213 | ``` 214 | location: address/set.go 215 | 216 | ▼ package 217 | address 218 | 219 | ▶ imports 220 | 221 | ▼ variables 222 | -addrSetEntry 223 | 224 | // 地址集合 225 | +Set : map[Address] 226 | 227 | ▼ functions 228 | -init() 229 | ``` 230 | 231 | ## 9.5 config 232 | 233 | - 对外提供功能 234 | - 提供对内存中配置的实例化操作 235 | - 对具体实例的设置和读取 236 | - 对配置文件的读写 237 | - 包含API、启动、数据存储、网络连接、挖矿、钱包、心跳相关配置 238 | 239 | ``` 240 | ▼ package 241 | config 242 | 243 | ▶ imports 244 | 245 | ▼ variables 246 | // 对特定参数的合法性校验规则集合 247 | // 1 目前只是限定昵称为字符 248 | +Validators 249 | 250 | ▼+APIConfig : struct 251 | [fields] 252 | // 是否允许跨域请求 253 | +AccessControlAllowCredentials : bool 254 | // 允许的方法列表 255 | +AccessControlAllowMethods : []string 256 | // 允许的元列表 257 | +AccessControlAllowOrigin : []string 258 | // 地址 259 | +Address : string 260 | [functions] 261 | // 实例化APIconfig 262 | -newDefaultAPIConfig() : *APIConfig 263 | 264 | ▼+BootstrapConfig : struct 265 | [fields] 266 | // 启动地址集合 267 | +Addresses : []string 268 | // 最小节点阈值 269 | +MinPeerThreshold : int 270 | // 启动时间阈值,目前为10s 271 | +Period : string 272 | [functions] 273 | // 实例化启动配置的接口 274 | -newDefaultBootstrapConfig() : *BootstrapConfig 275 | 276 | // 存储在内存之中的filecoin配置 277 | ▼+Config : struct 278 | [fields] 279 | // API相关 280 | +API : *APIConfig 281 | // 启动相关 282 | +Bootstrap : *BootstrapConfig 283 | // 数据存储相关 284 | +Datastore : *DatastoreConfig 285 | // 心跳相关 286 | +Heartbeat : *HeartbeatConfig 287 | // 挖矿相关 288 | +Mining : *MiningConfig 289 | // 网络连接相关 290 | +Swarm : *SwarmConfig 291 | // 钱包相关 292 | +Wallet : *WalletConfig 293 | [methods] 294 | // 获取配置,参数为API的上述子结构 295 | +Get(key string) : interface{}, error 296 | // 设置配置,参数为API的上述子结构 297 | +Set(dottedKey string, jsonString string) : error 298 | // 写对应目录的配置文件 299 | +WriteFile(file string) : error 300 | [functions] 301 | // 实例化配置,会调用各字节口的实例化 302 | +NewDefaultConfig() : *Config 303 | // 读对应目录的配置文件 304 | +ReadFile(file string) : *Config, error 305 | 306 | ▼+DatastoreConfig : struct 307 | [fields] 308 | // 路径 309 | +Path : string 310 | // 类型 311 | +Type : string 312 | [functions] 313 | -newDefaultDatastoreConfig() : *DatastoreConfig 314 | 315 | ▼+HeartbeatConfig : struct 316 | [fields] 317 | // 心跳周期 318 | +BeatPeriod : string 319 | // 心跳目标 320 | +BeatTarget : string 321 | // 昵称 322 | +Nickname : string 323 | // 重连时间 324 | +ReconnectPeriod : string 325 | [functions] 326 | -newDefaultHeartbeatConfig() : *HeartbeatConfig 327 | 328 | ▼+MiningConfig : struct 329 | [fields] 330 | // 自动密封间隔周期 331 | +AutoSealIntervalSeconds : uint 332 | // 区块签名地址 333 | +BlockSignerAddress : address.Address 334 | // 矿工地址 335 | +MinerAddress : address.Address 336 | // 存储报价 337 | +StoragePrice : *types.AttoFIL 338 | [functions] 339 | -newDefaultMiningConfig() : *MiningConfig 340 | 341 | ▼+SwarmConfig : struct 342 | [fields] 343 | // 地址 344 | +Address : string 345 | // 转发地址 346 | +PublicRelayAddress : string 347 | [functions] 348 | -newDefaultSwarmConfig() : *SwarmConfig 349 | 350 | ▼+WalletConfig : struct 351 | [fields] 352 | // 默认钱包地址 353 | +DefaultAddress : address.Address 354 | [functions] 355 | -newDefaultWalletConfig() : *WalletConfig 356 | 357 | ▼ functions 358 | -validate(dottedKey string, jsonString string) : error 359 | -validateLettersOnly(key string, value string) : error 360 | ``` 361 | 362 | ## 9.6 crypto 363 | 364 | - 对外提供功能 365 | - 生成私钥接口 366 | - 签名接口 367 | - 私钥转公钥接口 368 | - 从签名消息中提取公钥接口 369 | - 验证消息合法性接口 370 | - 主要用于地址生成、钱包相关 371 | 372 | ``` 373 | ▼ package 374 | crypto 375 | 376 | ▶ imports 377 | 378 | ▼ constants 379 | // 定义私钥长度32位 380 | +PrivateKeyBytes 381 | // 定义公钥长度65位 382 | +PublicKeyBytes 383 | 384 | ▼ functions 385 | // 从签名消息中恢复公钥 386 | +EcRecover(msg, signature []byte) : []byte, error 387 | // 比较私钥是否相同 388 | +Equals(sk, other []byte) : bool 389 | // 生成私钥,调用GenerateKeyFromSeed 390 | +GenerateKey() : []byte, error 391 | // 生成私钥 392 | +GenerateKeyFromSeed(seed io.Reader) : []byte, error 393 | // 由私钥得到公钥 394 | +PublicKey(sk []byte) : []byte 395 | // 使用私钥签名 396 | +Sign(sk, msg []byte) : []byte, error 397 | // 验证签名合法性 398 | +Verify(pk, msg, signature []byte) : bool 399 | ``` 400 | 401 | ## 9.7 util/convert 402 | 403 | - 提供功能 404 | ToCid:转cid功能 405 | 406 | ## 9.8 functional-tests 407 | 408 | - 测试脚本 409 | 410 | ## 9.9 flags 411 | 412 | - 通过ldflags注入,表示git提交版本号 413 | 414 | ``` 415 | var Commit string 416 | ``` 417 | 418 | ## 9.10 fixtures 419 | 420 | - 提供功能 421 | - 定义不同网络启动相关地址 422 | - 预先分配初始网络状态,比如代币的预先分配 423 | 424 | ``` 425 | ▼ package 426 | fixtures 427 | 428 | ▶ imports 429 | 430 | ▼ constants 431 | // 开发人员,开发网络启动相关地址 432 | -nightlyFilecoinBootstrap0 : string 433 | -nightlyFilecoinBootstrap1 : string 434 | -nightlyFilecoinBootstrap2 : string 435 | -nightlyFilecoinBootstrap3 : string 436 | -nightlyFilecoinBootstrap4 : string 437 | // 测试网络启动相关地址 438 | -testFilecoinBootstrap0 : string 439 | -testFilecoinBootstrap1 : string 440 | -testFilecoinBootstrap2 : string 441 | -testFilecoinBootstrap3 : string 442 | -testFilecoinBootstrap4 : string 443 | // 用户,开发网络启动相关地址 444 | -userFilecoinBootstrap0 : string 445 | -userFilecoinBootstrap1 : string 446 | -userFilecoinBootstrap2 : string 447 | -userFilecoinBootstrap3 : string 448 | -userFilecoinBootstrap4 : string 449 | 450 | ▼ variables 451 | // 开发人员,开发网络启动相关地址 452 | +DevnetNightlyBootstrapAddrs 453 | // 测试网络启动相关地址 454 | +DevnetTestBootstrapAddrs 455 | // 用户,开发网络启动相关地址 456 | +DevnetUserBootstrapAddrs 457 | // 预生成测试网络地址集合 458 | +TestAddresses : []string 459 | // 预生成测试矿工账户集合 460 | +TestMiners : []string 461 | // 预生成地址的私钥 462 | -testKeys : []string 463 | 464 | ▼-detailsStruct : struct 465 | [fields] 466 | // 创世区块cid 467 | +GenesisCid : cid.Cid 468 | +Keys : []*types.KeyInfo 469 | +Miners : [] 470 | 471 | ▼ functions 472 | // 预生成的Key文件路径 473 | +KeyFilePaths() : []string 474 | 475 | // 预生成信息 476 | // 1 解析gen.json文件到detailsStruct结构体 477 | // 2 追击Miners信息到TestMiners中 478 | -init() 479 | ``` 480 | - 如下为gen.json文件,可据此预先给特定矿工分配代币 481 | 482 | ``` 483 | { 484 | "keys": 5, 485 | "preAlloc": [ 486 | "1000000000000", 487 | "1000000000000", 488 | "1000000000000", 489 | "1000000000000", 490 | "1000000000000" 491 | ], 492 | "miners": [{ 493 | "owner": 0, 494 | "power": 1 495 | }] 496 | } 497 | ``` 498 | 499 | ## 9.11 filnet 500 | 501 | - 提供功能 502 | - 节点启动 503 | - 定期检查连接节点,如果数量不够会链接随机节点 504 | 505 | ``` 506 | location: filnet/address.go 507 | 508 | ▼ package 509 | filnet 510 | 511 | ▼ imports 512 | gx/ipfs/QmNTCey11oxhb1AxDnQBRHtdhap6Ctud872NjAYPYYXPuc/go-multiaddr 513 | gx/ipfs/QmRhFARzTHcFh8wUxwN5KvyTGq73FLC65EfFAhz8Ng7aGb/go-libp2p-peerstore 514 | 515 | ▼ functions 516 | // 节点id转换为完整的节点信息,包括所有的多地址格式 517 | +PeerAddrsToPeerInfos(addrs []string) : []pstore.PeerInfo, error 518 | ``` 519 | 520 | ``` 521 | location: filnet/bootstrap.go 522 | 523 | ▼ package 524 | filnet 525 | 526 | ▶ imports 527 | 528 | ▼ variables 529 | -log 530 | 531 | ▼+Bootstrapper : struct 532 | [fields] 533 | // 对应bootstrap 534 | +Bootstrap : func([]peer.ID) 535 | // 连接超时时间,用于连接随机节点 536 | +ConnectionTimeout : time.Duration 537 | // 最小连接节点数量阈值 538 | +MinPeerThreshold : int 539 | // 定时检查连接节点数量,小于阈值会处理 540 | +Period : time.Duration 541 | // 随机节点切片 542 | -bootstrapPeers : []pstore.PeerInfo 543 | -cancel : context.CancelFunc 544 | -ctx : context.Context 545 | -d : inet.Dialer 546 | -dhtBootStarted : bool 547 | -h : host.Host 548 | -r : routing.IpfsRouting 549 | -ticker : *time.Ticker 550 | [methods] 551 | // 定时调用Bootstrap 检查连接节点数量,小于阈值会处理 552 | +Start(ctx context.Context) 553 | // 停止节点 554 | +Stop() 555 | // 如果启动节点不够,将会尝试连接随机节点。 556 | -bootstrap(currentPeers []peer.ID) 557 | [functions] 558 | // 实例化 559 | +NewBootstrapper(bootstrapPeers []pstore.PeerInfo, h host.Host, d inet.Dialer, r routing.IpfsRouting, minPeer int, period time.Duration) : *Bootstrapper 560 | 561 | ▼ functions 562 | -hasPID(pids []peer.ID, pid peer.ID) : bool 563 | ``` 564 | -------------------------------------------------------------------------------- /img/active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynewyang/analysis-of-filecoin-in-Chinese/32e112119bfad01d64cdb7ac84778a38247ab6a7/img/active.png -------------------------------------------------------------------------------- /img/filecoin-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynewyang/analysis-of-filecoin-in-Chinese/32e112119bfad01d64cdb7ac84778a38247ab6a7/img/filecoin-1.png -------------------------------------------------------------------------------- /img/filecoin_arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynewyang/analysis-of-filecoin-in-Chinese/32e112119bfad01d64cdb7ac84778a38247ab6a7/img/filecoin_arch.png -------------------------------------------------------------------------------- /img/ipfsandfilecoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynewyang/analysis-of-filecoin-in-Chinese/32e112119bfad01d64cdb7ac84778a38247ab6a7/img/ipfsandfilecoin.png -------------------------------------------------------------------------------- /img/payment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynewyang/analysis-of-filecoin-in-Chinese/32e112119bfad01d64cdb7ac84778a38247ab6a7/img/payment.png -------------------------------------------------------------------------------- /img/post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynewyang/analysis-of-filecoin-in-Chinese/32e112119bfad01d64cdb7ac84778a38247ab6a7/img/post.png -------------------------------------------------------------------------------- /img/retrieval_protocol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynewyang/analysis-of-filecoin-in-Chinese/32e112119bfad01d64cdb7ac84778a38247ab6a7/img/retrieval_protocol.png -------------------------------------------------------------------------------- /img/sle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynewyang/analysis-of-filecoin-in-Chinese/32e112119bfad01d64cdb7ac84778a38247ab6a7/img/sle.png -------------------------------------------------------------------------------- /img/sle2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynewyang/analysis-of-filecoin-in-Chinese/32e112119bfad01d64cdb7ac84778a38247ab6a7/img/sle2.png -------------------------------------------------------------------------------- /img/storage_protocol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynewyang/analysis-of-filecoin-in-Chinese/32e112119bfad01d64cdb7ac84778a38247ab6a7/img/storage_protocol.png -------------------------------------------------------------------------------- /img/wikiissue1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynewyang/analysis-of-filecoin-in-Chinese/32e112119bfad01d64cdb7ac84778a38247ab6a7/img/wikiissue1.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # filecoin技术架构分析 2 | 3 | > 作者:杨尉(waynewyang),转载请注明出处 4 | 5 | ## 目录 6 | - [1 filecoin概念](1.md) 7 | - [2 filecoin通用语言理解](2.md) 8 | - [3 filecoin开发网使用](3.md) 9 | - [4 filecoin源码顶层架构分析](4.md) 10 | - [5 filecoin源码协议层分析之心跳协议](5.md) 11 | - [6 filecoin源码协议层分析之hello握手协议](6.md) 12 | - [7 filecoin源码协议层分析之存储协议](7.md) 13 | - [8 filecoin源码协议层分析之检索协议](8.md) 14 | - [9 filecoin源码分析之支撑包分析(1)](9.md) 15 | - [10 filecoin源码分析之支撑包分析(2/2)](10.md) 16 | - [11 filecoin源码分析之内部接口层api包分析](11.md) 17 | - [12 filecoin源码分析之内部接口层plumbing&porcelain接口](12.md) 18 | - [13 filecoin源码分析之服务层actor及vm](13.md) 19 | - [14 filecoin源码分析之服务层链同步、共识协议及挖矿](14.md) 20 | - [15 filecoin源码分析之节点运行逻辑](15.md) 21 | --------------------------------------------------------------------------------