@ -3,13 +3,14 @@ package pstoremem
import (
import (
"container/heap"
"container/heap"
"context"
"context"
"errors"
"fmt"
"fmt"
"sort"
"sort"
"sync"
"sync"
"time"
"time"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/peer"
pstore "github.com/libp2p/go-libp2p/core/peerstore"
"github.com/libp2p/go-libp2p/core/peerstore"
"github.com/libp2p/go-libp2p/core/record"
"github.com/libp2p/go-libp2p/core/record"
logging "github.com/ipfs/go-log/v2"
logging "github.com/ipfs/go-log/v2"
@ -23,7 +24,7 @@ type expiringAddr struct {
TTL time . Duration
TTL time . Duration
Expiry time . Time
Expiry time . Time
Peer peer . ID
Peer peer . ID
// to sort by expiry time
// to sort by expiry time, -1 means it's not in the heap
heapIndex int
heapIndex int
}
}
@ -31,6 +32,32 @@ func (e *expiringAddr) ExpiredBy(t time.Time) bool {
return ! t . Before ( e . Expiry )
return ! t . Before ( e . Expiry )
}
}
func ( e * expiringAddr ) IsConnected ( ) bool {
return ttlIsConnected ( e . TTL )
}
// ttlIsConnected returns true if the TTL is at least as long as the connected
// TTL.
func ttlIsConnected ( ttl time . Duration ) bool {
return ttl >= peerstore . ConnectedAddrTTL
}
var expiringAddrPool = sync . Pool { New : func ( ) any { return & expiringAddr { } } }
func getExpiringAddrs ( ) * expiringAddr {
a := expiringAddrPool . Get ( ) . ( * expiringAddr )
a . heapIndex = - 1
return a
}
func putExpiringAddrs ( ea * expiringAddr ) {
if ea == nil {
return
}
* ea = expiringAddr { }
expiringAddrPool . Put ( ea )
}
type peerRecordState struct {
type peerRecordState struct {
Envelope * record . Envelope
Envelope * record . Envelope
Seq uint64
Seq uint64
@ -40,7 +67,9 @@ type peerRecordState struct {
var _ heap . Interface = & peerAddrs { }
var _ heap . Interface = & peerAddrs { }
type peerAddrs struct {
type peerAddrs struct {
Addrs map [ peer . ID ] map [ string ] * expiringAddr // peer.ID -> addr.Bytes() -> *expiringAddr
Addrs map [ peer . ID ] map [ string ] * expiringAddr // peer.ID -> addr.Bytes() -> *expiringAddr
// expiringHeap only stores non-connected addresses. Since connected address
// basically have an infinite TTL
expiringHeap [ ] * expiringAddr
expiringHeap [ ] * expiringAddr
}
}
@ -61,10 +90,6 @@ func (pa *peerAddrs) Swap(i, j int) {
}
}
func ( pa * peerAddrs ) Push ( x any ) {
func ( pa * peerAddrs ) Push ( x any ) {
a := x . ( * expiringAddr )
a := x . ( * expiringAddr )
if _ , ok := pa . Addrs [ a . Peer ] ; ! ok {
pa . Addrs [ a . Peer ] = make ( map [ string ] * expiringAddr )
}
pa . Addrs [ a . Peer ] [ string ( a . Addr . Bytes ( ) ) ] = a
a . heapIndex = len ( pa . expiringHeap )
a . heapIndex = len ( pa . expiringHeap )
pa . expiringHeap = append ( pa . expiringHeap , a )
pa . expiringHeap = append ( pa . expiringHeap , a )
}
}
@ -72,34 +97,24 @@ func (pa *peerAddrs) Pop() any {
a := pa . expiringHeap [ len ( pa . expiringHeap ) - 1 ]
a := pa . expiringHeap [ len ( pa . expiringHeap ) - 1 ]
a . heapIndex = - 1
a . heapIndex = - 1
pa . expiringHeap = pa . expiringHeap [ 0 : len ( pa . expiringHeap ) - 1 ]
pa . expiringHeap = pa . expiringHeap [ 0 : len ( pa . expiringHeap ) - 1 ]
if m , ok := pa . Addrs [ a . Peer ] ; ok {
delete ( m , string ( a . Addr . Bytes ( ) ) )
if len ( m ) == 0 {
delete ( pa . Addrs , a . Peer )
}
}
return a
return a
}
}
func ( pa * peerAddrs ) Fix ( a * expiringAddr ) {
heap . Fix ( pa , a . heapIndex )
}
func ( pa * peerAddrs ) Delete ( a * expiringAddr ) {
func ( pa * peerAddrs ) Delete ( a * expiringAddr ) {
heap . Remove ( pa , a . heapIndex )
if ea , ok := pa . Addrs [ a . Peer ] [ string ( a . Addr . Bytes ( ) ) ] ; ok {
a . heapIndex = - 1
if ea . heapIndex != - 1 {
if m , ok := pa . Addrs [ a . Peer ] ; ok {
heap . Remove ( pa , a . heapIndex )
delete ( m , string ( a . Addr . Bytes ( ) ) )
}
if len ( m ) == 0 {
delete ( pa . Addrs [ a . Peer ] , string ( a . Addr . Bytes ( ) ) )
if len ( pa . Addrs [ a . Peer ] ) == 0 {
delete ( pa . Addrs , a . Peer )
delete ( pa . Addrs , a . Peer )
}
}
}
}
}
}
func ( pa * peerAddrs ) FindAddr ( p peer . ID , addrBytes ma . Multiaddr ) ( * expiringAddr , bool ) {
func ( pa * peerAddrs ) FindAddr ( p peer . ID , addr ma . Multiaddr ) ( * expiringAddr , bool ) {
if m , ok := pa . Addrs [ p ] ; ok {
if m , ok := pa . Addrs [ p ] ; ok {
v , ok := m [ string ( addrBytes . Bytes ( ) ) ]
v , ok := m [ string ( addr . Bytes ( ) ) ]
return v , ok
return v , ok
}
}
return nil , false
return nil , false
@ -115,12 +130,44 @@ func (pa *peerAddrs) NextExpiry() time.Time {
func ( pa * peerAddrs ) PopIfExpired ( now time . Time ) ( * expiringAddr , bool ) {
func ( pa * peerAddrs ) PopIfExpired ( now time . Time ) ( * expiringAddr , bool ) {
// Use `!Before` instead of `After` to ensure that we expire *at* now, and not *just after now*.
// Use `!Before` instead of `After` to ensure that we expire *at* now, and not *just after now*.
if len ( pa . expiringHeap ) > 0 && ! now . Before ( pa . NextExpiry ( ) ) {
if len ( pa . expiringHeap ) > 0 && ! now . Before ( pa . NextExpiry ( ) ) {
a := heap . Pop ( pa )
ea := heap . Pop ( pa ) . ( * expiringAddr )
return a . ( * expiringAddr ) , true
delete ( pa . Addrs [ ea . Peer ] , string ( ea . Addr . Bytes ( ) ) )
if len ( pa . Addrs [ ea . Peer ] ) == 0 {
delete ( pa . Addrs , ea . Peer )
}
return ea , true
}
}
return nil , false
return nil , false
}
}
func ( pa * peerAddrs ) Update ( a * expiringAddr ) {
if a . heapIndex == - 1 {
return
}
if a . IsConnected ( ) {
heap . Remove ( pa , a . heapIndex )
} else {
heap . Fix ( pa , a . heapIndex )
}
}
func ( pa * peerAddrs ) Insert ( a * expiringAddr ) {
a . heapIndex = - 1
if _ , ok := pa . Addrs [ a . Peer ] ; ! ok {
pa . Addrs [ a . Peer ] = make ( map [ string ] * expiringAddr )
}
pa . Addrs [ a . Peer ] [ string ( a . Addr . Bytes ( ) ) ] = a
// don't add connected addr to heap.
if a . IsConnected ( ) {
return
}
heap . Push ( pa , a )
}
func ( pa * peerAddrs ) NumUnconnectedAddrs ( ) int {
return len ( pa . expiringHeap )
}
type clock interface {
type clock interface {
Now ( ) time . Time
Now ( ) time . Time
}
}
@ -131,12 +178,18 @@ func (rc realclock) Now() time.Time {
return time . Now ( )
return time . Now ( )
}
}
const (
defaultMaxSignedPeerRecords = 100_000
defaultMaxUnconnectedAddrs = 1_000_000
)
// memoryAddrBook manages addresses.
// memoryAddrBook manages addresses.
type memoryAddrBook struct {
type memoryAddrBook struct {
mu sync . RWMutex
mu sync . RWMutex
// TODO bound the number of not connected addresses we store.
addrs peerAddrs
addrs peerAddrs
signedPeerRecords map [ peer . ID ] * peerRecordState
signedPeerRecords map [ peer . ID ] * peerRecordState
maxUnconnectedAddrs int
maxSignedPeerRecords int
refCount sync . WaitGroup
refCount sync . WaitGroup
cancel func ( )
cancel func ( )
@ -145,18 +198,20 @@ type memoryAddrBook struct {
clock clock
clock clock
}
}
var _ pstore . AddrBook = ( * memoryAddrBook ) ( nil )
var _ peer store . AddrBook = ( * memoryAddrBook ) ( nil )
var _ pstore . CertifiedAddrBook = ( * memoryAddrBook ) ( nil )
var _ peer store . CertifiedAddrBook = ( * memoryAddrBook ) ( nil )
func NewAddrBook ( ) * memoryAddrBook {
func NewAddrBook ( ) * memoryAddrBook {
ctx , cancel := context . WithCancel ( context . Background ( ) )
ctx , cancel := context . WithCancel ( context . Background ( ) )
ab := & memoryAddrBook {
ab := & memoryAddrBook {
addrs : newPeerAddrs ( ) ,
addrs : newPeerAddrs ( ) ,
signedPeerRecords : make ( map [ peer . ID ] * peerRecordState ) ,
signedPeerRecords : make ( map [ peer . ID ] * peerRecordState ) ,
subManager : NewAddrSubManager ( ) ,
subManager : NewAddrSubManager ( ) ,
cancel : cancel ,
cancel : cancel ,
clock : realclock { } ,
clock : realclock { } ,
maxUnconnectedAddrs : defaultMaxUnconnectedAddrs ,
maxSignedPeerRecords : defaultMaxUnconnectedAddrs ,
}
}
ab . refCount . Add ( 1 )
ab . refCount . Add ( 1 )
go ab . background ( ctx )
go ab . background ( ctx )
@ -172,6 +227,23 @@ func WithClock(clock clock) AddrBookOption {
}
}
}
}
// WithMaxAddresses sets the maximum number of unconnected addresses to store.
// The maximum number of connected addresses is bounded by the connection
// limits in the Connection Manager and Resource Manager.
func WithMaxAddresses ( n int ) AddrBookOption {
return func ( b * memoryAddrBook ) error {
b . maxUnconnectedAddrs = n
return nil
}
}
func WithMaxSignedPeerRecords ( n int ) AddrBookOption {
return func ( b * memoryAddrBook ) error {
b . maxSignedPeerRecords = n
return nil
}
}
// background periodically schedules a gc
// background periodically schedules a gc
func ( mab * memoryAddrBook ) background ( ctx context . Context ) {
func ( mab * memoryAddrBook ) background ( ctx context . Context ) {
defer mab . refCount . Done ( )
defer mab . refCount . Done ( )
@ -204,6 +276,7 @@ func (mab *memoryAddrBook) gc() {
if ! ok {
if ! ok {
return
return
}
}
putExpiringAddrs ( ea )
mab . maybeDeleteSignedPeerRecordUnlocked ( ea . Peer )
mab . maybeDeleteSignedPeerRecordUnlocked ( ea . Peer )
}
}
}
}
@ -252,6 +325,10 @@ func (mab *memoryAddrBook) ConsumePeerRecord(recordEnvelope *record.Envelope, tt
if found && lastState . Seq > rec . Seq {
if found && lastState . Seq > rec . Seq {
return false , nil
return false , nil
}
}
// check if we are over the max signed peer record limit
if ! found && len ( mab . signedPeerRecords ) >= mab . maxSignedPeerRecords {
return false , errors . New ( "too many signed peer records" )
}
mab . signedPeerRecords [ rec . PeerID ] = & peerRecordState {
mab . signedPeerRecords [ rec . PeerID ] = & peerRecordState {
Envelope : recordEnvelope ,
Envelope : recordEnvelope ,
Seq : rec . Seq ,
Seq : rec . Seq ,
@ -281,6 +358,11 @@ func (mab *memoryAddrBook) addAddrsUnlocked(p peer.ID, addrs []ma.Multiaddr, ttl
return
return
}
}
// we are over limit, drop these addrs.
if ! ttlIsConnected ( ttl ) && mab . addrs . NumUnconnectedAddrs ( ) >= mab . maxUnconnectedAddrs {
return
}
exp := mab . clock . Now ( ) . Add ( ttl )
exp := mab . clock . Now ( ) . Add ( ttl )
for _ , addr := range addrs {
for _ , addr := range addrs {
// Remove suffix of /p2p/peer-id from address
// Remove suffix of /p2p/peer-id from address
@ -296,8 +378,9 @@ func (mab *memoryAddrBook) addAddrsUnlocked(p peer.ID, addrs []ma.Multiaddr, ttl
a , found := mab . addrs . FindAddr ( p , addr )
a , found := mab . addrs . FindAddr ( p , addr )
if ! found {
if ! found {
// not found, announce it.
// not found, announce it.
entry := & expiringAddr { Addr : addr , Expiry : exp , TTL : ttl , Peer : p }
entry := getExpiringAddrs ( )
heap . Push ( & mab . addrs , entry )
* entry = expiringAddr { Addr : addr , Expiry : exp , TTL : ttl , Peer : p }
mab . addrs . Insert ( entry )
mab . subManager . BroadcastAddr ( p , addr )
mab . subManager . BroadcastAddr ( p , addr )
} else {
} else {
// update ttl & exp to whichever is greater between new and existing entry
// update ttl & exp to whichever is greater between new and existing entry
@ -311,7 +394,7 @@ func (mab *memoryAddrBook) addAddrsUnlocked(p peer.ID, addrs []ma.Multiaddr, ttl
a . Expiry = exp
a . Expiry = exp
}
}
if changed {
if changed {
mab . addrs . Fix ( a )
mab . addrs . Update ( a )
}
}
}
}
}
}
@ -344,17 +427,28 @@ func (mab *memoryAddrBook) SetAddrs(p peer.ID, addrs []ma.Multiaddr, ttl time.Du
if a , found := mab . addrs . FindAddr ( p , addr ) ; found {
if a , found := mab . addrs . FindAddr ( p , addr ) ; found {
if ttl > 0 {
if ttl > 0 {
a . Addr = addr
if a . IsConnected ( ) && ! ttlIsConnected ( ttl ) && mab . addrs . NumUnconnectedAddrs ( ) >= mab . maxUnconnectedAddrs {
a . Expiry = exp
mab . addrs . Delete ( a )
a . TTL = ttl
putExpiringAddrs ( a )
mab . addrs . Fix ( a )
} else {
mab . subManager . BroadcastAddr ( p , addr )
a . Addr = addr
a . Expiry = exp
a . TTL = ttl
mab . addrs . Update ( a )
mab . subManager . BroadcastAddr ( p , addr )
}
} else {
} else {
mab . addrs . Delete ( a )
mab . addrs . Delete ( a )
putExpiringAddrs ( a )
}
}
} else {
} else {
if ttl > 0 {
if ttl > 0 {
heap . Push ( & mab . addrs , & expiringAddr { Addr : addr , Expiry : exp , TTL : ttl , Peer : p } )
if ! ttlIsConnected ( ttl ) && mab . addrs . NumUnconnectedAddrs ( ) >= mab . maxUnconnectedAddrs {
continue
}
entry := getExpiringAddrs ( )
* entry = expiringAddr { Addr : addr , Expiry : exp , TTL : ttl , Peer : p }
mab . addrs . Insert ( entry )
mab . subManager . BroadcastAddr ( p , addr )
mab . subManager . BroadcastAddr ( p , addr )
}
}
}
}
@ -374,10 +468,17 @@ func (mab *memoryAddrBook) UpdateAddrs(p peer.ID, oldTTL time.Duration, newTTL t
if oldTTL == a . TTL {
if oldTTL == a . TTL {
if newTTL == 0 {
if newTTL == 0 {
mab . addrs . Delete ( a )
mab . addrs . Delete ( a )
putExpiringAddrs ( a )
} else {
} else {
a . TTL = newTTL
// We are over limit, drop these addresses.
a . Expiry = exp
if ttlIsConnected ( oldTTL ) && ! ttlIsConnected ( newTTL ) && mab . addrs . NumUnconnectedAddrs ( ) >= mab . maxUnconnectedAddrs {
mab . addrs . Fix ( a )
mab . addrs . Delete ( a )
putExpiringAddrs ( a )
} else {
a . TTL = newTTL
a . Expiry = exp
mab . addrs . Update ( a )
}
}
}
}
}
}
}
@ -436,6 +537,7 @@ func (mab *memoryAddrBook) ClearAddrs(p peer.ID) {
delete ( mab . signedPeerRecords , p )
delete ( mab . signedPeerRecords , p )
for _ , a := range mab . addrs . Addrs [ p ] {
for _ , a := range mab . addrs . Addrs [ p ] {
mab . addrs . Delete ( a )
mab . addrs . Delete ( a )
putExpiringAddrs ( a )
}
}
}
}