forked from TrueCloudLab/neoneo-go
Merge pull request #2741 from nspcc-dev/separate-broadcast-queue-handling
Rework broadcast logic
This commit is contained in:
commit
ec4983e88e
4 changed files with 95 additions and 84 deletions
|
@ -1,6 +1,7 @@
|
||||||
package network
|
package network
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -109,18 +110,21 @@ func (p *localPeer) EnqueueMessage(msg *Message) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return p.EnqueuePacket(true, b)
|
return p.EnqueueHPPacket(b)
|
||||||
}
|
}
|
||||||
func (p *localPeer) EnqueuePacket(block bool, m []byte) error {
|
func (p *localPeer) BroadcastPacket(_ context.Context, m []byte) error {
|
||||||
return p.EnqueueHPPacket(block, m)
|
return p.EnqueueHPPacket(m)
|
||||||
}
|
}
|
||||||
func (p *localPeer) EnqueueP2PMessage(msg *Message) error {
|
func (p *localPeer) EnqueueP2PMessage(msg *Message) error {
|
||||||
return p.EnqueueMessage(msg)
|
return p.EnqueueMessage(msg)
|
||||||
}
|
}
|
||||||
func (p *localPeer) EnqueueP2PPacket(m []byte) error {
|
func (p *localPeer) EnqueueP2PPacket(m []byte) error {
|
||||||
return p.EnqueueHPPacket(true, m)
|
return p.EnqueueHPPacket(m)
|
||||||
}
|
}
|
||||||
func (p *localPeer) EnqueueHPPacket(_ bool, m []byte) error {
|
func (p *localPeer) BroadcastHPPacket(_ context.Context, m []byte) error {
|
||||||
|
return p.EnqueueHPPacket(m)
|
||||||
|
}
|
||||||
|
func (p *localPeer) EnqueueHPPacket(m []byte) error {
|
||||||
msg := &Message{}
|
msg := &Message{}
|
||||||
r := io.NewBinReaderFromBuf(m)
|
r := io.NewBinReaderFromBuf(m)
|
||||||
err := msg.Decode(r)
|
err := msg.Decode(r)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package network
|
package network
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
||||||
|
@ -19,16 +20,21 @@ type Peer interface {
|
||||||
PeerAddr() net.Addr
|
PeerAddr() net.Addr
|
||||||
Disconnect(error)
|
Disconnect(error)
|
||||||
|
|
||||||
// EnqueueMessage is a temporary wrapper that sends a message via
|
// EnqueueMessage is a blocking packet enqueuer similar to EnqueueP2PMessage,
|
||||||
// EnqueuePacket if there is no error in serializing it.
|
// but using the lowest priority queue.
|
||||||
EnqueueMessage(*Message) error
|
EnqueueMessage(*Message) error
|
||||||
|
|
||||||
// EnqueuePacket is a blocking packet enqueuer, it doesn't return until
|
// BroadcastPacket is a context-bound packet enqueuer, it either puts the
|
||||||
// it puts the given packet into the queue. It accepts a slice of bytes that
|
// given packet into the queue or exits with errors if the context expires
|
||||||
|
// or peer disconnects. It accepts a slice of bytes that
|
||||||
// can be shared with other queues (so that message marshalling can be
|
// can be shared with other queues (so that message marshalling can be
|
||||||
// done once for all peers). It does nothing if the peer has not yet
|
// done once for all peers). It returns an error if the peer has not yet
|
||||||
// completed handshaking.
|
// completed handshaking.
|
||||||
EnqueuePacket(bool, []byte) error
|
BroadcastPacket(context.Context, []byte) error
|
||||||
|
|
||||||
|
// BroadcastHPPacket is the same as BroadcastPacket, but uses a high-priority
|
||||||
|
// queue.
|
||||||
|
BroadcastHPPacket(context.Context, []byte) error
|
||||||
|
|
||||||
// EnqueueP2PMessage is a temporary wrapper that sends a message via
|
// EnqueueP2PMessage is a temporary wrapper that sends a message via
|
||||||
// EnqueueP2PPacket if there is no error in serializing it.
|
// EnqueueP2PPacket if there is no error in serializing it.
|
||||||
|
@ -37,17 +43,17 @@ type Peer interface {
|
||||||
// EnqueueP2PPacket is a blocking packet enqueuer, it doesn't return until
|
// EnqueueP2PPacket is a blocking packet enqueuer, it doesn't return until
|
||||||
// it puts the given packet into the queue. It accepts a slice of bytes that
|
// it puts the given packet into the queue. It accepts a slice of bytes that
|
||||||
// can be shared with other queues (so that message marshalling can be
|
// can be shared with other queues (so that message marshalling can be
|
||||||
// done once for all peers). It does nothing if the peer has not yet
|
// done once for all peers). It returns an error if the peer has not yet
|
||||||
// completed handshaking. This queue is intended to be used for unicast
|
// completed handshaking. This queue is intended to be used for unicast
|
||||||
// peer to peer communication that is more important than broadcasts
|
// peer to peer communication that is more important than broadcasts
|
||||||
// (handled by EnqueuePacket) but less important than high-priority
|
// (handled by BroadcastPacket) but less important than high-priority
|
||||||
// messages (handled by EnqueueHPPacket).
|
// messages (handled by EnqueueHPPacket and BroadcastHPPacket).
|
||||||
EnqueueP2PPacket([]byte) error
|
EnqueueP2PPacket([]byte) error
|
||||||
|
|
||||||
// EnqueueHPPacket is a blocking high priority packet enqueuer, it
|
// EnqueueHPPacket is a blocking high priority packet enqueuer, it
|
||||||
// doesn't return until it puts the given packet into the high-priority
|
// doesn't return until it puts the given packet into the high-priority
|
||||||
// queue.
|
// queue.
|
||||||
EnqueueHPPacket(bool, []byte) error
|
EnqueueHPPacket([]byte) error
|
||||||
Version() *payload.Version
|
Version() *payload.Version
|
||||||
LastBlockIndex() uint32
|
LastBlockIndex() uint32
|
||||||
Handshaked() bool
|
Handshaked() bool
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package network
|
package network
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -755,7 +756,7 @@ func (s *Server) handleInvCmd(p Peer, inv *payload.Inventory) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if inv.Type == payload.ExtensibleType {
|
if inv.Type == payload.ExtensibleType {
|
||||||
return p.EnqueueHPPacket(true, pkt)
|
return p.EnqueueHPPacket(pkt)
|
||||||
}
|
}
|
||||||
return p.EnqueueP2PPacket(pkt)
|
return p.EnqueueP2PPacket(pkt)
|
||||||
}
|
}
|
||||||
|
@ -817,7 +818,7 @@ func (s *Server) handleGetDataCmd(p Peer, inv *payload.Inventory) error {
|
||||||
pkt, err := msg.Bytes()
|
pkt, err := msg.Bytes()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if inv.Type == payload.ExtensibleType {
|
if inv.Type == payload.ExtensibleType {
|
||||||
err = p.EnqueueHPPacket(true, pkt)
|
err = p.EnqueueHPPacket(pkt)
|
||||||
} else {
|
} else {
|
||||||
err = p.EnqueueP2PPacket(pkt)
|
err = p.EnqueueP2PPacket(pkt)
|
||||||
}
|
}
|
||||||
|
@ -1348,7 +1349,7 @@ func (s *Server) RequestTx(hashes ...util.Uint256) {
|
||||||
// iteratePeersWithSendMsg sends the given message to all peers using two functions
|
// iteratePeersWithSendMsg sends the given message to all peers using two functions
|
||||||
// passed, one is to send the message and the other is to filtrate peers (the
|
// passed, one is to send the message and the other is to filtrate peers (the
|
||||||
// peer is considered invalid if it returns false).
|
// peer is considered invalid if it returns false).
|
||||||
func (s *Server) iteratePeersWithSendMsg(msg *Message, send func(Peer, bool, []byte) error, peerOK func(Peer) bool) {
|
func (s *Server) iteratePeersWithSendMsg(msg *Message, send func(Peer, context.Context, []byte) error, peerOK func(Peer) bool) {
|
||||||
var deadN, peerN, sentN int
|
var deadN, peerN, sentN int
|
||||||
|
|
||||||
// Get a copy of s.peers to avoid holding a lock while sending.
|
// Get a copy of s.peers to avoid holding a lock while sending.
|
||||||
|
@ -1357,53 +1358,48 @@ func (s *Server) iteratePeersWithSendMsg(msg *Message, send func(Peer, bool, []b
|
||||||
if peerN == 0 {
|
if peerN == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
mrand.Shuffle(peerN, func(i, j int) {
|
|
||||||
peers[i], peers[j] = peers[j], peers[i]
|
|
||||||
})
|
|
||||||
pkt, err := msg.Bytes()
|
pkt, err := msg.Bytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If true, this node isn't counted any more, either it's dead or we
|
var replies = make(chan error, peerN) // Cache is there just to make goroutines exit faster.
|
||||||
// have already sent an Inv to it.
|
var ctx, cancel = context.WithTimeout(context.Background(), s.TimePerBlock/2)
|
||||||
finished := make([]bool, peerN)
|
for _, peer := range peers {
|
||||||
|
go func(p Peer, ctx context.Context, pkt []byte) {
|
||||||
// Try non-blocking sends first and only block if have to.
|
// Do this before packet is sent, reader thread can get the reply before this routine wakes up.
|
||||||
for _, blocking := range []bool{false, true} {
|
|
||||||
for i, peer := range peers {
|
|
||||||
// Send to 2/3 of good peers.
|
|
||||||
if 3*sentN >= 2*(peerN-deadN) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if finished[i] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
err := send(peer, blocking, pkt)
|
|
||||||
if err == nil {
|
|
||||||
if msg.Command == CMDGetAddr {
|
if msg.Command == CMDGetAddr {
|
||||||
peer.AddGetAddrSent()
|
p.AddGetAddrSent()
|
||||||
}
|
}
|
||||||
|
replies <- send(p, ctx, pkt)
|
||||||
|
}(peer, ctx, pkt)
|
||||||
|
}
|
||||||
|
for r := range replies {
|
||||||
|
if r == nil {
|
||||||
sentN++
|
sentN++
|
||||||
} else if !blocking && errors.Is(err, errBusy) {
|
|
||||||
// Can be retried.
|
|
||||||
continue
|
|
||||||
} else {
|
} else {
|
||||||
deadN++
|
deadN++
|
||||||
}
|
}
|
||||||
finished[i] = true
|
if sentN+deadN == peerN {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Send to 2/3 of good peers.
|
||||||
|
if 3*sentN >= 2*(peerN-deadN) && ctx.Err() == nil {
|
||||||
|
cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
cancel()
|
||||||
|
close(replies)
|
||||||
}
|
}
|
||||||
|
|
||||||
// broadcastMessage sends the message to all available peers.
|
// broadcastMessage sends the message to all available peers.
|
||||||
func (s *Server) broadcastMessage(msg *Message) {
|
func (s *Server) broadcastMessage(msg *Message) {
|
||||||
s.iteratePeersWithSendMsg(msg, Peer.EnqueuePacket, nil)
|
s.iteratePeersWithSendMsg(msg, Peer.BroadcastPacket, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// broadcastHPMessage sends the high-priority message to all available peers.
|
// broadcastHPMessage sends the high-priority message to all available peers.
|
||||||
func (s *Server) broadcastHPMessage(msg *Message) {
|
func (s *Server) broadcastHPMessage(msg *Message) {
|
||||||
s.iteratePeersWithSendMsg(msg, Peer.EnqueueHPPacket, nil)
|
s.iteratePeersWithSendMsg(msg, Peer.BroadcastHPPacket, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// relayBlocksLoop subscribes to new blocks in the ledger and broadcasts them
|
// relayBlocksLoop subscribes to new blocks in the ledger and broadcasts them
|
||||||
|
@ -1421,7 +1417,7 @@ mainloop:
|
||||||
msg := NewMessage(CMDInv, payload.NewInventory(payload.BlockType, []util.Uint256{b.Hash()}))
|
msg := NewMessage(CMDInv, payload.NewInventory(payload.BlockType, []util.Uint256{b.Hash()}))
|
||||||
// Filter out nodes that are more current (avoid spamming the network
|
// Filter out nodes that are more current (avoid spamming the network
|
||||||
// during initial sync).
|
// during initial sync).
|
||||||
s.iteratePeersWithSendMsg(msg, Peer.EnqueuePacket, func(p Peer) bool {
|
s.iteratePeersWithSendMsg(msg, Peer.BroadcastPacket, func(p Peer) bool {
|
||||||
return p.Handshaked() && p.LastBlockIndex() < b.Index
|
return p.Handshaked() && p.LastBlockIndex() < b.Index
|
||||||
})
|
})
|
||||||
s.extensiblePool.RemoveStale(b.Index)
|
s.extensiblePool.RemoveStale(b.Index)
|
||||||
|
@ -1467,7 +1463,7 @@ func (s *Server) broadcastTxHashes(hs []util.Uint256) {
|
||||||
|
|
||||||
// We need to filter out non-relaying nodes, so plain broadcast
|
// We need to filter out non-relaying nodes, so plain broadcast
|
||||||
// functions don't fit here.
|
// functions don't fit here.
|
||||||
s.iteratePeersWithSendMsg(msg, Peer.EnqueuePacket, Peer.IsFullNode)
|
s.iteratePeersWithSendMsg(msg, Peer.BroadcastPacket, Peer.IsFullNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// initStaleMemPools initializes mempools for stale tx/payload processing.
|
// initStaleMemPools initializes mempools for stale tx/payload processing.
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package network
|
package network
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
@ -31,7 +32,6 @@ const (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errGone = errors.New("the peer is gone already")
|
errGone = errors.New("the peer is gone already")
|
||||||
errBusy = errors.New("peer is busy")
|
|
||||||
errStateMismatch = errors.New("tried to send protocol message before handshake completed")
|
errStateMismatch = errors.New("tried to send protocol message before handshake completed")
|
||||||
errPingPong = errors.New("ping/pong timeout")
|
errPingPong = errors.New("ping/pong timeout")
|
||||||
errUnexpectedPong = errors.New("pong message wasn't expected")
|
errUnexpectedPong = errors.New("pong message wasn't expected")
|
||||||
|
@ -81,40 +81,45 @@ func NewTCPPeer(conn net.Conn, s *Server) *TCPPeer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// putPacketIntoQueue puts the given message into the given queue if the peer has
|
// putBroadcastPacketIntoQueue puts the given message into the given queue if
|
||||||
// done handshaking.
|
// the peer has done handshaking using the given context.
|
||||||
func (p *TCPPeer) putPacketIntoQueue(queue chan<- []byte, block bool, msg []byte) error {
|
func (p *TCPPeer) putBroadcastPacketIntoQueue(ctx context.Context, queue chan<- []byte, msg []byte) error {
|
||||||
if !p.Handshaked() {
|
if !p.Handshaked() {
|
||||||
return errStateMismatch
|
return errStateMismatch
|
||||||
}
|
}
|
||||||
var ret error
|
|
||||||
if block {
|
|
||||||
timer := time.NewTimer(p.server.TimePerBlock / 2)
|
|
||||||
select {
|
|
||||||
case queue <- msg:
|
|
||||||
case <-p.done:
|
|
||||||
ret = errGone
|
|
||||||
case <-timer.C:
|
|
||||||
ret = errBusy
|
|
||||||
}
|
|
||||||
if !errors.Is(ret, errBusy) && !timer.Stop() {
|
|
||||||
<-timer.C
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
select {
|
select {
|
||||||
case queue <- msg:
|
case queue <- msg:
|
||||||
case <-p.done:
|
case <-p.done:
|
||||||
return errGone
|
return errGone
|
||||||
default:
|
case <-ctx.Done():
|
||||||
return errBusy
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
}
|
return nil
|
||||||
return ret
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnqueuePacket implements the Peer interface.
|
// putPacketIntoQueue puts the given message into the given queue if the peer has
|
||||||
func (p *TCPPeer) EnqueuePacket(block bool, msg []byte) error {
|
// done handshaking.
|
||||||
return p.putPacketIntoQueue(p.sendQ, block, msg)
|
func (p *TCPPeer) putPacketIntoQueue(queue chan<- []byte, msg []byte) error {
|
||||||
|
if !p.Handshaked() {
|
||||||
|
return errStateMismatch
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case queue <- msg:
|
||||||
|
case <-p.done:
|
||||||
|
return errGone
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BroadcastPacket implements the Peer interface.
|
||||||
|
func (p *TCPPeer) BroadcastPacket(ctx context.Context, msg []byte) error {
|
||||||
|
return p.putBroadcastPacketIntoQueue(ctx, p.sendQ, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BroadcastHPPacket implements the Peer interface. It the peer is not yet
|
||||||
|
// handshaked it's a noop.
|
||||||
|
func (p *TCPPeer) BroadcastHPPacket(ctx context.Context, msg []byte) error {
|
||||||
|
return p.putBroadcastPacketIntoQueue(ctx, p.hpSendQ, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// putMessageIntoQueue serializes the given Message and puts it into given queue if
|
// putMessageIntoQueue serializes the given Message and puts it into given queue if
|
||||||
|
@ -124,7 +129,7 @@ func (p *TCPPeer) putMsgIntoQueue(queue chan<- []byte, msg *Message) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return p.putPacketIntoQueue(queue, true, b)
|
return p.putPacketIntoQueue(queue, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnqueueMessage is a temporary wrapper that sends a message via
|
// EnqueueMessage is a temporary wrapper that sends a message via
|
||||||
|
@ -135,7 +140,7 @@ func (p *TCPPeer) EnqueueMessage(msg *Message) error {
|
||||||
|
|
||||||
// EnqueueP2PPacket implements the Peer interface.
|
// EnqueueP2PPacket implements the Peer interface.
|
||||||
func (p *TCPPeer) EnqueueP2PPacket(msg []byte) error {
|
func (p *TCPPeer) EnqueueP2PPacket(msg []byte) error {
|
||||||
return p.putPacketIntoQueue(p.p2pSendQ, true, msg)
|
return p.putPacketIntoQueue(p.p2pSendQ, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnqueueP2PMessage implements the Peer interface.
|
// EnqueueP2PMessage implements the Peer interface.
|
||||||
|
@ -145,8 +150,8 @@ func (p *TCPPeer) EnqueueP2PMessage(msg *Message) error {
|
||||||
|
|
||||||
// EnqueueHPPacket implements the Peer interface. It the peer is not yet
|
// EnqueueHPPacket implements the Peer interface. It the peer is not yet
|
||||||
// handshaked it's a noop.
|
// handshaked it's a noop.
|
||||||
func (p *TCPPeer) EnqueueHPPacket(block bool, msg []byte) error {
|
func (p *TCPPeer) EnqueueHPPacket(msg []byte) error {
|
||||||
return p.putPacketIntoQueue(p.hpSendQ, block, msg)
|
return p.putPacketIntoQueue(p.hpSendQ, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *TCPPeer) writeMsg(msg *Message) error {
|
func (p *TCPPeer) writeMsg(msg *Message) error {
|
||||||
|
|
Loading…
Reference in a new issue