Skip to content

Commit 005495b

Browse files
authored
network: handle empty wsPeer supplied to transaction handler (algorand#6195)
1 parent 3920b49 commit 005495b

File tree

11 files changed

+128
-22
lines changed

11 files changed

+128
-22
lines changed

agreement/fuzzer/networkFacade_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ type facadePeer struct {
7979
}
8080

8181
func (p *facadePeer) GetNetwork() network.GossipNode { return p.net }
82+
func (p *facadePeer) RoutingAddr() []byte {
83+
buf := make([]byte, 8)
84+
binary.BigEndian.PutUint64(buf, uint64(p.id))
85+
return buf
86+
}
8287

8388
// MakeNetworkFacade creates a facade with a given nodeID.
8489
func MakeNetworkFacade(fuzzer *Fuzzer, nodeID int) *NetworkFacade {

catchup/fetcher_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,10 @@ func (p *testUnicastPeer) GetAddress() string {
251251

252252
func (p *testUnicastPeer) GetNetwork() network.GossipNode { return p.gn }
253253

254+
func (p *testUnicastPeer) RoutingAddr() []byte {
255+
panic("not implemented")
256+
}
257+
254258
func (p *testUnicastPeer) Request(ctx context.Context, tag protocol.Tag, topics network.Topics) (resp *network.Response, e error) {
255259

256260
responseChannel := make(chan *network.Response, 1)

data/txHandler.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,7 @@ func (handler *TxHandler) incomingMsgDupCheck(data []byte) (*crypto.Digest, bool
627627
// Returns:
628628
// - the capacity guard returned by the elastic rate limiter
629629
// - a boolean indicating if the sender is rate limited
630-
func (handler *TxHandler) incomingMsgErlCheck(sender network.DisconnectablePeer) (*util.ErlCapacityGuard, bool) {
630+
func (handler *TxHandler) incomingMsgErlCheck(sender network.DisconnectableAddressablePeer) (*util.ErlCapacityGuard, bool) {
631631
var capguard *util.ErlCapacityGuard
632632
var isCMEnabled bool
633633
var err error
@@ -715,11 +715,11 @@ func (handler *TxHandler) incomingTxGroupCanonicalDedup(unverifiedTxGroup []tran
715715
}
716716

717717
// incomingTxGroupAppRateLimit checks if the sender is rate limited by the per-application rate limiter.
718-
func (handler *TxHandler) incomingTxGroupAppRateLimit(unverifiedTxGroup []transactions.SignedTxn, sender network.DisconnectablePeer) bool {
718+
func (handler *TxHandler) incomingTxGroupAppRateLimit(unverifiedTxGroup []transactions.SignedTxn, sender network.DisconnectableAddressablePeer) bool {
719719
// rate limit per application in a group. Limiting any app in a group drops the entire message.
720720
if handler.appLimiter != nil {
721721
congestedARL := len(handler.backlogQueue) > handler.appLimiterBacklogThreshold
722-
if congestedARL && handler.appLimiter.shouldDrop(unverifiedTxGroup, sender.(network.IPAddressable).RoutingAddr()) {
722+
if congestedARL && handler.appLimiter.shouldDrop(unverifiedTxGroup, sender.RoutingAddr()) {
723723
transactionMessagesAppLimiterDrop.Inc(nil)
724724
return true
725725
}

network/connPerfMon_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func makeMsgPool(N int, peers []Peer) (out []IncomingMessage) {
4848

4949
addMsg := func(msgCount int) {
5050
for i := 0; i < msgCount; i++ {
51-
msg.Sender = peers[(int(msgIndex)+i)%len(peers)].(DisconnectablePeer)
51+
msg.Sender = peers[(int(msgIndex)+i)%len(peers)].(DisconnectableAddressablePeer)
5252
timer += int64(7 * time.Nanosecond)
5353
msg.Received = timer
5454
out = append(out, msg)

network/gossipNode.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,17 @@ type DisconnectablePeer interface {
3333
GetNetwork() GossipNode
3434
}
3535

36+
// DisconnectableAddressablePeer is a Peer with a long-living connection to a network that can be disconnected and has an IP address
37+
type DisconnectableAddressablePeer interface {
38+
DisconnectablePeer
39+
IPAddressable
40+
}
41+
42+
// IPAddressable is addressable with either IPv4 or IPv6 address
43+
type IPAddressable interface {
44+
RoutingAddr() []byte
45+
}
46+
3647
// PeerOption allows users to specify a subset of peers to query
3748
//
3849
//msgp:ignore PeerOption
@@ -118,7 +129,7 @@ var outgoingMessagesBufferSize = int(
118129

119130
// IncomingMessage represents a message arriving from some peer in our p2p network
120131
type IncomingMessage struct {
121-
Sender DisconnectablePeer
132+
Sender DisconnectableAddressablePeer
122133
Tag Tag
123134
Data []byte
124135
Err error

network/p2pNetwork.go

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -943,23 +943,35 @@ func (n *P2PNetwork) txTopicHandleLoop() {
943943
}
944944
}
945945

946+
type gsPeer struct {
947+
peerID peer.ID
948+
net *P2PNetwork
949+
}
950+
951+
func (p *gsPeer) GetNetwork() GossipNode {
952+
return p.net
953+
}
954+
955+
func (p *gsPeer) RoutingAddr() []byte {
956+
return []byte(p.peerID)
957+
}
958+
946959
// txTopicValidator calls txHandler to validate and process incoming transactions.
947960
func (n *P2PNetwork) txTopicValidator(ctx context.Context, peerID peer.ID, msg *pubsub.Message) pubsub.ValidationResult {
948-
var routingAddr [8]byte
949961
n.wsPeersLock.Lock()
950-
var wsp *wsPeer
951-
var ok bool
952-
if wsp, ok = n.wsPeers[peerID]; ok {
953-
copy(routingAddr[:], wsp.RoutingAddr())
962+
var sender DisconnectableAddressablePeer
963+
if wsp, ok := n.wsPeers[peerID]; ok {
964+
sender = wsp
954965
} else {
955-
// well, otherwise use last 8 bytes of peerID
956-
copy(routingAddr[:], peerID[len(peerID)-8:])
966+
// otherwise use the peerID to handle the case where this peer is not in the wsPeers map yet
967+
// this can happen when pubsub receives new peer notifications before the wsStreamHandler is called:
968+
// create a fake peer that is good enough for tx handler to work with.
969+
sender = &gsPeer{peerID: peerID, net: n}
957970
}
958971
n.wsPeersLock.Unlock()
959972

960973
inmsg := IncomingMessage{
961-
// Sender: gossipSubPeer{peerID: msg.ReceivedFrom, net: n, routingAddr: routingAddr},
962-
Sender: wsp,
974+
Sender: sender,
963975
Tag: protocol.TxnTag,
964976
Data: msg.Data,
965977
Net: n,

network/p2pNetwork_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import (
4242
"github.com/algorand/go-algorand/test/partitiontest"
4343

4444
pubsub "github.com/libp2p/go-libp2p-pubsub"
45+
pb "github.com/libp2p/go-libp2p-pubsub/pb"
4546
"github.com/libp2p/go-libp2p/core/crypto"
4647
"github.com/libp2p/go-libp2p/core/network"
4748
"github.com/libp2p/go-libp2p/core/peer"
@@ -1374,3 +1375,33 @@ func TestP2PEnableGossipService_BothDisable(t *testing.T) {
13741375
require.False(t, netA.hasPeers())
13751376
require.False(t, netB.hasPeers())
13761377
}
1378+
1379+
// TestP2PTxTopicValidator_NoWsPeer checks txTopicValidator does not call tx handler with empty Sender
1380+
func TestP2PTxTopicValidator_NoWsPeer(t *testing.T) {
1381+
partitiontest.PartitionTest(t)
1382+
1383+
log := logging.TestingLog(t)
1384+
1385+
// prepare configs
1386+
cfg := config.GetDefaultLocal()
1387+
cfg.DNSBootstrapID = "" // disable DNS lookups since the test uses phonebook addresses
1388+
1389+
net, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}, nil)
1390+
require.NoError(t, err)
1391+
1392+
peerID := peer.ID("12345678") // must be 8+ in size
1393+
msg := pubsub.Message{Message: &pb.Message{}, ID: string(peerID)}
1394+
validateIncomingTxMessage := func(rawmsg IncomingMessage) OutgoingMessage {
1395+
require.NotEmpty(t, rawmsg.Sender)
1396+
require.Implements(t, (*DisconnectableAddressablePeer)(nil), rawmsg.Sender)
1397+
return OutgoingMessage{Action: Accept}
1398+
}
1399+
net.handler.RegisterValidatorHandlers([]TaggedMessageValidatorHandler{
1400+
{Tag: protocol.TxnTag, MessageHandler: ValidateHandleFunc(validateIncomingTxMessage)},
1401+
})
1402+
1403+
ctx := context.Background()
1404+
require.NotContains(t, net.wsPeers, peerID)
1405+
res := net.txTopicValidator(ctx, peerID, &msg)
1406+
require.Equal(t, pubsub.ValidationAccept, res)
1407+
}

network/wsNetwork.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ const (
284284
type broadcastRequest struct {
285285
tags []Tag
286286
data [][]byte
287-
except *wsPeer
287+
except Peer
288288
done chan struct{}
289289
enqueueTime time.Time
290290
ctx context.Context
@@ -381,7 +381,7 @@ func (wn *msgBroadcaster) BroadcastArray(ctx context.Context, tags []protocol.Ta
381381

382382
request := broadcastRequest{tags: tags, data: data, enqueueTime: time.Now(), ctx: ctx}
383383
if except != nil {
384-
request.except = except.(*wsPeer)
384+
request.except = except
385385
}
386386

387387
broadcastQueue := wn.broadcastQueueBulk
@@ -1401,7 +1401,7 @@ func (wn *msgBroadcaster) innerBroadcast(request broadcastRequest, prio bool, pe
14011401
if wn.config.BroadcastConnectionsLimit >= 0 && sentMessageCount >= wn.config.BroadcastConnectionsLimit {
14021402
break
14031403
}
1404-
if peer == request.except {
1404+
if Peer(peer) == request.except {
14051405
continue
14061406
}
14071407
ok := peer.writeNonBlockMsgs(request.ctx, data, prio, digests, request.enqueueTime)

network/wsNetwork_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4639,3 +4639,40 @@ func TestWebsocketNetworkHTTPClient(t *testing.T) {
46394639
_, err = netB.GetHTTPClient("invalid")
46404640
require.Error(t, err)
46414641
}
4642+
4643+
// TestPeerComparisonInBroadcast tests that the peer comparison in the broadcast function works as expected
4644+
// when casting wsPeer to Peer (interface{}) type.
4645+
func TestPeerComparisonInBroadcast(t *testing.T) {
4646+
partitiontest.PartitionTest(t)
4647+
t.Parallel()
4648+
4649+
log := logging.TestingLog(t)
4650+
conf := config.GetDefaultLocal()
4651+
wn := &WebsocketNetwork{
4652+
log: log,
4653+
config: conf,
4654+
ctx: context.Background(),
4655+
}
4656+
wn.setup()
4657+
4658+
testPeer := &wsPeer{
4659+
wsPeerCore: makePeerCore(wn.ctx, wn, log, nil, "test-addr", nil, ""),
4660+
sendBufferBulk: make(chan sendMessages, sendBufferLength),
4661+
}
4662+
exceptPeer := &wsPeer{
4663+
wsPeerCore: makePeerCore(wn.ctx, wn, log, nil, "except-addr", nil, ""),
4664+
sendBufferBulk: make(chan sendMessages, sendBufferLength),
4665+
}
4666+
4667+
request := broadcastRequest{
4668+
tags: []protocol.Tag{"test-tag"},
4669+
data: [][]byte{[]byte("test-data")},
4670+
enqueueTime: time.Now(),
4671+
except: exceptPeer,
4672+
}
4673+
4674+
wn.broadcaster.innerBroadcast(request, false, []*wsPeer{testPeer, exceptPeer})
4675+
4676+
require.Equal(t, 1, len(testPeer.sendBufferBulk))
4677+
require.Equal(t, 0, len(exceptPeer.sendBufferBulk))
4678+
}

network/wsPeer.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,8 @@ type wsPeer struct {
282282

283283
// closers is a slice of functions to run when the peer is closed
284284
closers []func()
285+
// closersMu synchronizes access to closers
286+
closersMu deadlock.RWMutex
285287

286288
// peerType defines the peer's underlying connection type
287289
// used for separate p2p vs ws metrics
@@ -295,11 +297,6 @@ type HTTPPeer interface {
295297
GetHTTPClient() *http.Client
296298
}
297299

298-
// IPAddressable is addressable with either IPv4 or IPv6 address
299-
type IPAddressable interface {
300-
RoutingAddr() []byte
301-
}
302-
303300
// UnicastPeer is another possible interface for the opaque Peer.
304301
// It is possible that we can only initiate a connection to a peer over websockets.
305302
type UnicastPeer interface {
@@ -979,6 +976,8 @@ L:
979976
}
980977

981978
}
979+
wp.closersMu.RLock()
980+
defer wp.closersMu.RUnlock()
982981
// now call all registered closers
983982
for _, f := range wp.closers {
984983
f()
@@ -1115,6 +1114,9 @@ func (wp *wsPeer) sendMessagesOfInterest(messagesOfInterestGeneration uint32, me
11151114
}
11161115

11171116
func (wp *wsPeer) OnClose(f func()) {
1117+
wp.closersMu.Lock()
1118+
defer wp.closersMu.Unlock()
1119+
11181120
if wp.closers == nil {
11191121
wp.closers = []func(){}
11201122
}

rpcs/blockService_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ func (mup *mockUnicastPeer) GetNetwork() network.GossipNode {
7676
panic("not implemented")
7777
}
7878

79+
func (mup *mockUnicastPeer) RoutingAddr() []byte {
80+
panic("not implemented")
81+
}
82+
7983
// TestHandleCatchupReqNegative covers the error reporting in handleCatchupReq
8084
func TestHandleCatchupReqNegative(t *testing.T) {
8185
partitiontest.PartitionTest(t)

0 commit comments

Comments
 (0)