概述
为了维护点对点的比特币网络,需要有一个地址来源以便在节点进出时进行连接。比特币协议提供了 getaddr
和 addr
消息,允许节点之间相互传递已知的地址。然而,需要有一个机制来存储这些结果并从中选择节点。同样重要的是,远程节点不能被信任来发送有效的节点,也不能保证不会试图出于恶意目的仅向你提供他们控制的节点。
考虑到这一点,addrmgr
包提供了一个并发安全的地址管理器,用于以非确定性的方式缓存和选择节点。总体思路是调用者将地址添加到地址管理器,并在地址已连接、已知良好和已尝试时通知它。调用者还可以在需要时请求地址。
addrmgr
在内部将地址分组,并以加密随机的方式非确定性地选择这些组。这减少了从同一网络中选择多个地址的概率,从而通常有助于提供更大的节点多样性,更重要的是,极大地降低了攻击者能够强迫你的节点仅连接到他们控制的节点的可能性。
addrmgr
还能够理解可路由性和 Tor 地址,并尽力只返回可路由的地址。此外,它使用调用者提供的信息,包括已连接、已知良好和已尝试的地址,定期清除那些不再表现为良好的节点,同时在选择时偏向已知良好的节点。总体思路是尽最大努力只提供可用的地址。
btcd
是一个用 Go 语言编写的比特币全节点实现,其中包含了一个名为 addrmgr
的包,用于管理比特币网络中的对等节点(peer)的地址信息。addrmgr
负责发现、跟踪和维护网络中活跃节点的地址,从而确保节点能够有效地连接和通信。以下是对 btcd
中 addrmgr
的详细介绍。
addrmgr
包主要用于管理比特币网络中的节点地址。它提供了添加、删除、选择和更新节点地址的功能,确保节点能够发现和连接到其他节点,从而保持网络的连通性。并提供以下功能:
- 地址发现:通过 DNS 种子节点、硬编码的种子节点列表或从其他节点获取地址。
- 地址选择:根据特定的策略选择适合的节点进行连接。
- 地址更新:根据节点的连接状态和响应时间,动态更新节点的地址信息。
- 地址存储:将节点地址信息持久化存储,以便在节点重启时能够快速恢复。
主要结构
在 btcd
中,addrmgr
的核心结构是 AddrManager
,它负责管理所有的节点地址信息。以下是 AddrManager
结构的定义:
type AddrManager struct {
mtx sync.RWMutex
peersFile string
lookupFunc func(string) ([]net.IP, error)
rand *rand.Rand
key [32]byte
addrIndex map[string]*KnownAddress // address key to ka for all addrs.
addrNew [newBucketCount]map[string]*KnownAddress
addrTried [triedBucketCount]*list.List
started int32
shutdown int32
wg sync.WaitGroup
quit chan struct{}
nTried int
nNew int
lamtx sync.Mutex
localAddresses map[string]*localAddress
version int
}
字段解析:
AddrManager
结构体定义了一个并发安全的地址管理器,用于缓存比特币网络中的潜在节点。以下是各个字段的作用说明:
type AddrManager struct {
mtx sync.RWMutex
peersFile string
lookupFunc func(string) ([]net.IP, error)
rand *rand.Rand
key [32]byte
addrIndex map[string]*KnownAddress // address key to ka for all addrs.
addrNew [newBucketCount]map[string]*KnownAddress
addrTried [triedBucketCount]*list.List
started int32
shutdown int32
wg sync.WaitGroup
quit chan struct{}
nTried int
nNew int
lamtx sync.Mutex
localAddresses map[string]*localAddress
version int
}
字段说明
- mtx (sync.RWMutex):读写锁,用于保护 AddrManager 内部数据的并发访问。
- peersFile (string):存储节点地址的文件路径,用于持久化节点数据。
- lookupFunc (func(string) ([]net.IP, error)):域名解析函数,提供将域名转换为 IP 地址的功能。
- rand (*rand.Rand):随机数生成器,用于随机选择节点地址。
- key (32byte):加密密钥,用于地址选择的加密操作。
- addrIndex (mapstring*KnownAddress):地址索引,将地址字符串映射到
KnownAddress
结构体,用于快速查找和访问所有已知地址。 - addrNew (newBucketCountmapstring*KnownAddress):新地址桶数组,每个桶包含一个地址映射,用于存储新获取的节点地址。
- addrTried (triedBucketCount*list.List):已尝试地址桶数组,每个桶包含一个地址链表,用于存储已尝试连接的节点地址。
- started (int32):标志位,表示
AddrManager
是否已经启动。 - shutdown (int32):标志位,表示
AddrManager
是否已经关闭。 - wg (sync.WaitGroup):用于等待所有 goroutine 结束。
- quit (chan struct{}):关闭信号通道,用于通知 goroutine 停止运行。
- nTried (int):已尝试连接的节点数量。
- nNew (int):新获取的节点数量。
- lamtx (sync.Mutex):本地地址锁,用于保护
localAddresses
的并发访问。 - localAddresses (mapstring*localAddress):本地地址映射,存储本地节点的地址信息。
- version (int):版本号,用于跟踪
AddrManager
的版本。
主要方法
- 创建地址管理器:
func New(dataDir string, lookupFunc func(string) ([]net.IP, error)) *AddrManager
:从指定路径加载节点地址信息,并创建一个新的地址管理器。 - 新增地址:
func (a *AddrManager) AddAddresses(addrs []*wire.NetAddressV2, srcAddr *wire.NetAddressV2)
:并发安全地增加多个地址func (a *AddrManager) AddAddress(addr, srcAddr *wire.NetAddressV2)
:并发安全地增加地址func (a *AddrManager) AddAddressByIP(addrIP string) error
:通过ip:port
方式增加地址
- 获取已知地址数量:
func (a *AddrManager) NumAddresses() int
:返回已知地址的数量。 - 选择地址:
func (a *AddrManager) GetAddress() *KnownAddress
:返回一个应该可路由的地址。它选择一个从可能的地址中随机选择一个,优先考虑那些最近没有使用过。 - 连接地址:
func (a *AddrManager) Connected(addr *wire.NetAddressV2)
- 标记地址可用:
func (a *AddrManager) Good(addr *wire.NetAddressV2)
我正在参与2024腾讯技术创作特训营最新征文,快来和我瓜分大奖!