Yuque Archive
工作资料 - 新新新

第一个月进度记录

熟悉项目的过程记录

1. 编译环境(第一周)

本地用 arm64(Apple M1)的笔记本,一开始假设仓库项目的代码没有问题,怀疑本地环境有问题,尝试了各种编译环境去编译项目:

  • 本地 arm64
  • 本地 amd64(arch x64_64 转换)
  • 各种版本的 golang 和 nodejs 环境((arm + amd) * (14 + 15 + 16))
  • docker 容器 arm 和 amd 版本
  • 云服务器(阿里云主机)的 centos 和 ubuntu 版本

最终定位到 golang 项目的一个依赖包版本低,以及 seek 的 npm 只能用 10 的版本。

2. 节点运行(第二周)

  1. themis、scan、edge 配置文件各项参数熟悉、规划分配端口,整理部署脚本

  2. scan 公网 IP 配置测试、网络问题验证

  3. edge 文件操作

  4. 发现需要不止 1 个节点

  5. 尝试 themis 的 vbft 共识

  6. 发现账户没钱

  7. 尝试 themis 的 dbft 共识

  8. 还是没钱

  9. 回到 themis 的 solo 模式

  10. 启动多个 edge 节点并注册

  11. 文件任务提交成功,实际任务失败

  12. 没有注册 dns header

  13. 注册后日志提示成功

  14. 下载文件,任务成功,日志显示失败

  15. 任务 id 还是文件为 nil(未查清)

3. p2p网络(第三周)

  1. 文件上传需要创建 sector

  2. edge 启动后报错 create client in bootstrap error

  3. dns 启动时会从 themis 查询所有注册的 dns 节点

  4. 然后 dns 会把查出来的列表放到 trickers 里

  5. 当注册的 dns 节点为空,edge 节点确实报错说没有 bootstrap 列表

  6. 当 dns 节点有一个时,edge 总会多去连一个 10340 的端口

  7. 10340 的端口就是 trackers 列表里发出的请求

  8. 可是 10340 没有出现在任何配置里

  9. dsp-go-sdk/core/dns/tracker.go:47,trackers 的端口是写死的常量 10340

  10. 上传文件时,接受文件的节点会推送文件到 10340,但是 10340 端口都没有被监听

  11. edge 和 scan 的 networkid 对不上

  12. 云环境使用打包好的二进制包,本地未复现

  13. 跟一下 scan 的流程

  14. scan 启动的时候会报错 get external ip for ...

  15. 报错的两个地址都是系统合约的地址

  16. scan 启动了 3 种类型的 p2p 网络,channel、dns 和 tracker

  17. 之前的配置文件少了 tracker 的 networkid,默认为 0

  18. 跟一下 edge 的流程

  19. edge 启动了 3 个 p2p 网络,tracker、dsp、channel

  20. 文件上传时报错 expect filePdpSuccess

  21. (挂起)

  22. 先看同步模块

  23. themis 是区块链本身,scan 和 edge 都是边缘节点,本身没有链数据,通过 sdk 调用 themis

  24. scan 和 edge 不同步数据

  25. themis 的 p2p 用自己的,scan 和 edge 的网络模块是 carrier

  26. carrier 是一个工具性质的项目,网络模块一定是把序列化的二进制数据从一个节点传输到另一个节点,比起数据是怎么传输的,传输了哪些数据似乎更重要一点,所以先了解文件的处理机制,包括增删改查

  27. 去查文件上传(5)的问题

  28. 现在没有报 pdp 的问题……但是仍然有 error

  29. 在查为什么有 error 之前,先看一下下载的过程,确认文件是不是真的没有上传成功

  30. 下载文件是可以成功的,但仍然需要排查 error 的原因

  31. 先看文件的 storeType 和 userspace

  32. userspace 的 set 有一行退出进程 edge/http/rest/file.go:604

  33. userspace 模式上传的文件,没办法用 hash 下载

  34. 看文件的 prefix 是什么

  35. 看文件分片后的上传规则

  36. 要不要先了解一下 ipfs 的做法

  37. 文件的处理机制 ipfs 分模块描述的很详细,每个模块都水很深。ipfs 的 libp2p 是专门用于处理 p2p 网络的模块,对应 edge 用到的 carrier 项目。接下来还是要关注 p2p 模块的具体实现,深入了解 carrier 并与 libp2p 对比。

4. carrier 项目(第四周)

  1. 。先跑一下 examples

  2. 启动流程

  3. builder注册 trannsport,注册了四种协议,用 sync.Map 存起来

  4. build 添加 compnent,存到列表里

  5. build.Build(),在 Build 里面初始化了网络,监听处理守护进程的退出信号

  6. go listen

  7. layer 是一个接口,不同协议各自实现监听函数

  8. 依次启动 component 接口的实现,然后启动端口监听

  9. 看 tcp 协议数据交互细节

  10. 网络发现流程(discovery component)

  11. 数据格式的定义

  12. 网络重连的流程 (recon component)

  13. carrier 和 libp2p 的功能对比

遇到的问题或说明

第一周

第二周

1. edge 节点配置文件中的 LogLevel 不生效

代码中没有相应的处理逻辑;命令行的启动参数是生效的。

2. themis 必须用 solo 模式启动

themis 会在初始化 ledger 的时候把 10 亿 token 分发给 bookkeeper 列表中的地址。

  • 如果是 solo 模式,bookkeeper 账户就是启动节点用的账户地址
  • 如果是 vbft 共识,bookkeeper 是配置文件中 peers 下的所有节点
  • 如果是 dbft 共识,bookkeeper 是配置文件中 Bookkeeper 配置的节点

只要 bookkeeper 账户列表的数量 != 1,节点就会把 token 分发给节点内置的用于资产管理的系统账户 AFmseVrdL9f9oyCzZefL9tG6UbviEH9ugK。这个系统账户,节点用户用不了里面的资产。

3. edge 节点至少有 2 个

edge 在上传文件时,会排除掉当前收到请求的节点,除了当前节点,必须有另一个节点处理文件。

edge 的命令行是给当前节点(写死的 localhost + 当前目录配置文件的端口)发送 json rpc 请求,当前节点收到请求后给其他节点发送 p2p 消息。

命令行的请求地址是写死的 localhost,应该可以优化。

4. 注意 edge 的消息是异步的

在用 edge 的命令行上传文件时,如果显示上传成功,只是上传任务提交成功。比如 dns header 没有注册或者与其他的 edge 节点连接超时,都会上传任务提交成功但是文件上传失败。

上传文件提示文件是否存在,是在判断文件上传任务是否存在。只要任务存在,就会出现在 edge 的上传列表里(./edge file uploadlist),但是不代表任务成功。(任务没有状态参数吗?)

下载文件也是异步的,提示成功不代表真的成功。在提示成功后,本地有可能找不着文件。

第三周

1. scan 节点 dns 命令行的提示

操作结果的提示是 ./ddns 而不是 ./scan。

2. scan 节点 dns 命令行的参数

dns 注册时需要三个参数 dnsIp, dnsPort, initDeposit,其中 dnsPort 对应配置文件中的 ChannelPortOffset 而不是 DnsPortOffset.

开启自动注册就不用管了。

3. dsp 里的 tracker 的监听端口是写死的常量 10340

dsp-go-sdk/core/dns/tracker.go:47

scan 节点启动时会启动一个 tracker 的监听端口,edge 节点会通过从 themis 节点查询注册的 dns 信息和 scan 节点交互。

edge 节点上传文件或者节点启动时,都会推送文件信息到 tracker 节点。

但是 edge 只能查到 dns 的 p2p 端口(20338),查不到 tracker 的监听端口,所以就直接写成常量了。

4. 文件 hash 是怎么计算的

在 max 项目里,dag 的构建直接使用了 GitHub - ipfs/go-ipld-format: IPLD Node and Resolver interfaces in Go 的数据结构,哈希值是 dag 顶部节点的 cid。

详细的计算过程没追究,max 里构建了一种类型的 node,然后 new 了另一种类型的 node,再类型转换为返回类型的 node ,就有 cid 了。

5.. 上传文件任务的 taskid 怎么来的

上传文件时,命令行只会把输入的参数请求到 rpc 服务端,不带 taskid。

服务端在处理时,会读取传过来的 id 参数,如果是新任务这个参数一定是空的。rpc 服务端会调用 dsp 的方法,根据文件路径判断上传任务是否存在。首先判断 路径+文件名 是否完全一致,然后判断 ehecksum 是否一致。

dsp 在判断文件路径是否存在的时候,是遍历了所有的任务列表,如果有匹配的,就存在。因为这个时候没有 taskid,只能遍历。

然后 rpc 服务端调用 dsp 的新建任务函数,dsp 在处理任务时,如果 taskid 为空,就生成一个 uuid,然后用 taskid 作为 key,储存任务信息。dsp 后续根据 taskid 启动文件上传任务。

dsp-go-sdk/dsp/fs_upload.go:41 新建任务的函数参数是 taskid,但是只有 taskid 为空时才调用这个函数,这个参数在定义的时候可能是多余的。

6.. peer id 是什么

上传文件的时候会遇到 peer id 不存在的报错,当时未追查。

themis 在启动后,p2p 模块初始化了网络监听和操作的句柄,然后 consensus 模块从配置文件读了初始化的几个节点的公钥,peerid 是序列化后的公钥信息。consensus 模块使用 p2p 网络连接其他节点。

solo 模式有 peer id 吗?peerid 就是公钥,所以一定有,但是 solo 模式没有配置文件,这个公钥信息不一定在节点的环境里,上传文件时遇到的报错可能就是因为 solo 模式。(待查清)

7.. 节点的 p2p 网络

themis 的数据同步应该取决于共识。

scan 启动了 3 个 p2p网络 channel、dns、tracker,使用 carrier 启动端口监听。

然后 scan 启动了一个 channel service,这个服务里调用了 pylons 中的同步块的方法。同步块的时候注册了一个回调函数,函数里用 themis-go-sdk 获取最新块高度,然后遍历同步数据。只获取了块的摘要信息,块哈希之类,没有块交易数据。

edge 和 scan 类似,也是使用 carrier 监听端口。

节点的 Network 数据结构继承自 GitHub - ethereum/go-ethereum: Official Go implementation of the Ethereum protocol 或者 GitHub - rcrowley/go-metrics: Go port of Coda Hale's Metrics library 的Stoppable。

p2p 无非是启动监听端口,然后接收或者发送不同类型的消息到其他 ip。现在更应该关注不同节点的 p2p 网络分别处理了哪些类型的消息。

8. themis 的同步模块

同步模块是 p2p 网络的一部分。themis 用了 2 个协程分别处理块同步和块持久化。

在块同步里先同步块哈希,然后同步块数据。程序本地有一个缓存 map,同步过来的数据都放在缓存里,同步之前会删掉块高度小于当前块的缓存,然后发送请求。同步模块只管发请求,收到数据后在接收消息那里处理。

收到块头或者块数据后,更新本地缓存。收到块头会做详细的校验,块数据几乎不校验。块持久化协程定时把缓存写到数据库。

数据交互使用一个通用格式 ZeroCopySink,里面是 []byte。各种消息的类型都实现了序列化和反序列化的方法,把序列化的数据放到通用格式后进行网络传输。

9. edge 节点需要区分客户端和服务端

edge 的配置文件里有一个 FSType,默认为 0。

下载文件的时候,程序判断当前节点是不是客户端(type 是不是 0),如果是就创建下载文件夹和文件,否则跳过创建文件的逻辑。如果不设置为 0 ,下载文件就一定是失败的。

10. 日志级别的依据

dsp-go-sdk/task/base/base.go:146 储存任务信息是否有必要把全部任务信息打印出来,而且打出来的是没格式化好的,每次 opt 中的状态发生变更,都会打日志,造成冗余。

carrier/network/stream.go:210 满屏幕都是这个。

11. 文件的 prefix 是什么

提交文件的时候不带这个参数。prefix 包含了文件的所有者、加密方式、密码等信息,用于标识文件。上传文件的时候,用了 GitHub - ipfs/go-ipfs-chunker: go-ipfs-chunkers provides Splitter implementations for data before being ingested to IPFS 以 size 方式切割文件称 chunker,然后用 chunker 构建 dag 结构。在切割文件前,把文件内容和 prefix 用 multi io reader 读成了一个 reader。

下载文件的时候,对收到的二进制进行一些解码,就可以还原回 prefix,然后验证文件所有权之类。

12. 文件的分片规则

go-ipfs-chunker 会按照文件大小切割文件,目前的 size 是常量 256*1024 byte,也就是 256 K(会不会有点小)。

看样子文件切片的意义在于,分多次传输,避免网络问题。7.7 M 的文件会存在 32 个分片,只要 copyNum=0 就只需要 1 个节点。

如果仅仅是分片网络传输的话,为什么要用 dag 呢。猜测可能是想要复用分片数据,如果两个文件切出了相同 hash 的分片,两个文件就只需要一份这样的数据。

(似乎不是)ipfs 的每一个 chunker 都有 cid,是唯一而且加密的,更多是出于对文件的安全性考虑。

(也可能是)如果 cryptographically hashed 后的结果一样的话……相同拥有者相同文件不同版本复用的可能性大一点。

第四周

1. carrier 中同时存在 addr 和 listen addr

这两个值是数据结构上的配置项。从语义上理解,addr 是本地地址,listen addr 是服务监听的地址。

目前启动服务,需要同时调用 setAddr 和 setListenAddr 的函数,为两个变量赋值。如果都在本地,这两个函数传入的参数是一样的。addr 在 boostrap 中过滤本地地址时用到了,listen addr 是启动监听时的端口。

为什么要同时存在两个这样的地址变量?猜测一开始只有一个变量,遇到内外网、代理等复杂网络环境,一个地址不够用了,临时加上了一个地址变量。因为项目里的 example 还是只用到了 addr,说明以前监听的端口也是 addr。

libp2p 并没有这样的 “复杂” 设计。

2. gogo 的 protobuf 找不到 import 包

因为高版本的 go 语言默认开启 modules,protoc 无法从 go path 找到引入包。参考 example.proto:33:1: Import "github.com/gogo/protobuf/gogoproto/gogo.proto" was not found or had errors. · Issue #674 · gogo/protobuf

3. carrier 网络发现的流程(discovery componet)

在启动 listen 时,各 transport 调用接口实现的监听函数,启动的时候就已经是 TCP 或者其他协议了,不用自己考虑三次握手之类。

components 是协议无关的,componets 里使用 client 的方法发送消息,client 的消息发送则调用 network 的 write 方法。network.go 的 write 分 case 处理了不同协议类型的消息。接收消息自然是各启动各的服务监听。

节点发现的过程,主节点调用 bootstrap 的节点向目标节点发送 dial 生成 client,接着发送 ping (10) 消息,另一个节点的 discovery component 会收到并处理这个消息,同时回复 pong (11) 消息。

主节点收到 pong 消息,会调用一个寻找临近节点的函数,从日志看这个函数会发送 lookup addr request (12) 的消息,目标节点回复 response (13),这两种消息也是在 discovery 的组件里处理。收到 13 消息后结束,认为已经发现节点。

4. carrier 中路由表的操作

discovery component 启动的时候,创建了路由表,路由表是发现组件维护的。路由表是一个包含多个 bucket 的结构,buket 本身是一个列表。新建路由表时,会创建 32 * 8 = 256 个空的 bucket。

当调用更新路由表的函数时,程序根据 peer id 计算出该节点在路由表中的索引,peer id 是一个字节数组,取数组第一个 byte,索引 = (byte 中第一个不为 0 的 bit 的索引) * 8 + (第一个 byte 前面 0 的个数)。得到索引位置后,在索引对应的 bucket 列表中寻找到 target 节点,然后把 target 在列表中的位置放到最前。

discovery comonent 在收到 pong 消息的时候,调用 find node 函数,得到目标节点附近的 16 个bucket。在收到 lookup node request 的时候,同样是寻找得到目标节点附近的 bucket。寻找临近节点也就是把路由表中 bucket id 上下 size 的节点找出来。应该是认为在路由表中索引相近的节点就是物理位置相近的节点。

4. carrier 和 libp2p 对比

小结

第一个月(第 1 ~ 4 周)先了解了 saveio 的系统架构、节点的部署配置、文件操作的关键概念、项目的功能划分等,然后重点从 p2p 网络入手深入熟悉代码的细节流程。最近几天主要熟悉 carrier 的代码,以及和 libp2p 在功能和实现方式上进行对比,carrier 本身应该也没啥,代码量不大。

接下来第二个月(第 5 ~ 8 周)计划先熟悉一下 p2p sharing 中 libp2p 之外的功能模块,包括 DHTs、DAGs、Bitswap、IPLD、IPNS 等在 saveio 中的具体实现情况,重点关注项目对于 sybil attack 等问题的安全情况(前 2 周)。然后了解 content addressing 的详细机制(后 2 周)。共识机制和支付网络的优先级靠后。