forked from ava-labs/avalanchego
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add SDK Sampling interface (ava-labs#1877)
- Loading branch information
1 parent
a37bb0a
commit a3c4649
Showing
8 changed files
with
501 additions
and
111 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. | ||
// See the file LICENSE for licensing terms. | ||
|
||
package p2p | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/ava-labs/avalanchego/ids" | ||
) | ||
|
||
// NodeSampler samples nodes in network | ||
type NodeSampler interface { | ||
// Sample returns at most [limit] nodes. This may return fewer nodes if | ||
// fewer than [limit] are available. | ||
Sample(ctx context.Context, limit int) []ids.NodeID | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. | ||
// See the file LICENSE for licensing terms. | ||
|
||
package p2p | ||
|
||
import ( | ||
"context" | ||
"sync" | ||
|
||
"github.com/ava-labs/avalanchego/ids" | ||
"github.com/ava-labs/avalanchego/snow/validators" | ||
"github.com/ava-labs/avalanchego/utils/set" | ||
"github.com/ava-labs/avalanchego/version" | ||
) | ||
|
||
var ( | ||
_ validators.Connector = (*Peers)(nil) | ||
_ NodeSampler = (*Peers)(nil) | ||
) | ||
|
||
// Peers contains a set of nodes that we are connected to. | ||
type Peers struct { | ||
lock sync.RWMutex | ||
peers set.SampleableSet[ids.NodeID] | ||
} | ||
|
||
func (p *Peers) Connected(_ context.Context, nodeID ids.NodeID, _ *version.Application) error { | ||
p.lock.Lock() | ||
defer p.lock.Unlock() | ||
|
||
p.peers.Add(nodeID) | ||
|
||
return nil | ||
} | ||
|
||
func (p *Peers) Disconnected(_ context.Context, nodeID ids.NodeID) error { | ||
p.lock.Lock() | ||
defer p.lock.Unlock() | ||
|
||
p.peers.Remove(nodeID) | ||
|
||
return nil | ||
} | ||
|
||
func (p *Peers) Sample(_ context.Context, limit int) []ids.NodeID { | ||
p.lock.RLock() | ||
defer p.lock.RUnlock() | ||
|
||
return p.peers.Sample(limit) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. | ||
// See the file LICENSE for licensing terms. | ||
|
||
package p2p | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
|
||
"go.uber.org/mock/gomock" | ||
|
||
"github.com/ava-labs/avalanchego/ids" | ||
"github.com/ava-labs/avalanchego/snow/engine/common" | ||
"github.com/ava-labs/avalanchego/utils/logging" | ||
"github.com/ava-labs/avalanchego/utils/math" | ||
"github.com/ava-labs/avalanchego/utils/set" | ||
) | ||
|
||
// Sample should always return up to [limit] peers, and less if fewer than | ||
// [limit] peers are available. | ||
func TestPeersSample(t *testing.T) { | ||
nodeID1 := ids.GenerateTestNodeID() | ||
nodeID2 := ids.GenerateTestNodeID() | ||
nodeID3 := ids.GenerateTestNodeID() | ||
|
||
tests := []struct { | ||
name string | ||
connected set.Set[ids.NodeID] | ||
disconnected set.Set[ids.NodeID] | ||
limit int | ||
}{ | ||
{ | ||
name: "no peers", | ||
limit: 1, | ||
}, | ||
{ | ||
name: "one peer connected", | ||
connected: set.Of(nodeID1), | ||
limit: 1, | ||
}, | ||
{ | ||
name: "multiple peers connected", | ||
connected: set.Of(nodeID1, nodeID2, nodeID3), | ||
limit: 1, | ||
}, | ||
{ | ||
name: "peer connects and disconnects - 1", | ||
connected: set.Of(nodeID1), | ||
disconnected: set.Of(nodeID1), | ||
limit: 1, | ||
}, | ||
{ | ||
name: "peer connects and disconnects - 2", | ||
connected: set.Of(nodeID1, nodeID2), | ||
disconnected: set.Of(nodeID2), | ||
limit: 1, | ||
}, | ||
{ | ||
name: "peer connects and disconnects - 2", | ||
connected: set.Of(nodeID1, nodeID2, nodeID3), | ||
disconnected: set.Of(nodeID1, nodeID2), | ||
limit: 1, | ||
}, | ||
{ | ||
name: "less than limit peers", | ||
connected: set.Of(nodeID1, nodeID2, nodeID3), | ||
limit: 4, | ||
}, | ||
{ | ||
name: "limit peers", | ||
connected: set.Of(nodeID1, nodeID2, nodeID3), | ||
limit: 3, | ||
}, | ||
{ | ||
name: "more than limit peers", | ||
connected: set.Of(nodeID1, nodeID2, nodeID3), | ||
limit: 2, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
require := require.New(t) | ||
peers := &Peers{} | ||
|
||
for connected := range tt.connected { | ||
require.NoError(peers.Connected(context.Background(), connected, nil)) | ||
} | ||
|
||
for disconnected := range tt.disconnected { | ||
require.NoError(peers.Disconnected(context.Background(), disconnected)) | ||
} | ||
|
||
sampleable := set.Set[ids.NodeID]{} | ||
sampleable.Union(tt.connected) | ||
sampleable.Difference(tt.disconnected) | ||
|
||
sampled := peers.Sample(context.Background(), tt.limit) | ||
require.Len(sampled, math.Min(tt.limit, len(sampleable))) | ||
require.Subset(sampleable, sampled) | ||
}) | ||
} | ||
} | ||
|
||
func TestAppRequestAnyNodeSelection(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
peers []ids.NodeID | ||
expected error | ||
}{ | ||
{ | ||
name: "no peers", | ||
expected: ErrNoPeers, | ||
}, | ||
{ | ||
name: "has peers", | ||
peers: []ids.NodeID{ids.GenerateTestNodeID()}, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
require := require.New(t) | ||
ctrl := gomock.NewController(t) | ||
mockAppSender := common.NewMockSender(ctrl) | ||
|
||
expectedCalls := 0 | ||
if tt.expected == nil { | ||
expectedCalls = 1 | ||
} | ||
mockAppSender.EXPECT().SendAppRequest(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(expectedCalls) | ||
|
||
r := NewRouter(logging.NoLog{}, mockAppSender) | ||
peers := &Peers{} | ||
for _, peer := range tt.peers { | ||
require.NoError(peers.Connected(context.Background(), peer, nil)) | ||
} | ||
|
||
client, err := r.RegisterAppProtocol(1, nil, peers) | ||
require.NoError(err) | ||
|
||
err = client.AppRequestAny(context.Background(), []byte("foobar"), nil) | ||
require.ErrorIs(err, tt.expected) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.