Commit f37f7967 authored by Esad Akar's avatar Esad Akar Committed by GitHub

feat: bootnodes drop peers from oversaturated bins (#1715)

parent 04e90294
......@@ -8,4 +8,5 @@ var (
TimeToRetry = &timeToRetry
SaturationPeers = &saturationPeers
OverSaturationPeers = &overSaturationPeers
BootnodeOverSaturationPeers = &bootnodeOverSaturationPeers
)
......@@ -36,6 +36,7 @@ const (
var (
saturationPeers = 4
overSaturationPeers = 16
bootnodeOverSaturationPeers = 64
shortRetry = 30 * time.Second
timeToRetry = 2 * shortRetry
broadcastBinSize = 4
......@@ -44,6 +45,7 @@ var (
var (
errOverlayMismatch = errors.New("overlay mismatch")
errPruneEntry = errors.New("prune entry")
errEmptyBin = errors.New("empty bin")
)
type binSaturationFunc func(bin uint8, peers, connected *pslice.PSlice) (saturated bool, oversaturated bool)
......@@ -101,7 +103,11 @@ func New(base swarm.Address,
logger logging.Logger,
o Options) *Kad {
if o.SaturationFunc == nil {
o.SaturationFunc = binSaturated
os := overSaturationPeers
if o.BootnodeMode {
os = bootnodeOverSaturationPeers
}
o.SaturationFunc = binSaturated(os)
}
if o.BitSuffixLength == 0 {
o.BitSuffixLength = defaultBitSuffixLength
......@@ -543,7 +549,8 @@ func (k *Kad) connectBootnodes(ctx context.Context) {
// binSaturated indicates whether a certain bin is saturated or not.
// when a bin is not saturated it means we would like to proactively
// initiate connections to other peers in the bin.
func binSaturated(bin uint8, peers, connected *pslice.PSlice) (bool, bool) {
func binSaturated(oversaturationAmount int) binSaturationFunc {
return func(bin uint8, peers, connected *pslice.PSlice) (bool, bool) {
potentialDepth := recalcDepth(peers, swarm.MaxPO)
// short circuit for bins which are >= depth
......@@ -566,7 +573,8 @@ func binSaturated(bin uint8, peers, connected *pslice.PSlice) (bool, bool) {
return false, false, nil
})
return size >= saturationPeers, size >= overSaturationPeers
return size >= saturationPeers, size >= oversaturationAmount
}
}
// recalcDepth calculates and returns the kademlia depth.
......@@ -763,15 +771,26 @@ func (k *Kad) Pick(peer p2p.Peer) bool {
// Connected is called when a peer has dialed in.
func (k *Kad) Connected(ctx context.Context, peer p2p.Peer) error {
if !k.bootnode {
// don't run this check if we're a bootnode
po := swarm.Proximity(k.base.Bytes(), peer.Address.Bytes())
address := peer.Address
po := swarm.Proximity(k.base.Bytes(), address.Bytes())
if _, overSaturated := k.saturationFunc(po, k.knownPeers, k.connectedPeers); overSaturated {
return topology.ErrOversaturated
if k.bootnode {
randPeer, err := k.randomPeer(po)
if err != nil {
return err
}
_ = k.p2p.Disconnect(randPeer)
goto connected
}
if err := k.connected(ctx, peer.Address); err != nil {
return topology.ErrOversaturated
}
connected:
if err := k.connected(ctx, address); err != nil {
return err
}
......@@ -1203,3 +1222,19 @@ func randomSubset(addrs []swarm.Address, count int) ([]swarm.Address, error) {
return addrs[:count], nil
}
func (k *Kad) randomPeer(bin uint8) (swarm.Address, error) {
peers := k.connectedPeers.BinPeers(bin)
if len(peers) == 0 {
return swarm.ZeroAddress, errEmptyBin
}
rndIndx, err := random.Int(random.Reader, big.NewInt(int64(len(peers))))
if err != nil {
return swarm.ZeroAddress, err
}
return peers[rndIndx.Int64()], nil
}
......@@ -537,6 +537,60 @@ func TestOversaturationBootnode(t *testing.T) {
}
}
func TestBootnodeMaxConnections(t *testing.T) {
defer func(p int) {
*kademlia.BootnodeOverSaturationPeers = p
}(*kademlia.BootnodeOverSaturationPeers)
*kademlia.BootnodeOverSaturationPeers = 4
var (
conns int32 // how many connect calls were made to the p2p mock
base, kad, ab, _, signer = newTestKademlia(&conns, nil, kademlia.Options{BootnodeMode: true})
)
kad.SetRadius(swarm.MaxPO) // don't use radius for checks
if err := kad.Start(context.Background()); err != nil {
t.Fatal(err)
}
defer kad.Close()
// Add maximum accepted number of peers up until bin 5 without problems
for i := 0; i < 6; i++ {
for j := 0; j < *kademlia.BootnodeOverSaturationPeers; j++ {
addr := test.RandomAddressAt(base, i)
// if error is not nil as specified, connectOne goes fatal
connectOne(t, signer, kad, ab, addr, nil)
}
// see depth is limited to currently added peers proximity
kDepth(t, kad, i)
}
// see depth is 5
kDepth(t, kad, 5)
depth := 5
outSideDepthPeers := 5
for k := 0; k < depth; k++ {
// further connections should succeed outside of depth
for l := 0; l < outSideDepthPeers; l++ {
addr := test.RandomAddressAt(base, k)
// if error is not as specified, connectOne goes fatal
connectOne(t, signer, kad, ab, addr, nil)
// check that pick works correctly
if !kad.Pick(p2p.Peer{Address: addr}) {
t.Fatal("should pick the peer but didnt")
}
}
}
got := atomic.LoadInt32(&conns)
want := -int32(depth * outSideDepthPeers)
if got != want {
t.Fatalf("got %d, want %d", got, want)
}
}
// TestNotifierHooks tests that the Connected/Disconnected hooks
// result in the correct behavior once called.
func TestNotifierHooks(t *testing.T) {
......@@ -1055,7 +1109,8 @@ func newTestKademlia(connCounter, failedConnCounter *int32, kadOpts kademlia.Opt
}
func p2pMock(ab addressbook.Interface, signer beeCrypto.Signer, counter, failedCounter *int32) p2p.Service {
p2ps := p2pmock.New(p2pmock.WithConnectFunc(func(ctx context.Context, addr ma.Multiaddr) (*bzz.Address, error) {
p2ps := p2pmock.New(
p2pmock.WithConnectFunc(func(ctx context.Context, addr ma.Multiaddr) (*bzz.Address, error) {
if addr.Equal(nonConnectableAddress) {
_ = atomic.AddInt32(failedCounter, 1)
return nil, errors.New("non reachable node")
......@@ -1086,7 +1141,14 @@ func p2pMock(ab addressbook.Interface, signer beeCrypto.Signer, counter, failedC
}
return bzzAddr, nil
}))
}),
p2pmock.WithDisconnectFunc(func(swarm.Address) error {
if counter != nil {
_ = atomic.AddInt32(counter, -1)
}
return nil
}),
)
return p2ps
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment