44

IPFS初窥2

 5 years ago
source link: http://www.hi-roy.com/2018/09/19/IPFS初窥2/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

在上一篇文章中初步介绍了IPFS的基本概念和使用方法,今天更深入一些了解IPFS的设计理念。

根据 第三版 白皮书,IPFS体系可以分为7层:

  1. 身份:负责节点的身份生成和认证。
  2. 网络:负责节点间的网络连接,可以配置使用各种网络协议。
  3. 路由:维护路由信息来找出特定的节点和对象。默认使用DHT,可以替换。
  4. 交换:一个新型的块交换协议(BitSwap)来高效管理块分发。有点类似超市,对数据复制有激励。交易策略可以替换。
  5. 对象:带有链接的、内容寻址的不可变对象组成的Merkle DAG,可以代表任意数据结构。比如文件层级和通信系统。
  6. 文件:受到Git启发的版本控制文件层级系统。
  7. 命名:自验证的可变命名系统。

身份

节点间通过 NodeID 来识别彼此之间的身份,而 NodeID 是根据公钥和 S/Kademlia 静态加密算法生成的,一个节点可以生成多个 NodeID ,但这种行为将失去某些“好处”,至于什么好处在BitSwap时候细说。

文中还给了生成 NodeID 的伪代码:

difficulty = <integet parameter>
n = Node{}

do{
  n.PubKey,n.PrivKey = PKI.genKeyPair()
  n.NodeID = hash(n.PubKey)
  p = count_preceding_zero_bits((hash(n.NodeId)))
}while (p < difficulty)

代码比较好理解这里就不多解释,当节点间第一次建立链接时候会交换公钥,并且计算哈希值,如果不匹配则终止链接。另外IPFS更倾向于使用能自解释的哈希格式,比如使用了哪个哈希函数、摘要长度:

<function code><digest length><digest bytes>

这样做的好处有2个:

  1. 可以根据实际情况选择合适的函数(安全性Vs高性能)
  2. 可以根据选择函数的不同而演变。自描述的值准许使用兼容的、不同的参数。

网络和路由

这两个相对好理解就放一起记录了。

IPFS可以使用任何传输协议,但最适合的是 WebRTC DataChannelsuTP ,同时基于 uTP 提供了可靠传输。并且支持 ICE NAT 穿透技术,还可以选择检查哈希值来保证消息完整性,并通过使用HAMC以及发送方的公钥来保证真实性。

正是因为可以使用各种传输协议,所以IPFS也可使用 覆盖网络(overlay networks) 。因此IPFS使用多层地址的格式来存储地址信息。比如:

# sctp/ipv4链接

/ip4/10.20.30.40/sctp/1234/

# 通过tcp/ipv4代理的sctp/ipv4链接
/ipo/5.6.7.8/tcp/5678/ip4/1.3.4.5/sctp/1234/

IPFS通过使用路由来寻找其他节点的网络地址和谁可以提供特定的对象。基于DSHT、Coral和S/Kademila。IPFS DHT根据大小来使用不同的存储方式,比如小文件(小于1KB)直接存在DHT中,而大文件则存储那些可以提供数据的节点的引用。

另外不同的场景可以使用不同的路由系统,比如DHT用于广域网,静态HT用于本地网络。所以根据不同场景路由系统是可以改变的,只要实现了相应的接口。

块交换——BitSwap协议

BitSwap协议灵感来源于Bittorrent,使用 want_list 来记录自己想要什么, have_list 来记录自己有什么。和Bittorrent不同的是BitSwap不关注数据块来源于哪个文件。为了解决“吸血节点”问题,IPFS引入了信用机制:

  1. 节点通过账本记录和其他节点之间的收(负债)发(信用)字节长度。
  2. 根据不同的负债率,是否发送数据的概率不同。

如果一个节点决定不发送数据给请求方,随后则会根据 ignore_cooldown 时间来忽略接下来这个节点的请求,以便防止DDos攻击。

负债率的计算公式为:

$$r=\frac{bytes sent}{bytes recv+1}$$

分母加1防止出现除以0的异常。已知r后,发送数据的概率P为:

$$P(send|r)=1-\frac{1}{1+\exp(6-3r)}$$

这里有个点要注意,节点A的负债(debt)就是节点B的信用(credit),以 go-bitswap 中关于账本的代码为例,来源于 decision\ledger.go

type ledger struct {
	// Partner is the remote Peer.
	Partner peer.ID

	// Accounting tracks bytes sent and received.
	Accounting debtRatio

	// lastExchange is the time of the last data exchange.
	lastExchange time.Time

	// exchangeCount is the number of exchanges with this peer
	exchangeCount uint64

	// wantList is a (bounded, small) set of keys that Partner desires.
	wantList *wl.Wantlist

	// sentToPeer is a set of keys to ensure we dont send duplicate blocks
	// to a given peer
	sentToPeer map[string]time.Time

	// ref is the reference count for this ledger, its used to ensure we
	// don't drop the reference to this ledger in multi-connection scenarios
	ref int

	lk sync.Mutex
}

type debtRatio struct {
	BytesSent uint64
	BytesRecv uint64
}

func (dr *debtRatio) Value() float64 {
	return float64(dr.BytesSent) / float64(dr.BytesRecv+1)
}

func (l *ledger) SentBytes(n int) {
	l.exchangeCount++
	l.lastExchange = time.Now()
	l.Accounting.BytesSent += uint64(n)
}

func (l *ledger) ReceivedBytes(n int) {
	l.exchangeCount++
	l.lastExchange = time.Now()
	l.Accounting.BytesRecv += uint64(n)
}

举个例子方便理解:假设有A、B两个节点,目前A向B发送了60个字节的数据,从B接收了100字节数据,那么对于B来说则是向A发送100字节数据、接收60字节数据。当A再次向B请求下载数据时,B就会根据自己的账本计算负债率,并决定是否接受这次请求。

块交换生命周期大体分成4种:

  1. open:发送账本直到对方同意。
  2. sending:交换want_list和数据块。
  3. close:关闭链接。
  4. ignore:这个只有在策略是避免发送时候才会出现。

白皮书中说,在open阶段节点A会创建一个新账本或者使用旧账本发送给节点B,而B则在ignore_cooldown超时时间内概率性的决定忽略还是接受这次请求。(原文是:This should be done probabilistically with an ignore_cooldown timeout)。此外,如果决定了接受这次请求,则使用本地账本新建一个peer对象并修改账本的 last_seen 时间戳,然后比较接收到的账本和对方发来的账本是否一致,一致则建立链接,否则初始化一个空账本并发送。

这里Roy我有个疑惑,在 go-ipfs 源码中找了很多地方都没找到这个”概率性忽略或接受请求”以及对比账本是否一致部分的代码,甚至上面计算概率的公式都没找到,希望知道这部分代码实现位置的大佬能够指点一下小弟,先谢谢了。

建立链接后就可以开始交换数据了。在 go-ipfs 中,want_list是一个字典,定义如下:

type Wantlist struct {
	set map[string]*Entry
}

type Entry struct {
	Cid      cid.Cid
	Priority int

	SesTrk map[uint64]struct{}
}

block则更简洁:

type BasicBlock struct {
	cid  cid.Cid
	data []byte
}

在IPFS中,文件被分为若干Blocks,Block使用一个叫做 CID 标识符来索引,这是一个自描述的索引结构体,用来唯一标识一个Block。所以要想从节点下载一个Block,只需在知道 CID 即可,定义如下:

// Cid represents a self-describing content adressed
// identifier. It is formed by a Version, a Codec (which indicates
// a multicodec-packed content type) and a Multihash.
type Cid struct{ str string }

// NewCidV0 returns a Cid-wrapped multihash.
// They exist to allow IPFS to work with Cids while keeping
// compatibility with the plain-multihash format used used in IPFS.
// NewCidV1 should be used preferentially.
func NewCidV0(mhash mh.Multihash) Cid {
	// Need to make sure hash is valid for CidV0 otherwise we will
	// incorrectly detect it as CidV1 in the Version() method
	dec, err := mh.Decode(mhash)
	if err != nil {
		panic(err)
	}
	if dec.Code != mh.SHA2_256 || dec.Length != 32 {
		panic("invalid hash for cidv0")
	}
	return Cid{string(mhash)}
}

// NewCidV1 returns a new Cid using the given multicodec-packed
// content type.
func NewCidV1(codecType uint64, mhash mh.Multihash) Cid {
	hashlen := len(mhash)
	// two 8 bytes (max) numbers plus hash
	buf := make([]byte, 2*binary.MaxVarintLen64+hashlen)
	n := binary.PutUvarint(buf, 1)
	n += binary.PutUvarint(buf[n:], codecType)
	cn := copy(buf[n:], mhash)
	if cn != hashlen {
		panic("copy hash length is inconsistent")
	}

	return Cid{string(buf[:n+hashlen])}
}

当执行 ipfs daemon 后,可以使用 ipfs bitswap stat 来查看整体的情况:

-> % ipfs bitswap stat
bitswap status
	provides buffer: 0 / 256
	blocks received: 760
	blocks sent: 11
	data received: 195407444
	data sent: 2883738
	dup blocks received: 75
	dup data received: 18 MB
	wantlist [6 keys]
		QmeUUfUS4DMsKzMkTda3iunruYMvJEwDfGnF7KBAt2smwJ
		QmcQQyNtMy3Z1fekutzjwkZCPHXwVvs4ZqgiboMBbc2H3A
		Qmc4XdqSLbyjYGsaZihb1nGhNAWbU8hKmmUNeUjExsLQyi
		QmYxE2fXENrJvuzES3Tyive4o1zp9VoNJD5bft4soyv1H2
		QmPmLZxuK4hAk9kx6mipvXvUjbtMxTXXnbbahP2VicYLeu
		QmRtJugMr7yPPS7Kw5azRQaj3VB774ARSLJfw7gsKM1fzt
	partners [936]
		QmNMTsUaKTDdunSUN44TZuMPDpGTCLG8R2ooEo1ZmxJadq
		QmNPNDYybTYcZK2KBrrn24M4Lc7VcPnGDNX8ueur9TRCmK
		QmNQC6nB2LACt1h6nkJdFz1vaFRP5iTHeB1p9nsfpk4nnM
		QmNRPRpbgM1WTEUo4T4sadsASmBudPKqYupdtZcSoFkvSL
		QmNRSBnfjE6PMZ5AfnLccWpMjX2LtRFYedxQ2UqQxvozfV
		QmNRV7kyUxYaQ4KQxFXPYm8EfuzJbtGn1wSFenjXL6LD8y
		QmNUCfyL67rqBesyCLon9kRUpXtPEfVUzf7aGp1VXqGqW4
		QmNV721hfbzxuLdBvLaBpzmKtnPP4duf59SZ3AU1fV5CrK

也可以使用 ipfs bitswap ledger NodeID 来具体查看和某个节点之间的债务关系。

本文最后来个彩蛋吧:

%ipfs get Qmdsrpg2oXZTWGjat98VgpFQb5u1Vdw5Gun2rgQ2Xhxa2t                                                                                                                 
Saving file(s) to Qmdsrpg2oXZTWGjat98VgpFQb5u1Vdw5Gun2rgQ2Xhxa2t
322.39 MiB / 322.39 MiB[====================================================================================================================================================] 100.00% 17m47s

然后把下载好的文件后缀改成 mp4 ,慢慢欣赏。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK