Skip to content

Commit de3b16c

Browse files
Add p2p.Network component (#2283)
Signed-off-by: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> Co-authored-by: Stephen Buttolph <stephen@avalabs.org>
1 parent 0ab2046 commit de3b16c

File tree

12 files changed

+871
-633
lines changed

12 files changed

+871
-633
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ require (
1111
github.com/DataDog/zstd v1.5.2
1212
github.com/Microsoft/go-winio v0.5.2
1313
github.com/NYTimes/gziphandler v1.1.1
14-
github.com/ava-labs/coreth v0.12.9-rc.5
14+
github.com/ava-labs/coreth v0.12.9-rc.7
1515
github.com/ava-labs/ledger-avalanche/go v0.0.0-20231102202641-ae2ebdaeac34
1616
github.com/btcsuite/btcd/btcutil v1.1.3
1717
github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah
6666
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
6767
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
6868
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
69-
github.com/ava-labs/coreth v0.12.9-rc.5 h1:xYBgNm1uOPfUdUNm8+fS8ellHnEd4qfFNb6uZHo9tqI=
70-
github.com/ava-labs/coreth v0.12.9-rc.5/go.mod h1:rECKQfGFDeodrwGPlJSvFUJDbVr30jSMIVjQLi6pNX4=
69+
github.com/ava-labs/coreth v0.12.9-rc.7 h1:AlCmXnrJwo0NxlEXQHysQgRQSCA14PZW6iyJmeVYB34=
70+
github.com/ava-labs/coreth v0.12.9-rc.7/go.mod h1:yrf2vEah4Fgj6sJ4UpHewo4DLolwdpf2bJuLRT80PGw=
7171
github.com/ava-labs/ledger-avalanche/go v0.0.0-20231102202641-ae2ebdaeac34 h1:mg9Uw6oZFJKytJxgxnl3uxZOs/SB8CVHg6Io4Tf99Zc=
7272
github.com/ava-labs/ledger-avalanche/go v0.0.0-20231102202641-ae2ebdaeac34/go.mod h1:pJxaT9bUgeRNVmNRgtCHb7sFDIRKy7CzTQVi8gGNT6g=
7373
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=

network/p2p/client.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,9 @@ type CrossChainAppResponseCallback func(
4242
type Client struct {
4343
handlerID uint64
4444
handlerPrefix []byte
45-
router *Router
45+
router *router
4646
sender common.AppSender
47-
// nodeSampler is used to select nodes to route AppRequestAny to
48-
nodeSampler NodeSampler
47+
options *clientOptions
4948
}
5049

5150
// AppRequestAny issues an AppRequest to an arbitrary node decided by Client.
@@ -56,7 +55,7 @@ func (c *Client) AppRequestAny(
5655
appRequestBytes []byte,
5756
onResponse AppResponseCallback,
5857
) error {
59-
sampled := c.nodeSampler.Sample(ctx, 1)
58+
sampled := c.options.nodeSampler.Sample(ctx, 1)
6059
if len(sampled) != 1 {
6160
return ErrNoPeers
6261
}

network/p2p/gossip/gossip_test.go

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ import (
1313

1414
"github.com/stretchr/testify/require"
1515

16-
"go.uber.org/mock/gomock"
17-
1816
"github.com/ava-labs/avalanchego/ids"
1917
"github.com/ava-labs/avalanchego/network/p2p"
2018
"github.com/ava-labs/avalanchego/snow/engine/common"
@@ -117,10 +115,9 @@ func TestGossiperGossip(t *testing.T) {
117115
for _, tt := range tests {
118116
t.Run(tt.name, func(t *testing.T) {
119117
require := require.New(t)
120-
ctrl := gomock.NewController(t)
121118

122-
responseSender := common.NewMockSender(ctrl)
123-
responseRouter := p2p.NewRouter(logging.NoLog{}, responseSender, prometheus.NewRegistry(), "")
119+
responseSender := &common.SenderTest{}
120+
responseNetwork := p2p.NewNetwork(logging.NoLog{}, responseSender, prometheus.NewRegistry(), "")
124121
responseBloom, err := NewBloomFilter(1000, 0.01)
125122
require.NoError(err)
126123
responseSet := testSet{
@@ -130,31 +127,30 @@ func TestGossiperGossip(t *testing.T) {
130127
for _, item := range tt.responder {
131128
require.NoError(responseSet.Add(item))
132129
}
133-
peers := &p2p.Peers{}
134-
require.NoError(peers.Connected(context.Background(), ids.EmptyNodeID, nil))
135130

136131
handler, err := NewHandler[*testTx](responseSet, tt.config, prometheus.NewRegistry())
137132
require.NoError(err)
138-
_, err = responseRouter.RegisterAppProtocol(0x0, handler, peers)
133+
_, err = responseNetwork.NewAppProtocol(0x0, handler)
139134
require.NoError(err)
140135

141-
requestSender := common.NewMockSender(ctrl)
142-
requestRouter := p2p.NewRouter(logging.NoLog{}, requestSender, prometheus.NewRegistry(), "")
143-
144-
gossiped := make(chan struct{})
145-
requestSender.EXPECT().SendAppRequest(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
146-
Do(func(ctx context.Context, nodeIDs set.Set[ids.NodeID], requestID uint32, request []byte) {
136+
requestSender := &common.SenderTest{
137+
SendAppRequestF: func(ctx context.Context, nodeIDs set.Set[ids.NodeID], requestID uint32, request []byte) error {
147138
go func() {
148-
require.NoError(responseRouter.AppRequest(ctx, ids.EmptyNodeID, requestID, time.Time{}, request))
139+
require.NoError(responseNetwork.AppRequest(ctx, ids.EmptyNodeID, requestID, time.Time{}, request))
149140
}()
150-
}).AnyTimes()
141+
return nil
142+
},
143+
}
151144

152-
responseSender.EXPECT().
153-
SendAppResponse(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
154-
Do(func(ctx context.Context, nodeID ids.NodeID, requestID uint32, appResponseBytes []byte) {
155-
require.NoError(requestRouter.AppResponse(ctx, nodeID, requestID, appResponseBytes))
156-
close(gossiped)
157-
}).AnyTimes()
145+
requestNetwork := p2p.NewNetwork(logging.NoLog{}, requestSender, prometheus.NewRegistry(), "")
146+
require.NoError(requestNetwork.Connected(context.Background(), ids.EmptyNodeID, nil))
147+
148+
gossiped := make(chan struct{})
149+
responseSender.SendAppResponseF = func(ctx context.Context, nodeID ids.NodeID, requestID uint32, appResponseBytes []byte) error {
150+
require.NoError(requestNetwork.AppResponse(ctx, nodeID, requestID, appResponseBytes))
151+
close(gossiped)
152+
return nil
153+
}
158154

159155
bloom, err := NewBloomFilter(1000, 0.01)
160156
require.NoError(err)
@@ -166,7 +162,7 @@ func TestGossiperGossip(t *testing.T) {
166162
require.NoError(requestSet.Add(item))
167163
}
168164

169-
requestClient, err := requestRouter.RegisterAppProtocol(0x0, nil, peers)
165+
requestClient, err := requestNetwork.NewAppProtocol(0x0, nil)
170166
require.NoError(err)
171167

172168
config := Config{

network/p2p/network.go

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
4+
package p2p
5+
6+
import (
7+
"context"
8+
"encoding/binary"
9+
"sync"
10+
"time"
11+
12+
"github.com/prometheus/client_golang/prometheus"
13+
14+
"github.com/ava-labs/avalanchego/ids"
15+
"github.com/ava-labs/avalanchego/snow/engine/common"
16+
"github.com/ava-labs/avalanchego/snow/validators"
17+
"github.com/ava-labs/avalanchego/utils/logging"
18+
"github.com/ava-labs/avalanchego/utils/set"
19+
"github.com/ava-labs/avalanchego/version"
20+
)
21+
22+
var (
23+
_ validators.Connector = (*Network)(nil)
24+
_ common.AppHandler = (*Network)(nil)
25+
_ NodeSampler = (*peerSampler)(nil)
26+
)
27+
28+
// ClientOption configures Client
29+
type ClientOption interface {
30+
apply(options *clientOptions)
31+
}
32+
33+
type clientOptionFunc func(options *clientOptions)
34+
35+
func (o clientOptionFunc) apply(options *clientOptions) {
36+
o(options)
37+
}
38+
39+
// WithValidatorSampling configures Client.AppRequestAny to sample validators
40+
func WithValidatorSampling(validators *Validators) ClientOption {
41+
return clientOptionFunc(func(options *clientOptions) {
42+
options.nodeSampler = validators
43+
})
44+
}
45+
46+
// clientOptions holds client-configurable values
47+
type clientOptions struct {
48+
// nodeSampler is used to select nodes to route Client.AppRequestAny to
49+
nodeSampler NodeSampler
50+
}
51+
52+
// NewNetwork returns an instance of Network
53+
func NewNetwork(
54+
log logging.Logger,
55+
sender common.AppSender,
56+
metrics prometheus.Registerer,
57+
namespace string,
58+
) *Network {
59+
return &Network{
60+
Peers: &Peers{},
61+
log: log,
62+
sender: sender,
63+
metrics: metrics,
64+
namespace: namespace,
65+
router: newRouter(log, sender, metrics, namespace),
66+
}
67+
}
68+
69+
// Network exposes networking state and supports building p2p application
70+
// protocols
71+
type Network struct {
72+
Peers *Peers
73+
74+
log logging.Logger
75+
sender common.AppSender
76+
metrics prometheus.Registerer
77+
namespace string
78+
79+
router *router
80+
}
81+
82+
func (n *Network) AppRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, deadline time.Time, request []byte) error {
83+
return n.router.AppRequest(ctx, nodeID, requestID, deadline, request)
84+
}
85+
86+
func (n *Network) AppResponse(ctx context.Context, nodeID ids.NodeID, requestID uint32, response []byte) error {
87+
return n.router.AppResponse(ctx, nodeID, requestID, response)
88+
}
89+
90+
func (n *Network) AppRequestFailed(ctx context.Context, nodeID ids.NodeID, requestID uint32) error {
91+
return n.router.AppRequestFailed(ctx, nodeID, requestID)
92+
}
93+
94+
func (n *Network) AppGossip(ctx context.Context, nodeID ids.NodeID, msg []byte) error {
95+
return n.router.AppGossip(ctx, nodeID, msg)
96+
}
97+
98+
func (n *Network) CrossChainAppRequest(ctx context.Context, chainID ids.ID, requestID uint32, deadline time.Time, request []byte) error {
99+
return n.router.CrossChainAppRequest(ctx, chainID, requestID, deadline, request)
100+
}
101+
102+
func (n *Network) CrossChainAppResponse(ctx context.Context, chainID ids.ID, requestID uint32, response []byte) error {
103+
return n.router.CrossChainAppResponse(ctx, chainID, requestID, response)
104+
}
105+
106+
func (n *Network) CrossChainAppRequestFailed(ctx context.Context, chainID ids.ID, requestID uint32) error {
107+
return n.router.CrossChainAppRequestFailed(ctx, chainID, requestID)
108+
}
109+
110+
func (n *Network) Connected(_ context.Context, nodeID ids.NodeID, _ *version.Application) error {
111+
n.Peers.add(nodeID)
112+
return nil
113+
}
114+
115+
func (n *Network) Disconnected(_ context.Context, nodeID ids.NodeID) error {
116+
n.Peers.remove(nodeID)
117+
return nil
118+
}
119+
120+
// NewAppProtocol reserves an identifier for an application protocol handler and
121+
// returns a Client that can be used to send messages for the corresponding
122+
// protocol.
123+
func (n *Network) NewAppProtocol(handlerID uint64, handler Handler, options ...ClientOption) (*Client, error) {
124+
if err := n.router.addHandler(handlerID, handler); err != nil {
125+
return nil, err
126+
}
127+
128+
client := &Client{
129+
handlerID: handlerID,
130+
handlerPrefix: binary.AppendUvarint(nil, handlerID),
131+
sender: n.sender,
132+
router: n.router,
133+
options: &clientOptions{
134+
nodeSampler: &peerSampler{
135+
peers: n.Peers,
136+
},
137+
},
138+
}
139+
140+
for _, option := range options {
141+
option.apply(client.options)
142+
}
143+
144+
return client, nil
145+
}
146+
147+
// Peers contains metadata about the current set of connected peers
148+
type Peers struct {
149+
lock sync.RWMutex
150+
set set.SampleableSet[ids.NodeID]
151+
}
152+
153+
func (p *Peers) add(nodeID ids.NodeID) {
154+
p.lock.Lock()
155+
defer p.lock.Unlock()
156+
157+
p.set.Add(nodeID)
158+
}
159+
160+
func (p *Peers) remove(nodeID ids.NodeID) {
161+
p.lock.Lock()
162+
defer p.lock.Unlock()
163+
164+
p.set.Remove(nodeID)
165+
}
166+
167+
func (p *Peers) has(nodeID ids.NodeID) bool {
168+
p.lock.RLock()
169+
defer p.lock.RUnlock()
170+
171+
return p.set.Contains(nodeID)
172+
}
173+
174+
// Sample returns a pseudo-random sample of up to limit Peers
175+
func (p *Peers) Sample(limit int) []ids.NodeID {
176+
p.lock.RLock()
177+
defer p.lock.RUnlock()
178+
179+
return p.set.Sample(limit)
180+
}
181+
182+
type peerSampler struct {
183+
peers *Peers
184+
}
185+
186+
func (p peerSampler) Sample(_ context.Context, limit int) []ids.NodeID {
187+
return p.peers.Sample(limit)
188+
}

0 commit comments

Comments
 (0)