From a929e526a8132d4d408bd3b577621e9b00299458 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Thu, 26 Sep 2024 12:04:20 +0200 Subject: [PATCH 01/43] Clean up NodeKey --- gno.land/cmd/gnoland/secrets_init.go | 6 +- tm2/pkg/crypto/crypto.go | 13 +-- tm2/pkg/crypto/ed25519/ed25519.go | 8 +- tm2/pkg/p2p/key.go | 102 ++++++++--------- tm2/pkg/p2p/key_test.go | 163 ++++++++++++++++++++++----- tm2/pkg/p2p/switch.go | 2 +- 6 files changed, 193 insertions(+), 101 deletions(-) diff --git a/gno.land/cmd/gnoland/secrets_init.go b/gno.land/cmd/gnoland/secrets_init.go index 58dd0783f66..7a368255834 100644 --- a/gno.land/cmd/gnoland/secrets_init.go +++ b/gno.land/cmd/gnoland/secrets_init.go @@ -201,9 +201,5 @@ func generateLastSignValidatorState() *privval.FilePVLastSignState { // generateNodeKey generates the p2p node key func generateNodeKey() *p2p.NodeKey { - privKey := ed25519.GenPrivKey() - - return &p2p.NodeKey{ - PrivKey: privKey, - } + return p2p.GenerateNodeKey() } diff --git a/tm2/pkg/crypto/crypto.go b/tm2/pkg/crypto/crypto.go index 7757b75354e..1353e9dcf20 100644 --- a/tm2/pkg/crypto/crypto.go +++ b/tm2/pkg/crypto/crypto.go @@ -3,6 +3,7 @@ package crypto import ( "bytes" "encoding/json" + "errors" "fmt" "github.com/gnolang/gno/tm2/pkg/bech32" @@ -128,6 +129,8 @@ func (addr *Address) DecodeString(str string) error { // ---------------------------------------- // ID +var errZeroID = errors.New("address ID is zero") + // The bech32 representation w/ bech32 prefix. type ID string @@ -141,16 +144,12 @@ func (id ID) String() string { func (id ID) Validate() error { if id.IsZero() { - return fmt.Errorf("zero ID is invalid") + return errZeroID } + var addr Address - err := addr.DecodeID(id) - return err -} -func AddressFromID(id ID) (addr Address, err error) { - err = addr.DecodeString(string(id)) - return + return addr.DecodeID(id) } func (addr Address) ID() ID { diff --git a/tm2/pkg/crypto/ed25519/ed25519.go b/tm2/pkg/crypto/ed25519/ed25519.go index 8976994986c..f8b9529b788 100644 --- a/tm2/pkg/crypto/ed25519/ed25519.go +++ b/tm2/pkg/crypto/ed25519/ed25519.go @@ -68,11 +68,9 @@ func (privKey PrivKeyEd25519) PubKey() crypto.PubKey { // Equals - you probably don't need to use this. // Runs in constant time based on length of the keys. func (privKey PrivKeyEd25519) Equals(other crypto.PrivKey) bool { - if otherEd, ok := other.(PrivKeyEd25519); ok { - return subtle.ConstantTimeCompare(privKey[:], otherEd[:]) == 1 - } else { - return false - } + otherEd, ok := other.(PrivKeyEd25519) + + return ok && subtle.ConstantTimeCompare(privKey[:], otherEd[:]) == 1 } // GenPrivKey generates a new ed25519 private key. diff --git a/tm2/pkg/p2p/key.go b/tm2/pkg/p2p/key.go index a41edeb07f8..733ec288c97 100644 --- a/tm2/pkg/p2p/key.go +++ b/tm2/pkg/p2p/key.go @@ -1,7 +1,6 @@ package p2p import ( - "bytes" "fmt" "os" @@ -11,10 +10,6 @@ import ( osm "github.com/gnolang/gno/tm2/pkg/os" ) -// ------------------------------------------------------------------------------ -// Persistent peer ID -// TODO: encrypt on disk - // NodeKey is the persistent peer key. // It contains the nodes private key for authentication. // NOTE: keep in sync with gno.land/cmd/gnoland/secrets.go @@ -22,73 +17,72 @@ type NodeKey struct { crypto.PrivKey `json:"priv_key"` // our priv key } -func (nk NodeKey) ID() ID { - return nk.PubKey().Address().ID() +// ID returns the bech32 representation +// of the node's public p2p key, with +// the bech32 prefix +func (k NodeKey) ID() ID { + return k.PubKey().Address().ID() } // LoadOrGenNodeKey attempts to load the NodeKey from the given filePath. // If the file does not exist, it generates and saves a new NodeKey. -func LoadOrGenNodeKey(filePath string) (*NodeKey, error) { - if osm.FileExists(filePath) { - nodeKey, err := LoadNodeKey(filePath) - if err != nil { - return nil, err - } - return nodeKey, nil +func LoadOrGenNodeKey(path string) (*NodeKey, error) { + // Check if the key exists + if osm.FileExists(path) { + // Load the node key + return LoadNodeKey(path) + } + + // Key is not present on path, + // generate a fresh one + nodeKey := GenerateNodeKey() + if err := saveNodeKey(path, nodeKey); err != nil { + return nil, fmt.Errorf("unable to save node key, %w", err) } - return genNodeKey(filePath) + + return nodeKey, nil } -func LoadNodeKey(filePath string) (*NodeKey, error) { - jsonBytes, err := os.ReadFile(filePath) +// LoadNodeKey loads the node key from the given path +func LoadNodeKey(path string) (*NodeKey, error) { + // Load the key + jsonBytes, err := os.ReadFile(path) if err != nil { - return nil, err + return nil, fmt.Errorf("unable to read key, %w", err) } - nodeKey := new(NodeKey) - err = amino.UnmarshalJSON(jsonBytes, nodeKey) - if err != nil { - return nil, fmt.Errorf("Error reading NodeKey from %v: %w", filePath, err) + + var nodeKey NodeKey + + // Parse the key + if err = amino.UnmarshalJSON(jsonBytes, &nodeKey); err != nil { + return nil, fmt.Errorf("unable to JSON unmarshal node key, %w", err) } - return nodeKey, nil + + return &nodeKey, nil } -func genNodeKey(filePath string) (*NodeKey, error) { +// GenerateNodeKey generates a random +// node P2P key, based on ed25519 +func GenerateNodeKey() *NodeKey { privKey := ed25519.GenPrivKey() - nodeKey := &NodeKey{ + + return &NodeKey{ PrivKey: privKey, } +} - jsonBytes, err := amino.MarshalJSON(nodeKey) +// saveNodeKey saves the node key +func saveNodeKey(path string, nodeKey *NodeKey) error { + // Get Amino JSON + marshalledData, err := amino.MarshalJSONIndent(nodeKey, "", "\t") if err != nil { - return nil, err + return fmt.Errorf("unable to marshal node key into JSON, %w", err) } - err = os.WriteFile(filePath, jsonBytes, 0o600) - if err != nil { - return nil, err - } - return nodeKey, nil -} - -// ------------------------------------------------------------------------------ -// MakePoWTarget returns the big-endian encoding of 2^(targetBits - difficulty) - 1. -// It can be used as a Proof of Work target. -// NOTE: targetBits must be a multiple of 8 and difficulty must be less than targetBits. -func MakePoWTarget(difficulty, targetBits uint) []byte { - if targetBits%8 != 0 { - panic(fmt.Sprintf("targetBits (%d) not a multiple of 8", targetBits)) - } - if difficulty >= targetBits { - panic(fmt.Sprintf("difficulty (%d) >= targetBits (%d)", difficulty, targetBits)) + // Save the data to disk + if err := os.WriteFile(path, marshalledData, 0o644); err != nil { + return fmt.Errorf("unable to save node key to path, %w", err) } - targetBytes := targetBits / 8 - zeroPrefixLen := (int(difficulty) / 8) - prefix := bytes.Repeat([]byte{0}, zeroPrefixLen) - mod := (difficulty % 8) - if mod > 0 { - nonZeroPrefix := byte(1<<(8-mod) - 1) - prefix = append(prefix, nonZeroPrefix) - } - tailLen := int(targetBytes) - len(prefix) - return append(prefix, bytes.Repeat([]byte{0xFF}, tailLen)...) + + return nil } diff --git a/tm2/pkg/p2p/key_test.go b/tm2/pkg/p2p/key_test.go index 4f67cc0a5da..d8b8ff36188 100644 --- a/tm2/pkg/p2p/key_test.go +++ b/tm2/pkg/p2p/key_test.go @@ -1,53 +1,158 @@ package p2p import ( - "bytes" + "encoding/json" + "fmt" "os" - "path/filepath" "testing" - "github.com/gnolang/gno/tm2/pkg/random" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -func TestLoadOrGenNodeKey(t *testing.T) { +// generateKeys generates random node p2p keys +func generateKeys(t *testing.T, count int) []*NodeKey { + t.Helper() + + keys := make([]*NodeKey, count) + + for i := 0; i < count; i++ { + keys[i] = GenerateNodeKey() + } + + return keys +} + +func TestNodeKey_Generate(t *testing.T) { t.Parallel() - filePath := filepath.Join(os.TempDir(), random.RandStr(12)+"_peer_id.json") + keys := generateKeys(t, 10) - nodeKey, err := LoadOrGenNodeKey(filePath) - assert.Nil(t, err) + for _, key := range keys { + require.NotNil(t, key) + assert.NotNil(t, key.PrivKey) - nodeKey2, err := LoadOrGenNodeKey(filePath) - assert.Nil(t, err) + // Make sure all keys are unique + for _, keyInner := range keys { + if key.ID() == keyInner.ID() { + continue + } - assert.Equal(t, nodeKey, nodeKey2) + assert.False(t, key.Equals(keyInner)) + } + } } -// ---------------------------------------------------------- +func TestNodeKey_Load(t *testing.T) { + t.Parallel() + + t.Run("non-existing key", func(t *testing.T) { + t.Parallel() + + key, err := LoadNodeKey("definitely valid path") + + require.Nil(t, key) + assert.ErrorIs(t, err, os.ErrNotExist) + }) + + t.Run("invalid key format", func(t *testing.T) { + t.Parallel() + + // Generate a random path + path := fmt.Sprintf("%s/key.json", t.TempDir()) + + type random struct { + field string + } + + data, err := json.Marshal(&random{ + field: "random data", + }) + require.NoError(t, err) + + // Save the invalid data format + require.NoError(t, os.WriteFile(path, data, 0o644)) + + // Load the key, that's invalid + key, err := LoadNodeKey(path) + + require.NoError(t, err) + assert.Nil(t, key.PrivKey) + }) + + t.Run("valid key loaded", func(t *testing.T) { + t.Parallel() -func padBytes(bz []byte, targetBytes int) []byte { - return append(bz, bytes.Repeat([]byte{0xFF}, targetBytes-len(bz))...) + var ( + path = fmt.Sprintf("%s/key.json", t.TempDir()) + key = GenerateNodeKey() + ) + + // Save the key + require.NoError(t, saveNodeKey(path, key)) + + // Load the key, that's valid + loadedKey, err := LoadNodeKey(path) + require.NoError(t, err) + + assert.True(t, key.PrivKey.Equals(loadedKey.PrivKey)) + assert.Equal(t, key.ID(), loadedKey.ID()) + }) } -func TestPoWTarget(t *testing.T) { +func TestNodeKey_ID(t *testing.T) { t.Parallel() - targetBytes := 20 - cases := []struct { - difficulty uint - target []byte - }{ - {0, padBytes([]byte{}, targetBytes)}, - {1, padBytes([]byte{127}, targetBytes)}, - {8, padBytes([]byte{0}, targetBytes)}, - {9, padBytes([]byte{0, 127}, targetBytes)}, - {10, padBytes([]byte{0, 63}, targetBytes)}, - {16, padBytes([]byte{0, 0}, targetBytes)}, - {17, padBytes([]byte{0, 0, 127}, targetBytes)}, - } + keys := generateKeys(t, 10) + + for _, key := range keys { + // Make sure the ID is valid + id := key.ID() + require.NotNil(t, id) - for _, c := range cases { - assert.Equal(t, MakePoWTarget(c.difficulty, 20*8), c.target) + assert.NoError(t, id.Validate()) } } + +func TestNodeKey_LoadOrGenNodeKey(t *testing.T) { + t.Parallel() + + t.Run("existing key loaded", func(t *testing.T) { + t.Parallel() + + var ( + path = fmt.Sprintf("%s/key.json", t.TempDir()) + key = GenerateNodeKey() + ) + + // Save the key + require.NoError(t, saveNodeKey(path, key)) + + loadedKey, err := LoadOrGenNodeKey(path) + require.NoError(t, err) + + // Make sure the key was not generated + assert.True(t, key.PrivKey.Equals(loadedKey.PrivKey)) + }) + + t.Run("fresh key generated", func(t *testing.T) { + t.Parallel() + + path := fmt.Sprintf("%s/key.json", t.TempDir()) + + // Make sure there is no key at the path + _, err := os.Stat(path) + require.ErrorIs(t, err, os.ErrNotExist) + + // Generate the fresh key + key, err := LoadOrGenNodeKey(path) + require.NoError(t, err) + + // Load the saved key + loadedKey, err := LoadOrGenNodeKey(path) + require.NoError(t, err) + + // Make sure the keys are the same + assert.True(t, key.PrivKey.Equals(loadedKey.PrivKey)) + }) +} diff --git a/tm2/pkg/p2p/switch.go b/tm2/pkg/p2p/switch.go index cecfc21f3ef..37a0e81d60b 100644 --- a/tm2/pkg/p2p/switch.go +++ b/tm2/pkg/p2p/switch.go @@ -219,7 +219,7 @@ func (sw *Switch) OnStop() { if t, ok := sw.transport.(TransportLifecycle); ok { err := t.Close() if err != nil { - sw.Logger.Error("Error stopping transport on stop: ", err) + sw.Logger.Error("Error stopping transport on stop: ", "err", err) } } From a7f40f115ab8bb3e3587be279d59f2296a90a04d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Thu, 26 Sep 2024 12:11:13 +0200 Subject: [PATCH 02/43] Drop the random command from p2p --- tm2/pkg/p2p/cmd/stest/main.go | 86 ----------------------------------- 1 file changed, 86 deletions(-) delete mode 100644 tm2/pkg/p2p/cmd/stest/main.go diff --git a/tm2/pkg/p2p/cmd/stest/main.go b/tm2/pkg/p2p/cmd/stest/main.go deleted file mode 100644 index 2835e0cc1f0..00000000000 --- a/tm2/pkg/p2p/cmd/stest/main.go +++ /dev/null @@ -1,86 +0,0 @@ -package main - -import ( - "bufio" - "flag" - "fmt" - "net" - "os" - - "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" - p2pconn "github.com/gnolang/gno/tm2/pkg/p2p/conn" -) - -var ( - remote string - listen string -) - -func init() { - flag.StringVar(&listen, "listen", "", "set to :port if server, eg :8080") - flag.StringVar(&remote, "remote", "", "remote ip:port") - flag.Parse() -} - -func main() { - if listen != "" { - fmt.Println("listening at", listen) - ln, err := net.Listen("tcp", listen) - if err != nil { - // handle error - } - conn, err := ln.Accept() - if err != nil { - panic(err) - } - handleConnection(conn) - } else { - // connect to remote. - if remote == "" { - panic("must specify remote ip:port unless server") - } - fmt.Println("connecting to", remote) - conn, err := net.Dial("tcp", remote) - if err != nil { - panic(err) - } - handleConnection(conn) - } -} - -func handleConnection(conn net.Conn) { - priv := ed25519.GenPrivKey() - pub := priv.PubKey() - fmt.Println("local pubkey:", pub) - fmt.Println("local pubkey addr:", pub.Address()) - - sconn, err := p2pconn.MakeSecretConnection(conn, priv) - if err != nil { - panic(err) - } - // Read line from sconn and print. - go func() { - sc := bufio.NewScanner(sconn) - for sc.Scan() { - line := sc.Text() // GET the line string - fmt.Println(">>", line) - } - if err := sc.Err(); err != nil { - panic(err) - } - }() - // Read line from stdin and write. - for { - sc := bufio.NewScanner(os.Stdin) - for sc.Scan() { - line := sc.Text() + "\n" - _, err := sconn.Write([]byte(line)) - if err != nil { - panic(err) - } - } - if err := sc.Err(); err != nil { - panic(err) - } - } -} From e91a1a88826a58c79db11e56308fb08e3c5a6937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Thu, 26 Sep 2024 12:15:46 +0200 Subject: [PATCH 03/43] Drop the unused upnp package from p2p --- tm2/pkg/p2p/upnp/probe.go | 110 ----------- tm2/pkg/p2p/upnp/upnp.go | 392 -------------------------------------- 2 files changed, 502 deletions(-) delete mode 100644 tm2/pkg/p2p/upnp/probe.go delete mode 100644 tm2/pkg/p2p/upnp/upnp.go diff --git a/tm2/pkg/p2p/upnp/probe.go b/tm2/pkg/p2p/upnp/probe.go deleted file mode 100644 index 29480e7cecc..00000000000 --- a/tm2/pkg/p2p/upnp/probe.go +++ /dev/null @@ -1,110 +0,0 @@ -package upnp - -import ( - "fmt" - "log/slog" - "net" - "time" -) - -type UPNPCapabilities struct { - PortMapping bool - Hairpin bool -} - -func makeUPNPListener(intPort int, extPort int, logger *slog.Logger) (NAT, net.Listener, net.IP, error) { - nat, err := Discover() - if err != nil { - return nil, nil, nil, fmt.Errorf("NAT upnp could not be discovered: %w", err) - } - logger.Info(fmt.Sprintf("ourIP: %v", nat.(*upnpNAT).ourIP)) - - ext, err := nat.GetExternalAddress() - if err != nil { - return nat, nil, nil, fmt.Errorf("external address error: %w", err) - } - logger.Info(fmt.Sprintf("External address: %v", ext)) - - port, err := nat.AddPortMapping("tcp", extPort, intPort, "Tendermint UPnP Probe", 0) - if err != nil { - return nat, nil, ext, fmt.Errorf("port mapping error: %w", err) - } - logger.Info(fmt.Sprintf("Port mapping mapped: %v", port)) - - // also run the listener, open for all remote addresses. - listener, err := net.Listen("tcp", fmt.Sprintf(":%v", intPort)) - if err != nil { - return nat, nil, ext, fmt.Errorf("error establishing listener: %w", err) - } - return nat, listener, ext, nil -} - -func testHairpin(listener net.Listener, extAddr string, logger *slog.Logger) (supportsHairpin bool) { - // Listener - go func() { - inConn, err := listener.Accept() - if err != nil { - logger.Info(fmt.Sprintf("Listener.Accept() error: %v", err)) - return - } - logger.Info(fmt.Sprintf("Accepted incoming connection: %v -> %v", inConn.LocalAddr(), inConn.RemoteAddr())) - buf := make([]byte, 1024) - n, err := inConn.Read(buf) - if err != nil { - logger.Info(fmt.Sprintf("Incoming connection read error: %v", err)) - return - } - logger.Info(fmt.Sprintf("Incoming connection read %v bytes: %X", n, buf)) - if string(buf) == "test data" { - supportsHairpin = true - return - } - }() - - // Establish outgoing - outConn, err := net.Dial("tcp", extAddr) - if err != nil { - logger.Info(fmt.Sprintf("Outgoing connection dial error: %v", err)) - return - } - - n, err := outConn.Write([]byte("test data")) - if err != nil { - logger.Info(fmt.Sprintf("Outgoing connection write error: %v", err)) - return - } - logger.Info(fmt.Sprintf("Outgoing connection wrote %v bytes", n)) - - // Wait for data receipt - time.Sleep(1 * time.Second) - return supportsHairpin -} - -func Probe(logger *slog.Logger) (caps UPNPCapabilities, err error) { - logger.Info("Probing for UPnP!") - - intPort, extPort := 8001, 8001 - - nat, listener, ext, err := makeUPNPListener(intPort, extPort, logger) - if err != nil { - return - } - caps.PortMapping = true - - // Deferred cleanup - defer func() { - if err := nat.DeletePortMapping("tcp", intPort, extPort); err != nil { - logger.Error(fmt.Sprintf("Port mapping delete error: %v", err)) - } - if err := listener.Close(); err != nil { - logger.Error(fmt.Sprintf("Listener closing error: %v", err)) - } - }() - - supportsHairpin := testHairpin(listener, fmt.Sprintf("%v:%v", ext, extPort), logger) - if supportsHairpin { - caps.Hairpin = true - } - - return -} diff --git a/tm2/pkg/p2p/upnp/upnp.go b/tm2/pkg/p2p/upnp/upnp.go deleted file mode 100644 index cd47ac35553..00000000000 --- a/tm2/pkg/p2p/upnp/upnp.go +++ /dev/null @@ -1,392 +0,0 @@ -// Taken from taipei-torrent. -// Just enough UPnP to be able to forward ports -// For more information, see: http://www.upnp-hacks.org/upnp.html -package upnp - -// TODO: use syscalls to get actual ourIP, see issue #712 - -import ( - "bytes" - "encoding/xml" - "errors" - "fmt" - "io" - "net" - "net/http" - "strconv" - "strings" - "time" -) - -type upnpNAT struct { - serviceURL string - ourIP string - urnDomain string -} - -// protocol is either "udp" or "tcp" -type NAT interface { - GetExternalAddress() (addr net.IP, err error) - AddPortMapping(protocol string, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error) - DeletePortMapping(protocol string, externalPort, internalPort int) (err error) -} - -func Discover() (nat NAT, err error) { - ssdp, err := net.ResolveUDPAddr("udp4", "239.255.255.250:1900") - if err != nil { - return - } - conn, err := net.ListenPacket("udp4", ":0") - if err != nil { - return - } - socket := conn.(*net.UDPConn) - defer socket.Close() //nolint: errcheck - - if err := socket.SetDeadline(time.Now().Add(3 * time.Second)); err != nil { - return nil, err - } - - st := "InternetGatewayDevice:1" - - buf := bytes.NewBufferString( - "M-SEARCH * HTTP/1.1\r\n" + - "HOST: 239.255.255.250:1900\r\n" + - "ST: ssdp:all\r\n" + - "MAN: \"ssdp:discover\"\r\n" + - "MX: 2\r\n\r\n") - message := buf.Bytes() - answerBytes := make([]byte, 1024) - for i := 0; i < 3; i++ { - _, err = socket.WriteToUDP(message, ssdp) - if err != nil { - return - } - var n int - _, _, err = socket.ReadFromUDP(answerBytes) - if err != nil { - return - } - for { - n, _, err = socket.ReadFromUDP(answerBytes) - if err != nil { - break - } - answer := string(answerBytes[0:n]) - if !strings.Contains(answer, st) { - continue - } - // HTTP header field names are case-insensitive. - // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 - locString := "\r\nlocation:" - answer = strings.ToLower(answer) - locIndex := strings.Index(answer, locString) - if locIndex < 0 { - continue - } - loc := answer[locIndex+len(locString):] - endIndex := strings.Index(loc, "\r\n") - if endIndex < 0 { - continue - } - locURL := strings.TrimSpace(loc[0:endIndex]) - var serviceURL, urnDomain string - serviceURL, urnDomain, err = getServiceURL(locURL) - if err != nil { - return - } - var ourIP net.IP - ourIP, err = localIPv4() - if err != nil { - return - } - nat = &upnpNAT{serviceURL: serviceURL, ourIP: ourIP.String(), urnDomain: urnDomain} - return - } - } - err = errors.New("UPnP port discovery failed") - return nat, err -} - -type Envelope struct { - XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"` - Soap *SoapBody -} - -type SoapBody struct { - XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"` - ExternalIP *ExternalIPAddressResponse -} - -type ExternalIPAddressResponse struct { - XMLName xml.Name `xml:"GetExternalIPAddressResponse"` - IPAddress string `xml:"NewExternalIPAddress"` -} - -type ExternalIPAddress struct { - XMLName xml.Name `xml:"NewExternalIPAddress"` - IP string -} - -type UPNPService struct { - ServiceType string `xml:"serviceType"` - ControlURL string `xml:"controlURL"` -} - -type DeviceList struct { - Device []Device `xml:"device"` -} - -type ServiceList struct { - Service []UPNPService `xml:"service"` -} - -type Device struct { - XMLName xml.Name `xml:"device"` - DeviceType string `xml:"deviceType"` - DeviceList DeviceList `xml:"deviceList"` - ServiceList ServiceList `xml:"serviceList"` -} - -type Root struct { - Device Device -} - -func getChildDevice(d *Device, deviceType string) *Device { - dl := d.DeviceList.Device - for i := 0; i < len(dl); i++ { - if strings.Contains(dl[i].DeviceType, deviceType) { - return &dl[i] - } - } - return nil -} - -func getChildService(d *Device, serviceType string) *UPNPService { - sl := d.ServiceList.Service - for i := 0; i < len(sl); i++ { - if strings.Contains(sl[i].ServiceType, serviceType) { - return &sl[i] - } - } - return nil -} - -func localIPv4() (net.IP, error) { - tt, err := net.Interfaces() - if err != nil { - return nil, err - } - for _, t := range tt { - aa, err := t.Addrs() - if err != nil { - return nil, err - } - for _, a := range aa { - ipnet, ok := a.(*net.IPNet) - if !ok { - continue - } - v4 := ipnet.IP.To4() - if v4 == nil || v4[0] == 127 { // loopback address - continue - } - return v4, nil - } - } - return nil, errors.New("cannot find local IP address") -} - -func getServiceURL(rootURL string) (url, urnDomain string, err error) { - r, err := http.Get(rootURL) //nolint: gosec - if err != nil { - return - } - defer r.Body.Close() //nolint: errcheck - - if r.StatusCode >= 400 { - err = errors.New(fmt.Sprint(r.StatusCode)) - return - } - var root Root - err = xml.NewDecoder(r.Body).Decode(&root) - if err != nil { - return - } - a := &root.Device - if !strings.Contains(a.DeviceType, "InternetGatewayDevice:1") { - err = errors.New("no InternetGatewayDevice") - return - } - b := getChildDevice(a, "WANDevice:1") - if b == nil { - err = errors.New("no WANDevice") - return - } - c := getChildDevice(b, "WANConnectionDevice:1") - if c == nil { - err = errors.New("no WANConnectionDevice") - return - } - d := getChildService(c, "WANIPConnection:1") - if d == nil { - // Some routers don't follow the UPnP spec, and put WanIPConnection under WanDevice, - // instead of under WanConnectionDevice - d = getChildService(b, "WANIPConnection:1") - - if d == nil { - err = errors.New("no WANIPConnection") - return - } - } - // Extract the domain name, which isn't always 'schemas-upnp-org' - urnDomain = strings.Split(d.ServiceType, ":")[1] - url = combineURL(rootURL, d.ControlURL) - return url, urnDomain, err -} - -func combineURL(rootURL, subURL string) string { - protocolEnd := "://" - protoEndIndex := strings.Index(rootURL, protocolEnd) - a := rootURL[protoEndIndex+len(protocolEnd):] - rootIndex := strings.Index(a, "/") - return rootURL[0:protoEndIndex+len(protocolEnd)+rootIndex] + subURL -} - -func soapRequest(url, function, message, domain string) (r *http.Response, err error) { - fullMessage := "" + - "\r\n" + - "" + message + "" - - req, err := http.NewRequest("POST", url, strings.NewReader(fullMessage)) - if err != nil { - return nil, err - } - req.Header.Set("Content-Type", "text/xml ; charset=\"utf-8\"") - req.Header.Set("User-Agent", "Darwin/10.0.0, UPnP/1.0, MiniUPnPc/1.3") - // req.Header.Set("Transfer-Encoding", "chunked") - req.Header.Set("SOAPAction", "\"urn:"+domain+":service:WANIPConnection:1#"+function+"\"") - req.Header.Set("Connection", "Close") - req.Header.Set("Cache-Control", "no-cache") - req.Header.Set("Pragma", "no-cache") - - // log.Stderr("soapRequest ", req) - - r, err = http.DefaultClient.Do(req) - if err != nil { - return nil, err - } - /*if r.Body != nil { - defer r.Body.Close() - }*/ - - if r.StatusCode >= 400 { - // log.Stderr(function, r.StatusCode) - err = errors.New("Error " + strconv.Itoa(r.StatusCode) + " for " + function) - r = nil - return - } - return r, err -} - -type statusInfo struct { - externalIPAddress string -} - -func (n *upnpNAT) getExternalIPAddress() (info statusInfo, err error) { - message := "\r\n" + - "" - - var response *http.Response - response, err = soapRequest(n.serviceURL, "GetExternalIPAddress", message, n.urnDomain) - if response != nil { - defer response.Body.Close() //nolint: errcheck - } - if err != nil { - return - } - var envelope Envelope - data, err := io.ReadAll(response.Body) - if err != nil { - return - } - reader := bytes.NewReader(data) - err = xml.NewDecoder(reader).Decode(&envelope) - if err != nil { - return - } - - info = statusInfo{envelope.Soap.ExternalIP.IPAddress} - - if err != nil { - return - } - - return info, err -} - -// GetExternalAddress returns an external IP. If GetExternalIPAddress action -// fails or IP returned is invalid, GetExternalAddress returns an error. -func (n *upnpNAT) GetExternalAddress() (addr net.IP, err error) { - info, err := n.getExternalIPAddress() - if err != nil { - return - } - addr = net.ParseIP(info.externalIPAddress) - if addr == nil { - err = fmt.Errorf("failed to parse IP: %v", info.externalIPAddress) - } - return -} - -func (n *upnpNAT) AddPortMapping(protocol string, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error) { - // A single concatenation would break ARM compilation. - message := "\r\n" + - "" + strconv.Itoa(externalPort) - message += "" + protocol + "" - message += "" + strconv.Itoa(internalPort) + "" + - "" + n.ourIP + "" + - "1" - message += description + - "" + strconv.Itoa(timeout) + - "" - - var response *http.Response - response, err = soapRequest(n.serviceURL, "AddPortMapping", message, n.urnDomain) - if response != nil { - defer response.Body.Close() //nolint: errcheck - } - if err != nil { - return - } - - // TODO: check response to see if the port was forwarded - // log.Println(message, response) - // JAE: - // body, err := io.ReadAll(response.Body) - // fmt.Println(string(body), err) - mappedExternalPort = externalPort - _ = response - return -} - -func (n *upnpNAT) DeletePortMapping(protocol string, externalPort, internalPort int) (err error) { - message := "\r\n" + - "" + strconv.Itoa(externalPort) + - "" + protocol + "" + - "" - - var response *http.Response - response, err = soapRequest(n.serviceURL, "DeletePortMapping", message, n.urnDomain) - if response != nil { - defer response.Body.Close() //nolint: errcheck - } - if err != nil { - return - } - - // TODO: check response to see if the port was deleted - // log.Println(message, response) - _ = response - return -} From ea6d19a8d3954f10146ff7c77dc0d9646c2d1a10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Thu, 26 Sep 2024 17:16:53 +0200 Subject: [PATCH 04/43] Clean up NetAddress --- tm2/pkg/p2p/base_reactor.go | 4 +- tm2/pkg/p2p/conn/conn.go | 22 ++ tm2/pkg/p2p/conn/conn_go110.go | 15 -- tm2/pkg/p2p/conn/conn_notgo110.go | 36 --- tm2/pkg/p2p/mock/peer.go | 2 +- tm2/pkg/p2p/netaddress.go | 289 ++++++++++------------ tm2/pkg/p2p/netaddress_test.go | 381 +++++++++++++++++++++--------- tm2/pkg/p2p/node_info.go | 2 +- tm2/pkg/p2p/peer_test.go | 19 +- tm2/pkg/p2p/test_util.go | 8 +- tm2/pkg/p2p/transport.go | 12 +- tm2/pkg/p2p/transport_test.go | 49 ++-- 12 files changed, 468 insertions(+), 371 deletions(-) create mode 100644 tm2/pkg/p2p/conn/conn.go delete mode 100644 tm2/pkg/p2p/conn/conn_go110.go delete mode 100644 tm2/pkg/p2p/conn/conn_notgo110.go diff --git a/tm2/pkg/p2p/base_reactor.go b/tm2/pkg/p2p/base_reactor.go index 91b3981d109..09596035fce 100644 --- a/tm2/pkg/p2p/base_reactor.go +++ b/tm2/pkg/p2p/base_reactor.go @@ -47,10 +47,10 @@ type Reactor interface { Receive(chID byte, peer Peer, msgBytes []byte) } -//-------------------------------------- +// -------------------------------------- type BaseReactor struct { - service.BaseService // Provides Start, Stop, .Quit + service.BaseService // Provides Start, Stop, Quit Switch *Switch } diff --git a/tm2/pkg/p2p/conn/conn.go b/tm2/pkg/p2p/conn/conn.go new file mode 100644 index 00000000000..3215adc38ca --- /dev/null +++ b/tm2/pkg/p2p/conn/conn.go @@ -0,0 +1,22 @@ +package conn + +import ( + "net" + "time" +) + +// pipe wraps the networking conn interface +type pipe struct { + net.Conn +} + +func (p *pipe) SetDeadline(_ time.Time) error { + return nil +} + +func NetPipe() (net.Conn, net.Conn) { + p1, p2 := net.Pipe() + return &pipe{p1}, &pipe{p2} +} + +var _ net.Conn = (*pipe)(nil) diff --git a/tm2/pkg/p2p/conn/conn_go110.go b/tm2/pkg/p2p/conn/conn_go110.go deleted file mode 100644 index 37796ac791d..00000000000 --- a/tm2/pkg/p2p/conn/conn_go110.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build go1.10 - -package conn - -// Go1.10 has a proper net.Conn implementation that -// has the SetDeadline method implemented as per -// https://github.com/golang/go/commit/e2dd8ca946be884bb877e074a21727f1a685a706 -// lest we run into problems like -// https://github.com/tendermint/classic/issues/851 - -import "net" - -func NetPipe() (net.Conn, net.Conn) { - return net.Pipe() -} diff --git a/tm2/pkg/p2p/conn/conn_notgo110.go b/tm2/pkg/p2p/conn/conn_notgo110.go deleted file mode 100644 index f91b0c7ea63..00000000000 --- a/tm2/pkg/p2p/conn/conn_notgo110.go +++ /dev/null @@ -1,36 +0,0 @@ -//go:build !go1.10 - -package conn - -import ( - "net" - "time" -) - -// Only Go1.10 has a proper net.Conn implementation that -// has the SetDeadline method implemented as per -// -// https://github.com/golang/go/commit/e2dd8ca946be884bb877e074a21727f1a685a706 -// -// lest we run into problems like -// -// https://github.com/tendermint/classic/issues/851 -// -// so for go versions < Go1.10 use our custom net.Conn creator -// that doesn't return an `Unimplemented error` for net.Conn. -// Before https://github.com/tendermint/classic/commit/49faa79bdce5663894b3febbf4955fb1d172df04 -// we hadn't cared about errors from SetDeadline so swallow them up anyways. -type pipe struct { - net.Conn -} - -func (p *pipe) SetDeadline(t time.Time) error { - return nil -} - -func NetPipe() (net.Conn, net.Conn) { - p1, p2 := net.Pipe() - return &pipe{p1}, &pipe{p2} -} - -var _ net.Conn = (*pipe)(nil) diff --git a/tm2/pkg/p2p/mock/peer.go b/tm2/pkg/p2p/mock/peer.go index 906c168c3a8..7274369459d 100644 --- a/tm2/pkg/p2p/mock/peer.go +++ b/tm2/pkg/p2p/mock/peer.go @@ -25,7 +25,7 @@ func NewPeer(ip net.IP) *Peer { if ip == nil { _, netAddr = p2p.CreateRoutableAddr() } else { - netAddr = p2p.NewNetAddressFromIPPort("", ip, 26656) + netAddr = p2p.NewNetAddressFromIPPort(ip, 26656) } nodeKey := p2p.NodeKey{PrivKey: ed25519.GenPrivKey()} netAddr.ID = nodeKey.ID() diff --git a/tm2/pkg/p2p/netaddress.go b/tm2/pkg/p2p/netaddress.go index 1ce34afff34..02513b06979 100644 --- a/tm2/pkg/p2p/netaddress.go +++ b/tm2/pkg/p2p/netaddress.go @@ -5,7 +5,6 @@ package p2p import ( - "flag" "fmt" "net" "strconv" @@ -16,83 +15,93 @@ import ( "github.com/gnolang/gno/tm2/pkg/errors" ) +const ( + nilNetAddress = "" + badNetAddress = "" +) + +var ( + errInvalidTCPAddress = errors.New("invalid TCP address") + errUnsetIPAddress = errors.New("unset IP address") + errInvalidIP = errors.New("invalid IP address") + errUnspecifiedIP = errors.New("unspecified IP address") +) + type ID = crypto.ID // NetAddress defines information about a peer on the network -// including its Address, IP address, and port. -// NOTE: NetAddress is not meant to be mutated due to memoization. -// @amino2: immutable XXX +// including its ID, IP address, and port type NetAddress struct { - ID ID `json:"id"` // authenticated identifier (TODO) + ID ID `json:"id"` // authenticated identifier IP net.IP `json:"ip"` // part of "addr" Port uint16 `json:"port"` // part of "addr" - - // TODO: - // Name string `json:"name"` // optional DNS name - - // memoize .String() - str string } // NetAddressString returns id@addr. It strips the leading // protocol from protocolHostPort if it exists. func NetAddressString(id ID, protocolHostPort string) string { - addr := removeProtocolIfDefined(protocolHostPort) - return fmt.Sprintf("%s@%s", id, addr) + return fmt.Sprintf( + "%s@%s", + id, + removeProtocolIfDefined(protocolHostPort), + ) } // NewNetAddress returns a new NetAddress using the provided TCP -// address. When testing, other net.Addr (except TCP) will result in -// using 0.0.0.0:0. When normal run, other net.Addr (except TCP) will -// panic. Panics if ID is invalid. -// TODO: socks proxies? -func NewNetAddress(id ID, addr net.Addr) *NetAddress { +// address +func NewNetAddress(id ID, addr net.Addr) (*NetAddress, error) { + // Make sure the address is valid tcpAddr, ok := addr.(*net.TCPAddr) if !ok { - if flag.Lookup("test.v") == nil { // normal run - panic(fmt.Sprintf("Only TCPAddrs are supported. Got: %v", addr)) - } else { // in testing - netAddr := NewNetAddressFromIPPort("", net.IP("0.0.0.0"), 0) - netAddr.ID = id - return netAddr - } + return nil, errInvalidTCPAddress } + // Validate the ID if err := id.Validate(); err != nil { - panic(fmt.Sprintf("Invalid ID %v: %v (addr: %v)", id, err, addr)) + return nil, fmt.Errorf("unable to verify ID, %w", err) } - ip := tcpAddr.IP - port := uint16(tcpAddr.Port) - na := NewNetAddressFromIPPort("", ip, port) + na := NewNetAddressFromIPPort( + tcpAddr.IP, + uint16(tcpAddr.Port), + ) + + // Set the ID na.ID = id - return na + + return na, nil } // NewNetAddressFromString returns a new NetAddress using the provided address in // the form of "ID@IP:Port". // Also resolves the host if host is not an IP. -// Errors are of type ErrNetAddressXxx where Xxx is in (NoID, Invalid, Lookup) func NewNetAddressFromString(idaddr string) (*NetAddress, error) { - idaddr = removeProtocolIfDefined(idaddr) - spl := strings.Split(idaddr, "@") + var ( + prunedAddr = removeProtocolIfDefined(idaddr) + spl = strings.Split(prunedAddr, "@") + ) + if len(spl) != 2 { - return nil, NetAddressNoIDError{idaddr} + return nil, NetAddressNoIDError{prunedAddr} } - // get ID - id := crypto.ID(spl[0]) + var ( + id = crypto.ID(spl[0]) + addr = spl[1] + ) + + // Validate the ID if err := id.Validate(); err != nil { - return nil, NetAddressInvalidError{idaddr, err} + return nil, NetAddressInvalidError{prunedAddr, err} } - addr := spl[1] - // get host and port + // Extract the host and port host, portStr, err := net.SplitHostPort(addr) if err != nil { return nil, NetAddressInvalidError{addr, err} } - if len(host) == 0 { + + if host == "" { return nil, NetAddressInvalidError{ addr, errors.New("host is empty"), @@ -105,6 +114,7 @@ func NewNetAddressFromString(idaddr string) (*NetAddress, error) { if err != nil { return nil, NetAddressLookupError{host, err} } + ip = ips[0] } @@ -113,32 +123,38 @@ func NewNetAddressFromString(idaddr string) (*NetAddress, error) { return nil, NetAddressInvalidError{portStr, err} } - na := NewNetAddressFromIPPort("", ip, uint16(port)) + na := NewNetAddressFromIPPort(ip, uint16(port)) na.ID = id + return na, nil } // NewNetAddressFromStrings returns an array of NetAddress'es build using // the provided strings. func NewNetAddressFromStrings(idaddrs []string) ([]*NetAddress, []error) { - netAddrs := make([]*NetAddress, 0) - errs := make([]error, 0) + var ( + netAddrs = make([]*NetAddress, 0, len(idaddrs)) + errs = make([]error, 0, len(idaddrs)) + ) + for _, addr := range idaddrs { netAddr, err := NewNetAddressFromString(addr) if err != nil { errs = append(errs, err) - } else { - netAddrs = append(netAddrs, netAddr) + + continue } + + netAddrs = append(netAddrs, netAddr) } + return netAddrs, errs } -// NewNetAddressIPPort returns a new NetAddress using the provided IP +// NewNetAddressFromIPPort returns a new NetAddress using the provided IP // and port number. -func NewNetAddressFromIPPort(id ID, ip net.IP, port uint16) *NetAddress { +func NewNetAddressFromIPPort(ip net.IP, port uint16) *NetAddress { return &NetAddress{ - ID: id, IP: ip, Port: port, } @@ -146,88 +162,76 @@ func NewNetAddressFromIPPort(id ID, ip net.IP, port uint16) *NetAddress { // Equals reports whether na and other are the same addresses, // including their ID, IP, and Port. -func (na *NetAddress) Equals(other interface{}) bool { - if o, ok := other.(*NetAddress); ok { - return na.String() == o.String() - } - return false +func (na *NetAddress) Equals(other *NetAddress) bool { + return na.String() == other.String() } // Same returns true is na has the same non-empty ID or DialString as other. -func (na *NetAddress) Same(other interface{}) bool { - if o, ok := other.(*NetAddress); ok { - if na.DialString() == o.DialString() { - return true - } - if na.ID != "" && na.ID == o.ID { - return true - } - } - return false +func (na *NetAddress) Same(other *NetAddress) bool { + var ( + dialsSame = na.DialString() == other.DialString() + IDsSame = na.ID != "" && na.ID == other.ID + ) + + return dialsSame || IDsSame } // String representation: @: func (na *NetAddress) String() string { if na == nil { - return "" - } - if na.str != "" { - return na.str + return nilNetAddress } + str, err := na.MarshalAmino() if err != nil { - return "" + return badNetAddress } + return str } +// MarshalAmino stringifies a NetAddress. // Needed because (a) IP doesn't encode, and (b) the intend of this type is to // serialize to a string anyways. func (na NetAddress) MarshalAmino() (string, error) { - if na.str == "" { - addrStr := na.DialString() - if na.ID != "" { - addrStr = NetAddressString(na.ID, addrStr) - } - na.str = addrStr + addrStr := na.DialString() + + if na.ID != "" { + return NetAddressString(na.ID, addrStr), nil } - return na.str, nil + + return addrStr, nil } -func (na *NetAddress) UnmarshalAmino(str string) (err error) { - na2, err := NewNetAddressFromString(str) +func (na *NetAddress) UnmarshalAmino(raw string) (err error) { + netAddress, err := NewNetAddressFromString(raw) if err != nil { return err } - *na = *na2 + + *na = *netAddress + return nil } func (na *NetAddress) DialString() string { if na == nil { - return "" + return nilNetAddress } + return net.JoinHostPort( na.IP.String(), strconv.FormatUint(uint64(na.Port), 10), ) } -// Dial calls net.Dial on the address. -func (na *NetAddress) Dial() (net.Conn, error) { - conn, err := net.Dial("tcp", na.DialString()) - if err != nil { - return nil, err - } - return conn, nil -} - // DialTimeout calls net.DialTimeout on the address. func (na *NetAddress) DialTimeout(timeout time.Duration) (net.Conn, error) { conn, err := net.DialTimeout("tcp", na.DialString(), timeout) if err != nil { - return nil, err + return nil, fmt.Errorf("unable to dial address, %w", err) } + return conn, nil } @@ -236,47 +240,39 @@ func (na *NetAddress) Routable() bool { if err := na.Validate(); err != nil { return false } - // TODO(oga) bitcoind doesn't include RFC3849 here, but should we? - return !(na.RFC1918() || na.RFC3927() || na.RFC4862() || - na.RFC4193() || na.RFC4843() || na.Local()) -} -func (na *NetAddress) ValidateLocal() error { - if err := na.ID.Validate(); err != nil { - return err - } - if na.IP == nil { - return errors.New("no IP") - } - if len(na.IP) != 4 && len(na.IP) != 16 { - return fmt.Errorf("invalid IP bytes: %v", len(na.IP)) - } - if na.RFC3849() || na.IP.Equal(net.IPv4bcast) { - return errors.New("invalid IP", na.IP.IsUnspecified()) - } - return nil + // TODO(oga) bitcoind doesn't include RFC3849 here, but should we? + return !(na.RFC1918() || + na.RFC3927() || + na.RFC4862() || + na.RFC4193() || + na.RFC4843() || + na.Local()) } +// Validate validates the NetAddress params func (na *NetAddress) Validate() error { + // Validate the ID if err := na.ID.Validate(); err != nil { - return err + return fmt.Errorf("unable to validate ID, %w", err) } + + // Make sure the IP is set if na.IP == nil { - return errors.New("no IP") + return errUnsetIPAddress } + + // Make sure the IP is valid if len(na.IP) != 4 && len(na.IP) != 16 { - return fmt.Errorf("invalid IP bytes: %v", len(na.IP)) + return errInvalidIP } + + // Check if the IP is unspecified if na.IP.IsUnspecified() || na.RFC3849() || na.IP.Equal(net.IPv4bcast) { - return errors.New("invalid IP", na.IP.IsUnspecified()) + return errUnspecifiedIP } - return nil -} -// HasID returns true if the address has an ID. -// NOTE: It does not check whether the ID is valid or not. -func (na *NetAddress) HasID() bool { - return !na.ID.IsZero() + return nil } // Local returns true if it is a local address. @@ -284,56 +280,6 @@ func (na *NetAddress) Local() bool { return na.IP.IsLoopback() || zero4.Contains(na.IP) } -// ReachabilityTo checks whenever o can be reached from na. -func (na *NetAddress) ReachabilityTo(o *NetAddress) int { - const ( - Unreachable = 0 - Default = iota - Teredo - Ipv6_weak - Ipv4 - Ipv6_strong - ) - switch { - case !na.Routable(): - return Unreachable - case na.RFC4380(): - switch { - case !o.Routable(): - return Default - case o.RFC4380(): - return Teredo - case o.IP.To4() != nil: - return Ipv4 - default: // ipv6 - return Ipv6_weak - } - case na.IP.To4() != nil: - if o.Routable() && o.IP.To4() != nil { - return Ipv4 - } - return Default - default: /* ipv6 */ - var tunnelled bool - // Is our v6 is tunnelled? - if o.RFC3964() || o.RFC6052() || o.RFC6145() { - tunnelled = true - } - switch { - case !o.Routable(): - return Default - case o.RFC4380(): - return Teredo - case o.IP.To4() != nil: - return Ipv4 - case tunnelled: - // only prioritise ipv6 if we aren't tunnelling it. - return Ipv6_weak - } - return Ipv6_strong - } -} - // RFC1918: IPv4 Private networks (10.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12) // RFC3849: IPv6 Documentation address (2001:0DB8::/32) // RFC3927: IPv4 Autoconfig (169.254.0.0/16) @@ -376,9 +322,12 @@ func (na *NetAddress) RFC4862() bool { return rfc4862.Contains(na.IP) } func (na *NetAddress) RFC6052() bool { return rfc6052.Contains(na.IP) } func (na *NetAddress) RFC6145() bool { return rfc6145.Contains(na.IP) } +// removeProtocolIfDefined removes the protocol part of the given address func removeProtocolIfDefined(addr string) string { - if strings.Contains(addr, "://") { - return strings.Split(addr, "://")[1] + if !strings.Contains(addr, "://") { + // No protocol part + return addr } - return addr + + return strings.Split(addr, "://")[1] } diff --git a/tm2/pkg/p2p/netaddress_test.go b/tm2/pkg/p2p/netaddress_test.go index 413d020c153..c3fe01a66a7 100644 --- a/tm2/pkg/p2p/netaddress_test.go +++ b/tm2/pkg/p2p/netaddress_test.go @@ -1,178 +1,323 @@ package p2p import ( - "encoding/hex" + "fmt" "net" "testing" - "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestAddress2ID(t *testing.T) { - t.Parallel() +func BenchmarkNetAddress_String(b *testing.B) { + key := GenerateNodeKey() + + na, err := NewNetAddressFromString(NetAddressString(key.ID(), "127.0.0.1:0")) + require.NoError(b, err) - idbz, _ := hex.DecodeString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - id := crypto.AddressFromBytes(idbz).ID() - assert.Equal(t, crypto.ID("g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6"), id) + b.ResetTimer() - idbz, _ = hex.DecodeString("deadbeefdeadbeefdeadbeefdeadbeefdead0000") - id = crypto.AddressFromBytes(idbz).ID() - assert.Equal(t, crypto.ID("g1m6kmam774klwlh4dhmhaatd7al026qqqq9c22r"), id) + for i := 0; i < b.N; i++ { + _ = na.String() + } } func TestNewNetAddress(t *testing.T) { t.Parallel() - tcpAddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8080") - require.Nil(t, err) + t.Run("invalid TCP address", func(t *testing.T) { + t.Parallel() + + var ( + key = GenerateNodeKey() + address = "127.0.0.1:8080" + ) - assert.Panics(t, func() { - NewNetAddress("", tcpAddr) + udpAddr, err := net.ResolveUDPAddr("udp", address) + require.NoError(t, err) + + _, err = NewNetAddress(key.ID(), udpAddr) + require.Error(t, err) }) - idbz, _ := hex.DecodeString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - id := crypto.AddressFromBytes(idbz).ID() - // ^-- is "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6" + t.Run("invalid ID", func(t *testing.T) { + t.Parallel() + + var ( + id = "" // zero ID + address = "127.0.0.1:8080" + ) - addr := NewNetAddress(id, tcpAddr) - assert.Equal(t, "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", addr.String()) + tcpAddr, err := net.ResolveTCPAddr("tcp", address) + require.NoError(t, err) + + _, err = NewNetAddress(ID(id), tcpAddr) + require.Error(t, err) + }) - assert.NotPanics(t, func() { - NewNetAddress("", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8000}) - }, "Calling NewNetAddress with UDPAddr should not panic in testing") + t.Run("valid net address", func(t *testing.T) { + t.Parallel() + + var ( + key = GenerateNodeKey() + address = "127.0.0.1:8080" + ) + + tcpAddr, err := net.ResolveTCPAddr("tcp", address) + require.NoError(t, err) + + addr, err := NewNetAddress(key.ID(), tcpAddr) + require.NoError(t, err) + + assert.Equal(t, fmt.Sprintf("%s@%s", key.ID(), address), addr.String()) + }) } func TestNewNetAddressFromString(t *testing.T) { t.Parallel() - testCases := []struct { - name string - addr string - expected string - correct bool - }{ - {"no node id and no protocol", "127.0.0.1:8080", "", false}, - {"no node id w/ tcp input", "tcp://127.0.0.1:8080", "", false}, - {"no node id w/ udp input", "udp://127.0.0.1:8080", "", false}, - - {"no protocol", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", true}, - {"tcp input", "tcp://g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", true}, - {"udp input", "udp://g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", true}, - {"malformed tcp input", "tcp//g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", "", false}, - {"malformed udp input", "udp//g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", "", false}, - - // {"127.0.0:8080", false}, - {"invalid host", "notahost", "", false}, - {"invalid port", "127.0.0.1:notapath", "", false}, - {"invalid host w/ port", "notahost:8080", "", false}, - {"just a port", "8082", "", false}, - {"non-existent port", "127.0.0:8080000", "", false}, - - {"too short nodeId", "deadbeef@127.0.0.1:8080", "", false}, - {"too short, not hex nodeId", "this-isnot-hex@127.0.0.1:8080", "", false}, - {"not bech32 nodeId", "xxxm6kmam774klwlh4dhmhaatd7al02m0h0hdap9l@127.0.0.1:8080", "", false}, - - {"too short nodeId w/tcp", "tcp://deadbeef@127.0.0.1:8080", "", false}, - {"too short notHex nodeId w/tcp", "tcp://this-isnot-hex@127.0.0.1:8080", "", false}, - {"not bech32 nodeId w/tcp", "tcp://xxxxm6kmam774klwlh4dhmhaatd7al02m0h0hdap9l@127.0.0.1:8080", "", false}, - {"correct nodeId w/tcp", "tcp://g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", true}, - - {"no node id", "tcp://@127.0.0.1:8080", "", false}, - {"no node id or IP", "tcp://@", "", false}, - {"tcp no host, w/ port", "tcp://:26656", "", false}, - {"empty", "", "", false}, - {"node id delimiter 1", "@", "", false}, - {"node id delimiter 2", " @", "", false}, - {"node id delimiter 3", " @ ", "", false}, - } + t.Run("valid net address", func(t *testing.T) { + t.Parallel() + + testTable := []struct { + name string + addr string + expected string + }{ + {"no protocol", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080"}, + {"tcp input", "tcp://g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080"}, + {"udp input", "udp://g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080"}, + {"no protocol", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080"}, + {"tcp input", "tcp://g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080"}, + {"udp input", "udp://g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080"}, + {"correct nodeId w/tcp", "tcp://g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080"}, + } - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() + for _, testCase := range testTable { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() - addr, err := NewNetAddressFromString(tc.addr) - if tc.correct { - if assert.Nil(t, err, tc.addr) { - assert.Equal(t, tc.expected, addr.String()) - } - } else { - assert.NotNil(t, err, tc.addr) - } - }) - } + addr, err := NewNetAddressFromString(testCase.addr) + require.NoError(t, err) + + assert.Equal(t, testCase.expected, addr.String()) + }) + } + }) + + t.Run("invalid net address", func(t *testing.T) { + t.Parallel() + + testTable := []struct { + name string + addr string + }{ + {"no node id and no protocol", "127.0.0.1:8080"}, + {"no node id w/ tcp input", "tcp://127.0.0.1:8080"}, + {"no node id w/ udp input", "udp://127.0.0.1:8080"}, + + {"malformed tcp input", "tcp//g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080"}, + {"malformed udp input", "udp//g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080"}, + + {"invalid host", "notahost"}, + {"invalid port", "127.0.0.1:notapath"}, + {"invalid host w/ port", "notahost:8080"}, + {"just a port", "8082"}, + {"non-existent port", "127.0.0:8080000"}, + + {"too short nodeId", "deadbeef@127.0.0.1:8080"}, + {"too short, not hex nodeId", "this-isnot-hex@127.0.0.1:8080"}, + {"not bech32 nodeId", "xxxm6kmam774klwlh4dhmhaatd7al02m0h0hdap9l@127.0.0.1:8080"}, + + {"too short nodeId w/tcp", "tcp://deadbeef@127.0.0.1:8080"}, + {"too short notHex nodeId w/tcp", "tcp://this-isnot-hex@127.0.0.1:8080"}, + {"not bech32 nodeId w/tcp", "tcp://xxxxm6kmam774klwlh4dhmhaatd7al02m0h0hdap9l@127.0.0.1:8080"}, + + {"no node id", "tcp://@127.0.0.1:8080"}, + {"no node id or IP", "tcp://@"}, + {"tcp no host, w/ port", "tcp://:26656"}, + {"empty", ""}, + {"node id delimiter 1", "@"}, + {"node id delimiter 2", " @"}, + {"node id delimiter 3", " @ "}, + } + + for _, testCase := range testTable { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + addr, err := NewNetAddressFromString(testCase.addr) + + assert.Nil(t, addr) + assert.Error(t, err) + }) + } + }) } func TestNewNetAddressFromStrings(t *testing.T) { t.Parallel() - addrs, errs := NewNetAddressFromStrings([]string{ - "127.0.0.1:8080", - "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", - "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.2:8080", + t.Run("invalid addresses", func(t *testing.T) { + t.Parallel() + + var ( + keys = generateKeys(t, 10) + strs = make([]string, 0, len(keys)) + ) + + for index, key := range keys { + if index%2 != 0 { + strs = append( + strs, + fmt.Sprintf("%s@:8080", key.ID()), // missing host + ) + + continue + } + + strs = append( + strs, + fmt.Sprintf("%s@127.0.0.1:8080", key.ID()), + ) + } + + // Convert the strings + addrs, errs := NewNetAddressFromStrings(strs) + + assert.Len(t, errs, len(keys)/2) + assert.Equal(t, len(keys)/2, len(addrs)) + + for index, addr := range addrs { + assert.Contains(t, addr.String(), keys[index*2].ID()) + } + }) + + t.Run("valid addresses", func(t *testing.T) { + t.Parallel() + + var ( + keys = generateKeys(t, 10) + strs = make([]string, 0, len(keys)) + ) + + for _, key := range keys { + strs = append( + strs, + fmt.Sprintf("%s@127.0.0.1:8080", key.ID()), + ) + } + + // Convert the strings + addrs, errs := NewNetAddressFromStrings(strs) + + assert.Len(t, errs, 0) + assert.Equal(t, len(keys), len(addrs)) + + for index, addr := range addrs { + assert.Contains(t, addr.String(), keys[index].ID()) + } }) - assert.Len(t, errs, 1) - assert.Equal(t, 2, len(addrs)) } func TestNewNetAddressFromIPPort(t *testing.T) { t.Parallel() - addr := NewNetAddressFromIPPort("", net.ParseIP("127.0.0.1"), 8080) - assert.Equal(t, "127.0.0.1:8080", addr.String()) + var ( + host = "127.0.0.1" + port = uint16(8080) + ) + + addr := NewNetAddressFromIPPort(net.ParseIP(host), port) + + assert.Equal( + t, + fmt.Sprintf("%s:%d", host, port), + addr.String(), + ) } -func TestNetAddressProperties(t *testing.T) { +func TestNetAddress_Local(t *testing.T) { t.Parallel() - // TODO add more test cases - testCases := []struct { - addr string - valid bool - local bool - routable bool + testTable := []struct { + name string + addr string + isLocal bool }{ - {"g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", true, true, false}, - {"g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@ya.ru:80", true, false, true}, + { + "local loopback", + "127.0.0.1:8080", + true, + }, + { + "local loopback, zero", + "0.0.0.0:8080", + true, + }, + { + "non-local address", + "200.100.200.100:8080", + false, + }, } - for _, tc := range testCases { - addr, err := NewNetAddressFromString(tc.addr) - require.Nil(t, err) + for _, testCase := range testTable { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + key := GenerateNodeKey() - err = addr.Validate() - if tc.valid { - assert.NoError(t, err) - } else { - assert.Error(t, err) - } - assert.Equal(t, tc.local, addr.Local()) - assert.Equal(t, tc.routable, addr.Routable()) + addr, err := NewNetAddressFromString( + fmt.Sprintf( + "%s@%s", + key.ID(), + testCase.addr, + ), + ) + require.NoError(t, err) + + assert.Equal(t, testCase.isLocal, addr.Local()) + }) } } -func TestNetAddressReachabilityTo(t *testing.T) { +func TestNetAddress_Routable(t *testing.T) { t.Parallel() - // TODO add more test cases - testCases := []struct { - addr string - other string - reachability int + testTable := []struct { + name string + addr string + isRoutable bool }{ - {"g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8081", 0}, - {"g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@ya.ru:80", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", 1}, + { + "local loopback", + "127.0.0.1:8080", + false, + }, + { + "routable address", + "gno.land:80", + true, + }, } - for _, tc := range testCases { - addr, err := NewNetAddressFromString(tc.addr) - require.Nil(t, err) + for _, testCase := range testTable { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + key := GenerateNodeKey() - other, err := NewNetAddressFromString(tc.other) - require.Nil(t, err) + addr, err := NewNetAddressFromString( + fmt.Sprintf( + "%s@%s", + key.ID(), + testCase.addr, + ), + ) + require.NoError(t, err) - assert.Equal(t, tc.reachability, addr.ReachabilityTo(other)) + assert.Equal(t, testCase.isRoutable, addr.Routable()) + }) } } diff --git a/tm2/pkg/p2p/node_info.go b/tm2/pkg/p2p/node_info.go index 48ba8f7776b..5a65bd8858c 100644 --- a/tm2/pkg/p2p/node_info.go +++ b/tm2/pkg/p2p/node_info.go @@ -67,7 +67,7 @@ func (info NodeInfo) Validate() error { if info.NetAddress == nil { return fmt.Errorf("info.NetAddress cannot be nil") } - if err := info.NetAddress.ValidateLocal(); err != nil { + if err := info.NetAddress.Validate(); err != nil { return err } diff --git a/tm2/pkg/p2p/peer_test.go b/tm2/pkg/p2p/peer_test.go index 28217c4486e..685d5e5db20 100644 --- a/tm2/pkg/p2p/peer_test.go +++ b/tm2/pkg/p2p/peer_test.go @@ -157,21 +157,30 @@ func (rp *remotePeer) ID() ID { return rp.PrivKey.PubKey().Address().ID() } -func (rp *remotePeer) Start() { +func (rp *remotePeer) Start() error { if rp.listenAddr == "" { rp.listenAddr = "127.0.0.1:0" } - l, e := net.Listen("tcp", rp.listenAddr) // any available address - if e != nil { - golog.Fatalf("net.Listen tcp :0: %+v", e) + l, err := net.Listen("tcp", rp.listenAddr) // any available address + if err != nil { + golog.Fatalf("net.Listen tcp :0: %+v", err) + + return err } + rp.listener = l - rp.addr = NewNetAddress(rp.PrivKey.PubKey().Address().ID(), l.Addr()) + rp.addr, err = NewNetAddress(rp.PrivKey.PubKey().Address().ID(), l.Addr()) + if err != nil { + return err + } + if rp.channels == nil { rp.channels = []byte{testCh} } go rp.accept() + + return nil } func (rp *remotePeer) Stop() { diff --git a/tm2/pkg/p2p/test_util.go b/tm2/pkg/p2p/test_util.go index dd0d9cd6bc7..5b54d148bf1 100644 --- a/tm2/pkg/p2p/test_util.go +++ b/tm2/pkg/p2p/test_util.go @@ -222,9 +222,9 @@ func testVersionSet() versionset.VersionSet { } func testNodeInfoWithNetwork(id ID, name, network string) NodeInfo { - return NodeInfo{ + info := NodeInfo{ VersionSet: testVersionSet(), - NetAddress: NewNetAddressFromIPPort(id, net.ParseIP("127.0.0.1"), 0), + NetAddress: NewNetAddressFromIPPort(net.ParseIP("127.0.0.1"), 0), Network: network, Software: "p2ptest", Version: "v1.2.3-rc.0-deadbeef", @@ -235,4 +235,8 @@ func testNodeInfoWithNetwork(id ID, name, network string) NodeInfo { RPCAddress: fmt.Sprintf("127.0.0.1:%d", 0), }, } + + info.NetAddress.ID = id + + return info } diff --git a/tm2/pkg/p2p/transport.go b/tm2/pkg/p2p/transport.go index 5bfae9e52b8..db605f8ee87 100644 --- a/tm2/pkg/p2p/transport.go +++ b/tm2/pkg/p2p/transport.go @@ -314,7 +314,7 @@ func (mt *MultiplexTransport) acceptPeers() { if err == nil { addr := c.RemoteAddr() id := secretConn.RemotePubKey().Address().ID() - netAddr = NewNetAddress(id, addr) + netAddr, _ = NewNetAddress(id, addr) } } @@ -454,8 +454,16 @@ func (mt *MultiplexTransport) upgrade( // Reject self. if mt.nodeInfo.ID() == nodeInfo.ID() { + addr, err := NewNetAddress(nodeInfo.ID(), c.RemoteAddr()) + if err != nil { + return nil, NodeInfo{}, NetAddressInvalidError{ + Addr: c.RemoteAddr().String(), + Err: err, + } + } + return nil, NodeInfo{}, RejectedError{ - addr: *NewNetAddress(nodeInfo.ID(), c.RemoteAddr()), + addr: *addr, conn: c, id: nodeInfo.ID(), isSelf: true, diff --git a/tm2/pkg/p2p/transport_test.go b/tm2/pkg/p2p/transport_test.go index 63b1c26e666..f91eaaeac9e 100644 --- a/tm2/pkg/p2p/transport_test.go +++ b/tm2/pkg/p2p/transport_test.go @@ -12,6 +12,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" "github.com/gnolang/gno/tm2/pkg/p2p/conn" "github.com/gnolang/gno/tm2/pkg/testutils" + "github.com/stretchr/testify/require" ) var defaultNodeName = "host_peer" @@ -63,9 +64,10 @@ func TestTransportMultiplexConnFilter(t *testing.T) { errc := make(chan error) go func() { - addr := NewNetAddress(id, mt.listener.Addr()) + addr, err := NewNetAddress(id, mt.listener.Addr()) + require.NoError(t, err) - _, err := addr.Dial() + _, err = addr.DialTimeout(5 * time.Second) if err != nil { errc <- err return @@ -119,9 +121,10 @@ func TestTransportMultiplexConnFilterTimeout(t *testing.T) { errc := make(chan error) go func() { - addr := NewNetAddress(id, mt.listener.Addr()) + addr, err := NewNetAddress(id, mt.listener.Addr()) + require.NoError(t, err) - _, err := addr.Dial() + _, err = addr.DialTimeout(5 * time.Second) if err != nil { errc <- err return @@ -144,7 +147,8 @@ func TestTransportMultiplexAcceptMultiple(t *testing.T) { t.Parallel() mt := testSetupMultiplexTransport(t) - laddr := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) + laddr, err := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) + require.NoError(t, err) var ( seed = rand.New(rand.NewSource(time.Now().UnixNano())) @@ -234,9 +238,10 @@ func TestFlappyTransportMultiplexAcceptNonBlocking(t *testing.T) { // Simulate slow Peer. go func() { - addr := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) + addr, err := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) + require.NoError(t, err) - c, err := addr.Dial() + c, err := addr.DialTimeout(5 * time.Second) if err != nil { errc <- err return @@ -279,9 +284,10 @@ func TestFlappyTransportMultiplexAcceptNonBlocking(t *testing.T) { PrivKey: fastNodePV, }, ) - addr := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) + addr, err := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) + require.NoError(t, err) - _, err := dialer.Dial(*addr, peerConfig{}) + _, err = dialer.Dial(*addr, peerConfig{}) if err != nil { errc <- err return @@ -323,9 +329,10 @@ func TestTransportMultiplexValidateNodeInfo(t *testing.T) { ) ) - addr := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) + addr, err := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) + require.NoError(t, err) - _, err := dialer.Dial(*addr, peerConfig{}) + _, err = dialer.Dial(*addr, peerConfig{}) if err != nil { errc <- err return @@ -364,9 +371,10 @@ func TestTransportMultiplexRejectMismatchID(t *testing.T) { PrivKey: ed25519.GenPrivKey(), }, ) - addr := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) + addr, err := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) + require.NoError(t, err) - _, err := dialer.Dial(*addr, peerConfig{}) + _, err = dialer.Dial(*addr, peerConfig{}) if err != nil { errc <- err return @@ -405,9 +413,10 @@ func TestTransportMultiplexDialRejectWrongID(t *testing.T) { ) wrongID := ed25519.GenPrivKey().PubKey().Address().ID() - addr := NewNetAddress(wrongID, mt.listener.Addr()) + addr, err := NewNetAddress(wrongID, mt.listener.Addr()) + require.NoError(t, err) - _, err := dialer.Dial(*addr, peerConfig{}) + _, err = dialer.Dial(*addr, peerConfig{}) if err != nil { t.Logf("connection failed: %v", err) if err, ok := err.(RejectedError); ok { @@ -437,9 +446,10 @@ func TestTransportMultiplexRejectIncompatible(t *testing.T) { }, ) ) - addr := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) + addr, err := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) + require.NoError(t, err) - _, err := dialer.Dial(*addr, peerConfig{}) + _, err = dialer.Dial(*addr, peerConfig{}) if err != nil { errc <- err return @@ -466,9 +476,10 @@ func TestTransportMultiplexRejectSelf(t *testing.T) { errc := make(chan error) go func() { - addr := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) + addr, err := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) + require.NoError(t, err) - _, err := mt.Dial(*addr, peerConfig{}) + _, err = mt.Dial(*addr, peerConfig{}) if err != nil { errc <- err return From 38a446cb4c14802d5846ab072533440fe74687f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Fri, 27 Sep 2024 10:47:04 +0200 Subject: [PATCH 05/43] Save --- tm2/pkg/p2p/config/config.go | 25 ------- tm2/pkg/p2p/fuzz.go | 131 ----------------------------------- tm2/pkg/p2p/test_util.go | 6 -- 3 files changed, 162 deletions(-) delete mode 100644 tm2/pkg/p2p/fuzz.go diff --git a/tm2/pkg/p2p/config/config.go b/tm2/pkg/p2p/config/config.go index 07692145fee..71eadef3cf6 100644 --- a/tm2/pkg/p2p/config/config.go +++ b/tm2/pkg/p2p/config/config.go @@ -76,9 +76,6 @@ type P2PConfig struct { // Testing params. // Force dial to fail TestDialFail bool `json:"test_dial_fail" toml:"test_dial_fail"` - // FUzz connection - TestFuzz bool `json:"test_fuzz" toml:"test_fuzz"` - TestFuzzConfig *FuzzConnConfig `json:"test_fuzz_config" toml:"test_fuzz_config"` } // DefaultP2PConfig returns a default configuration for the peer-to-peer layer @@ -99,8 +96,6 @@ func DefaultP2PConfig() *P2PConfig { HandshakeTimeout: 20 * time.Second, DialTimeout: 3 * time.Second, TestDialFail: false, - TestFuzz: false, - TestFuzzConfig: DefaultFuzzConnConfig(), } } @@ -136,23 +131,3 @@ func (cfg *P2PConfig) ValidateBasic() error { } return nil } - -// FuzzConnConfig is a FuzzedConnection configuration. -type FuzzConnConfig struct { - Mode int - MaxDelay time.Duration - ProbDropRW float64 - ProbDropConn float64 - ProbSleep float64 -} - -// DefaultFuzzConnConfig returns the default config. -func DefaultFuzzConnConfig() *FuzzConnConfig { - return &FuzzConnConfig{ - Mode: FuzzModeDrop, - MaxDelay: 3 * time.Second, - ProbDropRW: 0.2, - ProbDropConn: 0.00, - ProbSleep: 0.00, - } -} diff --git a/tm2/pkg/p2p/fuzz.go b/tm2/pkg/p2p/fuzz.go deleted file mode 100644 index 03cf88cf750..00000000000 --- a/tm2/pkg/p2p/fuzz.go +++ /dev/null @@ -1,131 +0,0 @@ -package p2p - -import ( - "net" - "sync" - "time" - - "github.com/gnolang/gno/tm2/pkg/p2p/config" - "github.com/gnolang/gno/tm2/pkg/random" -) - -// FuzzedConnection wraps any net.Conn and depending on the mode either delays -// reads/writes or randomly drops reads/writes/connections. -type FuzzedConnection struct { - conn net.Conn - - mtx sync.Mutex - start <-chan time.Time - active bool - - config *config.FuzzConnConfig -} - -// FuzzConnAfterFromConfig creates a new FuzzedConnection from a config. -// Fuzzing starts when the duration elapses. -func FuzzConnAfterFromConfig( - conn net.Conn, - d time.Duration, - config *config.FuzzConnConfig, -) net.Conn { - return &FuzzedConnection{ - conn: conn, - start: time.After(d), - active: false, - config: config, - } -} - -// Config returns the connection's config. -func (fc *FuzzedConnection) Config() *config.FuzzConnConfig { - return fc.config -} - -// Read implements net.Conn. -func (fc *FuzzedConnection) Read(data []byte) (n int, err error) { - if fc.fuzz() { - return 0, nil - } - return fc.conn.Read(data) -} - -// Write implements net.Conn. -func (fc *FuzzedConnection) Write(data []byte) (n int, err error) { - if fc.fuzz() { - return 0, nil - } - return fc.conn.Write(data) -} - -// Close implements net.Conn. -func (fc *FuzzedConnection) Close() error { return fc.conn.Close() } - -// LocalAddr implements net.Conn. -func (fc *FuzzedConnection) LocalAddr() net.Addr { return fc.conn.LocalAddr() } - -// RemoteAddr implements net.Conn. -func (fc *FuzzedConnection) RemoteAddr() net.Addr { return fc.conn.RemoteAddr() } - -// SetDeadline implements net.Conn. -func (fc *FuzzedConnection) SetDeadline(t time.Time) error { return fc.conn.SetDeadline(t) } - -// SetReadDeadline implements net.Conn. -func (fc *FuzzedConnection) SetReadDeadline(t time.Time) error { - return fc.conn.SetReadDeadline(t) -} - -// SetWriteDeadline implements net.Conn. -func (fc *FuzzedConnection) SetWriteDeadline(t time.Time) error { - return fc.conn.SetWriteDeadline(t) -} - -func (fc *FuzzedConnection) randomDuration() time.Duration { - maxDelayMillis := int(fc.config.MaxDelay.Nanoseconds() / 1000) - return time.Millisecond * time.Duration(random.RandInt()%maxDelayMillis) //nolint: gas -} - -// implements the fuzz (delay, kill conn) -// and returns whether or not the read/write should be ignored -func (fc *FuzzedConnection) fuzz() bool { - if !fc.shouldFuzz() { - return false - } - - switch fc.config.Mode { - case config.FuzzModeDrop: - // randomly drop the r/w, drop the conn, or sleep - r := random.RandFloat64() - switch { - case r <= fc.config.ProbDropRW: - return true - case r < fc.config.ProbDropRW+fc.config.ProbDropConn: - // XXX: can't this fail because machine precision? - // XXX: do we need an error? - fc.Close() //nolint: errcheck, gas - return true - case r < fc.config.ProbDropRW+fc.config.ProbDropConn+fc.config.ProbSleep: - time.Sleep(fc.randomDuration()) - } - case config.FuzzModeDelay: - // sleep a bit - time.Sleep(fc.randomDuration()) - } - return false -} - -func (fc *FuzzedConnection) shouldFuzz() bool { - if fc.active { - return true - } - - fc.mtx.Lock() - defer fc.mtx.Unlock() - - select { - case <-fc.start: - fc.active = true - return true - default: - return false - } -} diff --git a/tm2/pkg/p2p/test_util.go b/tm2/pkg/p2p/test_util.go index 5b54d148bf1..ac202e7ae97 100644 --- a/tm2/pkg/p2p/test_util.go +++ b/tm2/pkg/p2p/test_util.go @@ -189,12 +189,6 @@ func testPeerConn( ) (pc peerConn, err error) { conn := rawConn - // Fuzz connection - if cfg.TestFuzz { - // so we have time to do peer handshakes and get set up - conn = FuzzConnAfterFromConfig(conn, 10*time.Second, cfg.TestFuzzConfig) - } - // Encrypt connection conn, err = upgradeSecretConn(conn, cfg.HandshakeTimeout, ourNodePrivKey) if err != nil { From 398f86ad6ac9816691a745115f86d15c2e24181f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Fri, 27 Sep 2024 10:47:19 +0200 Subject: [PATCH 06/43] Save --- tm2/pkg/p2p/node_info.go | 107 ++++++++++++++++------------------ tm2/pkg/p2p/node_info_test.go | 6 -- 2 files changed, 51 insertions(+), 62 deletions(-) diff --git a/tm2/pkg/p2p/node_info.go b/tm2/pkg/p2p/node_info.go index 5a65bd8858c..1e44783d9d6 100644 --- a/tm2/pkg/p2p/node_info.go +++ b/tm2/pkg/p2p/node_info.go @@ -1,9 +1,9 @@ package p2p import ( + "errors" "fmt" - "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore" "github.com/gnolang/gno/tm2/pkg/strings" "github.com/gnolang/gno/tm2/pkg/versionset" ) @@ -13,6 +13,8 @@ const ( maxNumChannels = 16 // plenty of room for upgrades, for now ) +var errInvalidNetworkAddress = errors.New("invalid network address") + // Max size of the NodeInfo struct func MaxNodeInfoSize() int { return maxNodeInfoSize @@ -51,106 +53,99 @@ type NodeInfoOther struct { // It returns an error if there // are too many Channels, if there are any duplicate Channels, // if the ListenAddr is malformed, or if the ListenAddr is a host name -// that can not be resolved to some IP. -// TODO: constraints for Moniker/Other? Or is that for the UI ? -// JAE: It needs to be done on the client, but to prevent ambiguous -// unicode characters, maybe it's worth sanitizing it here. -// In the future we might want to validate these, once we have a -// name-resolution system up. -// International clients could then use punycode (or we could use -// url-encoding), and we just need to be careful with how we handle that in our -// clients. (e.g. off by default). +// that can not be resolved to some IP func (info NodeInfo) Validate() error { - // ID is already validated. TODO validate - - // Validate ListenAddr. + // Validate the network address if info.NetAddress == nil { - return fmt.Errorf("info.NetAddress cannot be nil") + return errInvalidNetworkAddress } + if err := info.NetAddress.Validate(); err != nil { - return err + return fmt.Errorf("unable to validate net address, %w", err) } - // Network is validated in CompatibleWith. - // Validate Version if len(info.Version) > 0 && - (!strings.IsASCIIText(info.Version) || strings.ASCIITrim(info.Version) == "") { - return fmt.Errorf("info.Version must be valid ASCII text without tabs, but got %v", info.Version) + (!strings.IsASCIIText(info.Version) || + strings.ASCIITrim(info.Version) == "") { + return fmt.Errorf("info.Version must be valid ASCII text without tabs, but got %s", info.Version) } // Validate Channels - ensure max and check for duplicates. if len(info.Channels) > maxNumChannels { - return fmt.Errorf("info.Channels is too long (%v). Max is %v", len(info.Channels), maxNumChannels) + return fmt.Errorf("info.Channels is too long (%d). Max is %d", len(info.Channels), maxNumChannels) } - channels := make(map[byte]struct{}) + + channelMap := make(map[byte]struct{}, len(info.Channels)) for _, ch := range info.Channels { - _, ok := channels[ch] - if ok { + if _, ok := channelMap[ch]; ok { return fmt.Errorf("info.Channels contains duplicate channel id %v", ch) } - channels[ch] = struct{}{} + + // Mark the channel as present + channelMap[ch] = struct{}{} } // Validate Moniker. if !strings.IsASCIIText(info.Moniker) || strings.ASCIITrim(info.Moniker) == "" { - return fmt.Errorf("info.Moniker must be valid non-empty ASCII text without tabs, but got %v", info.Moniker) + return fmt.Errorf("info.Moniker must be valid non-empty ASCII text without tabs, but got %s", info.Moniker) } - // Validate Other. - other := info.Other - txIndex := other.TxIndex - switch txIndex { - case "", eventstore.StatusOn, eventstore.StatusOff: - default: - return fmt.Errorf("info.Other.TxIndex should be either 'on', 'off', or empty string, got '%v'", txIndex) - } // XXX: Should we be more strict about address formats? - rpcAddr := other.RPCAddress + rpcAddr := info.Other.RPCAddress if len(rpcAddr) > 0 && (!strings.IsASCIIText(rpcAddr) || strings.ASCIITrim(rpcAddr) == "") { - return fmt.Errorf("info.Other.RPCAddress=%v must be valid ASCII text without tabs", rpcAddr) + return fmt.Errorf("info.Other.RPCAddress=%s must be valid ASCII text without tabs", rpcAddr) } return nil } +// ID returns the local node ID func (info NodeInfo) ID() ID { return info.NetAddress.ID } -// CompatibleWith checks if two NodeInfo are compatible with eachother. -// CONTRACT: two nodes are compatible if the Block version and network match -// and they have at least one channel in common. +// CompatibleWith checks if two NodeInfo are compatible with each other. +// CONTRACT: two nodes are compatible if the Block version and networks match, +// and they have at least one channel in common func (info NodeInfo) CompatibleWith(other NodeInfo) error { - // check protocol versions - _, err := info.VersionSet.CompatibleWith(other.VersionSet) - if err != nil { - return err + // Validate the protocol versions + if _, err := info.VersionSet.CompatibleWith(other.VersionSet); err != nil { + return fmt.Errorf("incompatible version sets, %w", err) } - // nodes must be on the same network + // Make sure nodes are on the same network if info.Network != other.Network { - return fmt.Errorf("Peer is on a different network. Got %v, expected %v", other.Network, info.Network) + return fmt.Errorf( + "peer is on a different network. Got %q, expected %q", + other.Network, + info.Network, + ) } - // if we have no channels, we're just testing - if len(info.Channels) == 0 { - return nil - } - - // for each of our channels, check if they have it - found := false -OUTER_LOOP: + // Make sure there is at least 1 channel in common + commonFound := false for _, ch1 := range info.Channels { for _, ch2 := range other.Channels { if ch1 == ch2 { - found = true - break OUTER_LOOP // only need one + commonFound = true + + break } } + + if commonFound { + break + } } - if !found { - return fmt.Errorf("Peer has no common channels. Our channels: %v ; Peer channels: %v", info.Channels, other.Channels) + + if !commonFound { + return fmt.Errorf( + "peer has no common channels. Our channels: %v ; Peer channels: %v", + info.Channels, + other.Channels, + ) } + return nil } diff --git a/tm2/pkg/p2p/node_info_test.go b/tm2/pkg/p2p/node_info_test.go index 58f1dab8854..9e591ebb13b 100644 --- a/tm2/pkg/p2p/node_info_test.go +++ b/tm2/pkg/p2p/node_info_test.go @@ -53,12 +53,6 @@ func TestNodeInfoValidate(t *testing.T) { {"Empty Moniker", func(ni *NodeInfo) { ni.Moniker = "" }, true}, {"Good Moniker", func(ni *NodeInfo) { ni.Moniker = "hey its me" }, false}, - {"Non-ASCII TxIndex", func(ni *NodeInfo) { ni.Other.TxIndex = nonAscii }, true}, - {"Empty tab TxIndex", func(ni *NodeInfo) { ni.Other.TxIndex = emptyTab }, true}, - {"Empty space TxIndex", func(ni *NodeInfo) { ni.Other.TxIndex = emptySpace }, true}, - {"Empty TxIndex", func(ni *NodeInfo) { ni.Other.TxIndex = "" }, false}, - {"Off TxIndex", func(ni *NodeInfo) { ni.Other.TxIndex = "off" }, false}, - {"Non-ASCII RPCAddress", func(ni *NodeInfo) { ni.Other.RPCAddress = nonAscii }, true}, {"Empty tab RPCAddress", func(ni *NodeInfo) { ni.Other.RPCAddress = emptyTab }, true}, {"Empty space RPCAddress", func(ni *NodeInfo) { ni.Other.RPCAddress = emptySpace }, true}, From 373f4892f5233f8a95918c2499d5cfc780eff6c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Mon, 30 Sep 2024 15:48:17 +0200 Subject: [PATCH 07/43] Tidy up node info --- tm2/pkg/crypto/crypto.go | 4 +- tm2/pkg/p2p/config/config.go | 10 - tm2/pkg/p2p/node_info.go | 33 +-- tm2/pkg/p2p/node_info_test.go | 449 ++++++++++++++++++++++++++-------- 4 files changed, 363 insertions(+), 133 deletions(-) diff --git a/tm2/pkg/crypto/crypto.go b/tm2/pkg/crypto/crypto.go index 1353e9dcf20..7908a082d3b 100644 --- a/tm2/pkg/crypto/crypto.go +++ b/tm2/pkg/crypto/crypto.go @@ -129,7 +129,7 @@ func (addr *Address) DecodeString(str string) error { // ---------------------------------------- // ID -var errZeroID = errors.New("address ID is zero") +var ErrZeroID = errors.New("address ID is zero") // The bech32 representation w/ bech32 prefix. type ID string @@ -144,7 +144,7 @@ func (id ID) String() string { func (id ID) Validate() error { if id.IsZero() { - return errZeroID + return ErrZeroID } var addr Address diff --git a/tm2/pkg/p2p/config/config.go b/tm2/pkg/p2p/config/config.go index 71eadef3cf6..8b6ac6c8566 100644 --- a/tm2/pkg/p2p/config/config.go +++ b/tm2/pkg/p2p/config/config.go @@ -6,16 +6,6 @@ import ( "github.com/gnolang/gno/tm2/pkg/errors" ) -// ----------------------------------------------------------------------------- -// P2PConfig - -const ( - // FuzzModeDrop is a mode in which we randomly drop reads/writes, connections or sleep - FuzzModeDrop = iota - // FuzzModeDelay is a mode in which we randomly sleep - FuzzModeDelay -) - // P2PConfig defines the configuration options for the Tendermint peer-to-peer networking layer type P2PConfig struct { RootDir string `json:"rpc" toml:"home"` diff --git a/tm2/pkg/p2p/node_info.go b/tm2/pkg/p2p/node_info.go index 1e44783d9d6..7573f506672 100644 --- a/tm2/pkg/p2p/node_info.go +++ b/tm2/pkg/p2p/node_info.go @@ -13,7 +13,16 @@ const ( maxNumChannels = 16 // plenty of room for upgrades, for now ) -var errInvalidNetworkAddress = errors.New("invalid network address") +var ( + errInvalidNetworkAddress = errors.New("invalid node network address") + errInvalidVersion = errors.New("invalid node version") + errInvalidMoniker = errors.New("invalid node moniker") + errInvalidRPCAddress = errors.New("invalid node RPC address") + errExcessiveChannels = errors.New("excessive node channels") + errDuplicateChannels = errors.New("duplicate node channels") + errIncompatibleNetworks = errors.New("incompatible networks") + errNoCommonChannels = errors.New("no common channels") +) // Max size of the NodeInfo struct func MaxNodeInfoSize() int { @@ -68,18 +77,18 @@ func (info NodeInfo) Validate() error { if len(info.Version) > 0 && (!strings.IsASCIIText(info.Version) || strings.ASCIITrim(info.Version) == "") { - return fmt.Errorf("info.Version must be valid ASCII text without tabs, but got %s", info.Version) + return errInvalidVersion } // Validate Channels - ensure max and check for duplicates. if len(info.Channels) > maxNumChannels { - return fmt.Errorf("info.Channels is too long (%d). Max is %d", len(info.Channels), maxNumChannels) + return errExcessiveChannels } channelMap := make(map[byte]struct{}, len(info.Channels)) for _, ch := range info.Channels { if _, ok := channelMap[ch]; ok { - return fmt.Errorf("info.Channels contains duplicate channel id %v", ch) + return errDuplicateChannels } // Mark the channel as present @@ -88,13 +97,13 @@ func (info NodeInfo) Validate() error { // Validate Moniker. if !strings.IsASCIIText(info.Moniker) || strings.ASCIITrim(info.Moniker) == "" { - return fmt.Errorf("info.Moniker must be valid non-empty ASCII text without tabs, but got %s", info.Moniker) + return errInvalidMoniker } // XXX: Should we be more strict about address formats? rpcAddr := info.Other.RPCAddress if len(rpcAddr) > 0 && (!strings.IsASCIIText(rpcAddr) || strings.ASCIITrim(rpcAddr) == "") { - return fmt.Errorf("info.Other.RPCAddress=%s must be valid ASCII text without tabs", rpcAddr) + return errInvalidRPCAddress } return nil @@ -116,11 +125,7 @@ func (info NodeInfo) CompatibleWith(other NodeInfo) error { // Make sure nodes are on the same network if info.Network != other.Network { - return fmt.Errorf( - "peer is on a different network. Got %q, expected %q", - other.Network, - info.Network, - ) + return errIncompatibleNetworks } // Make sure there is at least 1 channel in common @@ -140,11 +145,7 @@ func (info NodeInfo) CompatibleWith(other NodeInfo) error { } if !commonFound { - return fmt.Errorf( - "peer has no common channels. Our channels: %v ; Peer channels: %v", - info.Channels, - other.Channels, - ) + return errNoCommonChannels } return nil diff --git a/tm2/pkg/p2p/node_info_test.go b/tm2/pkg/p2p/node_info_test.go index 9e591ebb13b..c0ad97fb1ab 100644 --- a/tm2/pkg/p2p/node_info_test.go +++ b/tm2/pkg/p2p/node_info_test.go @@ -5,124 +5,363 @@ import ( "net" "testing" - "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" + "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/versionset" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -func TestNodeInfoValidate(t *testing.T) { +func TestNodeInfo_Validate(t *testing.T) { t.Parallel() - // empty fails - ni := NodeInfo{} - assert.Error(t, ni.Validate()) + generateNetAddress := func() *NetAddress { + tcpAddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8080") + require.NoError(t, err) - channels := make([]byte, maxNumChannels) - for i := 0; i < maxNumChannels; i++ { - channels[i] = byte(i) - } - dupChannels := make([]byte, 5) - copy(dupChannels, channels[:5]) - dupChannels = append(dupChannels, testCh) - - nonAscii := "¢§µ" - emptyTab := fmt.Sprintf("\t") - emptySpace := fmt.Sprintf(" ") - - testCases := []struct { - testName string - malleateNodeInfo func(*NodeInfo) - expectErr bool - }{ - {"Too Many Channels", func(ni *NodeInfo) { ni.Channels = append(channels, byte(maxNumChannels)) }, true}, //nolint: gocritic - {"Duplicate Channel", func(ni *NodeInfo) { ni.Channels = dupChannels }, true}, - {"Good Channels", func(ni *NodeInfo) { ni.Channels = ni.Channels[:5] }, false}, - - {"Nil NetAddress", func(ni *NodeInfo) { ni.NetAddress = nil }, true}, - {"Zero NetAddress ID", func(ni *NodeInfo) { ni.NetAddress.ID = "" }, true}, - {"Invalid NetAddress IP", func(ni *NodeInfo) { ni.NetAddress.IP = net.IP([]byte{0x00}) }, true}, - - {"Non-ASCII Version", func(ni *NodeInfo) { ni.Version = nonAscii }, true}, - {"Empty tab Version", func(ni *NodeInfo) { ni.Version = emptyTab }, true}, - {"Empty space Version", func(ni *NodeInfo) { ni.Version = emptySpace }, true}, - {"Empty Version", func(ni *NodeInfo) { ni.Version = "" }, false}, - - {"Non-ASCII Moniker", func(ni *NodeInfo) { ni.Moniker = nonAscii }, true}, - {"Empty tab Moniker", func(ni *NodeInfo) { ni.Moniker = emptyTab }, true}, - {"Empty space Moniker", func(ni *NodeInfo) { ni.Moniker = emptySpace }, true}, - {"Empty Moniker", func(ni *NodeInfo) { ni.Moniker = "" }, true}, - {"Good Moniker", func(ni *NodeInfo) { ni.Moniker = "hey its me" }, false}, - - {"Non-ASCII RPCAddress", func(ni *NodeInfo) { ni.Other.RPCAddress = nonAscii }, true}, - {"Empty tab RPCAddress", func(ni *NodeInfo) { ni.Other.RPCAddress = emptyTab }, true}, - {"Empty space RPCAddress", func(ni *NodeInfo) { ni.Other.RPCAddress = emptySpace }, true}, - {"Empty RPCAddress", func(ni *NodeInfo) { ni.Other.RPCAddress = "" }, false}, - {"Good RPCAddress", func(ni *NodeInfo) { ni.Other.RPCAddress = "0.0.0.0:26657" }, false}, + address, err := NewNetAddress(GenerateNodeKey().ID(), tcpAddr) + require.NoError(t, err) + + return address } - nodeKey := NodeKey{PrivKey: ed25519.GenPrivKey()} - name := "testing" - - // test case passes - ni = testNodeInfo(nodeKey.ID(), name) - ni.Channels = channels - assert.NoError(t, ni.Validate()) - - for _, tc := range testCases { - ni := testNodeInfo(nodeKey.ID(), name) - ni.Channels = channels - tc.malleateNodeInfo(&ni) - err := ni.Validate() - if tc.expectErr { - assert.Error(t, err, tc.testName) - } else { - assert.NoError(t, err, tc.testName) + t.Run("invalid net address", func(t *testing.T) { + t.Parallel() + + testTable := []struct { + name string + address *NetAddress + expectedErr error + }{ + { + "unset net address", + nil, + errInvalidNetworkAddress, + }, + { + "zero net address ID", + &NetAddress{ + ID: "", // zero + }, + crypto.ErrZeroID, + }, + { + "invalid net address IP", + &NetAddress{ + ID: GenerateNodeKey().ID(), + IP: net.IP([]byte{0x00}), + }, + errInvalidIP, + }, } - } + + for _, testCase := range testTable { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + info := &NodeInfo{ + NetAddress: testCase.address, + } + + assert.ErrorIs(t, info.Validate(), testCase.expectedErr) + }) + } + }) + + t.Run("invalid version", func(t *testing.T) { + t.Parallel() + + testTable := []struct { + name string + version string + }{ + { + "non-ascii version", + "¢§µ", + }, + { + "empty tab version", + fmt.Sprintf("\t"), + }, + { + "empty space version", + fmt.Sprintf(" "), + }, + } + + for _, testCase := range testTable { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + info := &NodeInfo{ + NetAddress: generateNetAddress(), + Version: testCase.version, + } + + assert.ErrorIs(t, info.Validate(), errInvalidVersion) + }) + } + }) + + t.Run("invalid moniker", func(t *testing.T) { + t.Parallel() + + testTable := []struct { + name string + moniker string + }{ + { + "empty moniker", + "", + }, + { + "non-ascii moniker", + "¢§µ", + }, + { + "empty tab moniker", + fmt.Sprintf("\t"), + }, + { + "empty space moniker", + fmt.Sprintf(" "), + }, + } + + for _, testCase := range testTable { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + info := &NodeInfo{ + NetAddress: generateNetAddress(), + Moniker: testCase.moniker, + } + + assert.ErrorIs(t, info.Validate(), errInvalidMoniker) + }) + } + }) + + t.Run("invalid RPC Address", func(t *testing.T) { + t.Parallel() + + testTable := []struct { + name string + rpcAddress string + }{ + { + "non-ascii moniker", + "¢§µ", + }, + { + "empty tab RPC address", + fmt.Sprintf("\t"), + }, + { + "empty space RPC address", + fmt.Sprintf(" "), + }, + } + + for _, testCase := range testTable { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + info := &NodeInfo{ + NetAddress: generateNetAddress(), + Moniker: "valid moniker", + Other: NodeInfoOther{ + RPCAddress: testCase.rpcAddress, + }, + } + + assert.ErrorIs(t, info.Validate(), errInvalidRPCAddress) + }) + } + }) + + t.Run("invalid channels", func(t *testing.T) { + t.Parallel() + + testTable := []struct { + name string + channels []byte + expectedErr error + }{ + { + "too many channels", + make([]byte, maxNumChannels+1), + errExcessiveChannels, + }, + { + "duplicate channels", + []byte{ + byte(10), + byte(20), + byte(10), + }, + errDuplicateChannels, + }, + } + + for _, testCase := range testTable { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + info := &NodeInfo{ + NetAddress: generateNetAddress(), + Moniker: "valid moniker", + Channels: testCase.channels, + } + + assert.ErrorIs(t, info.Validate(), testCase.expectedErr) + }) + } + }) + + t.Run("valid node info", func(t *testing.T) { + t.Parallel() + + info := &NodeInfo{ + NetAddress: generateNetAddress(), + Moniker: "valid moniker", + Channels: []byte{10, 20, 30}, + Other: NodeInfoOther{ + RPCAddress: "0.0.0.0:26657", + }, + } + + assert.NoError(t, info.Validate()) + }) } -func TestNodeInfoCompatible(t *testing.T) { +func TestNodeInfo_CompatibleWith(t *testing.T) { t.Parallel() - nodeKey1 := NodeKey{PrivKey: ed25519.GenPrivKey()} - nodeKey2 := NodeKey{PrivKey: ed25519.GenPrivKey()} - name := "testing" - - var newTestChannel byte = 0x2 - - // test NodeInfo is compatible - ni1 := testNodeInfo(nodeKey1.ID(), name) - ni2 := testNodeInfo(nodeKey2.ID(), name) - assert.NoError(t, ni1.CompatibleWith(ni2)) - - // add another channel; still compatible - ni2.Channels = []byte{newTestChannel, testCh} - assert.NoError(t, ni1.CompatibleWith(ni2)) - - // wrong NodeInfo type is not compatible - _, netAddr := CreateRoutableAddr() - ni3 := NodeInfo{NetAddress: netAddr} - assert.Error(t, ni1.CompatibleWith(ni3)) - - testCases := []struct { - testName string - malleateNodeInfo func(*NodeInfo) - }{ - {"Bad block version", func(ni *NodeInfo) { - ni.VersionSet.Set(versionset.VersionInfo{Name: "Block", Version: "badversion"}) - }}, - {"Wrong block version", func(ni *NodeInfo) { - ni.VersionSet.Set(versionset.VersionInfo{Name: "Block", Version: "v999.999.999-wrong"}) - }}, - {"Wrong network", func(ni *NodeInfo) { ni.Network += "-wrong" }}, - {"No common channels", func(ni *NodeInfo) { ni.Channels = []byte{newTestChannel} }}, - } + t.Run("incompatible version sets", func(t *testing.T) { + t.Parallel() - for i, tc := range testCases { - t.Logf("case #%v", i) - ni := testNodeInfo(nodeKey2.ID(), name) - tc.malleateNodeInfo(&ni) - fmt.Printf("case #%v\n", i) - assert.Error(t, ni1.CompatibleWith(ni)) - } + var ( + name = "Block" + + infoOne = &NodeInfo{ + VersionSet: []versionset.VersionInfo{ + { + Name: name, + Version: "badversion", + }, + }, + } + + infoTwo = &NodeInfo{ + VersionSet: []versionset.VersionInfo{ + { + Name: name, + Version: "v0.0.0", + }, + }, + } + ) + + assert.Error(t, infoTwo.CompatibleWith(*infoOne)) + }) + + t.Run("incompatible networks", func(t *testing.T) { + t.Parallel() + + var ( + name = "Block" + version = "v0.0.0" + + infoOne = &NodeInfo{ + Network: "+wrong", + VersionSet: []versionset.VersionInfo{ + { + Name: name, + Version: version, + }, + }, + } + + infoTwo = &NodeInfo{ + Network: "gno", + VersionSet: []versionset.VersionInfo{ + { + Name: name, + Version: version, + }, + }, + } + ) + + assert.ErrorIs(t, infoTwo.CompatibleWith(*infoOne), errIncompatibleNetworks) + }) + + t.Run("no common channels", func(t *testing.T) { + t.Parallel() + + var ( + name = "Block" + version = "v0.0.0" + network = "gno" + + infoOne = &NodeInfo{ + Network: network, + VersionSet: []versionset.VersionInfo{ + { + Name: name, + Version: version, + }, + }, + Channels: []byte{10}, + } + + infoTwo = &NodeInfo{ + Network: network, + VersionSet: []versionset.VersionInfo{ + { + Name: name, + Version: version, + }, + }, + Channels: []byte{20}, + } + ) + + assert.ErrorIs(t, infoTwo.CompatibleWith(*infoOne), errNoCommonChannels) + }) + + t.Run("fully compatible node infos", func(t *testing.T) { + t.Parallel() + + var ( + name = "Block" + version = "v0.0.0" + network = "gno" + channels = []byte{10, 20, 30} + + infoOne = &NodeInfo{ + Network: network, + VersionSet: []versionset.VersionInfo{ + { + Name: name, + Version: version, + }, + }, + Channels: channels, + } + + infoTwo = &NodeInfo{ + Network: network, + VersionSet: []versionset.VersionInfo{ + { + Name: name, + Version: version, + }, + }, + Channels: channels[1:], + } + ) + + assert.NoError(t, infoTwo.CompatibleWith(*infoOne)) + }) } From f934a8450008830a0cb5f3f206562a9941285d92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Tue, 1 Oct 2024 13:13:24 +0200 Subject: [PATCH 08/43] Temporary save --- tm2/pkg/bft/blockchain/pool.go | 40 +- tm2/pkg/bft/blockchain/pool_test.go | 16 +- tm2/pkg/bft/consensus/state.go | 18 +- .../bft/consensus/types/height_vote_set.go | 10 +- tm2/pkg/bft/node/node.go | 2 +- tm2/pkg/bft/rpc/client/batch_test.go | 6 +- tm2/pkg/bft/rpc/client/client_test.go | 10 +- tm2/pkg/bft/rpc/client/e2e_test.go | 4 +- tm2/pkg/bft/rpc/core/pipe.go | 2 +- tm2/pkg/bft/rpc/core/types/responses_test.go | 3 +- tm2/pkg/p2p/config/config.go | 5 - tm2/pkg/p2p/mock_test.go | 152 ++ tm2/pkg/p2p/netaddress.go | 2 - tm2/pkg/p2p/node_info.go | 11 +- tm2/pkg/p2p/peer.go | 213 +-- tm2/pkg/p2p/peer_set.go | 147 -- tm2/pkg/p2p/peer_set_test.go | 190 --- tm2/pkg/p2p/peer_test.go | 455 +++--- tm2/pkg/p2p/set.go | 96 ++ tm2/pkg/p2p/set_test.go | 211 +++ tm2/pkg/p2p/switch.go | 21 +- tm2/pkg/p2p/switch_test.go | 1383 ++++++++--------- tm2/pkg/p2p/test_util.go | 453 +++--- tm2/pkg/p2p/transport.go | 28 +- tm2/pkg/p2p/transport_test.go | 1303 ++++++++-------- tm2/pkg/p2p/types.go | 44 + 26 files changed, 2400 insertions(+), 2425 deletions(-) create mode 100644 tm2/pkg/p2p/mock_test.go delete mode 100644 tm2/pkg/p2p/peer_set.go delete mode 100644 tm2/pkg/p2p/peer_set_test.go create mode 100644 tm2/pkg/p2p/set.go create mode 100644 tm2/pkg/p2p/set_test.go diff --git a/tm2/pkg/bft/blockchain/pool.go b/tm2/pkg/bft/blockchain/pool.go index 5a82eb4d1d6..f29476cca28 100644 --- a/tm2/pkg/bft/blockchain/pool.go +++ b/tm2/pkg/bft/blockchain/pool.go @@ -12,7 +12,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/flow" "github.com/gnolang/gno/tm2/pkg/log" - "github.com/gnolang/gno/tm2/pkg/p2p" + types2 "github.com/gnolang/gno/tm2/pkg/p2p" "github.com/gnolang/gno/tm2/pkg/service" ) @@ -69,7 +69,7 @@ type BlockPool struct { requesters map[int64]*bpRequester height int64 // the lowest key in requesters. // peers - peers map[p2p.ID]*bpPeer + peers map[types2.ID]*bpPeer maxPeerHeight int64 // the biggest reported height // atomic @@ -83,7 +83,7 @@ type BlockPool struct { // requests and errors will be sent to requestsCh and errorsCh accordingly. func NewBlockPool(start int64, requestsCh chan<- BlockRequest, errorsCh chan<- peerError) *BlockPool { bp := &BlockPool{ - peers: make(map[p2p.ID]*bpPeer), + peers: make(map[types2.ID]*bpPeer), requesters: make(map[int64]*bpRequester), height: start, @@ -226,13 +226,13 @@ func (pool *BlockPool) PopRequest() { // RedoRequest invalidates the block at pool.height, // Remove the peer and redo request from others. // Returns the ID of the removed peer. -func (pool *BlockPool) RedoRequest(height int64) p2p.ID { +func (pool *BlockPool) RedoRequest(height int64) types2.ID { pool.mtx.Lock() defer pool.mtx.Unlock() request := pool.requesters[height] peerID := request.getPeerID() - if peerID != p2p.ID("") { + if peerID != types2.ID("") { // RemovePeer will redo all requesters associated with this peer. pool.removePeer(peerID) } @@ -241,7 +241,7 @@ func (pool *BlockPool) RedoRequest(height int64) p2p.ID { // AddBlock validates that the block comes from the peer it was expected from and calls the requester to store it. // TODO: ensure that blocks come in order for each peer. -func (pool *BlockPool) AddBlock(peerID p2p.ID, block *types.Block, blockSize int) { +func (pool *BlockPool) AddBlock(peerID types2.ID, block *types.Block, blockSize int) { pool.mtx.Lock() defer pool.mtx.Unlock() @@ -278,7 +278,7 @@ func (pool *BlockPool) MaxPeerHeight() int64 { } // SetPeerHeight sets the peer's alleged blockchain height. -func (pool *BlockPool) SetPeerHeight(peerID p2p.ID, height int64) { +func (pool *BlockPool) SetPeerHeight(peerID types2.ID, height int64) { pool.mtx.Lock() defer pool.mtx.Unlock() @@ -298,14 +298,14 @@ func (pool *BlockPool) SetPeerHeight(peerID p2p.ID, height int64) { // RemovePeer removes the peer with peerID from the pool. If there's no peer // with peerID, function is a no-op. -func (pool *BlockPool) RemovePeer(peerID p2p.ID) { +func (pool *BlockPool) RemovePeer(peerID types2.ID) { pool.mtx.Lock() defer pool.mtx.Unlock() pool.removePeer(peerID) } -func (pool *BlockPool) removePeer(peerID p2p.ID) { +func (pool *BlockPool) removePeer(peerID types2.ID) { for _, requester := range pool.requesters { if requester.getPeerID() == peerID { requester.redo(peerID) @@ -386,14 +386,14 @@ func (pool *BlockPool) requestersLen() int64 { return int64(len(pool.requesters)) } -func (pool *BlockPool) sendRequest(height int64, peerID p2p.ID) { +func (pool *BlockPool) sendRequest(height int64, peerID types2.ID) { if !pool.IsRunning() { return } pool.requestsCh <- BlockRequest{height, peerID} } -func (pool *BlockPool) sendError(err error, peerID p2p.ID) { +func (pool *BlockPool) sendError(err error, peerID types2.ID) { if !pool.IsRunning() { return } @@ -424,7 +424,7 @@ func (pool *BlockPool) debug() string { type bpPeer struct { pool *BlockPool - id p2p.ID + id types2.ID recvMonitor *flow.Monitor height int64 @@ -435,7 +435,7 @@ type bpPeer struct { logger *slog.Logger } -func newBPPeer(pool *BlockPool, peerID p2p.ID, height int64) *bpPeer { +func newBPPeer(pool *BlockPool, peerID types2.ID, height int64) *bpPeer { peer := &bpPeer{ pool: pool, id: peerID, @@ -499,10 +499,10 @@ type bpRequester struct { pool *BlockPool height int64 gotBlockCh chan struct{} - redoCh chan p2p.ID // redo may send multitime, add peerId to identify repeat + redoCh chan types2.ID // redo may send multitime, add peerId to identify repeat mtx sync.Mutex - peerID p2p.ID + peerID types2.ID block *types.Block } @@ -511,7 +511,7 @@ func newBPRequester(pool *BlockPool, height int64) *bpRequester { pool: pool, height: height, gotBlockCh: make(chan struct{}, 1), - redoCh: make(chan p2p.ID, 1), + redoCh: make(chan types2.ID, 1), peerID: "", block: nil, @@ -526,7 +526,7 @@ func (bpr *bpRequester) OnStart() error { } // Returns true if the peer matches and block doesn't already exist. -func (bpr *bpRequester) setBlock(block *types.Block, peerID p2p.ID) bool { +func (bpr *bpRequester) setBlock(block *types.Block, peerID types2.ID) bool { bpr.mtx.Lock() if bpr.block != nil || bpr.peerID != peerID { bpr.mtx.Unlock() @@ -548,7 +548,7 @@ func (bpr *bpRequester) getBlock() *types.Block { return bpr.block } -func (bpr *bpRequester) getPeerID() p2p.ID { +func (bpr *bpRequester) getPeerID() types2.ID { bpr.mtx.Lock() defer bpr.mtx.Unlock() return bpr.peerID @@ -570,7 +570,7 @@ func (bpr *bpRequester) reset() { // Tells bpRequester to pick another peer and try again. // NOTE: Nonblocking, and does nothing if another redo // was already requested. -func (bpr *bpRequester) redo(peerID p2p.ID) { +func (bpr *bpRequester) redo(peerID types2.ID) { select { case bpr.redoCh <- peerID: default: @@ -631,5 +631,5 @@ OUTER_LOOP: // delivering the block type BlockRequest struct { Height int64 - PeerID p2p.ID + PeerID types2.ID } diff --git a/tm2/pkg/bft/blockchain/pool_test.go b/tm2/pkg/bft/blockchain/pool_test.go index a4d5636d5e3..96455744f23 100644 --- a/tm2/pkg/bft/blockchain/pool_test.go +++ b/tm2/pkg/bft/blockchain/pool_test.go @@ -5,12 +5,12 @@ import ( "testing" "time" + types2 "github.com/gnolang/gno/tm2/pkg/p2p" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/log" - "github.com/gnolang/gno/tm2/pkg/p2p" "github.com/gnolang/gno/tm2/pkg/random" ) @@ -19,7 +19,7 @@ func init() { } type testPeer struct { - id p2p.ID + id types2.ID height int64 inputChan chan inputData // make sure each peer's data is sequential } @@ -47,7 +47,7 @@ func (p testPeer) simulateInput(input inputData) { // input.t.Logf("Added block from peer %v (height: %v)", input.request.PeerID, input.request.Height) } -type testPeers map[p2p.ID]testPeer +type testPeers map[types2.ID]testPeer func (ps testPeers) start() { for _, v := range ps { @@ -64,7 +64,7 @@ func (ps testPeers) stop() { func makePeers(numPeers int, minHeight, maxHeight int64) testPeers { peers := make(testPeers, numPeers) for i := 0; i < numPeers; i++ { - peerID := p2p.ID(random.RandStr(12)) + peerID := types2.ID(random.RandStr(12)) height := minHeight + random.RandInt63n(maxHeight-minHeight) peers[peerID] = testPeer{peerID, height, make(chan inputData, 10)} } @@ -172,7 +172,7 @@ func TestBlockPoolTimeout(t *testing.T) { // Pull from channels counter := 0 - timedOut := map[p2p.ID]struct{}{} + timedOut := map[types2.ID]struct{}{} for { select { case err := <-errorsCh: @@ -195,7 +195,7 @@ func TestBlockPoolRemovePeer(t *testing.T) { peers := make(testPeers, 10) for i := 0; i < 10; i++ { - peerID := p2p.ID(fmt.Sprintf("%d", i+1)) + peerID := types2.ID(fmt.Sprintf("%d", i+1)) height := int64(i + 1) peers[peerID] = testPeer{peerID, height, make(chan inputData)} } @@ -215,10 +215,10 @@ func TestBlockPoolRemovePeer(t *testing.T) { assert.EqualValues(t, 10, pool.MaxPeerHeight()) // remove not-existing peer - assert.NotPanics(t, func() { pool.RemovePeer(p2p.ID("Superman")) }) + assert.NotPanics(t, func() { pool.RemovePeer(types2.ID("Superman")) }) // remove peer with biggest height - pool.RemovePeer(p2p.ID("10")) + pool.RemovePeer(types2.ID("10")) assert.EqualValues(t, 9, pool.MaxPeerHeight()) // remove all peers diff --git a/tm2/pkg/bft/consensus/state.go b/tm2/pkg/bft/consensus/state.go index 3f71dac368c..eec01564b32 100644 --- a/tm2/pkg/bft/consensus/state.go +++ b/tm2/pkg/bft/consensus/state.go @@ -23,7 +23,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/errors" "github.com/gnolang/gno/tm2/pkg/events" osm "github.com/gnolang/gno/tm2/pkg/os" - "github.com/gnolang/gno/tm2/pkg/p2p" + types2 "github.com/gnolang/gno/tm2/pkg/p2p" "github.com/gnolang/gno/tm2/pkg/service" "github.com/gnolang/gno/tm2/pkg/telemetry" "github.com/gnolang/gno/tm2/pkg/telemetry/metrics" @@ -53,7 +53,7 @@ type newRoundStepInfo struct { // msgs from the reactor which may update the state type msgInfo struct { Msg ConsensusMessage `json:"msg"` - PeerID p2p.ID `json:"peer_key"` + PeerID types2.ID `json:"peer_key"` } // WAL message. @@ -399,7 +399,7 @@ func (cs *ConsensusState) OpenWAL(walFile string) (walm.WAL, error) { // TODO: should these return anything or let callers just use events? // AddVote inputs a vote. -func (cs *ConsensusState) AddVote(vote *types.Vote, peerID p2p.ID) (added bool, err error) { +func (cs *ConsensusState) AddVote(vote *types.Vote, peerID types2.ID) (added bool, err error) { if peerID == "" { cs.internalMsgQueue <- msgInfo{&VoteMessage{vote}, ""} } else { @@ -411,7 +411,7 @@ func (cs *ConsensusState) AddVote(vote *types.Vote, peerID p2p.ID) (added bool, } // SetProposal inputs a proposal. -func (cs *ConsensusState) SetProposal(proposal *types.Proposal, peerID p2p.ID) error { +func (cs *ConsensusState) SetProposal(proposal *types.Proposal, peerID types2.ID) error { if peerID == "" { cs.internalMsgQueue <- msgInfo{&ProposalMessage{proposal}, ""} } else { @@ -423,7 +423,7 @@ func (cs *ConsensusState) SetProposal(proposal *types.Proposal, peerID p2p.ID) e } // AddProposalBlockPart inputs a part of the proposal block. -func (cs *ConsensusState) AddProposalBlockPart(height int64, round int, part *types.Part, peerID p2p.ID) error { +func (cs *ConsensusState) AddProposalBlockPart(height int64, round int, part *types.Part, peerID types2.ID) error { if peerID == "" { cs.internalMsgQueue <- msgInfo{&BlockPartMessage{height, round, part}, ""} } else { @@ -435,7 +435,7 @@ func (cs *ConsensusState) AddProposalBlockPart(height int64, round int, part *ty } // SetProposalAndBlock inputs the proposal and all block parts. -func (cs *ConsensusState) SetProposalAndBlock(proposal *types.Proposal, block *types.Block, parts *types.PartSet, peerID p2p.ID) error { +func (cs *ConsensusState) SetProposalAndBlock(proposal *types.Proposal, block *types.Block, parts *types.PartSet, peerID types2.ID) error { if err := cs.SetProposal(proposal, peerID); err != nil { return err } @@ -1444,7 +1444,7 @@ func (cs *ConsensusState) defaultSetProposal(proposal *types.Proposal) error { // NOTE: block is not necessarily valid. // Asynchronously triggers either enterPrevote (before we timeout of propose) or tryFinalizeCommit, once we have the full block. -func (cs *ConsensusState) addProposalBlockPart(msg *BlockPartMessage, peerID p2p.ID) (added bool, err error) { +func (cs *ConsensusState) addProposalBlockPart(msg *BlockPartMessage, peerID types2.ID) (added bool, err error) { height, round, part := msg.Height, msg.Round, msg.Part // Blocks might be reused, so round mismatch is OK @@ -1514,7 +1514,7 @@ func (cs *ConsensusState) addProposalBlockPart(msg *BlockPartMessage, peerID p2p } // Attempt to add the vote. if its a duplicate signature, dupeout the validator -func (cs *ConsensusState) tryAddVote(vote *types.Vote, peerID p2p.ID) (bool, error) { +func (cs *ConsensusState) tryAddVote(vote *types.Vote, peerID types2.ID) (bool, error) { added, err := cs.addVote(vote, peerID) if err != nil { // If the vote height is off, we'll just ignore it, @@ -1547,7 +1547,7 @@ func (cs *ConsensusState) tryAddVote(vote *types.Vote, peerID p2p.ID) (bool, err // ----------------------------------------------------------------------------- -func (cs *ConsensusState) addVote(vote *types.Vote, peerID p2p.ID) (added bool, err error) { +func (cs *ConsensusState) addVote(vote *types.Vote, peerID types2.ID) (added bool, err error) { cs.Logger.Debug("addVote", "voteHeight", vote.Height, "voteType", vote.Type, "valIndex", vote.ValidatorIndex, "csHeight", cs.Height) // A precommit for the previous height? diff --git a/tm2/pkg/bft/consensus/types/height_vote_set.go b/tm2/pkg/bft/consensus/types/height_vote_set.go index b81937ebd1e..854ac34f946 100644 --- a/tm2/pkg/bft/consensus/types/height_vote_set.go +++ b/tm2/pkg/bft/consensus/types/height_vote_set.go @@ -8,7 +8,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/bft/types" - "github.com/gnolang/gno/tm2/pkg/p2p" + types2 "github.com/gnolang/gno/tm2/pkg/p2p" ) type RoundVoteSet struct { @@ -41,7 +41,7 @@ type HeightVoteSet struct { mtx sync.Mutex round int // max tracked round roundVoteSets map[int]RoundVoteSet // keys: [0...round] - peerCatchupRounds map[p2p.ID][]int // keys: peer.ID; values: at most 2 rounds + peerCatchupRounds map[types2.ID][]int // keys: peer.ID; values: at most 2 rounds } func NewHeightVoteSet(chainID string, height int64, valSet *types.ValidatorSet) *HeightVoteSet { @@ -59,7 +59,7 @@ func (hvs *HeightVoteSet) Reset(height int64, valSet *types.ValidatorSet) { hvs.height = height hvs.valSet = valSet hvs.roundVoteSets = make(map[int]RoundVoteSet) - hvs.peerCatchupRounds = make(map[p2p.ID][]int) + hvs.peerCatchupRounds = make(map[types2.ID][]int) hvs.addRound(0) hvs.round = 0 @@ -108,7 +108,7 @@ func (hvs *HeightVoteSet) addRound(round int) { // Duplicate votes return added=false, err=nil. // By convention, peerID is "" if origin is self. -func (hvs *HeightVoteSet) AddVote(vote *types.Vote, peerID p2p.ID) (added bool, err error) { +func (hvs *HeightVoteSet) AddVote(vote *types.Vote, peerID types2.ID) (added bool, err error) { hvs.mtx.Lock() defer hvs.mtx.Unlock() if !types.IsVoteTypeValid(vote.Type) { @@ -176,7 +176,7 @@ func (hvs *HeightVoteSet) getVoteSet(round int, type_ types.SignedMsgType) *type // NOTE: if there are too many peers, or too much peer churn, // this can cause memory issues. // TODO: implement ability to remove peers too -func (hvs *HeightVoteSet) SetPeerMaj23(round int, type_ types.SignedMsgType, peerID p2p.ID, blockID types.BlockID) error { +func (hvs *HeightVoteSet) SetPeerMaj23(round int, type_ types.SignedMsgType, peerID types2.ID, blockID types.BlockID) error { hvs.mtx.Lock() defer hvs.mtx.Unlock() if !types.IsVoteTypeValid(type_) { diff --git a/tm2/pkg/bft/node/node.go b/tm2/pkg/bft/node/node.go index e29de3dd1ae..779998e1885 100644 --- a/tm2/pkg/bft/node/node.go +++ b/tm2/pkg/bft/node/node.go @@ -367,7 +367,7 @@ func createTransport(config *cfg.Config, nodeInfo p2p.NodeInfo, nodeKey *p2p.Nod peerFilters = append( peerFilters, // ABCI query for ID filtering. - func(_ p2p.IPeerSet, p p2p.Peer) error { + func(_ p2p.PeerSet, p p2p.Peer) error { res, err := proxyApp.Query().QuerySync(abci.RequestQuery{ Path: fmt.Sprintf("/p2p/filter/id/%s", p.ID()), }) diff --git a/tm2/pkg/bft/rpc/client/batch_test.go b/tm2/pkg/bft/rpc/client/batch_test.go index 52930e5c372..2f8e04e4bf1 100644 --- a/tm2/pkg/bft/rpc/client/batch_test.go +++ b/tm2/pkg/bft/rpc/client/batch_test.go @@ -10,7 +10,7 @@ import ( ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" types "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/types" bfttypes "github.com/gnolang/gno/tm2/pkg/bft/types" - "github.com/gnolang/gno/tm2/pkg/p2p" + types2 "github.com/gnolang/gno/tm2/pkg/p2p" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -116,7 +116,7 @@ func TestRPCBatch_Send(t *testing.T) { var ( numRequests = 10 expectedStatus = &ctypes.ResultStatus{ - NodeInfo: p2p.NodeInfo{ + NodeInfo: types2.NodeInfo{ Moniker: "dummy", }, } @@ -160,7 +160,7 @@ func TestRPCBatch_Endpoints(t *testing.T) { { statusMethod, &ctypes.ResultStatus{ - NodeInfo: p2p.NodeInfo{ + NodeInfo: types2.NodeInfo{ Moniker: "dummy", }, }, diff --git a/tm2/pkg/bft/rpc/client/client_test.go b/tm2/pkg/bft/rpc/client/client_test.go index cb88c91fc5f..f0a509d31b6 100644 --- a/tm2/pkg/bft/rpc/client/client_test.go +++ b/tm2/pkg/bft/rpc/client/client_test.go @@ -14,7 +14,7 @@ import ( ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" types "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/types" bfttypes "github.com/gnolang/gno/tm2/pkg/bft/types" - "github.com/gnolang/gno/tm2/pkg/p2p" + types2 "github.com/gnolang/gno/tm2/pkg/p2p" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -114,7 +114,7 @@ func TestRPCClient_Status(t *testing.T) { var ( expectedStatus = &ctypes.ResultStatus{ - NodeInfo: p2p.NodeInfo{ + NodeInfo: types2.NodeInfo{ Moniker: "dummy", }, } @@ -811,17 +811,17 @@ func TestRPCClient_Batch(t *testing.T) { var ( expectedStatuses = []*ctypes.ResultStatus{ { - NodeInfo: p2p.NodeInfo{ + NodeInfo: types2.NodeInfo{ Moniker: "dummy", }, }, { - NodeInfo: p2p.NodeInfo{ + NodeInfo: types2.NodeInfo{ Moniker: "dummy", }, }, { - NodeInfo: p2p.NodeInfo{ + NodeInfo: types2.NodeInfo{ Moniker: "dummy", }, }, diff --git a/tm2/pkg/bft/rpc/client/e2e_test.go b/tm2/pkg/bft/rpc/client/e2e_test.go index 08d4b9b735d..5bbe286a9da 100644 --- a/tm2/pkg/bft/rpc/client/e2e_test.go +++ b/tm2/pkg/bft/rpc/client/e2e_test.go @@ -13,7 +13,7 @@ import ( ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" types "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/types" bfttypes "github.com/gnolang/gno/tm2/pkg/bft/types" - "github.com/gnolang/gno/tm2/pkg/p2p" + types2 "github.com/gnolang/gno/tm2/pkg/p2p" "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -170,7 +170,7 @@ func TestRPCClient_E2E_Endpoints(t *testing.T) { { statusMethod, &ctypes.ResultStatus{ - NodeInfo: p2p.NodeInfo{ + NodeInfo: types2.NodeInfo{ Moniker: "dummy", }, }, diff --git a/tm2/pkg/bft/rpc/core/pipe.go b/tm2/pkg/bft/rpc/core/pipe.go index 9493e7c5873..f0de0b75d2b 100644 --- a/tm2/pkg/bft/rpc/core/pipe.go +++ b/tm2/pkg/bft/rpc/core/pipe.go @@ -45,7 +45,7 @@ type peers interface { AddPersistentPeers([]string) error DialPeersAsync([]string) error NumPeers() (outbound, inbound, dialig int) - Peers() p2p.IPeerSet + Peers() p2p.PeerSet } // ---------------------------------------------- diff --git a/tm2/pkg/bft/rpc/core/types/responses_test.go b/tm2/pkg/bft/rpc/core/types/responses_test.go index 268a8d25c34..619eab80780 100644 --- a/tm2/pkg/bft/rpc/core/types/responses_test.go +++ b/tm2/pkg/bft/rpc/core/types/responses_test.go @@ -3,9 +3,8 @@ package core_types import ( "testing" - "github.com/stretchr/testify/assert" - "github.com/gnolang/gno/tm2/pkg/p2p" + "github.com/stretchr/testify/assert" ) func TestStatusIndexer(t *testing.T) { diff --git a/tm2/pkg/p2p/config/config.go b/tm2/pkg/p2p/config/config.go index 8b6ac6c8566..ce145fd2e73 100644 --- a/tm2/pkg/p2p/config/config.go +++ b/tm2/pkg/p2p/config/config.go @@ -62,10 +62,6 @@ type P2PConfig struct { // Peer connection configuration. HandshakeTimeout time.Duration `json:"handshake_timeout" toml:"handshake_timeout" comment:"Peer connection configuration."` DialTimeout time.Duration `json:"dial_timeout" toml:"dial_timeout"` - - // Testing params. - // Force dial to fail - TestDialFail bool `json:"test_dial_fail" toml:"test_dial_fail"` } // DefaultP2PConfig returns a default configuration for the peer-to-peer layer @@ -85,7 +81,6 @@ func DefaultP2PConfig() *P2PConfig { AllowDuplicateIP: false, HandshakeTimeout: 20 * time.Second, DialTimeout: 3 * time.Second, - TestDialFail: false, } } diff --git a/tm2/pkg/p2p/mock_test.go b/tm2/pkg/p2p/mock_test.go new file mode 100644 index 00000000000..51b73fd666f --- /dev/null +++ b/tm2/pkg/p2p/mock_test.go @@ -0,0 +1,152 @@ +package p2p + +import ( + "net" + + "github.com/gnolang/gno/tm2/pkg/p2p/conn" + "github.com/gnolang/gno/tm2/pkg/service" +) + +type ( + flushStopDelegate func() + idDelegate func() ID + remoteIPDelegate func() net.IP + remoteAddrDelegate func() net.Addr + isOutboundDelegate func() bool + isPersistentDelegate func() bool + closeConnDelegate func() error + nodeInfoDelegate func() NodeInfo + statusDelegate func() conn.ConnectionStatus + socketAddrDelegate func() *NetAddress + sendDelegate func(byte, []byte) bool + trySendDelegate func(byte, []byte) bool + setDelegate func(string, any) + getDelegate func(string) any +) + +type mockPeer struct { + service.BaseService + + flushStopFn flushStopDelegate + idFn idDelegate + remoteIPFn remoteIPDelegate + remoteAddrFn remoteAddrDelegate + isOutboundFn isOutboundDelegate + isPersistentFn isPersistentDelegate + closeConnFn closeConnDelegate + nodeInfoFn nodeInfoDelegate + statusFn statusDelegate + socketAddrFn socketAddrDelegate + sendFn sendDelegate + trySendFn trySendDelegate + setFn setDelegate + getFn getDelegate +} + +func (m *mockPeer) FlushStop() { + if m.flushStopFn != nil { + m.flushStopFn() + } +} + +func (m *mockPeer) ID() ID { + if m.idFn != nil { + return m.idFn() + } + + return "" +} + +func (m *mockPeer) RemoteIP() net.IP { + if m.remoteIPFn != nil { + return m.remoteIPFn() + } + + return nil +} + +func (m *mockPeer) RemoteAddr() net.Addr { + if m.remoteAddrFn != nil { + return m.remoteAddrFn() + } + + return nil +} + +func (m *mockPeer) IsOutbound() bool { + if m.isOutboundFn != nil { + return m.isOutboundFn() + } + + return false +} + +func (m *mockPeer) IsPersistent() bool { + if m.isPersistentFn != nil { + return m.isPersistentFn() + } + + return false +} + +func (m *mockPeer) CloseConn() error { + if m.closeConnFn != nil { + return m.closeConnFn() + } + + return nil +} + +func (m *mockPeer) NodeInfo() NodeInfo { + if m.nodeInfoFn != nil { + return m.nodeInfoFn() + } + + return NodeInfo{} +} + +func (m *mockPeer) Status() conn.ConnectionStatus { + if m.statusFn != nil { + return m.statusFn() + } + + return conn.ConnectionStatus{} +} + +func (m *mockPeer) SocketAddr() *NetAddress { + if m.socketAddrFn != nil { + return m.socketAddrFn() + } + + return nil +} + +func (m *mockPeer) Send(classifier byte, data []byte) bool { + if m.sendFn != nil { + return m.sendFn(classifier, data) + } + + return false +} + +func (m *mockPeer) TrySend(classifier byte, data []byte) bool { + if m.trySendFn != nil { + return m.trySendFn(classifier, data) + } + + return false +} + +func (m *mockPeer) Set(key string, data any) { + if m.setFn != nil { + m.setFn(key, data) + } +} + +func (m *mockPeer) Get(key string) any { + if m.getFn != nil { + return m.getFn(key) + } + + return nil +} diff --git a/tm2/pkg/p2p/netaddress.go b/tm2/pkg/p2p/netaddress.go index 02513b06979..f22472375a4 100644 --- a/tm2/pkg/p2p/netaddress.go +++ b/tm2/pkg/p2p/netaddress.go @@ -27,8 +27,6 @@ var ( errUnspecifiedIP = errors.New("unspecified IP address") ) -type ID = crypto.ID - // NetAddress defines information about a peer on the network // including its ID, IP address, and port type NetAddress struct { diff --git a/tm2/pkg/p2p/node_info.go b/tm2/pkg/p2p/node_info.go index 7573f506672..0b99851edf3 100644 --- a/tm2/pkg/p2p/node_info.go +++ b/tm2/pkg/p2p/node_info.go @@ -9,8 +9,8 @@ import ( ) const ( - maxNodeInfoSize = 10240 // 10KB - maxNumChannels = 16 // plenty of room for upgrades, for now + MaxNodeInfoSize = int64(10240) // 10KB + maxNumChannels = 16 // plenty of room for upgrades, for now ) var ( @@ -24,13 +24,6 @@ var ( errNoCommonChannels = errors.New("no common channels") ) -// Max size of the NodeInfo struct -func MaxNodeInfoSize() int { - return maxNodeInfoSize -} - -// ------------------------------------------------------------- - // NodeInfo is the basic node information exchanged // between two peers during the Tendermint P2P handshake. type NodeInfo struct { diff --git a/tm2/pkg/p2p/peer.go b/tm2/pkg/p2p/peer.go index ef2ddcf2c25..a6df628deae 100644 --- a/tm2/pkg/p2p/peer.go +++ b/tm2/pkg/p2p/peer.go @@ -10,158 +10,80 @@ import ( "github.com/gnolang/gno/tm2/pkg/service" ) -// Peer is an interface representing a peer connected on a reactor. -type Peer interface { - service.Service - FlushStop() - - ID() ID // peer's cryptographic ID - RemoteIP() net.IP // remote IP of the connection - RemoteAddr() net.Addr // remote address of the connection - - IsOutbound() bool // did we dial the peer - IsPersistent() bool // do we redial this peer when we disconnect - - CloseConn() error // close original connection - - NodeInfo() NodeInfo // peer's info - Status() connm.ConnectionStatus - SocketAddr() *NetAddress // actual address of the socket - - Send(byte, []byte) bool - TrySend(byte, []byte) bool - - Set(string, interface{}) - Get(string) interface{} -} - -// ---------------------------------------------------------- - -// peerConn contains the raw connection and its config. -type peerConn struct { - outbound bool - persistent bool - conn net.Conn // source connection - - socketAddr *NetAddress - - // cached RemoteIP() - ip net.IP +// ConnInfo wraps the remote peer connection +type ConnInfo struct { + Outbound bool // flag indicating if the connection is dialed + Persistent bool // flag indicating if the connection is persistent + Conn net.Conn // the source connection + RemoteIP net.IP // the remote IP of the peer + SocketAddr *NetAddress } -func newPeerConn( - outbound, persistent bool, - conn net.Conn, - socketAddr *NetAddress, -) peerConn { - return peerConn{ - outbound: outbound, - persistent: persistent, - conn: conn, - socketAddr: socketAddr, - } -} - -// ID only exists for SecretConnection. -// NOTE: Will panic if conn is not *SecretConnection. -func (pc peerConn) ID() ID { - return (pc.conn.(*connm.SecretConnection).RemotePubKey()).Address().ID() -} - -// Return the IP from the connection RemoteAddr -func (pc peerConn) RemoteIP() net.IP { - if pc.ip != nil { - return pc.ip - } - - host, _, err := net.SplitHostPort(pc.conn.RemoteAddr().String()) - if err != nil { - panic(err) - } - - ips, err := net.LookupIP(host) - if err != nil { - panic(err) - } - - pc.ip = ips[0] - - return pc.ip -} - -// peer implements Peer. -// +// peer is a wrapper for a remote peer // Before using a peer, you will need to perform a handshake on connection. type peer struct { service.BaseService - // raw peerConn and the multiplex connection - peerConn - mconn *connm.MConnection + connInfo *ConnInfo + remoteIP net.IP + mconn *connm.MConnection - // peer's node info and the channel it knows about - // channels = nodeInfo.Channels - // cached to avoid copying nodeInfo in hasChannel nodeInfo NodeInfo - channels []byte - - // User data - Data *cmap.CMap + data *cmap.CMap } -type PeerOption func(*peer) - -func newPeer( - pc peerConn, +// TODO cleanup +func New( + connInfo *ConnInfo, mConfig connm.MConnConfig, nodeInfo NodeInfo, reactorsByCh map[byte]Reactor, chDescs []*connm.ChannelDescriptor, onPeerError func(Peer, interface{}), - options ...PeerOption, ) *peer { p := &peer{ - peerConn: pc, + connInfo: connInfo, nodeInfo: nodeInfo, - channels: nodeInfo.Channels, // TODO - Data: cmap.NewCMap(), + data: cmap.NewCMap(), } p.mconn = createMConnection( - pc.conn, + connInfo.Conn, p, reactorsByCh, chDescs, onPeerError, mConfig, ) + p.BaseService = *service.NewBaseService(nil, "Peer", p) - for _, option := range options { - option(p) - } return p } -// String representation. +// RemoteIP returns the IP from the remote connection +func (p *peer) RemoteIP() net.IP { + return p.connInfo.RemoteIP +} + +// RemoteAddr returns the address from the remote connection +func (p *peer) RemoteAddr() net.Addr { + return p.connInfo.Conn.RemoteAddr() +} + func (p *peer) String() string { - if p.outbound { - return fmt.Sprintf("Peer{%v %v out}", p.mconn, p.ID()) + if p.connInfo.Outbound { + return fmt.Sprintf("Peer{%s %s out}", p.mconn, p.ID()) } - return fmt.Sprintf("Peer{%v %v in}", p.mconn, p.ID()) + return fmt.Sprintf("Peer{%s %s in}", p.mconn, p.ID()) } -// --------------------------------------------------- -// Implements service.Service - -// SetLogger implements BaseService. func (p *peer) SetLogger(l *slog.Logger) { p.Logger = l p.mconn.SetLogger(l) } -// OnStart implements BaseService. func (p *peer) OnStart() error { if err := p.BaseService.OnStart(); err != nil { return err @@ -185,11 +107,15 @@ func (p *peer) FlushStop() { // OnStop implements BaseService. func (p *peer) OnStop() { p.BaseService.OnStop() - p.mconn.Stop() // stop everything and close the conn -} -// --------------------------------------------------- -// Implements Peer + if err := p.mconn.Stop(); err != nil { + p.Logger.Error( + "unable to gracefully close mconn", + "err", + err, + ) + } +} // ID returns the peer's ID - the hex encoded hash of its pubkey. func (p *peer) ID() ID { @@ -198,12 +124,12 @@ func (p *peer) ID() ID { // IsOutbound returns true if the connection is outbound, false otherwise. func (p *peer) IsOutbound() bool { - return p.peerConn.outbound + return p.connInfo.Outbound } // IsPersistent returns true if the peer is persistent, false otherwise. func (p *peer) IsPersistent() bool { - return p.peerConn.persistent + return p.connInfo.Persistent } // NodeInfo returns a copy of the peer's NodeInfo. @@ -216,7 +142,7 @@ func (p *peer) NodeInfo() NodeInfo { // For inbound peers, it's the address returned by the underlying connection // (not what's reported in the peer's NodeInfo). func (p *peer) SocketAddr() *NetAddress { - return p.peerConn.socketAddr + return p.connInfo.SocketAddr } // Status returns the peer's ConnectionStatus. @@ -227,43 +153,39 @@ func (p *peer) Status() connm.ConnectionStatus { // Send msg bytes to the channel identified by chID byte. Returns false if the // send queue is full after timeout, specified by MConnection. func (p *peer) Send(chID byte, msgBytes []byte) bool { - if !p.IsRunning() { + if !p.IsRunning() || !p.hasChannel(chID) { // see Switch#Broadcast, where we fetch the list of peers and loop over // them - while we're looping, one peer may be removed and stopped. return false - } else if !p.hasChannel(chID) { - return false } - res := p.mconn.Send(chID, msgBytes) - return res + + return p.mconn.Send(chID, msgBytes) } // TrySend msg bytes to the channel identified by chID byte. Immediately returns // false if the send queue is full. func (p *peer) TrySend(chID byte, msgBytes []byte) bool { - if !p.IsRunning() { - return false - } else if !p.hasChannel(chID) { + if !p.IsRunning() || !p.hasChannel(chID) { return false } - res := p.mconn.TrySend(chID, msgBytes) - return res + + return p.mconn.TrySend(chID, msgBytes) } // Get the data for a given key. func (p *peer) Get(key string) interface{} { - return p.Data.Get(key) + return p.data.Get(key) } // Set sets the data for the given key. func (p *peer) Set(key string, data interface{}) { - p.Data.Set(key, data) + p.data.Set(key, data) } // hasChannel returns true if the peer reported // knowing about the given chID. func (p *peer) hasChannel(chID byte) bool { - for _, ch := range p.channels { + for _, ch := range p.nodeInfo.Channels { if ch == chID { return true } @@ -275,44 +197,19 @@ func (p *peer) hasChannel(chID byte) bool { "channel", chID, "channels", - p.channels, + p.nodeInfo.Channels, ) return false } // CloseConn closes original connection. Used for cleaning up in cases where the peer had not been started at all. func (p *peer) CloseConn() error { - return p.peerConn.conn.Close() + return p.connInfo.Conn.Close() } -// --------------------------------------------------- -// methods only used for testing -// TODO: can we remove these? - -// CloseConn closes the underlying connection -func (pc *peerConn) CloseConn() { - pc.conn.Close() //nolint: errcheck -} - -// RemoteAddr returns peer's remote network address. -func (p *peer) RemoteAddr() net.Addr { - return p.peerConn.conn.RemoteAddr() -} - -// CanSend returns true if the send queue is not full, false otherwise. -func (p *peer) CanSend(chID byte) bool { - if !p.IsRunning() { - return false - } - return p.mconn.CanSend(chID) -} - -// ------------------------------------------------------------------ -// helper funcs - func createMConnection( conn net.Conn, - p *peer, + p Peer, reactorsByCh map[byte]Reactor, chDescs []*connm.ChannelDescriptor, onPeerError func(Peer, interface{}), diff --git a/tm2/pkg/p2p/peer_set.go b/tm2/pkg/p2p/peer_set.go deleted file mode 100644 index 396ba56da11..00000000000 --- a/tm2/pkg/p2p/peer_set.go +++ /dev/null @@ -1,147 +0,0 @@ -package p2p - -import ( - "net" - "sync" -) - -// IPeerSet has a (immutable) subset of the methods of PeerSet. -type IPeerSet interface { - Has(key ID) bool - HasIP(ip net.IP) bool - Get(key ID) Peer - List() []Peer - Size() int -} - -// ----------------------------------------------------------------------------- - -// PeerSet is a special structure for keeping a table of peers. -// Iteration over the peers is super fast and thread-safe. -type PeerSet struct { - mtx sync.Mutex - lookup map[ID]*peerSetItem - list []Peer -} - -type peerSetItem struct { - peer Peer - index int -} - -// NewPeerSet creates a new peerSet with a list of initial capacity of 256 items. -func NewPeerSet() *PeerSet { - return &PeerSet{ - lookup: make(map[ID]*peerSetItem), - list: make([]Peer, 0, 256), - } -} - -// Add adds the peer to the PeerSet. -// It returns an error carrying the reason, if the peer is already present. -func (ps *PeerSet) Add(peer Peer) error { - ps.mtx.Lock() - defer ps.mtx.Unlock() - - if ps.lookup[peer.ID()] != nil { - return SwitchDuplicatePeerIDError{peer.ID()} - } - - index := len(ps.list) - // Appending is safe even with other goroutines - // iterating over the ps.list slice. - ps.list = append(ps.list, peer) - ps.lookup[peer.ID()] = &peerSetItem{peer, index} - return nil -} - -// Has returns true if the set contains the peer referred to by this -// peerKey, otherwise false. -func (ps *PeerSet) Has(peerKey ID) bool { - ps.mtx.Lock() - _, ok := ps.lookup[peerKey] - ps.mtx.Unlock() - return ok -} - -// HasIP returns true if the set contains the peer referred to by this IP -// address, otherwise false. -func (ps *PeerSet) HasIP(peerIP net.IP) bool { - ps.mtx.Lock() - defer ps.mtx.Unlock() - - return ps.hasIP(peerIP) -} - -// hasIP does not acquire a lock so it can be used in public methods which -// already lock. -func (ps *PeerSet) hasIP(peerIP net.IP) bool { - for _, item := range ps.lookup { - if item.peer.RemoteIP().Equal(peerIP) { - return true - } - } - - return false -} - -// Get looks up a peer by the provided peerKey. Returns nil if peer is not -// found. -func (ps *PeerSet) Get(peerKey ID) Peer { - ps.mtx.Lock() - defer ps.mtx.Unlock() - item, ok := ps.lookup[peerKey] - if ok { - return item.peer - } - return nil -} - -// Remove discards peer by its Key, if the peer was previously memoized. -// Returns true if the peer was removed, and false if it was not found. -// in the set. -func (ps *PeerSet) Remove(peer Peer) bool { - ps.mtx.Lock() - defer ps.mtx.Unlock() - - item := ps.lookup[peer.ID()] - if item == nil { - return false - } - - index := item.index - // Create a new copy of the list but with one less item. - // (we must copy because we'll be mutating the list). - newList := make([]Peer, len(ps.list)-1) - copy(newList, ps.list) - // If it's the last peer, that's an easy special case. - if index == len(ps.list)-1 { - ps.list = newList - delete(ps.lookup, peer.ID()) - return true - } - - // Replace the popped item with the last item in the old list. - lastPeer := ps.list[len(ps.list)-1] - lastPeerKey := lastPeer.ID() - lastPeerItem := ps.lookup[lastPeerKey] - newList[index] = lastPeer - lastPeerItem.index = index - ps.list = newList - delete(ps.lookup, peer.ID()) - return true -} - -// Size returns the number of unique items in the peerSet. -func (ps *PeerSet) Size() int { - ps.mtx.Lock() - defer ps.mtx.Unlock() - return len(ps.list) -} - -// List returns the threadsafe list of peers. -func (ps *PeerSet) List() []Peer { - ps.mtx.Lock() - defer ps.mtx.Unlock() - return ps.list -} diff --git a/tm2/pkg/p2p/peer_set_test.go b/tm2/pkg/p2p/peer_set_test.go deleted file mode 100644 index 7aca84d59b0..00000000000 --- a/tm2/pkg/p2p/peer_set_test.go +++ /dev/null @@ -1,190 +0,0 @@ -package p2p - -import ( - "net" - "sync" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" - "github.com/gnolang/gno/tm2/pkg/service" -) - -// mockPeer for testing the PeerSet -type mockPeer struct { - service.BaseService - ip net.IP - id ID -} - -func (mp *mockPeer) FlushStop() { mp.Stop() } -func (mp *mockPeer) TrySend(chID byte, msgBytes []byte) bool { return true } -func (mp *mockPeer) Send(chID byte, msgBytes []byte) bool { return true } -func (mp *mockPeer) NodeInfo() NodeInfo { return NodeInfo{} } -func (mp *mockPeer) Status() ConnectionStatus { return ConnectionStatus{} } -func (mp *mockPeer) ID() ID { return mp.id } -func (mp *mockPeer) IsOutbound() bool { return false } -func (mp *mockPeer) IsPersistent() bool { return true } -func (mp *mockPeer) Get(s string) interface{} { return s } -func (mp *mockPeer) Set(string, interface{}) {} -func (mp *mockPeer) RemoteIP() net.IP { return mp.ip } -func (mp *mockPeer) SocketAddr() *NetAddress { return nil } -func (mp *mockPeer) RemoteAddr() net.Addr { return &net.TCPAddr{IP: mp.ip, Port: 8800} } -func (mp *mockPeer) CloseConn() error { return nil } - -// Returns a mock peer -func newMockPeer(ip net.IP) *mockPeer { - if ip == nil { - ip = net.IP{127, 0, 0, 1} - } - nodeKey := NodeKey{PrivKey: ed25519.GenPrivKey()} - return &mockPeer{ - ip: ip, - id: nodeKey.ID(), - } -} - -func TestPeerSetAddRemoveOne(t *testing.T) { - t.Parallel() - - peerSet := NewPeerSet() - - var peerList []Peer - for i := 0; i < 5; i++ { - p := newMockPeer(net.IP{127, 0, 0, byte(i)}) - if err := peerSet.Add(p); err != nil { - t.Error(err) - } - peerList = append(peerList, p) - } - - n := len(peerList) - // 1. Test removing from the front - for i, peerAtFront := range peerList { - removed := peerSet.Remove(peerAtFront) - assert.True(t, removed) - wantSize := n - i - 1 - for j := 0; j < 2; j++ { - assert.Equal(t, false, peerSet.Has(peerAtFront.ID()), "#%d Run #%d: failed to remove peer", i, j) - assert.Equal(t, wantSize, peerSet.Size(), "#%d Run #%d: failed to remove peer and decrement size", i, j) - // Test the route of removing the now non-existent element - removed := peerSet.Remove(peerAtFront) - assert.False(t, removed) - } - } - - // 2. Next we are testing removing the peer at the end - // a) Replenish the peerSet - for _, peer := range peerList { - if err := peerSet.Add(peer); err != nil { - t.Error(err) - } - } - - // b) In reverse, remove each element - for i := n - 1; i >= 0; i-- { - peerAtEnd := peerList[i] - removed := peerSet.Remove(peerAtEnd) - assert.True(t, removed) - assert.Equal(t, false, peerSet.Has(peerAtEnd.ID()), "#%d: failed to remove item at end", i) - assert.Equal(t, i, peerSet.Size(), "#%d: differing sizes after peerSet.Remove(atEndPeer)", i) - } -} - -func TestPeerSetAddRemoveMany(t *testing.T) { - t.Parallel() - peerSet := NewPeerSet() - - peers := []Peer{} - N := 100 - for i := 0; i < N; i++ { - peer := newMockPeer(net.IP{127, 0, 0, byte(i)}) - if err := peerSet.Add(peer); err != nil { - t.Errorf("Failed to add new peer") - } - if peerSet.Size() != i+1 { - t.Errorf("Failed to add new peer and increment size") - } - peers = append(peers, peer) - } - - for i, peer := range peers { - removed := peerSet.Remove(peer) - assert.True(t, removed) - if peerSet.Has(peer.ID()) { - t.Errorf("Failed to remove peer") - } - if peerSet.Size() != len(peers)-i-1 { - t.Errorf("Failed to remove peer and decrement size") - } - } -} - -func TestPeerSetAddDuplicate(t *testing.T) { - t.Parallel() - peerSet := NewPeerSet() - peer := newMockPeer(nil) - - n := 20 - errsChan := make(chan error) - // Add the same asynchronously to test the - // concurrent guarantees of our APIs, and - // our expectation in the end is that only - // one addition succeeded, but the rest are - // instances of ErrSwitchDuplicatePeer. - for i := 0; i < n; i++ { - go func() { - errsChan <- peerSet.Add(peer) - }() - } - - // Now collect and tally the results - errsTally := make(map[string]int) - for i := 0; i < n; i++ { - err := <-errsChan - - switch err.(type) { - case SwitchDuplicatePeerIDError: - errsTally["duplicateID"]++ - default: - errsTally["other"]++ - } - } - - // Our next procedure is to ensure that only one addition - // succeeded and that the rest are each ErrSwitchDuplicatePeer. - wantErrCount, gotErrCount := n-1, errsTally["duplicateID"] - assert.Equal(t, wantErrCount, gotErrCount, "invalid ErrSwitchDuplicatePeer count") - - wantNilErrCount, gotNilErrCount := 1, errsTally["other"] - assert.Equal(t, wantNilErrCount, gotNilErrCount, "invalid nil errCount") -} - -func TestPeerSetGet(t *testing.T) { - t.Parallel() - - var ( - peerSet = NewPeerSet() - peer = newMockPeer(nil) - ) - - assert.Nil(t, peerSet.Get(peer.ID()), "expecting a nil lookup, before .Add") - - if err := peerSet.Add(peer); err != nil { - t.Fatalf("Failed to add new peer: %v", err) - } - - var wg sync.WaitGroup - for i := 0; i < 10; i++ { - // Add them asynchronously to test the - // concurrent guarantees of our APIs. - wg.Add(1) - go func(i int) { - defer wg.Done() - have, want := peerSet.Get(peer.ID()), peer - assert.Equal(t, have, want, "%d: have %v, want %v", i, have, want) - }(i) - } - wg.Wait() -} diff --git a/tm2/pkg/p2p/peer_test.go b/tm2/pkg/p2p/peer_test.go index 685d5e5db20..ba95cd25206 100644 --- a/tm2/pkg/p2p/peer_test.go +++ b/tm2/pkg/p2p/peer_test.go @@ -1,242 +1,217 @@ package p2p -import ( - "fmt" - golog "log" - "net" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/gnolang/gno/tm2/pkg/crypto" - "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" - "github.com/gnolang/gno/tm2/pkg/errors" - "github.com/gnolang/gno/tm2/pkg/log" - "github.com/gnolang/gno/tm2/pkg/p2p/config" - "github.com/gnolang/gno/tm2/pkg/p2p/conn" -) - -func TestPeerBasic(t *testing.T) { - t.Parallel() - - assert, require := assert.New(t), require.New(t) - - // simulate remote peer - rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} - rp.Start() - defer rp.Stop() - - p, err := createOutboundPeerAndPerformHandshake(t, rp.Addr(), cfg, conn.DefaultMConnConfig()) - require.Nil(err) - - err = p.Start() - require.Nil(err) - defer p.Stop() - - assert.True(p.IsRunning()) - assert.True(p.IsOutbound()) - assert.False(p.IsPersistent()) - p.persistent = true - assert.True(p.IsPersistent()) - assert.Equal(rp.Addr().DialString(), p.RemoteAddr().String()) - assert.Equal(rp.ID(), p.ID()) -} - -func TestPeerSend(t *testing.T) { - t.Parallel() - - assert, require := assert.New(t), require.New(t) - - config := cfg - - // simulate remote peer - rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: config} - rp.Start() - defer rp.Stop() - - p, err := createOutboundPeerAndPerformHandshake(t, rp.Addr(), config, conn.DefaultMConnConfig()) - require.Nil(err) - - err = p.Start() - require.Nil(err) - - defer p.Stop() - - assert.True(p.CanSend(testCh)) - assert.True(p.Send(testCh, []byte("Asylum"))) -} - -func createOutboundPeerAndPerformHandshake( - t *testing.T, - addr *NetAddress, - config *config.P2PConfig, - mConfig conn.MConnConfig, -) (*peer, error) { - t.Helper() - - chDescs := []*conn.ChannelDescriptor{ - {ID: testCh, Priority: 1}, - } - reactorsByCh := map[byte]Reactor{testCh: NewTestReactor(chDescs, true)} - pk := ed25519.GenPrivKey() - pc, err := testOutboundPeerConn(addr, config, false, pk) - if err != nil { - return nil, err - } - timeout := 1 * time.Second - ourNodeInfo := testNodeInfo(addr.ID, "host_peer") - peerNodeInfo, err := handshake(pc.conn, timeout, ourNodeInfo) - if err != nil { - return nil, err - } - - p := newPeer(pc, mConfig, peerNodeInfo, reactorsByCh, chDescs, func(p Peer, r interface{}) {}) - p.SetLogger(log.NewTestingLogger(t).With("peer", addr)) - return p, nil -} - -func testDial(addr *NetAddress, cfg *config.P2PConfig) (net.Conn, error) { - if cfg.TestDialFail { - return nil, fmt.Errorf("dial err (peerConfig.DialFail == true)") - } - - conn, err := addr.DialTimeout(cfg.DialTimeout) - if err != nil { - return nil, err - } - return conn, nil -} - -func testOutboundPeerConn( - addr *NetAddress, - config *config.P2PConfig, - persistent bool, - ourNodePrivKey crypto.PrivKey, -) (peerConn, error) { - var pc peerConn - conn, err := testDial(addr, config) - if err != nil { - return pc, errors.Wrap(err, "Error creating peer") - } - - pc, err = testPeerConn(conn, config, true, persistent, ourNodePrivKey, addr) - if err != nil { - if cerr := conn.Close(); cerr != nil { - return pc, errors.Wrap(err, cerr.Error()) - } - return pc, err - } - - // ensure dialed ID matches connection ID - if addr.ID != pc.ID() { - if cerr := conn.Close(); cerr != nil { - return pc, errors.Wrap(err, cerr.Error()) - } - return pc, SwitchAuthenticationFailureError{addr, pc.ID()} - } - - return pc, nil -} - -type remotePeer struct { - PrivKey crypto.PrivKey - Config *config.P2PConfig - addr *NetAddress - channels []byte - listenAddr string - listener net.Listener -} - -func (rp *remotePeer) Addr() *NetAddress { - return rp.addr -} - -func (rp *remotePeer) ID() ID { - return rp.PrivKey.PubKey().Address().ID() -} - -func (rp *remotePeer) Start() error { - if rp.listenAddr == "" { - rp.listenAddr = "127.0.0.1:0" - } - - l, err := net.Listen("tcp", rp.listenAddr) // any available address - if err != nil { - golog.Fatalf("net.Listen tcp :0: %+v", err) - - return err - } - - rp.listener = l - rp.addr, err = NewNetAddress(rp.PrivKey.PubKey().Address().ID(), l.Addr()) - if err != nil { - return err - } - - if rp.channels == nil { - rp.channels = []byte{testCh} - } - go rp.accept() - - return nil -} - -func (rp *remotePeer) Stop() { - rp.listener.Close() -} - -func (rp *remotePeer) Dial(addr *NetAddress) (net.Conn, error) { - conn, err := addr.DialTimeout(1 * time.Second) - if err != nil { - return nil, err - } - pc, err := testInboundPeerConn(conn, rp.Config, rp.PrivKey) - if err != nil { - return nil, err - } - _, err = handshake(pc.conn, time.Second, rp.nodeInfo()) - if err != nil { - return nil, err - } - return conn, err -} - -func (rp *remotePeer) accept() { - conns := []net.Conn{} - - for { - conn, err := rp.listener.Accept() - if err != nil { - golog.Printf("Failed to accept conn: %+v", err) - for _, conn := range conns { - _ = conn.Close() - } - return - } - - pc, err := testInboundPeerConn(conn, rp.Config, rp.PrivKey) - if err != nil { - golog.Fatalf("Failed to create a peer: %+v", err) - } - - _, err = handshake(pc.conn, time.Second, rp.nodeInfo()) - if err != nil { - golog.Fatalf("Failed to perform handshake: %+v", err) - } - - conns = append(conns, conn) - } -} - -func (rp *remotePeer) nodeInfo() NodeInfo { - return NodeInfo{ - VersionSet: testVersionSet(), - NetAddress: rp.Addr(), - Network: "testing", - Version: "1.2.3-rc0-deadbeef", - Channels: rp.channels, - Moniker: "remote_peer", - } -} +// func TestPeerBasic(t *testing.T) { +// t.Parallel() +// +// // simulate remote peer +// rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: p2p.cfg} +// rp.Start() +// defer rp.Stop() +// +// p, err := createOutboundPeerAndPerformHandshake(t, rp.Addr(), p2p.cfg, conn.DefaultMConnConfig()) +// require.Nil(err) +// +// err = p.Start() +// require.Nil(err) +// defer p.Stop() +// +// assert.True(p.IsRunning()) +// assert.True(p.IsOutbound()) +// assert.False(p.IsPersistent()) +// p.persistent = true +// assert.True(p.IsPersistent()) +// assert.Equal(rp.Addr().DialString(), p.RemoteAddr().String()) +// assert.Equal(rp.ID(), p.ID()) +// } +// +// func TestPeerSend(t *testing.T) { +// t.Parallel() +// +// assert, require := assert.New(t), require.New(t) +// +// config := p2p.cfg +// +// // simulate remote peer +// rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: config} +// rp.Start() +// defer rp.Stop() +// +// p, err := createOutboundPeerAndPerformHandshake(t, rp.Addr(), config, conn.DefaultMConnConfig()) +// require.Nil(err) +// +// err = p.Start() +// require.Nil(err) +// +// defer p.Stop() +// +// assert.True(p.Send(p2p.testCh, []byte("Asylum"))) +// } +// +// func createOutboundPeerAndPerformHandshake( +// t *testing.T, +// addr *p2p.NetAddress, +// config *config.P2PConfig, +// mConfig conn.MConnConfig, +// ) (*peer, error) { +// t.Helper() +// +// chDescs := []*conn.ChannelDescriptor{ +// {ID: p2p.testCh, Priority: 1}, +// } +// reactorsByCh := map[byte]p2p.Reactor{p2p.testCh: p2p.NewTestReactor(chDescs, true)} +// pk := ed25519.GenPrivKey() +// pc, err := testOutboundPeerConn(addr, config, false, pk) +// if err != nil { +// return nil, err +// } +// timeout := 1 * time.Second +// ourNodeInfo := p2p.testNodeInfo(addr.ID, "host_peer") +// peerNodeInfo, err := p2p.handshake(pc.conn, timeout, ourNodeInfo) +// if err != nil { +// return nil, err +// } +// +// p := newPeer(pc, mConfig, peerNodeInfo, reactorsByCh, chDescs, func(p p2p.Peer, r interface{}) {}) +// p.SetLogger(log.NewTestingLogger(t).With("peer", addr)) +// return p, nil +// } +// +// func testDial(addr *p2p.NetAddress, cfg *config.P2PConfig) (net.Conn, error) { +// conn, err := addr.DialTimeout(cfg.DialTimeout) +// if err != nil { +// return nil, err +// } +// return conn, nil +// } +// +// func testOutboundPeerConn( +// addr *p2p.NetAddress, +// config *config.P2PConfig, +// persistent bool, +// ourNodePrivKey crypto.PrivKey, +// ) (peerConn, error) { +// var pc peerConn +// conn, err := testDial(addr, config) +// if err != nil { +// return pc, errors.Wrap(err, "Error creating peer") +// } +// +// pc, err = p2p.testPeerConn(conn, config, true, persistent, ourNodePrivKey, addr) +// if err != nil { +// if cerr := conn.Close(); cerr != nil { +// return pc, errors.Wrap(err, cerr.Error()) +// } +// return pc, err +// } +// +// // ensure dialed ID matches connection ID +// if addr.ID != pc.ID() { +// if cerr := conn.Close(); cerr != nil { +// return pc, errors.Wrap(err, cerr.Error()) +// } +// return pc, p2p.SwitchAuthenticationFailureError{addr, pc.ID()} +// } +// +// return pc, nil +// } +// +// type remotePeer struct { +// PrivKey crypto.PrivKey +// Config *config.P2PConfig +// addr *p2p.NetAddress +// channels []byte +// listenAddr string +// listener net.Listener +// } +// +// func (rp *remotePeer) Addr() *p2p.NetAddress { +// return rp.addr +// } +// +// func (rp *remotePeer) ID() p2p.ID { +// return rp.PrivKey.PubKey().Address().ID() +// } +// +// func (rp *remotePeer) Start() error { +// if rp.listenAddr == "" { +// rp.listenAddr = "127.0.0.1:0" +// } +// +// l, err := net.Listen("tcp", rp.listenAddr) // any available address +// if err != nil { +// golog.Fatalf("net.Listen tcp :0: %+v", err) +// +// return err +// } +// +// rp.listener = l +// rp.addr, err = p2p.NewNetAddress(rp.PrivKey.PubKey().Address().ID(), l.Addr()) +// if err != nil { +// return err +// } +// +// if rp.channels == nil { +// rp.channels = []byte{p2p.testCh} +// } +// go rp.accept() +// +// return nil +// } +// +// func (rp *remotePeer) Stop() { +// rp.listener.Close() +// } +// +// func (rp *remotePeer) Dial(addr *p2p.NetAddress) (net.Conn, error) { +// conn, err := addr.DialTimeout(1 * time.Second) +// if err != nil { +// return nil, err +// } +// pc, err := p2p.testInboundPeerConn(conn, rp.Config, rp.PrivKey) +// if err != nil { +// return nil, err +// } +// _, err = p2p.handshake(pc.conn, time.Second, rp.nodeInfo()) +// if err != nil { +// return nil, err +// } +// return conn, err +// } +// +// func (rp *remotePeer) accept() { +// conns := []net.Conn{} +// +// for { +// conn, err := rp.listener.Accept() +// if err != nil { +// golog.Printf("Failed to accept conn: %+v", err) +// for _, conn := range conns { +// _ = conn.Close() +// } +// return +// } +// +// pc, err := p2p.testInboundPeerConn(conn, rp.Config, rp.PrivKey) +// if err != nil { +// golog.Fatalf("Failed to create a peer: %+v", err) +// } +// +// _, err = p2p.handshake(pc.conn, time.Second, rp.nodeInfo()) +// if err != nil { +// golog.Fatalf("Failed to perform handshake: %+v", err) +// } +// +// conns = append(conns, conn) +// } +// } +// +// func (rp *remotePeer) nodeInfo() p2p.NodeInfo { +// return p2p.NodeInfo{ +// VersionSet: p2p.testVersionSet(), +// NetAddress: rp.Addr(), +// Network: "testing", +// Version: "1.2.3-rc0-deadbeef", +// Channels: rp.channels, +// Moniker: "remote_peer", +// } +// } diff --git a/tm2/pkg/p2p/set.go b/tm2/pkg/p2p/set.go new file mode 100644 index 00000000000..1b796d5ae72 --- /dev/null +++ b/tm2/pkg/p2p/set.go @@ -0,0 +1,96 @@ +package p2p + +import ( + "net" + "sync" +) + +type Set struct { + peers sync.Map // p2p.ID -> p2p.Peer +} + +// NewSet creates an empty peer set +func NewSet() *Set { + return &Set{} +} + +// Add adds the peer to the set +func (s *Set) Add(peer Peer) { + s.peers.Store(peer.ID(), peer) +} + +// Has returns true if the set contains the peer referred to by this +// peerKey, otherwise false. +func (s *Set) Has(peerKey ID) bool { + _, ok := s.peers.Load(peerKey) + + return ok +} + +// HasIP returns true if the set contains the peer referred to by this IP +// address, otherwise false. +func (s *Set) HasIP(peerIP net.IP) bool { + hasIP := false + + s.peers.Range(func(_, value interface{}) bool { + peer := value.(Peer) + + if peer.RemoteIP().Equal(peerIP) { + hasIP = true + + return false + } + + return true + }) + + return hasIP +} + +// Get looks up a peer by the provided peerKey. Returns nil if peer is not +// found. +func (s *Set) Get(key ID) Peer { + peerRaw, found := s.peers.Load(key) + if !found { + // TODO change this to an error, it doesn't make + // sense to propagate an implementation detail like this + return nil + } + + return peerRaw.(Peer) +} + +// Remove discards peer by its Key, if the peer was previously memoized. +// Returns true if the peer was removed, and false if it was not found. +// in the set. +func (s *Set) Remove(key ID) bool { + _, existed := s.peers.LoadAndDelete(key) + + return existed +} + +// Size returns the number of unique peers in the peer table +func (s *Set) Size() int { + size := 0 + + s.peers.Range(func(_, _ any) bool { + size++ + + return true + }) + + return size +} + +// List returns the list of peers +func (s *Set) List() []Peer { + peers := make([]Peer, 0) + + s.peers.Range(func(_, value any) bool { + peers = append(peers, value.(Peer)) + + return true + }) + + return peers +} diff --git a/tm2/pkg/p2p/set_test.go b/tm2/pkg/p2p/set_test.go new file mode 100644 index 00000000000..307c783b16d --- /dev/null +++ b/tm2/pkg/p2p/set_test.go @@ -0,0 +1,211 @@ +package p2p + +import ( + "net" + "sort" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// generatePeers generates random node peers +func generatePeers(t *testing.T, count int) []*mockPeer { + t.Helper() + + peers := make([]*mockPeer, count) + + for i := 0; i < count; i++ { + id := GenerateNodeKey().ID() + peers[i] = &mockPeer{ + idFn: func() ID { + return id + }, + } + } + + return peers +} + +func TestSet_Add(t *testing.T) { + t.Parallel() + + var ( + numPeers = 100 + peers = generatePeers(t, numPeers) + + s = NewSet() + ) + + for _, peer := range peers { + // Add the peer + s.Add(peer) + + // Make sure the peer is present + assert.True(t, s.Has(peer.ID())) + } + + assert.EqualValues(t, numPeers, s.Size()) +} + +func TestSet_Remove(t *testing.T) { + t.Parallel() + + var ( + numPeers = 100 + peers = generatePeers(t, numPeers) + + s = NewSet() + ) + + // Add the initial peers + for _, peer := range peers { + // Add the peer + s.Add(peer) + + // Make sure the peer is present + require.True(t, s.Has(peer.ID())) + } + + require.EqualValues(t, numPeers, s.Size()) + + // Remove the peers + // Add the initial peers + for _, peer := range peers { + // Add the peer + s.Remove(peer.ID()) + + // Make sure the peer is present + assert.False(t, s.Has(peer.ID())) + } +} + +func TestSet_HasIP(t *testing.T) { + t.Parallel() + + t.Run("present peer with IP", func(t *testing.T) { + t.Parallel() + + var ( + peers = generatePeers(t, 100) + ip = net.ParseIP("0.0.0.0") + + s = NewSet() + ) + + // Make sure at least one peer has the set IP + peers[len(peers)/2].remoteIPFn = func() net.IP { + return ip + } + + // Add the peers + for _, peer := range peers { + s.Add(peer) + } + + // Make sure the peer is present + assert.True(t, s.HasIP(ip)) + }) + + t.Run("missing peer with IP", func(t *testing.T) { + t.Parallel() + + var ( + peers = generatePeers(t, 100) + ip = net.ParseIP("0.0.0.0") + + s = NewSet() + ) + + // Add the peers + for _, peer := range peers { + s.Add(peer) + } + + // Make sure the peer is not present + assert.False(t, s.HasIP(ip)) + }) +} + +func TestSet_Get(t *testing.T) { + t.Parallel() + + t.Run("existing peer", func(t *testing.T) { + t.Parallel() + + var ( + peers = generatePeers(t, 100) + s = NewSet() + ) + + for _, peer := range peers { + id := peer.ID() + s.Add(peer) + + assert.True(t, s.Get(id).ID() == id) + } + }) + + t.Run("missing peer", func(t *testing.T) { + t.Parallel() + + var ( + peers = generatePeers(t, 100) + s = NewSet() + ) + + for _, peer := range peers { + s.Add(peer) + } + + p := s.Get("random ID") + assert.Nil(t, p) + }) +} + +func TestSet_List(t *testing.T) { + t.Parallel() + + t.Run("empty peer set", func(t *testing.T) { + t.Parallel() + + // Empty set + s := NewSet() + + // Linearize the set + assert.Len(t, s.List(), 0) + }) + + t.Run("existing peer set", func(t *testing.T) { + t.Parallel() + + var ( + peers = generatePeers(t, 100) + s = NewSet() + ) + + for _, peer := range peers { + s.Add(peer) + } + + // Linearize the set + listedPeers := s.List() + + require.Len(t, listedPeers, len(peers)) + + // Make sure the lists are sorted + // for easier comparison + sort.Slice(listedPeers, func(i, j int) bool { + return listedPeers[i].ID() < listedPeers[j].ID() + }) + + sort.Slice(peers, func(i, j int) bool { + return peers[i].ID() < peers[j].ID() + }) + + // Compare the lists + for index, listedPeer := range listedPeers { + assert.Equal(t, listedPeer.ID(), peers[index].ID()) + } + }) +} diff --git a/tm2/pkg/p2p/switch.go b/tm2/pkg/p2p/switch.go index 37a0e81d60b..e9c343d4e0a 100644 --- a/tm2/pkg/p2p/switch.go +++ b/tm2/pkg/p2p/switch.go @@ -46,7 +46,7 @@ func MConnConfig(cfg *config.P2PConfig) conn.MConnConfig { // PeerFilterFunc to be implemented by filter hooks after a new Peer has been // fully setup. -type PeerFilterFunc func(IPeerSet, Peer) error +type PeerFilterFunc func(PeerSet, Peer) error // ----------------------------------------------------------------------------- @@ -61,7 +61,7 @@ type Switch struct { reactors map[string]Reactor chDescs []*conn.ChannelDescriptor reactorsByCh map[byte]Reactor - peers *PeerSet + peers PeerSet dialing *cmap.CMap reconnecting *cmap.CMap nodeInfo NodeInfo // our node info @@ -97,7 +97,7 @@ func NewSwitch( reactors: make(map[string]Reactor), chDescs: make([]*conn.ChannelDescriptor, 0), reactorsByCh: make(map[byte]Reactor), - peers: NewPeerSet(), + peers: NewSet(), dialing: cmap.NewCMap(), reconnecting: cmap.NewCMap(), transport: transport, @@ -297,7 +297,7 @@ func (sw *Switch) MaxNumOutboundPeers() int { } // Peers returns the set of peers that are connected to the switch. -func (sw *Switch) Peers() IPeerSet { +func (sw *Switch) Peers() PeerSet { return sw.peers } @@ -338,7 +338,7 @@ func (sw *Switch) stopAndRemovePeer(peer Peer, reason interface{}) { // reconnect to our node and the switch calls InitPeer before // RemovePeer is finished. // https://github.com/tendermint/classic/issues/3338 - sw.peers.Remove(peer) + sw.peers.Remove(peer.ID()) } // reconnectToPeer tries to reconnect to the addr, first repeatedly @@ -603,12 +603,6 @@ func (sw *Switch) addOutboundPeerWithConfig( ) error { sw.Logger.Info("Dialing peer", "address", addr) - // XXX(xla): Remove the leakage of test concerns in implementation. - if cfg.TestDialFail { - go sw.reconnectToPeer(addr) - return fmt.Errorf("dial err (peerConfig.DialFail == true)") - } - p, err := sw.transport.Dial(*addr, peerConfig{ chDescs: sw.chDescs, onPeerError: sw.StopPeerForError, @@ -705,10 +699,7 @@ func (sw *Switch) addPeer(p Peer) error { // Add the peer to PeerSet. Do this before starting the reactors // so that if Receive errors, we will find the peer and remove it. - // Add should not err since we already checked peers.Has(). - if err := sw.peers.Add(p); err != nil { - return err - } + sw.peers.Add(p) // Start all the reactor protocols on the peer. for _, reactor := range sw.reactors { diff --git a/tm2/pkg/p2p/switch_test.go b/tm2/pkg/p2p/switch_test.go index a7033b466fe..9931622bcee 100644 --- a/tm2/pkg/p2p/switch_test.go +++ b/tm2/pkg/p2p/switch_test.go @@ -1,704 +1,683 @@ package p2p -import ( - "bytes" - "errors" - "fmt" - "io" - "net" - "sync" - "sync/atomic" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" - "github.com/gnolang/gno/tm2/pkg/log" - "github.com/gnolang/gno/tm2/pkg/p2p/config" - "github.com/gnolang/gno/tm2/pkg/p2p/conn" - "github.com/gnolang/gno/tm2/pkg/testutils" -) - -var cfg *config.P2PConfig - -func init() { - cfg = config.DefaultP2PConfig() - cfg.PexReactor = true - cfg.AllowDuplicateIP = true -} - -type PeerMessage struct { - PeerID ID - Bytes []byte - Counter int -} - -type TestReactor struct { - BaseReactor - - mtx sync.Mutex - channels []*conn.ChannelDescriptor - logMessages bool - msgsCounter int - msgsReceived map[byte][]PeerMessage -} - -func NewTestReactor(channels []*conn.ChannelDescriptor, logMessages bool) *TestReactor { - tr := &TestReactor{ - channels: channels, - logMessages: logMessages, - msgsReceived: make(map[byte][]PeerMessage), - } - tr.BaseReactor = *NewBaseReactor("TestReactor", tr) - tr.SetLogger(log.NewNoopLogger()) - return tr -} - -func (tr *TestReactor) GetChannels() []*conn.ChannelDescriptor { - return tr.channels -} - -func (tr *TestReactor) AddPeer(peer Peer) {} - -func (tr *TestReactor) RemovePeer(peer Peer, reason interface{}) {} - -func (tr *TestReactor) Receive(chID byte, peer Peer, msgBytes []byte) { - if tr.logMessages { - tr.mtx.Lock() - defer tr.mtx.Unlock() - // fmt.Printf("Received: %X, %X\n", chID, msgBytes) - tr.msgsReceived[chID] = append(tr.msgsReceived[chID], PeerMessage{peer.ID(), msgBytes, tr.msgsCounter}) - tr.msgsCounter++ - } -} - -func (tr *TestReactor) getMsgs(chID byte) []PeerMessage { - tr.mtx.Lock() - defer tr.mtx.Unlock() - return tr.msgsReceived[chID] -} - -// ----------------------------------------------------------------------------- - -// convenience method for creating two switches connected to each other. -// XXX: note this uses net.Pipe and not a proper TCP conn -func MakeSwitchPair(_ testing.TB, initSwitch func(int, *Switch) *Switch) (*Switch, *Switch) { - // Create two switches that will be interconnected. - switches := MakeConnectedSwitches(cfg, 2, initSwitch, Connect2Switches) - return switches[0], switches[1] -} - -func initSwitchFunc(i int, sw *Switch) *Switch { - // Make two reactors of two channels each - sw.AddReactor("foo", NewTestReactor([]*conn.ChannelDescriptor{ - {ID: byte(0x00), Priority: 10}, - {ID: byte(0x01), Priority: 10}, - }, true)) - sw.AddReactor("bar", NewTestReactor([]*conn.ChannelDescriptor{ - {ID: byte(0x02), Priority: 10}, - {ID: byte(0x03), Priority: 10}, - }, true)) - - return sw -} - -func TestSwitches(t *testing.T) { - t.Parallel() - - s1, s2 := MakeSwitchPair(t, initSwitchFunc) - defer s1.Stop() - defer s2.Stop() - - if s1.Peers().Size() != 1 { - t.Errorf("Expected exactly 1 peer in s1, got %v", s1.Peers().Size()) - } - if s2.Peers().Size() != 1 { - t.Errorf("Expected exactly 1 peer in s2, got %v", s2.Peers().Size()) - } - - // Lets send some messages - ch0Msg := []byte("channel zero") - ch1Msg := []byte("channel foo") - ch2Msg := []byte("channel bar") - - s1.Broadcast(byte(0x00), ch0Msg) - s1.Broadcast(byte(0x01), ch1Msg) - s1.Broadcast(byte(0x02), ch2Msg) - - assertMsgReceivedWithTimeout(t, ch0Msg, byte(0x00), s2.Reactor("foo").(*TestReactor), 10*time.Millisecond, 5*time.Second) - assertMsgReceivedWithTimeout(t, ch1Msg, byte(0x01), s2.Reactor("foo").(*TestReactor), 10*time.Millisecond, 5*time.Second) - assertMsgReceivedWithTimeout(t, ch2Msg, byte(0x02), s2.Reactor("bar").(*TestReactor), 10*time.Millisecond, 5*time.Second) -} - -func assertMsgReceivedWithTimeout(t *testing.T, msgBytes []byte, channel byte, reactor *TestReactor, checkPeriod, timeout time.Duration) { - t.Helper() - - ticker := time.NewTicker(checkPeriod) - for { - select { - case <-ticker.C: - msgs := reactor.getMsgs(channel) - if len(msgs) > 0 { - if !bytes.Equal(msgs[0].Bytes, msgBytes) { - t.Fatalf("Unexpected message bytes. Wanted: %X, Got: %X", msgBytes, msgs[0].Bytes) - } - return - } - - case <-time.After(timeout): - t.Fatalf("Expected to have received 1 message in channel #%v, got zero", channel) - } - } -} - -func TestSwitchFiltersOutItself(t *testing.T) { - t.Parallel() - - s1 := MakeSwitch(cfg, 1, "127.0.0.1", "123.123.123", initSwitchFunc) - - // simulate s1 having a public IP by creating a remote peer with the same ID - rp := &remotePeer{PrivKey: s1.nodeKey.PrivKey, Config: cfg} - rp.Start() - - // addr should be rejected in addPeer based on the same ID - err := s1.DialPeerWithAddress(rp.Addr()) - if assert.Error(t, err) { - if err, ok := err.(RejectedError); ok { - if !err.IsSelf() { - t.Errorf("expected self to be rejected") - } - } else { - t.Errorf("expected RejectedError") - } - } - - rp.Stop() - - assertNoPeersAfterTimeout(t, s1, 100*time.Millisecond) -} - -func TestSwitchPeerFilter(t *testing.T) { - t.Parallel() - - var ( - filters = []PeerFilterFunc{ - func(_ IPeerSet, _ Peer) error { return nil }, - func(_ IPeerSet, _ Peer) error { return fmt.Errorf("denied!") }, - func(_ IPeerSet, _ Peer) error { return nil }, - } - sw = MakeSwitch( - cfg, - 1, - "testing", - "123.123.123", - initSwitchFunc, - SwitchPeerFilters(filters...), - ) - ) - defer sw.Stop() - - // simulate remote peer - rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} - rp.Start() - defer rp.Stop() - - p, err := sw.transport.Dial(*rp.Addr(), peerConfig{ - chDescs: sw.chDescs, - onPeerError: sw.StopPeerForError, - isPersistent: sw.isPeerPersistentFn(), - reactorsByCh: sw.reactorsByCh, - }) - if err != nil { - t.Fatal(err) - } - - err = sw.addPeer(p) - if err, ok := err.(RejectedError); ok { - if !err.IsFiltered() { - t.Errorf("expected peer to be filtered") - } - } else { - t.Errorf("expected RejectedError") - } -} - -func TestSwitchPeerFilterTimeout(t *testing.T) { - t.Parallel() - - var ( - filters = []PeerFilterFunc{ - func(_ IPeerSet, _ Peer) error { - time.Sleep(10 * time.Millisecond) - return nil - }, - } - sw = MakeSwitch( - cfg, - 1, - "testing", - "123.123.123", - initSwitchFunc, - SwitchFilterTimeout(5*time.Millisecond), - SwitchPeerFilters(filters...), - ) - ) - defer sw.Stop() - - // simulate remote peer - rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} - rp.Start() - defer rp.Stop() - - p, err := sw.transport.Dial(*rp.Addr(), peerConfig{ - chDescs: sw.chDescs, - onPeerError: sw.StopPeerForError, - isPersistent: sw.isPeerPersistentFn(), - reactorsByCh: sw.reactorsByCh, - }) - if err != nil { - t.Fatal(err) - } - - err = sw.addPeer(p) - if _, ok := err.(FilterTimeoutError); !ok { - t.Errorf("expected FilterTimeoutError") - } -} - -func TestSwitchPeerFilterDuplicate(t *testing.T) { - t.Parallel() - - sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) - sw.Start() - defer sw.Stop() - - // simulate remote peer - rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} - rp.Start() - defer rp.Stop() - - p, err := sw.transport.Dial(*rp.Addr(), peerConfig{ - chDescs: sw.chDescs, - onPeerError: sw.StopPeerForError, - isPersistent: sw.isPeerPersistentFn(), - reactorsByCh: sw.reactorsByCh, - }) - if err != nil { - t.Fatal(err) - } - - if err := sw.addPeer(p); err != nil { - t.Fatal(err) - } - - err = sw.addPeer(p) - if errRej, ok := err.(RejectedError); ok { - if !errRej.IsDuplicate() { - t.Errorf("expected peer to be duplicate. got %v", errRej) - } - } else { - t.Errorf("expected RejectedError, got %v", err) - } -} - -func assertNoPeersAfterTimeout(t *testing.T, sw *Switch, timeout time.Duration) { - t.Helper() - - time.Sleep(timeout) - if sw.Peers().Size() != 0 { - t.Fatalf("Expected %v to not connect to some peers, got %d", sw, sw.Peers().Size()) - } -} - -func TestSwitchStopsNonPersistentPeerOnError(t *testing.T) { - t.Parallel() - - assert, require := assert.New(t), require.New(t) - - sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) - err := sw.Start() - if err != nil { - t.Error(err) - } - defer sw.Stop() - - // simulate remote peer - rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} - rp.Start() - defer rp.Stop() - - p, err := sw.transport.Dial(*rp.Addr(), peerConfig{ - chDescs: sw.chDescs, - onPeerError: sw.StopPeerForError, - isPersistent: sw.isPeerPersistentFn(), - reactorsByCh: sw.reactorsByCh, - }) - require.Nil(err) - - err = sw.addPeer(p) - require.Nil(err) - - require.NotNil(sw.Peers().Get(rp.ID())) - - // simulate failure by closing connection - p.(*peer).CloseConn() - - assertNoPeersAfterTimeout(t, sw, 100*time.Millisecond) - assert.False(p.IsRunning()) -} - -func TestSwitchStopPeerForError(t *testing.T) { - t.Parallel() - - // make two connected switches - sw1, sw2 := MakeSwitchPair(t, func(i int, sw *Switch) *Switch { - return initSwitchFunc(i, sw) - }) - - assert.Equal(t, len(sw1.Peers().List()), 1) - - // send messages to the peer from sw1 - p := sw1.Peers().List()[0] - p.Send(0x1, []byte("here's a message to send")) - - // stop sw2. this should cause the p to fail, - // which results in calling StopPeerForError internally - sw2.Stop() - - // now call StopPeerForError explicitly, eg. from a reactor - sw1.StopPeerForError(p, fmt.Errorf("some err")) - - assert.Equal(t, len(sw1.Peers().List()), 0) -} - -func TestSwitchReconnectsToOutboundPersistentPeer(t *testing.T) { - t.Parallel() - - sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) - err := sw.Start() - require.NoError(t, err) - defer sw.Stop() - - // 1. simulate failure by closing connection - rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} - rp.Start() - defer rp.Stop() - - err = sw.AddPersistentPeers([]string{rp.Addr().String()}) - require.NoError(t, err) - - err = sw.DialPeerWithAddress(rp.Addr()) - require.Nil(t, err) - require.NotNil(t, sw.Peers().Get(rp.ID())) - - p := sw.Peers().List()[0] - p.(*peer).CloseConn() - - waitUntilSwitchHasAtLeastNPeers(sw, 1) - assert.False(t, p.IsRunning()) // old peer instance - assert.Equal(t, 1, sw.Peers().Size()) // new peer instance - - // 2. simulate first time dial failure - rp = &remotePeer{ - PrivKey: ed25519.GenPrivKey(), - Config: cfg, - // Use different interface to prevent duplicate IP filter, this will break - // beyond two peers. - listenAddr: "127.0.0.1:0", - } - rp.Start() - defer rp.Stop() - - conf := config.DefaultP2PConfig() - conf.TestDialFail = true // will trigger a reconnect - err = sw.addOutboundPeerWithConfig(rp.Addr(), conf) - require.NotNil(t, err) - // DialPeerWithAddres - sw.peerConfig resets the dialer - waitUntilSwitchHasAtLeastNPeers(sw, 2) - assert.Equal(t, 2, sw.Peers().Size()) -} - -func TestSwitchReconnectsToInboundPersistentPeer(t *testing.T) { - t.Parallel() - - sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) - err := sw.Start() - require.NoError(t, err) - defer sw.Stop() - - // 1. simulate failure by closing the connection - rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} - rp.Start() - defer rp.Stop() - - err = sw.AddPersistentPeers([]string{rp.Addr().String()}) - require.NoError(t, err) - - conn, err := rp.Dial(sw.NetAddress()) - require.NoError(t, err) - time.Sleep(100 * time.Millisecond) - require.NotNil(t, sw.Peers().Get(rp.ID())) - - conn.Close() - - waitUntilSwitchHasAtLeastNPeers(sw, 1) - assert.Equal(t, 1, sw.Peers().Size()) -} - -func TestSwitchDialPeersAsync(t *testing.T) { - t.Parallel() - - if testing.Short() { - return - } - - sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) - err := sw.Start() - require.NoError(t, err) - defer sw.Stop() - - rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} - rp.Start() - defer rp.Stop() - - err = sw.DialPeersAsync([]string{rp.Addr().String()}) - require.NoError(t, err) - time.Sleep(dialRandomizerIntervalMilliseconds * time.Millisecond) - require.NotNil(t, sw.Peers().Get(rp.ID())) -} - -func waitUntilSwitchHasAtLeastNPeers(sw *Switch, n int) { - for i := 0; i < 20; i++ { - time.Sleep(250 * time.Millisecond) - has := sw.Peers().Size() - if has >= n { - break - } - } -} - -func TestSwitchFullConnectivity(t *testing.T) { - t.Parallel() - - switches := MakeConnectedSwitches(cfg, 3, initSwitchFunc, Connect2Switches) - defer func() { - for _, sw := range switches { - sw.Stop() - } - }() - - for i, sw := range switches { - if sw.Peers().Size() != 2 { - t.Fatalf("Expected each switch to be connected to 2 other, but %d switch only connected to %d", sw.Peers().Size(), i) - } - } -} - -func TestSwitchAcceptRoutine(t *testing.T) { - t.Parallel() - - cfg.MaxNumInboundPeers = 5 - - // make switch - sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) - err := sw.Start() - require.NoError(t, err) - defer sw.Stop() - - remotePeers := make([]*remotePeer, 0) - assert.Equal(t, 0, sw.Peers().Size()) - - // 1. check we connect up to MaxNumInboundPeers - for i := 0; i < cfg.MaxNumInboundPeers; i++ { - rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} - remotePeers = append(remotePeers, rp) - rp.Start() - c, err := rp.Dial(sw.NetAddress()) - require.NoError(t, err) - // spawn a reading routine to prevent connection from closing - go func(c net.Conn) { - for { - one := make([]byte, 1) - _, err := c.Read(one) - if err != nil { - return - } - } - }(c) - } - time.Sleep(100 * time.Millisecond) - assert.Equal(t, cfg.MaxNumInboundPeers, sw.Peers().Size()) - - // 2. check we close new connections if we already have MaxNumInboundPeers peers - rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} - rp.Start() - conn, err := rp.Dial(sw.NetAddress()) - require.NoError(t, err) - // check conn is closed - one := make([]byte, 1) - conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) - _, err = conn.Read(one) - assert.Equal(t, io.EOF, err) - assert.Equal(t, cfg.MaxNumInboundPeers, sw.Peers().Size()) - rp.Stop() - - // stop remote peers - for _, rp := range remotePeers { - rp.Stop() - } -} - -type errorTransport struct { - acceptErr error -} - -func (et errorTransport) NetAddress() NetAddress { - panic("not implemented") -} - -func (et errorTransport) Accept(c peerConfig) (Peer, error) { - return nil, et.acceptErr -} - -func (errorTransport) Dial(NetAddress, peerConfig) (Peer, error) { - panic("not implemented") -} - -func (errorTransport) Cleanup(Peer) { - panic("not implemented") -} - -func TestSwitchAcceptRoutineErrorCases(t *testing.T) { - t.Parallel() - - sw := NewSwitch(cfg, errorTransport{FilterTimeoutError{}}) - assert.NotPanics(t, func() { - err := sw.Start() - assert.NoError(t, err) - sw.Stop() - }) - - sw = NewSwitch(cfg, errorTransport{RejectedError{conn: nil, err: errors.New("filtered"), isFiltered: true}}) - assert.NotPanics(t, func() { - err := sw.Start() - assert.NoError(t, err) - sw.Stop() - }) - - sw = NewSwitch(cfg, errorTransport{TransportClosedError{}}) - assert.NotPanics(t, func() { - err := sw.Start() - assert.NoError(t, err) - sw.Stop() - }) -} - -// mockReactor checks that InitPeer never called before RemovePeer. If that's -// not true, InitCalledBeforeRemoveFinished will return true. -type mockReactor struct { - *BaseReactor - - // atomic - removePeerInProgress uint32 - initCalledBeforeRemoveFinished uint32 -} - -func (r *mockReactor) RemovePeer(peer Peer, reason interface{}) { - atomic.StoreUint32(&r.removePeerInProgress, 1) - defer atomic.StoreUint32(&r.removePeerInProgress, 0) - time.Sleep(100 * time.Millisecond) -} - -func (r *mockReactor) InitPeer(peer Peer) Peer { - if atomic.LoadUint32(&r.removePeerInProgress) == 1 { - atomic.StoreUint32(&r.initCalledBeforeRemoveFinished, 1) - } - - return peer -} - -func (r *mockReactor) InitCalledBeforeRemoveFinished() bool { - return atomic.LoadUint32(&r.initCalledBeforeRemoveFinished) == 1 -} - -// see stopAndRemovePeer -func TestFlappySwitchInitPeerIsNotCalledBeforeRemovePeer(t *testing.T) { - t.Parallel() - - testutils.FilterStability(t, testutils.Flappy) - - // make reactor - reactor := &mockReactor{} - reactor.BaseReactor = NewBaseReactor("mockReactor", reactor) - - // make switch - sw := MakeSwitch(cfg, 1, "testing", "123.123.123", func(i int, sw *Switch) *Switch { - sw.AddReactor("mock", reactor) - return sw - }) - err := sw.Start() - require.NoError(t, err) - defer sw.Stop() - - // add peer - rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} - rp.Start() - defer rp.Stop() - _, err = rp.Dial(sw.NetAddress()) - require.NoError(t, err) - // wait till the switch adds rp to the peer set - time.Sleep(100 * time.Millisecond) - - // stop peer asynchronously - go sw.StopPeerForError(sw.Peers().Get(rp.ID()), "test") - - // simulate peer reconnecting to us - _, err = rp.Dial(sw.NetAddress()) - require.NoError(t, err) - // wait till the switch adds rp to the peer set - time.Sleep(100 * time.Millisecond) - - // make sure reactor.RemovePeer is finished before InitPeer is called - assert.False(t, reactor.InitCalledBeforeRemoveFinished()) -} - -func BenchmarkSwitchBroadcast(b *testing.B) { - s1, s2 := MakeSwitchPair(b, func(i int, sw *Switch) *Switch { - // Make bar reactors of bar channels each - sw.AddReactor("foo", NewTestReactor([]*conn.ChannelDescriptor{ - {ID: byte(0x00), Priority: 10}, - {ID: byte(0x01), Priority: 10}, - }, false)) - sw.AddReactor("bar", NewTestReactor([]*conn.ChannelDescriptor{ - {ID: byte(0x02), Priority: 10}, - {ID: byte(0x03), Priority: 10}, - }, false)) - return sw - }) - defer s1.Stop() - defer s2.Stop() - - // Allow time for goroutines to boot up - time.Sleep(1 * time.Second) - - b.ResetTimer() - - numSuccess, numFailure := 0, 0 - - // Send random message from foo channel to another - for i := 0; i < b.N; i++ { - chID := byte(i % 4) - successChan := s1.Broadcast(chID, []byte("test data")) - for s := range successChan { - if s { - numSuccess++ - } else { - numFailure++ - } - } - } - - b.Logf("success: %v, failure: %v", numSuccess, numFailure) -} +// var cfg *config.P2PConfig +// +// func init() { +// cfg = config.DefaultP2PConfig() +// cfg.PexReactor = true +// cfg.AllowDuplicateIP = true +// } +// +// type PeerMessage struct { +// PeerID ID +// Bytes []byte +// Counter int +// } +// +// type TestReactor struct { +// BaseReactor +// +// mtx sync.Mutex +// channels []*conn.ChannelDescriptor +// logMessages bool +// msgsCounter int +// msgsReceived map[byte][]PeerMessage +// } +// +// func NewTestReactor(channels []*conn.ChannelDescriptor, logMessages bool) *TestReactor { +// tr := &TestReactor{ +// channels: channels, +// logMessages: logMessages, +// msgsReceived: make(map[byte][]PeerMessage), +// } +// tr.BaseReactor = *NewBaseReactor("TestReactor", tr) +// tr.SetLogger(log.NewNoopLogger()) +// return tr +// } +// +// func (tr *TestReactor) GetChannels() []*conn.ChannelDescriptor { +// return tr.channels +// } +// +// func (tr *TestReactor) AddPeer(peer Peer) {} +// +// func (tr *TestReactor) RemovePeer(peer Peer, reason interface{}) {} +// +// func (tr *TestReactor) Receive(chID byte, peer Peer, msgBytes []byte) { +// if tr.logMessages { +// tr.mtx.Lock() +// defer tr.mtx.Unlock() +// // fmt.Printf("Received: %X, %X\n", chID, msgBytes) +// tr.msgsReceived[chID] = append(tr.msgsReceived[chID], PeerMessage{peer.ID(), msgBytes, tr.msgsCounter}) +// tr.msgsCounter++ +// } +// } +// +// func (tr *TestReactor) getMsgs(chID byte) []PeerMessage { +// tr.mtx.Lock() +// defer tr.mtx.Unlock() +// return tr.msgsReceived[chID] +// } +// +// // ----------------------------------------------------------------------------- +// +// // convenience method for creating two switches connected to each other. +// // XXX: note this uses net.Pipe and not a proper TCP conn +// func MakeSwitchPair(_ testing.TB, initSwitch func(int, *Switch) *Switch) (*Switch, *Switch) { +// // Create two switches that will be interconnected. +// switches := MakeConnectedSwitches(cfg, 2, initSwitch, Connect2Switches) +// return switches[0], switches[1] +// } +// +// func initSwitchFunc(i int, sw *Switch) *Switch { +// // Make two reactors of two channels each +// sw.AddReactor("foo", NewTestReactor([]*conn.ChannelDescriptor{ +// {ID: byte(0x00), Priority: 10}, +// {ID: byte(0x01), Priority: 10}, +// }, true)) +// sw.AddReactor("bar", NewTestReactor([]*conn.ChannelDescriptor{ +// {ID: byte(0x02), Priority: 10}, +// {ID: byte(0x03), Priority: 10}, +// }, true)) +// +// return sw +// } +// +// func TestSwitches(t *testing.T) { +// t.Parallel() +// +// s1, s2 := MakeSwitchPair(t, initSwitchFunc) +// defer s1.Stop() +// defer s2.Stop() +// +// if s1.Peers().Size() != 1 { +// t.Errorf("Expected exactly 1 peer in s1, got %v", s1.Peers().Size()) +// } +// if s2.Peers().Size() != 1 { +// t.Errorf("Expected exactly 1 peer in s2, got %v", s2.Peers().Size()) +// } +// +// // Lets send some messages +// ch0Msg := []byte("channel zero") +// ch1Msg := []byte("channel foo") +// ch2Msg := []byte("channel bar") +// +// s1.Broadcast(byte(0x00), ch0Msg) +// s1.Broadcast(byte(0x01), ch1Msg) +// s1.Broadcast(byte(0x02), ch2Msg) +// +// assertMsgReceivedWithTimeout(t, ch0Msg, byte(0x00), s2.Reactor("foo").(*TestReactor), 10*time.Millisecond, 5*time.Second) +// assertMsgReceivedWithTimeout(t, ch1Msg, byte(0x01), s2.Reactor("foo").(*TestReactor), 10*time.Millisecond, 5*time.Second) +// assertMsgReceivedWithTimeout(t, ch2Msg, byte(0x02), s2.Reactor("bar").(*TestReactor), 10*time.Millisecond, 5*time.Second) +// } +// +// func assertMsgReceivedWithTimeout(t *testing.T, msgBytes []byte, channel byte, reactor *TestReactor, checkPeriod, timeout time.Duration) { +// t.Helper() +// +// ticker := time.NewTicker(checkPeriod) +// for { +// select { +// case <-ticker.C: +// msgs := reactor.getMsgs(channel) +// if len(msgs) > 0 { +// if !bytes.Equal(msgs[0].Bytes, msgBytes) { +// t.Fatalf("Unexpected message bytes. Wanted: %X, Got: %X", msgBytes, msgs[0].Bytes) +// } +// return +// } +// +// case <-time.After(timeout): +// t.Fatalf("Expected to have received 1 message in channel #%v, got zero", channel) +// } +// } +// } +// +// func TestSwitchFiltersOutItself(t *testing.T) { +// t.Parallel() +// +// s1 := MakeSwitch(cfg, 1, "127.0.0.1", "123.123.123", initSwitchFunc) +// +// // simulate s1 having a public IP by creating a remote peer with the same ID +// rp := &peer.remotePeer{PrivKey: s1.nodeKey.PrivKey, Config: cfg} +// rp.Start() +// +// // addr should be rejected in addPeer based on the same ID +// err := s1.DialPeerWithAddress(rp.Addr()) +// if assert.Error(t, err) { +// if err, ok := err.(RejectedError); ok { +// if !err.IsSelf() { +// t.Errorf("expected self to be rejected") +// } +// } else { +// t.Errorf("expected RejectedError") +// } +// } +// +// rp.Stop() +// +// assertNoPeersAfterTimeout(t, s1, 100*time.Millisecond) +// } +// +// func TestSwitchPeerFilter(t *testing.T) { +// t.Parallel() +// +// var ( +// filters = []PeerFilterFunc{ +// func(_ PeerSet, _ Peer) error { return nil }, +// func(_ PeerSet, _ Peer) error { return fmt.Errorf("denied!") }, +// func(_ PeerSet, _ Peer) error { return nil }, +// } +// sw = MakeSwitch( +// cfg, +// 1, +// "testing", +// "123.123.123", +// initSwitchFunc, +// SwitchPeerFilters(filters...), +// ) +// ) +// defer sw.Stop() +// +// // simulate remote peer +// rp := &peer.remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} +// rp.Start() +// defer rp.Stop() +// +// p, err := sw.transport.Dial(*rp.Addr(), peerConfig{ +// chDescs: sw.chDescs, +// onPeerError: sw.StopPeerForError, +// isPersistent: sw.isPeerPersistentFn(), +// reactorsByCh: sw.reactorsByCh, +// }) +// if err != nil { +// t.Fatal(err) +// } +// +// err = sw.addPeer(p) +// if err, ok := err.(RejectedError); ok { +// if !err.IsFiltered() { +// t.Errorf("expected peer to be filtered") +// } +// } else { +// t.Errorf("expected RejectedError") +// } +// } +// +// func TestSwitchPeerFilterTimeout(t *testing.T) { +// t.Parallel() +// +// var ( +// filters = []PeerFilterFunc{ +// func(_ PeerSet, _ Peer) error { +// time.Sleep(10 * time.Millisecond) +// return nil +// }, +// } +// sw = MakeSwitch( +// cfg, +// 1, +// "testing", +// "123.123.123", +// initSwitchFunc, +// SwitchFilterTimeout(5*time.Millisecond), +// SwitchPeerFilters(filters...), +// ) +// ) +// defer sw.Stop() +// +// // simulate remote peer +// rp := &peer.remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} +// rp.Start() +// defer rp.Stop() +// +// p, err := sw.transport.Dial(*rp.Addr(), peerConfig{ +// chDescs: sw.chDescs, +// onPeerError: sw.StopPeerForError, +// isPersistent: sw.isPeerPersistentFn(), +// reactorsByCh: sw.reactorsByCh, +// }) +// if err != nil { +// t.Fatal(err) +// } +// +// err = sw.addPeer(p) +// if _, ok := err.(FilterTimeoutError); !ok { +// t.Errorf("expected FilterTimeoutError") +// } +// } +// +// func TestSwitchPeerFilterDuplicate(t *testing.T) { +// t.Parallel() +// +// sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) +// sw.Start() +// defer sw.Stop() +// +// // simulate remote peer +// rp := &peer.remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} +// rp.Start() +// defer rp.Stop() +// +// p, err := sw.transport.Dial(*rp.Addr(), peerConfig{ +// chDescs: sw.chDescs, +// onPeerError: sw.StopPeerForError, +// isPersistent: sw.isPeerPersistentFn(), +// reactorsByCh: sw.reactorsByCh, +// }) +// if err != nil { +// t.Fatal(err) +// } +// +// if err := sw.addPeer(p); err != nil { +// t.Fatal(err) +// } +// +// err = sw.addPeer(p) +// if errRej, ok := err.(RejectedError); ok { +// if !errRej.IsDuplicate() { +// t.Errorf("expected peer to be duplicate. got %v", errRej) +// } +// } else { +// t.Errorf("expected RejectedError, got %v", err) +// } +// } +// +// func assertNoPeersAfterTimeout(t *testing.T, sw *Switch, timeout time.Duration) { +// t.Helper() +// +// time.Sleep(timeout) +// if sw.Peers().Size() != 0 { +// t.Fatalf("Expected %v to not connect to some peers, got %d", sw, sw.Peers().Size()) +// } +// } +// +// func TestSwitchStopsNonPersistentPeerOnError(t *testing.T) { +// t.Parallel() +// +// assert, require := assert.New(t), require.New(t) +// +// sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) +// err := sw.Start() +// if err != nil { +// t.Error(err) +// } +// defer sw.Stop() +// +// // simulate remote peer +// rp := &peer.remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} +// rp.Start() +// defer rp.Stop() +// +// p, err := sw.transport.Dial(*rp.Addr(), peerConfig{ +// chDescs: sw.chDescs, +// onPeerError: sw.StopPeerForError, +// isPersistent: sw.isPeerPersistentFn(), +// reactorsByCh: sw.reactorsByCh, +// }) +// require.Nil(err) +// +// err = sw.addPeer(p) +// require.Nil(err) +// +// require.NotNil(sw.Peers().Get(rp.ID())) +// +// // simulate failure by closing connection +// p.(*peer.peer).CloseConn() +// +// assertNoPeersAfterTimeout(t, sw, 100*time.Millisecond) +// assert.False(p.IsRunning()) +// } +// +// func TestSwitchStopPeerForError(t *testing.T) { +// t.Parallel() +// +// // make two connected switches +// sw1, sw2 := MakeSwitchPair(t, func(i int, sw *Switch) *Switch { +// return initSwitchFunc(i, sw) +// }) +// +// assert.Equal(t, len(sw1.Peers().List()), 1) +// +// // send messages to the peer from sw1 +// p := sw1.Peers().List()[0] +// p.Send(0x1, []byte("here's a message to send")) +// +// // stop sw2. this should cause the p to fail, +// // which results in calling StopPeerForError internally +// sw2.Stop() +// +// // now call StopPeerForError explicitly, eg. from a reactor +// sw1.StopPeerForError(p, fmt.Errorf("some err")) +// +// assert.Equal(t, len(sw1.Peers().List()), 0) +// } +// +// func TestSwitchReconnectsToOutboundPersistentPeer(t *testing.T) { +// t.Parallel() +// +// sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) +// err := sw.Start() +// require.NoError(t, err) +// defer sw.Stop() +// +// // 1. simulate failure by closing connection +// rp := &peer.remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} +// rp.Start() +// defer rp.Stop() +// +// err = sw.AddPersistentPeers([]string{rp.Addr().String()}) +// require.NoError(t, err) +// +// err = sw.DialPeerWithAddress(rp.Addr()) +// require.Nil(t, err) +// require.NotNil(t, sw.Peers().Get(rp.ID())) +// +// p := sw.Peers().List()[0] +// p.(*peer.peer).CloseConn() +// +// waitUntilSwitchHasAtLeastNPeers(sw, 1) +// assert.False(t, p.IsRunning()) // old peer instance +// assert.Equal(t, 1, sw.Peers().Size()) // new peer instance +// +// // 2. simulate first time dial failure +// rp = &peer.remotePeer{ +// PrivKey: ed25519.GenPrivKey(), +// Config: cfg, +// // Use different interface to prevent duplicate IP filter, this will break +// // beyond two peers. +// listenAddr: "127.0.0.1:0", +// } +// rp.Start() +// defer rp.Stop() +// +// conf := config.DefaultP2PConfig() +// conf.TestDialFail = true // will trigger a reconnect +// err = sw.addOutboundPeerWithConfig(rp.Addr(), conf) +// require.NotNil(t, err) +// // DialPeerWithAddres - sw.peerConfig resets the dialer +// waitUntilSwitchHasAtLeastNPeers(sw, 2) +// assert.Equal(t, 2, sw.Peers().Size()) +// } +// +// func TestSwitchReconnectsToInboundPersistentPeer(t *testing.T) { +// t.Parallel() +// +// sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) +// err := sw.Start() +// require.NoError(t, err) +// defer sw.Stop() +// +// // 1. simulate failure by closing the connection +// rp := &peer.remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} +// rp.Start() +// defer rp.Stop() +// +// err = sw.AddPersistentPeers([]string{rp.Addr().String()}) +// require.NoError(t, err) +// +// conn, err := rp.Dial(sw.NetAddress()) +// require.NoError(t, err) +// time.Sleep(100 * time.Millisecond) +// require.NotNil(t, sw.Peers().Get(rp.ID())) +// +// conn.Close() +// +// waitUntilSwitchHasAtLeastNPeers(sw, 1) +// assert.Equal(t, 1, sw.Peers().Size()) +// } +// +// func TestSwitchDialPeersAsync(t *testing.T) { +// t.Parallel() +// +// if testing.Short() { +// return +// } +// +// sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) +// err := sw.Start() +// require.NoError(t, err) +// defer sw.Stop() +// +// rp := &peer.remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} +// rp.Start() +// defer rp.Stop() +// +// err = sw.DialPeersAsync([]string{rp.Addr().String()}) +// require.NoError(t, err) +// time.Sleep(dialRandomizerIntervalMilliseconds * time.Millisecond) +// require.NotNil(t, sw.Peers().Get(rp.ID())) +// } +// +// func waitUntilSwitchHasAtLeastNPeers(sw *Switch, n int) { +// for i := 0; i < 20; i++ { +// time.Sleep(250 * time.Millisecond) +// has := sw.Peers().Size() +// if has >= n { +// break +// } +// } +// } +// +// func TestSwitchFullConnectivity(t *testing.T) { +// t.Parallel() +// +// switches := MakeConnectedSwitches(cfg, 3, initSwitchFunc, Connect2Switches) +// defer func() { +// for _, sw := range switches { +// sw.Stop() +// } +// }() +// +// for i, sw := range switches { +// if sw.Peers().Size() != 2 { +// t.Fatalf("Expected each switch to be connected to 2 other, but %d switch only connected to %d", sw.Peers().Size(), i) +// } +// } +// } +// +// func TestSwitchAcceptRoutine(t *testing.T) { +// t.Parallel() +// +// cfg.MaxNumInboundPeers = 5 +// +// // make switch +// sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) +// err := sw.Start() +// require.NoError(t, err) +// defer sw.Stop() +// +// remotePeers := make([]*peer.remotePeer, 0) +// assert.Equal(t, 0, sw.Peers().Size()) +// +// // 1. check we connect up to MaxNumInboundPeers +// for i := 0; i < cfg.MaxNumInboundPeers; i++ { +// rp := &peer.remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} +// remotePeers = append(remotePeers, rp) +// rp.Start() +// c, err := rp.Dial(sw.NetAddress()) +// require.NoError(t, err) +// // spawn a reading routine to prevent connection from closing +// go func(c net.Conn) { +// for { +// one := make([]byte, 1) +// _, err := c.Read(one) +// if err != nil { +// return +// } +// } +// }(c) +// } +// time.Sleep(100 * time.Millisecond) +// assert.Equal(t, cfg.MaxNumInboundPeers, sw.Peers().Size()) +// +// // 2. check we close new connections if we already have MaxNumInboundPeers peers +// rp := &peer.remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} +// rp.Start() +// conn, err := rp.Dial(sw.NetAddress()) +// require.NoError(t, err) +// // check conn is closed +// one := make([]byte, 1) +// conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) +// _, err = conn.Read(one) +// assert.Equal(t, io.EOF, err) +// assert.Equal(t, cfg.MaxNumInboundPeers, sw.Peers().Size()) +// rp.Stop() +// +// // stop remote peers +// for _, rp := range remotePeers { +// rp.Stop() +// } +// } +// +// type errorTransport struct { +// acceptErr error +// } +// +// func (et errorTransport) NetAddress() NetAddress { +// panic("not implemented") +// } +// +// func (et errorTransport) Accept(c peerConfig) (Peer, error) { +// return nil, et.acceptErr +// } +// +// func (errorTransport) Dial(NetAddress, peerConfig) (Peer, error) { +// panic("not implemented") +// } +// +// func (errorTransport) Cleanup(Peer) { +// panic("not implemented") +// } +// +// func TestSwitchAcceptRoutineErrorCases(t *testing.T) { +// t.Parallel() +// +// sw := NewSwitch(cfg, errorTransport{FilterTimeoutError{}}) +// assert.NotPanics(t, func() { +// err := sw.Start() +// assert.NoError(t, err) +// sw.Stop() +// }) +// +// sw = NewSwitch(cfg, errorTransport{RejectedError{conn: nil, err: errors.New("filtered"), isFiltered: true}}) +// assert.NotPanics(t, func() { +// err := sw.Start() +// assert.NoError(t, err) +// sw.Stop() +// }) +// +// sw = NewSwitch(cfg, errorTransport{TransportClosedError{}}) +// assert.NotPanics(t, func() { +// err := sw.Start() +// assert.NoError(t, err) +// sw.Stop() +// }) +// } +// +// // mockReactor checks that InitPeer never called before RemovePeer. If that's +// // not true, InitCalledBeforeRemoveFinished will return true. +// type mockReactor struct { +// *BaseReactor +// +// // atomic +// removePeerInProgress uint32 +// initCalledBeforeRemoveFinished uint32 +// } +// +// func (r *mockReactor) RemovePeer(peer Peer, reason interface{}) { +// atomic.StoreUint32(&r.removePeerInProgress, 1) +// defer atomic.StoreUint32(&r.removePeerInProgress, 0) +// time.Sleep(100 * time.Millisecond) +// } +// +// func (r *mockReactor) InitPeer(peer Peer) Peer { +// if atomic.LoadUint32(&r.removePeerInProgress) == 1 { +// atomic.StoreUint32(&r.initCalledBeforeRemoveFinished, 1) +// } +// +// return peer +// } +// +// func (r *mockReactor) InitCalledBeforeRemoveFinished() bool { +// return atomic.LoadUint32(&r.initCalledBeforeRemoveFinished) == 1 +// } +// +// // see stopAndRemovePeer +// func TestFlappySwitchInitPeerIsNotCalledBeforeRemovePeer(t *testing.T) { +// t.Parallel() +// +// testutils.FilterStability(t, testutils.Flappy) +// +// // make reactor +// reactor := &mockReactor{} +// reactor.BaseReactor = NewBaseReactor("mockReactor", reactor) +// +// // make switch +// sw := MakeSwitch(cfg, 1, "testing", "123.123.123", func(i int, sw *Switch) *Switch { +// sw.AddReactor("mock", reactor) +// return sw +// }) +// err := sw.Start() +// require.NoError(t, err) +// defer sw.Stop() +// +// // add peer +// rp := &peer.remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} +// rp.Start() +// defer rp.Stop() +// _, err = rp.Dial(sw.NetAddress()) +// require.NoError(t, err) +// // wait till the switch adds rp to the peer set +// time.Sleep(100 * time.Millisecond) +// +// // stop peer asynchronously +// go sw.StopPeerForError(sw.Peers().Get(rp.ID()), "test") +// +// // simulate peer reconnecting to us +// _, err = rp.Dial(sw.NetAddress()) +// require.NoError(t, err) +// // wait till the switch adds rp to the peer set +// time.Sleep(100 * time.Millisecond) +// +// // make sure reactor.RemovePeer is finished before InitPeer is called +// assert.False(t, reactor.InitCalledBeforeRemoveFinished()) +// } +// +// func BenchmarkSwitchBroadcast(b *testing.B) { +// s1, s2 := MakeSwitchPair(b, func(i int, sw *Switch) *Switch { +// // Make bar reactors of bar channels each +// sw.AddReactor("foo", NewTestReactor([]*conn.ChannelDescriptor{ +// {ID: byte(0x00), Priority: 10}, +// {ID: byte(0x01), Priority: 10}, +// }, false)) +// sw.AddReactor("bar", NewTestReactor([]*conn.ChannelDescriptor{ +// {ID: byte(0x02), Priority: 10}, +// {ID: byte(0x03), Priority: 10}, +// }, false)) +// return sw +// }) +// defer s1.Stop() +// defer s2.Stop() +// +// // Allow time for goroutines to boot up +// time.Sleep(1 * time.Second) +// +// b.ResetTimer() +// +// numSuccess, numFailure := 0, 0 +// +// // Send random message from foo channel to another +// for i := 0; i < b.N; i++ { +// chID := byte(i % 4) +// successChan := s1.Broadcast(chID, []byte("test data")) +// for s := range successChan { +// if s { +// numSuccess++ +// } else { +// numFailure++ +// } +// } +// } +// +// b.Logf("success: %v, failure: %v", numSuccess, numFailure) +// } diff --git a/tm2/pkg/p2p/test_util.go b/tm2/pkg/p2p/test_util.go index ac202e7ae97..a67b3731fe8 100644 --- a/tm2/pkg/p2p/test_util.go +++ b/tm2/pkg/p2p/test_util.go @@ -1,236 +1,221 @@ package p2p -import ( - "fmt" - "net" - "time" - - "github.com/gnolang/gno/tm2/pkg/crypto" - "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" - "github.com/gnolang/gno/tm2/pkg/errors" - "github.com/gnolang/gno/tm2/pkg/log" - "github.com/gnolang/gno/tm2/pkg/p2p/config" - "github.com/gnolang/gno/tm2/pkg/p2p/conn" - "github.com/gnolang/gno/tm2/pkg/random" - "github.com/gnolang/gno/tm2/pkg/versionset" -) - -const testCh = 0x01 - -// ------------------------------------------------ - -func CreateRoutableAddr() (addr string, netAddr *NetAddress) { - for { - id := ed25519.GenPrivKey().PubKey().Address().ID() - var err error - addr = fmt.Sprintf("%s@%v.%v.%v.%v:26656", id, random.RandInt()%256, random.RandInt()%256, random.RandInt()%256, random.RandInt()%256) - netAddr, err = NewNetAddressFromString(addr) - if err != nil { - panic(err) - } - if netAddr.Routable() { - break - } - } - return -} - -// ------------------------------------------------------------------ -// Connects switches via arbitrary net.Conn. Used for testing. - -const TEST_HOST = "localhost" - -// MakeConnectedSwitches returns n switches, connected according to the connect func. -// If connect==Connect2Switches, the switches will be fully connected. -// initSwitch defines how the i'th switch should be initialized (ie. with what reactors). -// NOTE: panics if any switch fails to start. -func MakeConnectedSwitches(cfg *config.P2PConfig, n int, initSwitch func(int, *Switch) *Switch, connect func([]*Switch, int, int)) []*Switch { - switches := make([]*Switch, n) - for i := 0; i < n; i++ { - switches[i] = MakeSwitch(cfg, i, TEST_HOST, "123.123.123", initSwitch) - } - - if err := StartSwitches(switches); err != nil { - panic(err) - } - - for i := 0; i < n; i++ { - for j := i + 1; j < n; j++ { - connect(switches, i, j) - } - } - - return switches -} - -// Connect2Switches will connect switches i and j via net.Pipe(). -// Blocks until a connection is established. -// NOTE: caller ensures i and j are within bounds. -func Connect2Switches(switches []*Switch, i, j int) { - switchI := switches[i] - switchJ := switches[j] - - c1, c2 := conn.NetPipe() - - doneCh := make(chan struct{}) - go func() { - err := switchI.addPeerWithConnection(c1) - if err != nil { - panic(err) - } - doneCh <- struct{}{} - }() - go func() { - err := switchJ.addPeerWithConnection(c2) - if err != nil { - panic(err) - } - doneCh <- struct{}{} - }() - <-doneCh - <-doneCh -} - -func (sw *Switch) addPeerWithConnection(conn net.Conn) error { - pc, err := testInboundPeerConn(conn, sw.config, sw.nodeKey.PrivKey) - if err != nil { - if err := conn.Close(); err != nil { - sw.Logger.Error("Error closing connection", "err", err) - } - return err - } - - ni, err := handshake(conn, time.Second, sw.nodeInfo) - if err != nil { - if err := conn.Close(); err != nil { - sw.Logger.Error("Error closing connection", "err", err) - } - return err - } - - p := newPeer( - pc, - MConnConfig(sw.config), - ni, - sw.reactorsByCh, - sw.chDescs, - sw.StopPeerForError, - ) - - if err = sw.addPeer(p); err != nil { - pc.CloseConn() - return err - } - - return nil -} - -// StartSwitches calls sw.Start() for each given switch. -// It returns the first encountered error. -func StartSwitches(switches []*Switch) error { - for _, s := range switches { - err := s.Start() // start switch and reactors - if err != nil { - return err - } - } - return nil -} - -func MakeSwitch( - cfg *config.P2PConfig, - i int, - network, version string, - initSwitch func(int, *Switch) *Switch, - opts ...SwitchOption, -) *Switch { - nodeKey := NodeKey{ - PrivKey: ed25519.GenPrivKey(), - } - nodeInfo := testNodeInfo(nodeKey.ID(), fmt.Sprintf("node%d", i)) - - t := NewMultiplexTransport(nodeInfo, nodeKey, MConnConfig(cfg)) - - if err := t.Listen(*nodeInfo.NetAddress); err != nil { - panic(err) - } - - // TODO: let the config be passed in? - sw := initSwitch(i, NewSwitch(cfg, t, opts...)) - sw.SetLogger(log.NewNoopLogger().With("switch", i)) - sw.SetNodeKey(&nodeKey) - - for ch := range sw.reactorsByCh { - nodeInfo.Channels = append(nodeInfo.Channels, ch) - } - - // TODO: We need to setup reactors ahead of time so the NodeInfo is properly - // populated and we don't have to do those awkward overrides and setters. - t.nodeInfo = nodeInfo - sw.SetNodeInfo(nodeInfo) - - return sw -} - -func testInboundPeerConn( - conn net.Conn, - config *config.P2PConfig, - ourNodePrivKey crypto.PrivKey, -) (peerConn, error) { - return testPeerConn(conn, config, false, false, ourNodePrivKey, nil) -} - -func testPeerConn( - rawConn net.Conn, - cfg *config.P2PConfig, - outbound, persistent bool, - ourNodePrivKey crypto.PrivKey, - socketAddr *NetAddress, -) (pc peerConn, err error) { - conn := rawConn - - // Encrypt connection - conn, err = upgradeSecretConn(conn, cfg.HandshakeTimeout, ourNodePrivKey) - if err != nil { - return pc, errors.Wrap(err, "Error creating peer") - } - - // Only the information we already have - return newPeerConn(outbound, persistent, conn, socketAddr), nil -} - -// ---------------------------------------------------------------- -// rand node info - -func testNodeInfo(id ID, name string) NodeInfo { - return testNodeInfoWithNetwork(id, name, "testing") -} - -func testVersionSet() versionset.VersionSet { - return versionset.VersionSet{ - versionset.VersionInfo{ - Name: "p2p", - Version: "v0.0.0", // dontcare - }, - } -} - -func testNodeInfoWithNetwork(id ID, name, network string) NodeInfo { - info := NodeInfo{ - VersionSet: testVersionSet(), - NetAddress: NewNetAddressFromIPPort(net.ParseIP("127.0.0.1"), 0), - Network: network, - Software: "p2ptest", - Version: "v1.2.3-rc.0-deadbeef", - Channels: []byte{testCh}, - Moniker: name, - Other: NodeInfoOther{ - TxIndex: "on", - RPCAddress: fmt.Sprintf("127.0.0.1:%d", 0), - }, - } - - info.NetAddress.ID = id - - return info -} +// const testCh = 0x01 +// +// // ------------------------------------------------ +// +// func CreateRoutableAddr() (addr string, netAddr *NetAddress) { +// for { +// id := ed25519.GenPrivKey().PubKey().Address().ID() +// var err error +// addr = fmt.Sprintf("%s@%v.%v.%v.%v:26656", id, random.RandInt()%256, random.RandInt()%256, random.RandInt()%256, random.RandInt()%256) +// netAddr, err = NewNetAddressFromString(addr) +// if err != nil { +// panic(err) +// } +// if netAddr.Routable() { +// break +// } +// } +// return +// } +// +// // ------------------------------------------------------------------ +// // Connects switches via arbitrary net.Conn. Used for testing. +// +// const TEST_HOST = "localhost" +// +// // MakeConnectedSwitches returns n switches, connected according to the connect func. +// // If connect==Connect2Switches, the switches will be fully connected. +// // initSwitch defines how the i'th switch should be initialized (ie. with what reactors). +// // NOTE: panics if any switch fails to start. +// func MakeConnectedSwitches(cfg *config.P2PConfig, n int, initSwitch func(int, *Switch) *Switch, connect func([]*Switch, int, int)) []*Switch { +// switches := make([]*Switch, n) +// for i := 0; i < n; i++ { +// switches[i] = MakeSwitch(cfg, i, TEST_HOST, "123.123.123", initSwitch) +// } +// +// if err := StartSwitches(switches); err != nil { +// panic(err) +// } +// +// for i := 0; i < n; i++ { +// for j := i + 1; j < n; j++ { +// connect(switches, i, j) +// } +// } +// +// return switches +// } +// +// // Connect2Switches will connect switches i and j via net.Pipe(). +// // Blocks until a connection is established. +// // NOTE: caller ensures i and j are within bounds. +// func Connect2Switches(switches []*Switch, i, j int) { +// switchI := switches[i] +// switchJ := switches[j] +// +// c1, c2 := conn.NetPipe() +// +// doneCh := make(chan struct{}) +// go func() { +// err := switchI.addPeerWithConnection(c1) +// if err != nil { +// panic(err) +// } +// doneCh <- struct{}{} +// }() +// go func() { +// err := switchJ.addPeerWithConnection(c2) +// if err != nil { +// panic(err) +// } +// doneCh <- struct{}{} +// }() +// <-doneCh +// <-doneCh +// } +// +// func (sw *Switch) addPeerWithConnection(conn net.Conn) error { +// pc, err := testInboundPeerConn(conn, sw.config, sw.nodeKey.PrivKey) +// if err != nil { +// if err := conn.Close(); err != nil { +// sw.Logger.Error("Error closing connection", "err", err) +// } +// return err +// } +// +// ni, err := handshake(conn, time.Second, sw.nodeInfo) +// if err != nil { +// if err := conn.Close(); err != nil { +// sw.Logger.Error("Error closing connection", "err", err) +// } +// return err +// } +// +// p := peer.newPeer( +// pc, +// MConnConfig(sw.config), +// ni, +// sw.reactorsByCh, +// sw.chDescs, +// sw.StopPeerForError, +// ) +// +// if err = sw.addPeer(p); err != nil { +// pc.CloseConn() +// return err +// } +// +// return nil +// } +// +// // StartSwitches calls sw.Start() for each given switch. +// // It returns the first encountered error. +// func StartSwitches(switches []*Switch) error { +// for _, s := range switches { +// err := s.Start() // start switch and reactors +// if err != nil { +// return err +// } +// } +// return nil +// } +// +// func MakeSwitch( +// cfg *config.P2PConfig, +// i int, +// network, version string, +// initSwitch func(int, *Switch) *Switch, +// opts ...SwitchOption, +// ) *Switch { +// nodeKey := NodeKey{ +// PrivKey: ed25519.GenPrivKey(), +// } +// nodeInfo := testNodeInfo(nodeKey.ID(), fmt.Sprintf("node%d", i)) +// +// t := NewMultiplexTransport(nodeInfo, nodeKey, MConnConfig(cfg)) +// +// if err := t.Listen(*nodeInfo.NetAddress); err != nil { +// panic(err) +// } +// +// // TODO: let the config be passed in? +// sw := initSwitch(i, NewSwitch(cfg, t, opts...)) +// sw.SetLogger(log.NewNoopLogger().With("switch", i)) +// sw.SetNodeKey(&nodeKey) +// +// for ch := range sw.reactorsByCh { +// nodeInfo.Channels = append(nodeInfo.Channels, ch) +// } +// +// // TODO: We need to setup reactors ahead of time so the NodeInfo is properly +// // populated and we don't have to do those awkward overrides and setters. +// t.nodeInfo = nodeInfo +// sw.SetNodeInfo(nodeInfo) +// +// return sw +// } +// +// func testInboundPeerConn( +// conn net.Conn, +// config *config.P2PConfig, +// ourNodePrivKey crypto.PrivKey, +// ) (peer.peerConn, error) { +// return testPeerConn(conn, config, false, false, ourNodePrivKey, nil) +// } +// +// func testPeerConn( +// rawConn net.Conn, +// cfg *config.P2PConfig, +// outbound, persistent bool, +// ourNodePrivKey crypto.PrivKey, +// socketAddr *NetAddress, +// ) (pc peer.peerConn, err error) { +// conn := rawConn +// +// // Encrypt connection +// conn, err = upgradeSecretConn(conn, cfg.HandshakeTimeout, ourNodePrivKey) +// if err != nil { +// return pc, errors.Wrap(err, "Error creating peer") +// } +// +// // Only the information we already have +// return peer.NewPeerConn(outbound, persistent, conn, socketAddr), nil +// } +// +// // ---------------------------------------------------------------- +// // rand node info +// +// func testNodeInfo(id ID, name string) NodeInfo { +// return testNodeInfoWithNetwork(id, name, "testing") +// } +// +// func testVersionSet() versionset.VersionSet { +// return versionset.VersionSet{ +// versionset.VersionInfo{ +// Name: "p2p", +// Version: "v0.0.0", // dontcare +// }, +// } +// } +// +// func testNodeInfoWithNetwork(id ID, name, network string) NodeInfo { +// info := NodeInfo{ +// VersionSet: testVersionSet(), +// NetAddress: NewNetAddressFromIPPort(net.ParseIP("127.0.0.1"), 0), +// Network: network, +// Software: "p2ptest", +// Version: "v1.2.3-rc.0-deadbeef", +// Channels: []byte{testCh}, +// Moniker: name, +// Other: NodeInfoOther{ +// TxIndex: "on", +// RPCAddress: fmt.Sprintf("127.0.0.1:%d", 0), +// }, +// } +// +// info.NetAddress.ID = id +// +// return info +// } diff --git a/tm2/pkg/p2p/transport.go b/tm2/pkg/p2p/transport.go index db605f8ee87..9168dafd4a7 100644 --- a/tm2/pkg/p2p/transport.go +++ b/tm2/pkg/p2p/transport.go @@ -498,14 +498,26 @@ func (mt *MultiplexTransport) wrapPeer( } } - peerConn := newPeerConn( - cfg.outbound, - persistent, - c, - socketAddr, - ) + // Extract the host + host, _, err := net.SplitHostPort(c.RemoteAddr().String()) + if err != nil { + panic(err) // TODO propagate + } + + ips, err := net.LookupIP(host) + if err != nil { + panic(err) // TODO propagate + } + + peerConn := &ConnInfo{ + Outbound: cfg.outbound, + Persistent: persistent, + Conn: c, + RemoteIP: ips[0], + SocketAddr: socketAddr, + } - p := newPeer( + p := New( peerConn, mt.mConfig, ni, @@ -541,7 +553,7 @@ func handshake( _, err := amino.UnmarshalSizedReader( c, &peerNodeInfo, - int64(MaxNodeInfoSize()), + int64(MaxNodeInfoSize), ) errc <- err }(errc, c) diff --git a/tm2/pkg/p2p/transport_test.go b/tm2/pkg/p2p/transport_test.go index f91eaaeac9e..959d83d1131 100644 --- a/tm2/pkg/p2p/transport_test.go +++ b/tm2/pkg/p2p/transport_test.go @@ -1,661 +1,646 @@ package p2p -import ( - "fmt" - "math/rand" - "net" - "reflect" - "testing" - "time" - - "github.com/gnolang/gno/tm2/pkg/amino" - "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" - "github.com/gnolang/gno/tm2/pkg/p2p/conn" - "github.com/gnolang/gno/tm2/pkg/testutils" - "github.com/stretchr/testify/require" -) - -var defaultNodeName = "host_peer" - -func emptyNodeInfo() NodeInfo { - return NodeInfo{} -} - -// newMultiplexTransport returns a tcp connected multiplexed peer -// using the default MConnConfig. It's a convenience function used -// for testing. -func newMultiplexTransport( - nodeInfo NodeInfo, - nodeKey NodeKey, -) *MultiplexTransport { - return NewMultiplexTransport( - nodeInfo, nodeKey, conn.DefaultMConnConfig(), - ) -} - -func TestTransportMultiplexConnFilter(t *testing.T) { - t.Parallel() - - mt := newMultiplexTransport( - emptyNodeInfo(), - NodeKey{ - PrivKey: ed25519.GenPrivKey(), - }, - ) - id := mt.nodeKey.ID() - - MultiplexTransportConnFilters( - func(_ ConnSet, _ net.Conn, _ []net.IP) error { return nil }, - func(_ ConnSet, _ net.Conn, _ []net.IP) error { return nil }, - func(_ ConnSet, _ net.Conn, _ []net.IP) error { - return fmt.Errorf("rejected") - }, - )(mt) - - addr, err := NewNetAddressFromString(NetAddressString(id, "127.0.0.1:0")) - if err != nil { - t.Fatal(err) - } - - if err := mt.Listen(*addr); err != nil { - t.Fatal(err) - } - - errc := make(chan error) - - go func() { - addr, err := NewNetAddress(id, mt.listener.Addr()) - require.NoError(t, err) - - _, err = addr.DialTimeout(5 * time.Second) - if err != nil { - errc <- err - return - } - - close(errc) - }() - - if err := <-errc; err != nil { - t.Errorf("connection failed: %v", err) - } - - _, err = mt.Accept(peerConfig{}) - if err, ok := err.(RejectedError); ok { - if !err.IsFiltered() { - t.Errorf("expected peer to be filtered") - } - } else { - t.Errorf("expected RejectedError") - } -} - -func TestTransportMultiplexConnFilterTimeout(t *testing.T) { - t.Parallel() - - mt := newMultiplexTransport( - emptyNodeInfo(), - NodeKey{ - PrivKey: ed25519.GenPrivKey(), - }, - ) - id := mt.nodeKey.ID() - - MultiplexTransportFilterTimeout(5 * time.Millisecond)(mt) - MultiplexTransportConnFilters( - func(_ ConnSet, _ net.Conn, _ []net.IP) error { - time.Sleep(100 * time.Millisecond) - return nil - }, - )(mt) - - addr, err := NewNetAddressFromString(NetAddressString(id, "127.0.0.1:0")) - if err != nil { - t.Fatal(err) - } - - if err := mt.Listen(*addr); err != nil { - t.Fatal(err) - } - - errc := make(chan error) - - go func() { - addr, err := NewNetAddress(id, mt.listener.Addr()) - require.NoError(t, err) - - _, err = addr.DialTimeout(5 * time.Second) - if err != nil { - errc <- err - return - } - - close(errc) - }() - - if err := <-errc; err != nil { - t.Errorf("connection failed: %v", err) - } - - _, err = mt.Accept(peerConfig{}) - if _, ok := err.(FilterTimeoutError); !ok { - t.Errorf("expected FilterTimeoutError") - } -} - -func TestTransportMultiplexAcceptMultiple(t *testing.T) { - t.Parallel() - - mt := testSetupMultiplexTransport(t) - laddr, err := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) - require.NoError(t, err) - - var ( - seed = rand.New(rand.NewSource(time.Now().UnixNano())) - nDialers = seed.Intn(64) + 64 - errc = make(chan error, nDialers) - ) - - // Setup dialers. - for i := 0; i < nDialers; i++ { - go testDialer(*laddr, errc) - } - - // Catch connection errors. - for i := 0; i < nDialers; i++ { - if err := <-errc; err != nil { - t.Fatal(err) - } - } - - ps := []Peer{} - - // Accept all peers. - for i := 0; i < cap(errc); i++ { - p, err := mt.Accept(peerConfig{}) - if err != nil { - t.Fatal(err) - } - - if err := p.Start(); err != nil { - t.Fatal(err) - } - - ps = append(ps, p) - } - - if have, want := len(ps), cap(errc); have != want { - t.Errorf("have %v, want %v", have, want) - } - - // Stop all peers. - for _, p := range ps { - if err := p.Stop(); err != nil { - t.Fatal(err) - } - } - - if err := mt.Close(); err != nil { - t.Errorf("close errored: %v", err) - } -} - -func testDialer(dialAddr NetAddress, errc chan error) { - var ( - pv = ed25519.GenPrivKey() - dialer = newMultiplexTransport( - testNodeInfo(pv.PubKey().Address().ID(), defaultNodeName), - NodeKey{ - PrivKey: pv, - }, - ) - ) - - _, err := dialer.Dial(dialAddr, peerConfig{}) - if err != nil { - errc <- err - return - } - - // Signal that the connection was established. - errc <- nil -} - -func TestFlappyTransportMultiplexAcceptNonBlocking(t *testing.T) { - t.Parallel() - - testutils.FilterStability(t, testutils.Flappy) - - mt := testSetupMultiplexTransport(t) - - var ( - fastNodePV = ed25519.GenPrivKey() - fastNodeInfo = testNodeInfo(fastNodePV.PubKey().Address().ID(), "fastnode") - errc = make(chan error) - fastc = make(chan struct{}) - slowc = make(chan struct{}) - ) - - // Simulate slow Peer. - go func() { - addr, err := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) - require.NoError(t, err) - - c, err := addr.DialTimeout(5 * time.Second) - if err != nil { - errc <- err - return - } - - close(slowc) - - select { - case <-fastc: - // Fast peer connected. - case <-time.After(100 * time.Millisecond): - // We error if the fast peer didn't succeed. - errc <- fmt.Errorf("Fast peer timed out") - } - - sc, err := upgradeSecretConn(c, 100*time.Millisecond, ed25519.GenPrivKey()) - if err != nil { - errc <- err - return - } - - _, err = handshake(sc, 100*time.Millisecond, - testNodeInfo( - ed25519.GenPrivKey().PubKey().Address().ID(), - "slow_peer", - )) - if err != nil { - errc <- err - return - } - }() - - // Simulate fast Peer. - go func() { - <-slowc - - dialer := newMultiplexTransport( - fastNodeInfo, - NodeKey{ - PrivKey: fastNodePV, - }, - ) - addr, err := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) - require.NoError(t, err) - - _, err = dialer.Dial(*addr, peerConfig{}) - if err != nil { - errc <- err - return - } - - close(errc) - close(fastc) - }() - - if err := <-errc; err != nil { - t.Errorf("connection failed: %v", err) - } - - p, err := mt.Accept(peerConfig{}) - if err != nil { - t.Fatal(err) - } - - if have, want := p.NodeInfo(), fastNodeInfo; !reflect.DeepEqual(have, want) { - t.Errorf("have %v, want %v", have, want) - } -} - -func TestTransportMultiplexValidateNodeInfo(t *testing.T) { - t.Parallel() - - mt := testSetupMultiplexTransport(t) - - errc := make(chan error) - - go func() { - var ( - pv = ed25519.GenPrivKey() - dialer = newMultiplexTransport( - testNodeInfo(pv.PubKey().Address().ID(), ""), // Should not be empty - NodeKey{ - PrivKey: pv, - }, - ) - ) - - addr, err := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) - require.NoError(t, err) - - _, err = dialer.Dial(*addr, peerConfig{}) - if err != nil { - errc <- err - return - } - - close(errc) - }() - - if err := <-errc; err != nil { - t.Errorf("connection failed: %v", err) - } - - _, err := mt.Accept(peerConfig{}) - if err, ok := err.(RejectedError); ok { - if !err.IsNodeInfoInvalid() { - t.Errorf("expected NodeInfo to be invalid") - } - } else { - t.Errorf("expected RejectedError") - } -} - -func TestTransportMultiplexRejectMismatchID(t *testing.T) { - t.Parallel() - - mt := testSetupMultiplexTransport(t) - - errc := make(chan error) - - go func() { - dialer := newMultiplexTransport( - testNodeInfo( - ed25519.GenPrivKey().PubKey().Address().ID(), "dialer", - ), - NodeKey{ - PrivKey: ed25519.GenPrivKey(), - }, - ) - addr, err := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) - require.NoError(t, err) - - _, err = dialer.Dial(*addr, peerConfig{}) - if err != nil { - errc <- err - return - } - - close(errc) - }() - - if err := <-errc; err != nil { - t.Errorf("connection failed: %v", err) - } - - _, err := mt.Accept(peerConfig{}) - if err, ok := err.(RejectedError); ok { - if !err.IsAuthFailure() { - t.Errorf("expected auth failure") - } - } else { - t.Errorf("expected RejectedError") - } -} - -func TestTransportMultiplexDialRejectWrongID(t *testing.T) { - t.Parallel() - - mt := testSetupMultiplexTransport(t) - - var ( - pv = ed25519.GenPrivKey() - dialer = newMultiplexTransport( - testNodeInfo(pv.PubKey().Address().ID(), ""), // Should not be empty - NodeKey{ - PrivKey: pv, - }, - ) - ) - - wrongID := ed25519.GenPrivKey().PubKey().Address().ID() - addr, err := NewNetAddress(wrongID, mt.listener.Addr()) - require.NoError(t, err) - - _, err = dialer.Dial(*addr, peerConfig{}) - if err != nil { - t.Logf("connection failed: %v", err) - if err, ok := err.(RejectedError); ok { - if !err.IsAuthFailure() { - t.Errorf("expected auth failure") - } - } else { - t.Errorf("expected RejectedError") - } - } -} - -func TestTransportMultiplexRejectIncompatible(t *testing.T) { - t.Parallel() - - mt := testSetupMultiplexTransport(t) - - errc := make(chan error) - - go func() { - var ( - pv = ed25519.GenPrivKey() - dialer = newMultiplexTransport( - testNodeInfoWithNetwork(pv.PubKey().Address().ID(), "dialer", "incompatible-network"), - NodeKey{ - PrivKey: pv, - }, - ) - ) - addr, err := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) - require.NoError(t, err) - - _, err = dialer.Dial(*addr, peerConfig{}) - if err != nil { - errc <- err - return - } - - close(errc) - }() - - _, err := mt.Accept(peerConfig{}) - if err, ok := err.(RejectedError); ok { - if !err.IsIncompatible() { - t.Errorf("expected to reject incompatible") - } - } else { - t.Errorf("expected RejectedError") - } -} - -func TestTransportMultiplexRejectSelf(t *testing.T) { - t.Parallel() - - mt := testSetupMultiplexTransport(t) - - errc := make(chan error) - - go func() { - addr, err := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) - require.NoError(t, err) - - _, err = mt.Dial(*addr, peerConfig{}) - if err != nil { - errc <- err - return - } - - close(errc) - }() - - if err := <-errc; err != nil { - if err, ok := err.(RejectedError); ok { - if !err.IsSelf() { - t.Errorf("expected to reject self, got: %v", err) - } - } else { - t.Errorf("expected RejectedError") - } - } else { - t.Errorf("expected connection failure") - } - - _, err := mt.Accept(peerConfig{}) - if err, ok := err.(RejectedError); ok { - if !err.IsSelf() { - t.Errorf("expected to reject self, got: %v", err) - } - } else { - t.Errorf("expected RejectedError") - } -} - -func TestTransportConnDuplicateIPFilter(t *testing.T) { - t.Parallel() - - filter := ConnDuplicateIPFilter() - - if err := filter(nil, &testTransportConn{}, nil); err != nil { - t.Fatal(err) - } - - var ( - c = &testTransportConn{} - cs = NewConnSet() - ) - - cs.Set(c, []net.IP{ - {10, 0, 10, 1}, - {10, 0, 10, 2}, - {10, 0, 10, 3}, - }) - - if err := filter(cs, c, []net.IP{ - {10, 0, 10, 2}, - }); err == nil { - t.Errorf("expected Peer to be rejected as duplicate") - } -} - -func TestTransportHandshake(t *testing.T) { - t.Parallel() - - ln, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatal(err) - } - - var ( - peerPV = ed25519.GenPrivKey() - peerNodeInfo = testNodeInfo(peerPV.PubKey().Address().ID(), defaultNodeName) - ) - - go func() { - c, err := net.Dial(ln.Addr().Network(), ln.Addr().String()) - if err != nil { - t.Error(err) - return - } - - go func(c net.Conn) { - _, err := amino.MarshalSizedWriter(c, peerNodeInfo) - if err != nil { - t.Error(err) - } - }(c) - go func(c net.Conn) { - var ni NodeInfo - - _, err := amino.UnmarshalSizedReader( - c, - &ni, - int64(MaxNodeInfoSize()), - ) - if err != nil { - t.Error(err) - } - }(c) - }() - - c, err := ln.Accept() - if err != nil { - t.Fatal(err) - } - - ni, err := handshake(c, 100*time.Millisecond, emptyNodeInfo()) - if err != nil { - t.Fatal(err) - } - - if have, want := ni, peerNodeInfo; !reflect.DeepEqual(have, want) { - t.Errorf("have %v, want %v", have, want) - } -} - -// create listener -func testSetupMultiplexTransport(t *testing.T) *MultiplexTransport { - t.Helper() - - var ( - pv = ed25519.GenPrivKey() - id = pv.PubKey().Address().ID() - mt = newMultiplexTransport( - testNodeInfo( - id, "transport", - ), - NodeKey{ - PrivKey: pv, - }, - ) - ) - - addr, err := NewNetAddressFromString(NetAddressString(id, "127.0.0.1:0")) - if err != nil { - t.Fatal(err) - } - - if err := mt.Listen(*addr); err != nil { - t.Fatal(err) - } - - return mt -} - -type testTransportAddr struct{} - -func (a *testTransportAddr) Network() string { return "tcp" } -func (a *testTransportAddr) String() string { return "test.local:1234" } - -type testTransportConn struct{} - -func (c *testTransportConn) Close() error { - return fmt.Errorf("Close() not implemented") -} - -func (c *testTransportConn) LocalAddr() net.Addr { - return &testTransportAddr{} -} - -func (c *testTransportConn) RemoteAddr() net.Addr { - return &testTransportAddr{} -} - -func (c *testTransportConn) Read(_ []byte) (int, error) { - return -1, fmt.Errorf("Read() not implemented") -} - -func (c *testTransportConn) SetDeadline(_ time.Time) error { - return fmt.Errorf("SetDeadline() not implemented") -} - -func (c *testTransportConn) SetReadDeadline(_ time.Time) error { - return fmt.Errorf("SetReadDeadline() not implemented") -} - -func (c *testTransportConn) SetWriteDeadline(_ time.Time) error { - return fmt.Errorf("SetWriteDeadline() not implemented") -} - -func (c *testTransportConn) Write(_ []byte) (int, error) { - return -1, fmt.Errorf("Write() not implemented") -} +// var defaultNodeName = "host_peer" +// +// func emptyNodeInfo() NodeInfo { +// return NodeInfo{} +// } +// +// // newMultiplexTransport returns a tcp connected multiplexed peer +// // using the default MConnConfig. It's a convenience function used +// // for testing. +// func newMultiplexTransport( +// nodeInfo NodeInfo, +// nodeKey NodeKey, +// ) *MultiplexTransport { +// return NewMultiplexTransport( +// nodeInfo, nodeKey, conn.DefaultMConnConfig(), +// ) +// } +// +// func TestTransportMultiplexConnFilter(t *testing.T) { +// t.Parallel() +// +// mt := newMultiplexTransport( +// emptyNodeInfo(), +// NodeKey{ +// PrivKey: ed25519.GenPrivKey(), +// }, +// ) +// id := mt.nodeKey.ID() +// +// MultiplexTransportConnFilters( +// func(_ ConnSet, _ net.Conn, _ []net.IP) error { return nil }, +// func(_ ConnSet, _ net.Conn, _ []net.IP) error { return nil }, +// func(_ ConnSet, _ net.Conn, _ []net.IP) error { +// return fmt.Errorf("rejected") +// }, +// )(mt) +// +// addr, err := NewNetAddressFromString(NetAddressString(id, "127.0.0.1:0")) +// if err != nil { +// t.Fatal(err) +// } +// +// if err := mt.Listen(*addr); err != nil { +// t.Fatal(err) +// } +// +// errc := make(chan error) +// +// go func() { +// addr, err := NewNetAddress(id, mt.listener.Addr()) +// require.NoError(t, err) +// +// _, err = addr.DialTimeout(5 * time.Second) +// if err != nil { +// errc <- err +// return +// } +// +// close(errc) +// }() +// +// if err := <-errc; err != nil { +// t.Errorf("connection failed: %v", err) +// } +// +// _, err = mt.Accept(peerConfig{}) +// if err, ok := err.(RejectedError); ok { +// if !err.IsFiltered() { +// t.Errorf("expected peer to be filtered") +// } +// } else { +// t.Errorf("expected RejectedError") +// } +// } +// +// func TestTransportMultiplexConnFilterTimeout(t *testing.T) { +// t.Parallel() +// +// mt := newMultiplexTransport( +// emptyNodeInfo(), +// NodeKey{ +// PrivKey: ed25519.GenPrivKey(), +// }, +// ) +// id := mt.nodeKey.ID() +// +// MultiplexTransportFilterTimeout(5 * time.Millisecond)(mt) +// MultiplexTransportConnFilters( +// func(_ ConnSet, _ net.Conn, _ []net.IP) error { +// time.Sleep(100 * time.Millisecond) +// return nil +// }, +// )(mt) +// +// addr, err := NewNetAddressFromString(NetAddressString(id, "127.0.0.1:0")) +// if err != nil { +// t.Fatal(err) +// } +// +// if err := mt.Listen(*addr); err != nil { +// t.Fatal(err) +// } +// +// errc := make(chan error) +// +// go func() { +// addr, err := NewNetAddress(id, mt.listener.Addr()) +// require.NoError(t, err) +// +// _, err = addr.DialTimeout(5 * time.Second) +// if err != nil { +// errc <- err +// return +// } +// +// close(errc) +// }() +// +// if err := <-errc; err != nil { +// t.Errorf("connection failed: %v", err) +// } +// +// _, err = mt.Accept(peerConfig{}) +// if _, ok := err.(FilterTimeoutError); !ok { +// t.Errorf("expected FilterTimeoutError") +// } +// } +// +// func TestTransportMultiplexAcceptMultiple(t *testing.T) { +// t.Parallel() +// +// mt := testSetupMultiplexTransport(t) +// laddr, err := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) +// require.NoError(t, err) +// +// var ( +// seed = rand.New(rand.NewSource(time.Now().UnixNano())) +// nDialers = seed.Intn(64) + 64 +// errc = make(chan error, nDialers) +// ) +// +// // Setup dialers. +// for i := 0; i < nDialers; i++ { +// go testDialer(*laddr, errc) +// } +// +// // Catch connection errors. +// for i := 0; i < nDialers; i++ { +// if err := <-errc; err != nil { +// t.Fatal(err) +// } +// } +// +// ps := []Peer{} +// +// // Accept all peers. +// for i := 0; i < cap(errc); i++ { +// p, err := mt.Accept(peerConfig{}) +// if err != nil { +// t.Fatal(err) +// } +// +// if err := p.Start(); err != nil { +// t.Fatal(err) +// } +// +// ps = append(ps, p) +// } +// +// if have, want := len(ps), cap(errc); have != want { +// t.Errorf("have %v, want %v", have, want) +// } +// +// // Stop all peers. +// for _, p := range ps { +// if err := p.Stop(); err != nil { +// t.Fatal(err) +// } +// } +// +// if err := mt.Close(); err != nil { +// t.Errorf("close errored: %v", err) +// } +// } +// +// func testDialer(dialAddr NetAddress, errc chan error) { +// var ( +// pv = ed25519.GenPrivKey() +// dialer = newMultiplexTransport( +// testNodeInfo(pv.PubKey().Address().ID(), defaultNodeName), +// NodeKey{ +// PrivKey: pv, +// }, +// ) +// ) +// +// _, err := dialer.Dial(dialAddr, peerConfig{}) +// if err != nil { +// errc <- err +// return +// } +// +// // Signal that the connection was established. +// errc <- nil +// } +// +// func TestFlappyTransportMultiplexAcceptNonBlocking(t *testing.T) { +// t.Parallel() +// +// testutils.FilterStability(t, testutils.Flappy) +// +// mt := testSetupMultiplexTransport(t) +// +// var ( +// fastNodePV = ed25519.GenPrivKey() +// fastNodeInfo = testNodeInfo(fastNodePV.PubKey().Address().ID(), "fastnode") +// errc = make(chan error) +// fastc = make(chan struct{}) +// slowc = make(chan struct{}) +// ) +// +// // Simulate slow Peer. +// go func() { +// addr, err := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) +// require.NoError(t, err) +// +// c, err := addr.DialTimeout(5 * time.Second) +// if err != nil { +// errc <- err +// return +// } +// +// close(slowc) +// +// select { +// case <-fastc: +// // Fast peer connected. +// case <-time.After(100 * time.Millisecond): +// // We error if the fast peer didn't succeed. +// errc <- fmt.Errorf("Fast peer timed out") +// } +// +// sc, err := upgradeSecretConn(c, 100*time.Millisecond, ed25519.GenPrivKey()) +// if err != nil { +// errc <- err +// return +// } +// +// _, err = handshake(sc, 100*time.Millisecond, +// testNodeInfo( +// ed25519.GenPrivKey().PubKey().Address().ID(), +// "slow_peer", +// )) +// if err != nil { +// errc <- err +// return +// } +// }() +// +// // Simulate fast Peer. +// go func() { +// <-slowc +// +// dialer := newMultiplexTransport( +// fastNodeInfo, +// NodeKey{ +// PrivKey: fastNodePV, +// }, +// ) +// addr, err := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) +// require.NoError(t, err) +// +// _, err = dialer.Dial(*addr, peerConfig{}) +// if err != nil { +// errc <- err +// return +// } +// +// close(errc) +// close(fastc) +// }() +// +// if err := <-errc; err != nil { +// t.Errorf("connection failed: %v", err) +// } +// +// p, err := mt.Accept(peerConfig{}) +// if err != nil { +// t.Fatal(err) +// } +// +// if have, want := p.NodeInfo(), fastNodeInfo; !reflect.DeepEqual(have, want) { +// t.Errorf("have %v, want %v", have, want) +// } +// } +// +// func TestTransportMultiplexValidateNodeInfo(t *testing.T) { +// t.Parallel() +// +// mt := testSetupMultiplexTransport(t) +// +// errc := make(chan error) +// +// go func() { +// var ( +// pv = ed25519.GenPrivKey() +// dialer = newMultiplexTransport( +// testNodeInfo(pv.PubKey().Address().ID(), ""), // Should not be empty +// NodeKey{ +// PrivKey: pv, +// }, +// ) +// ) +// +// addr, err := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) +// require.NoError(t, err) +// +// _, err = dialer.Dial(*addr, peerConfig{}) +// if err != nil { +// errc <- err +// return +// } +// +// close(errc) +// }() +// +// if err := <-errc; err != nil { +// t.Errorf("connection failed: %v", err) +// } +// +// _, err := mt.Accept(peerConfig{}) +// if err, ok := err.(RejectedError); ok { +// if !err.IsNodeInfoInvalid() { +// t.Errorf("expected NodeInfo to be invalid") +// } +// } else { +// t.Errorf("expected RejectedError") +// } +// } +// +// func TestTransportMultiplexRejectMismatchID(t *testing.T) { +// t.Parallel() +// +// mt := testSetupMultiplexTransport(t) +// +// errc := make(chan error) +// +// go func() { +// dialer := newMultiplexTransport( +// testNodeInfo( +// ed25519.GenPrivKey().PubKey().Address().ID(), "dialer", +// ), +// NodeKey{ +// PrivKey: ed25519.GenPrivKey(), +// }, +// ) +// addr, err := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) +// require.NoError(t, err) +// +// _, err = dialer.Dial(*addr, peerConfig{}) +// if err != nil { +// errc <- err +// return +// } +// +// close(errc) +// }() +// +// if err := <-errc; err != nil { +// t.Errorf("connection failed: %v", err) +// } +// +// _, err := mt.Accept(peerConfig{}) +// if err, ok := err.(RejectedError); ok { +// if !err.IsAuthFailure() { +// t.Errorf("expected auth failure") +// } +// } else { +// t.Errorf("expected RejectedError") +// } +// } +// +// func TestTransportMultiplexDialRejectWrongID(t *testing.T) { +// t.Parallel() +// +// mt := testSetupMultiplexTransport(t) +// +// var ( +// pv = ed25519.GenPrivKey() +// dialer = newMultiplexTransport( +// testNodeInfo(pv.PubKey().Address().ID(), ""), // Should not be empty +// NodeKey{ +// PrivKey: pv, +// }, +// ) +// ) +// +// wrongID := ed25519.GenPrivKey().PubKey().Address().ID() +// addr, err := NewNetAddress(wrongID, mt.listener.Addr()) +// require.NoError(t, err) +// +// _, err = dialer.Dial(*addr, peerConfig{}) +// if err != nil { +// t.Logf("connection failed: %v", err) +// if err, ok := err.(RejectedError); ok { +// if !err.IsAuthFailure() { +// t.Errorf("expected auth failure") +// } +// } else { +// t.Errorf("expected RejectedError") +// } +// } +// } +// +// func TestTransportMultiplexRejectIncompatible(t *testing.T) { +// t.Parallel() +// +// mt := testSetupMultiplexTransport(t) +// +// errc := make(chan error) +// +// go func() { +// var ( +// pv = ed25519.GenPrivKey() +// dialer = newMultiplexTransport( +// testNodeInfoWithNetwork(pv.PubKey().Address().ID(), "dialer", "incompatible-network"), +// NodeKey{ +// PrivKey: pv, +// }, +// ) +// ) +// addr, err := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) +// require.NoError(t, err) +// +// _, err = dialer.Dial(*addr, peerConfig{}) +// if err != nil { +// errc <- err +// return +// } +// +// close(errc) +// }() +// +// _, err := mt.Accept(peerConfig{}) +// if err, ok := err.(RejectedError); ok { +// if !err.IsIncompatible() { +// t.Errorf("expected to reject incompatible") +// } +// } else { +// t.Errorf("expected RejectedError") +// } +// } +// +// func TestTransportMultiplexRejectSelf(t *testing.T) { +// t.Parallel() +// +// mt := testSetupMultiplexTransport(t) +// +// errc := make(chan error) +// +// go func() { +// addr, err := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) +// require.NoError(t, err) +// +// _, err = mt.Dial(*addr, peerConfig{}) +// if err != nil { +// errc <- err +// return +// } +// +// close(errc) +// }() +// +// if err := <-errc; err != nil { +// if err, ok := err.(RejectedError); ok { +// if !err.IsSelf() { +// t.Errorf("expected to reject self, got: %v", err) +// } +// } else { +// t.Errorf("expected RejectedError") +// } +// } else { +// t.Errorf("expected connection failure") +// } +// +// _, err := mt.Accept(peerConfig{}) +// if err, ok := err.(RejectedError); ok { +// if !err.IsSelf() { +// t.Errorf("expected to reject self, got: %v", err) +// } +// } else { +// t.Errorf("expected RejectedError") +// } +// } +// +// func TestTransportConnDuplicateIPFilter(t *testing.T) { +// t.Parallel() +// +// filter := ConnDuplicateIPFilter() +// +// if err := filter(nil, &testTransportConn{}, nil); err != nil { +// t.Fatal(err) +// } +// +// var ( +// c = &testTransportConn{} +// cs = NewConnSet() +// ) +// +// cs.Set(c, []net.IP{ +// {10, 0, 10, 1}, +// {10, 0, 10, 2}, +// {10, 0, 10, 3}, +// }) +// +// if err := filter(cs, c, []net.IP{ +// {10, 0, 10, 2}, +// }); err == nil { +// t.Errorf("expected Peer to be rejected as duplicate") +// } +// } +// +// func TestTransportHandshake(t *testing.T) { +// t.Parallel() +// +// ln, err := net.Listen("tcp", "127.0.0.1:0") +// if err != nil { +// t.Fatal(err) +// } +// +// var ( +// peerPV = ed25519.GenPrivKey() +// peerNodeInfo = testNodeInfo(peerPV.PubKey().Address().ID(), defaultNodeName) +// ) +// +// go func() { +// c, err := net.Dial(ln.Addr().Network(), ln.Addr().String()) +// if err != nil { +// t.Error(err) +// return +// } +// +// go func(c net.Conn) { +// _, err := amino.MarshalSizedWriter(c, peerNodeInfo) +// if err != nil { +// t.Error(err) +// } +// }(c) +// go func(c net.Conn) { +// var ni NodeInfo +// +// _, err := amino.UnmarshalSizedReader( +// c, +// &ni, +// int64(MaxNodeInfoSize), +// ) +// if err != nil { +// t.Error(err) +// } +// }(c) +// }() +// +// c, err := ln.Accept() +// if err != nil { +// t.Fatal(err) +// } +// +// ni, err := handshake(c, 100*time.Millisecond, emptyNodeInfo()) +// if err != nil { +// t.Fatal(err) +// } +// +// if have, want := ni, peerNodeInfo; !reflect.DeepEqual(have, want) { +// t.Errorf("have %v, want %v", have, want) +// } +// } +// +// // create listener +// func testSetupMultiplexTransport(t *testing.T) *MultiplexTransport { +// t.Helper() +// +// var ( +// pv = ed25519.GenPrivKey() +// id = pv.PubKey().Address().ID() +// mt = newMultiplexTransport( +// testNodeInfo( +// id, "transport", +// ), +// NodeKey{ +// PrivKey: pv, +// }, +// ) +// ) +// +// addr, err := NewNetAddressFromString(NetAddressString(id, "127.0.0.1:0")) +// if err != nil { +// t.Fatal(err) +// } +// +// if err := mt.Listen(*addr); err != nil { +// t.Fatal(err) +// } +// +// return mt +// } +// +// type testTransportAddr struct{} +// +// func (a *testTransportAddr) Network() string { return "tcp" } +// func (a *testTransportAddr) String() string { return "test.local:1234" } +// +// type testTransportConn struct{} +// +// func (c *testTransportConn) Close() error { +// return fmt.Errorf("Close() not implemented") +// } +// +// func (c *testTransportConn) LocalAddr() net.Addr { +// return &testTransportAddr{} +// } +// +// func (c *testTransportConn) RemoteAddr() net.Addr { +// return &testTransportAddr{} +// } +// +// func (c *testTransportConn) Read(_ []byte) (int, error) { +// return -1, fmt.Errorf("Read() not implemented") +// } +// +// func (c *testTransportConn) SetDeadline(_ time.Time) error { +// return fmt.Errorf("SetDeadline() not implemented") +// } +// +// func (c *testTransportConn) SetReadDeadline(_ time.Time) error { +// return fmt.Errorf("SetReadDeadline() not implemented") +// } +// +// func (c *testTransportConn) SetWriteDeadline(_ time.Time) error { +// return fmt.Errorf("SetWriteDeadline() not implemented") +// } +// +// func (c *testTransportConn) Write(_ []byte) (int, error) { +// return -1, fmt.Errorf("Write() not implemented") +// } diff --git a/tm2/pkg/p2p/types.go b/tm2/pkg/p2p/types.go index 150325f52bb..199dd4af6d3 100644 --- a/tm2/pkg/p2p/types.go +++ b/tm2/pkg/p2p/types.go @@ -1,10 +1,54 @@ package p2p import ( + "net" + + "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/p2p/conn" + connm "github.com/gnolang/gno/tm2/pkg/p2p/conn" + "github.com/gnolang/gno/tm2/pkg/service" ) type ( ChannelDescriptor = conn.ChannelDescriptor ConnectionStatus = conn.ConnectionStatus ) + +type ID = crypto.ID + +// Peer is a wrapper for a connected peer +type Peer interface { + service.Service + + FlushStop() + + ID() ID // peer's cryptographic ID + RemoteIP() net.IP // remote IP of the connection + RemoteAddr() net.Addr // remote address of the connection + + IsOutbound() bool // did we dial the peer + IsPersistent() bool // do we redial this peer when we disconnect + + CloseConn() error // close original connection + + NodeInfo() NodeInfo // peer's info + Status() connm.ConnectionStatus + SocketAddr() *NetAddress // actual address of the socket + + Send(byte, []byte) bool + TrySend(byte, []byte) bool + + Set(string, any) + Get(string) any +} + +// PeerSet has a (immutable) subset of the methods of PeerSet. +type PeerSet interface { + Add(peer Peer) + Remove(key ID) bool + Has(key ID) bool + HasIP(ip net.IP) bool + Get(key ID) Peer + List() []Peer + Size() int +} From 93fa4690e22675f81622ef95740ca7f4c2a9a9a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Tue, 1 Oct 2024 16:15:37 +0200 Subject: [PATCH 09/43] Tidy peer --- tm2/pkg/p2p/mock_test.go | 157 ++++++++ tm2/pkg/p2p/peer.go | 149 +++---- tm2/pkg/p2p/peer_test.go | 843 +++++++++++++++++++++++++++++---------- tm2/pkg/p2p/transport.go | 16 +- 4 files changed, 867 insertions(+), 298 deletions(-) diff --git a/tm2/pkg/p2p/mock_test.go b/tm2/pkg/p2p/mock_test.go index 51b73fd666f..d97090b8a97 100644 --- a/tm2/pkg/p2p/mock_test.go +++ b/tm2/pkg/p2p/mock_test.go @@ -1,7 +1,9 @@ package p2p import ( + "log/slog" "net" + "time" "github.com/gnolang/gno/tm2/pkg/p2p/conn" "github.com/gnolang/gno/tm2/pkg/service" @@ -150,3 +152,158 @@ func (m *mockPeer) Get(key string) any { return nil } + +type ( + readDelegate func([]byte) (int, error) + writeDelegate func([]byte) (int, error) + closeDelegate func() error + localAddrDelegate func() net.Addr + setDeadlineDelegate func(time.Time) error +) + +type mockConn struct { + readFn readDelegate + writeFn writeDelegate + closeFn closeDelegate + localAddrFn localAddrDelegate + remoteAddrFn remoteAddrDelegate + setDeadlineFn setDeadlineDelegate + setReadDeadlineFn setDeadlineDelegate + setWriteDeadlineFn setDeadlineDelegate +} + +func (m *mockConn) Read(b []byte) (int, error) { + if m.readFn != nil { + return m.readFn(b) + } + + return 0, nil +} + +func (m *mockConn) Write(b []byte) (int, error) { + if m.writeFn != nil { + return m.writeFn(b) + } + + return 0, nil +} + +func (m *mockConn) Close() error { + if m.closeFn != nil { + return m.closeFn() + } + + return nil +} + +func (m *mockConn) LocalAddr() net.Addr { + if m.localAddrFn != nil { + return m.localAddrFn() + } + + return nil +} + +func (m *mockConn) RemoteAddr() net.Addr { + if m.remoteAddrFn != nil { + return m.remoteAddrFn() + } + + return nil +} + +func (m *mockConn) SetDeadline(t time.Time) error { + if m.setDeadlineFn != nil { + return m.setDeadlineFn(t) + } + + return nil +} + +func (m *mockConn) SetReadDeadline(t time.Time) error { + if m.setReadDeadlineFn != nil { + return m.setReadDeadlineFn(t) + } + + return nil +} + +func (m *mockConn) SetWriteDeadline(t time.Time) error { + if m.setWriteDeadlineFn != nil { + return m.setWriteDeadlineFn(t) + } + + return nil +} + +type ( + startDelegate func() error + stopDelegate func() error + stringDelegate func() string +) + +type mockMConn struct { + flushFn flushStopDelegate + startFn startDelegate + stopFn stopDelegate + sendFn sendDelegate + trySendFn trySendDelegate + statusFn statusDelegate + stringFn stringDelegate +} + +func (m *mockMConn) FlushStop() { + if m.flushFn != nil { + m.flushFn() + } +} + +func (m *mockMConn) Start() error { + if m.startFn != nil { + return m.startFn() + } + + return nil +} + +func (m *mockMConn) Stop() error { + if m.stopFn != nil { + return m.stopFn() + } + + return nil +} + +func (m *mockMConn) Send(ch byte, data []byte) bool { + if m.sendFn != nil { + return m.sendFn(ch, data) + } + + return false +} + +func (m *mockMConn) TrySend(ch byte, data []byte) bool { + if m.trySendFn != nil { + return m.trySendFn(ch, data) + } + + return false +} + +func (m *mockMConn) SetLogger(_ *slog.Logger) {} + +func (m *mockMConn) Status() conn.ConnectionStatus { + if m.statusFn != nil { + return m.statusFn() + } + + return conn.ConnectionStatus{} +} + +func (m *mockMConn) String() string { + if m.stringFn != nil { + return m.stringFn() + } + + return "" +} diff --git a/tm2/pkg/p2p/peer.go b/tm2/pkg/p2p/peer.go index a6df628deae..e227b5fd619 100644 --- a/tm2/pkg/p2p/peer.go +++ b/tm2/pkg/p2p/peer.go @@ -10,6 +10,13 @@ import ( "github.com/gnolang/gno/tm2/pkg/service" ) +type MultiplexConnConfig struct { + MConfig connm.MConnConfig + ReactorsByCh map[byte]Reactor + ChDescs []*connm.ChannelDescriptor + OnPeerError func(Peer, interface{}) +} + // ConnInfo wraps the remote peer connection type ConnInfo struct { Outbound bool // flag indicating if the connection is dialed @@ -19,40 +26,43 @@ type ConnInfo struct { SocketAddr *NetAddress } +type multiplexConn interface { + FlushStop() + Start() error + Stop() error + Send(byte, []byte) bool + TrySend(byte, []byte) bool + SetLogger(*slog.Logger) + Status() connm.ConnectionStatus + String() string +} + // peer is a wrapper for a remote peer // Before using a peer, you will need to perform a handshake on connection. type peer struct { service.BaseService - connInfo *ConnInfo - remoteIP net.IP - mconn *connm.MConnection + connInfo *ConnInfo // Metadata about the connection + nodeInfo NodeInfo // Information about the peer's node + mConn multiplexConn // The multiplexed connection - nodeInfo NodeInfo - data *cmap.CMap + data *cmap.CMap // Arbitrary data store associated with the peer } -// TODO cleanup -func New( +// NewPeer creates an uninitialized peer instance +func NewPeer( connInfo *ConnInfo, - mConfig connm.MConnConfig, nodeInfo NodeInfo, - reactorsByCh map[byte]Reactor, - chDescs []*connm.ChannelDescriptor, - onPeerError func(Peer, interface{}), -) *peer { + mConfig *MultiplexConnConfig, +) Peer { p := &peer{ connInfo: connInfo, nodeInfo: nodeInfo, data: cmap.NewCMap(), } - p.mconn = createMConnection( + p.mConn = p.createMConnection( connInfo.Conn, - p, - reactorsByCh, - chDescs, - onPeerError, mConfig, ) @@ -73,24 +83,48 @@ func (p *peer) RemoteAddr() net.Addr { func (p *peer) String() string { if p.connInfo.Outbound { - return fmt.Sprintf("Peer{%s %s out}", p.mconn, p.ID()) + return fmt.Sprintf("Peer{%s %s out}", p.mConn, p.ID()) } - return fmt.Sprintf("Peer{%s %s in}", p.mconn, p.ID()) + return fmt.Sprintf("Peer{%s %s in}", p.mConn, p.ID()) +} + +// IsOutbound returns true if the connection is outbound, false otherwise. +func (p *peer) IsOutbound() bool { + return p.connInfo.Outbound +} + +// IsPersistent returns true if the peer is persistent, false otherwise. +func (p *peer) IsPersistent() bool { + return p.connInfo.Persistent +} + +// SocketAddr returns the address of the socket. +// For outbound peers, it's the address dialed (after DNS resolution). +// For inbound peers, it's the address returned by the underlying connection +// (not what's reported in the peer's NodeInfo). +func (p *peer) SocketAddr() *NetAddress { + return p.connInfo.SocketAddr +} + +// CloseConn closes original connection. +// Used for cleaning up in cases where the peer had not been started at all. +func (p *peer) CloseConn() error { + return p.connInfo.Conn.Close() } func (p *peer) SetLogger(l *slog.Logger) { p.Logger = l - p.mconn.SetLogger(l) + p.mConn.SetLogger(l) } func (p *peer) OnStart() error { if err := p.BaseService.OnStart(); err != nil { - return err + return fmt.Errorf("unable to start base service, %w", err) } - if err := p.mconn.Start(); err != nil { - return err + if err := p.mConn.Start(); err != nil { + return fmt.Errorf("unable to start multiplex connection, %w", err) } return nil @@ -101,16 +135,16 @@ func (p *peer) OnStart() error { // NOTE: it is not safe to call this method more than once. func (p *peer) FlushStop() { p.BaseService.OnStop() - p.mconn.FlushStop() // stop everything and close the conn + p.mConn.FlushStop() // stop everything and close the conn } // OnStop implements BaseService. func (p *peer) OnStop() { p.BaseService.OnStop() - if err := p.mconn.Stop(); err != nil { + if err := p.mConn.Stop(); err != nil { p.Logger.Error( - "unable to gracefully close mconn", + "unable to gracefully close mConn", "err", err, ) @@ -122,32 +156,14 @@ func (p *peer) ID() ID { return p.nodeInfo.NetAddress.ID } -// IsOutbound returns true if the connection is outbound, false otherwise. -func (p *peer) IsOutbound() bool { - return p.connInfo.Outbound -} - -// IsPersistent returns true if the peer is persistent, false otherwise. -func (p *peer) IsPersistent() bool { - return p.connInfo.Persistent -} - // NodeInfo returns a copy of the peer's NodeInfo. func (p *peer) NodeInfo() NodeInfo { return p.nodeInfo } -// SocketAddr returns the address of the socket. -// For outbound peers, it's the address dialed (after DNS resolution). -// For inbound peers, it's the address returned by the underlying connection -// (not what's reported in the peer's NodeInfo). -func (p *peer) SocketAddr() *NetAddress { - return p.connInfo.SocketAddr -} - // Status returns the peer's ConnectionStatus. func (p *peer) Status() connm.ConnectionStatus { - return p.mconn.Status() + return p.mConn.Status() } // Send msg bytes to the channel identified by chID byte. Returns false if the @@ -159,7 +175,7 @@ func (p *peer) Send(chID byte, msgBytes []byte) bool { return false } - return p.mconn.Send(chID, msgBytes) + return p.mConn.Send(chID, msgBytes) } // TrySend msg bytes to the channel identified by chID byte. Immediately returns @@ -169,16 +185,16 @@ func (p *peer) TrySend(chID byte, msgBytes []byte) bool { return false } - return p.mconn.TrySend(chID, msgBytes) + return p.mConn.TrySend(chID, msgBytes) } // Get the data for a given key. -func (p *peer) Get(key string) interface{} { +func (p *peer) Get(key string) any { return p.data.Get(key) } // Set sets the data for the given key. -func (p *peer) Set(key string, data interface{}) { +func (p *peer) Set(key string, data any) { p.data.Set(key, data) } @@ -190,50 +206,35 @@ func (p *peer) hasChannel(chID byte) bool { return true } } - // NOTE: probably will want to remove this - // but could be helpful while the feature is new - p.Logger.Debug( - "Unknown channel for peer", - "channel", - chID, - "channels", - p.nodeInfo.Channels, - ) - return false -} -// CloseConn closes original connection. Used for cleaning up in cases where the peer had not been started at all. -func (p *peer) CloseConn() error { - return p.connInfo.Conn.Close() + return false } -func createMConnection( +// TODO cover +func (p *peer) createMConnection( conn net.Conn, - p Peer, - reactorsByCh map[byte]Reactor, - chDescs []*connm.ChannelDescriptor, - onPeerError func(Peer, interface{}), - config connm.MConnConfig, + config *MultiplexConnConfig, ) *connm.MConnection { onReceive := func(chID byte, msgBytes []byte) { - reactor := reactorsByCh[chID] + reactor := config.ReactorsByCh[chID] if reactor == nil { // Note that its ok to panic here as it's caught in the connm._recover, // which does onPeerError. panic(fmt.Sprintf("Unknown channel %X", chID)) } + reactor.Receive(chID, p, msgBytes) } - onError := func(r interface{}) { - onPeerError(p, r) + onError := func(r any) { + config.OnPeerError(p, r) } return connm.NewMConnectionWithConfig( conn, - chDescs, + config.ChDescs, onReceive, onError, - config, + config.MConfig, ) } diff --git a/tm2/pkg/p2p/peer_test.go b/tm2/pkg/p2p/peer_test.go index ba95cd25206..9fecced0b46 100644 --- a/tm2/pkg/p2p/peer_test.go +++ b/tm2/pkg/p2p/peer_test.go @@ -1,217 +1,630 @@ package p2p -// func TestPeerBasic(t *testing.T) { -// t.Parallel() -// -// // simulate remote peer -// rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: p2p.cfg} -// rp.Start() -// defer rp.Stop() -// -// p, err := createOutboundPeerAndPerformHandshake(t, rp.Addr(), p2p.cfg, conn.DefaultMConnConfig()) -// require.Nil(err) -// -// err = p.Start() -// require.Nil(err) -// defer p.Stop() -// -// assert.True(p.IsRunning()) -// assert.True(p.IsOutbound()) -// assert.False(p.IsPersistent()) -// p.persistent = true -// assert.True(p.IsPersistent()) -// assert.Equal(rp.Addr().DialString(), p.RemoteAddr().String()) -// assert.Equal(rp.ID(), p.ID()) -// } -// -// func TestPeerSend(t *testing.T) { -// t.Parallel() -// -// assert, require := assert.New(t), require.New(t) -// -// config := p2p.cfg -// -// // simulate remote peer -// rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: config} -// rp.Start() -// defer rp.Stop() -// -// p, err := createOutboundPeerAndPerformHandshake(t, rp.Addr(), config, conn.DefaultMConnConfig()) -// require.Nil(err) -// -// err = p.Start() -// require.Nil(err) -// -// defer p.Stop() -// -// assert.True(p.Send(p2p.testCh, []byte("Asylum"))) -// } -// -// func createOutboundPeerAndPerformHandshake( -// t *testing.T, -// addr *p2p.NetAddress, -// config *config.P2PConfig, -// mConfig conn.MConnConfig, -// ) (*peer, error) { -// t.Helper() -// -// chDescs := []*conn.ChannelDescriptor{ -// {ID: p2p.testCh, Priority: 1}, -// } -// reactorsByCh := map[byte]p2p.Reactor{p2p.testCh: p2p.NewTestReactor(chDescs, true)} -// pk := ed25519.GenPrivKey() -// pc, err := testOutboundPeerConn(addr, config, false, pk) -// if err != nil { -// return nil, err -// } -// timeout := 1 * time.Second -// ourNodeInfo := p2p.testNodeInfo(addr.ID, "host_peer") -// peerNodeInfo, err := p2p.handshake(pc.conn, timeout, ourNodeInfo) -// if err != nil { -// return nil, err -// } -// -// p := newPeer(pc, mConfig, peerNodeInfo, reactorsByCh, chDescs, func(p p2p.Peer, r interface{}) {}) -// p.SetLogger(log.NewTestingLogger(t).With("peer", addr)) -// return p, nil -// } -// -// func testDial(addr *p2p.NetAddress, cfg *config.P2PConfig) (net.Conn, error) { -// conn, err := addr.DialTimeout(cfg.DialTimeout) -// if err != nil { -// return nil, err -// } -// return conn, nil -// } -// -// func testOutboundPeerConn( -// addr *p2p.NetAddress, -// config *config.P2PConfig, -// persistent bool, -// ourNodePrivKey crypto.PrivKey, -// ) (peerConn, error) { -// var pc peerConn -// conn, err := testDial(addr, config) -// if err != nil { -// return pc, errors.Wrap(err, "Error creating peer") -// } -// -// pc, err = p2p.testPeerConn(conn, config, true, persistent, ourNodePrivKey, addr) -// if err != nil { -// if cerr := conn.Close(); cerr != nil { -// return pc, errors.Wrap(err, cerr.Error()) -// } -// return pc, err -// } -// -// // ensure dialed ID matches connection ID -// if addr.ID != pc.ID() { -// if cerr := conn.Close(); cerr != nil { -// return pc, errors.Wrap(err, cerr.Error()) -// } -// return pc, p2p.SwitchAuthenticationFailureError{addr, pc.ID()} -// } -// -// return pc, nil -// } -// -// type remotePeer struct { -// PrivKey crypto.PrivKey -// Config *config.P2PConfig -// addr *p2p.NetAddress -// channels []byte -// listenAddr string -// listener net.Listener -// } -// -// func (rp *remotePeer) Addr() *p2p.NetAddress { -// return rp.addr -// } -// -// func (rp *remotePeer) ID() p2p.ID { -// return rp.PrivKey.PubKey().Address().ID() -// } -// -// func (rp *remotePeer) Start() error { -// if rp.listenAddr == "" { -// rp.listenAddr = "127.0.0.1:0" -// } -// -// l, err := net.Listen("tcp", rp.listenAddr) // any available address -// if err != nil { -// golog.Fatalf("net.Listen tcp :0: %+v", err) -// -// return err -// } -// -// rp.listener = l -// rp.addr, err = p2p.NewNetAddress(rp.PrivKey.PubKey().Address().ID(), l.Addr()) -// if err != nil { -// return err -// } -// -// if rp.channels == nil { -// rp.channels = []byte{p2p.testCh} -// } -// go rp.accept() -// -// return nil -// } -// -// func (rp *remotePeer) Stop() { -// rp.listener.Close() -// } -// -// func (rp *remotePeer) Dial(addr *p2p.NetAddress) (net.Conn, error) { -// conn, err := addr.DialTimeout(1 * time.Second) -// if err != nil { -// return nil, err -// } -// pc, err := p2p.testInboundPeerConn(conn, rp.Config, rp.PrivKey) -// if err != nil { -// return nil, err -// } -// _, err = p2p.handshake(pc.conn, time.Second, rp.nodeInfo()) -// if err != nil { -// return nil, err -// } -// return conn, err -// } -// -// func (rp *remotePeer) accept() { -// conns := []net.Conn{} -// -// for { -// conn, err := rp.listener.Accept() -// if err != nil { -// golog.Printf("Failed to accept conn: %+v", err) -// for _, conn := range conns { -// _ = conn.Close() -// } -// return -// } -// -// pc, err := p2p.testInboundPeerConn(conn, rp.Config, rp.PrivKey) -// if err != nil { -// golog.Fatalf("Failed to create a peer: %+v", err) -// } -// -// _, err = p2p.handshake(pc.conn, time.Second, rp.nodeInfo()) -// if err != nil { -// golog.Fatalf("Failed to perform handshake: %+v", err) -// } -// -// conns = append(conns, conn) -// } -// } -// -// func (rp *remotePeer) nodeInfo() p2p.NodeInfo { -// return p2p.NodeInfo{ -// VersionSet: p2p.testVersionSet(), -// NetAddress: rp.Addr(), -// Network: "testing", -// Version: "1.2.3-rc0-deadbeef", -// Channels: rp.channels, -// Moniker: "remote_peer", -// } -// } +import ( + "errors" + "fmt" + "io" + "log/slog" + "net" + "testing" + "time" + + "github.com/gnolang/gno/tm2/pkg/cmap" + "github.com/gnolang/gno/tm2/pkg/p2p/config" + "github.com/gnolang/gno/tm2/pkg/p2p/conn" + "github.com/gnolang/gno/tm2/pkg/service" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPeer_Properties(t *testing.T) { + t.Parallel() + + t.Run("connection info", func(t *testing.T) { + t.Parallel() + + t.Run("remote IP", func(t *testing.T) { + t.Parallel() + + var ( + info = &ConnInfo{ + RemoteIP: net.IP{127, 0, 0, 1}, + } + + p = &peer{ + connInfo: info, + } + ) + + assert.Equal(t, info.RemoteIP, p.RemoteIP()) + }) + + t.Run("remote address", func(t *testing.T) { + t.Parallel() + + tcpAddr, err := net.ResolveTCPAddr("tcp", "localhost:8080") + require.NoError(t, err) + + var ( + info = &ConnInfo{ + Conn: &mockConn{ + remoteAddrFn: func() net.Addr { + return tcpAddr + }, + }, + } + + p = &peer{ + connInfo: info, + } + ) + + assert.Equal(t, tcpAddr.String(), p.RemoteAddr().String()) + }) + + t.Run("socket address", func(t *testing.T) { + t.Parallel() + + tcpAddr, err := net.ResolveTCPAddr("tcp", "localhost:8080") + require.NoError(t, err) + + netAddr, err := NewNetAddress(GenerateNodeKey().ID(), tcpAddr) + require.NoError(t, err) + + var ( + info = &ConnInfo{ + SocketAddr: netAddr, + } + + p = &peer{ + connInfo: info, + } + ) + + assert.Equal(t, netAddr.String(), p.SocketAddr().String()) + }) + + t.Run("set logger", func(t *testing.T) { + t.Parallel() + + var ( + l = slog.New(slog.NewTextHandler(io.Discard, nil)) + + p = &peer{ + mConn: &mockMConn{}, + } + ) + + p.SetLogger(l) + + assert.Equal(t, l, p.Logger) + }) + + t.Run("peer start", func(t *testing.T) { + t.Parallel() + + var ( + expectedErr = errors.New("some error") + + mConn = &mockMConn{ + startFn: func() error { + return expectedErr + }, + } + + p = &peer{ + mConn: mConn, + } + ) + + assert.ErrorIs(t, p.OnStart(), expectedErr) + }) + + t.Run("peer stop", func(t *testing.T) { + t.Parallel() + + var ( + stopCalled = false + expectedErr = errors.New("some error") + + mConn = &mockMConn{ + stopFn: func() error { + stopCalled = true + + return expectedErr + }, + } + + p = &peer{ + mConn: mConn, + } + ) + + p.BaseService = *service.NewBaseService(nil, "Peer", p) + + p.OnStop() + + assert.True(t, stopCalled) + }) + + t.Run("flush stop", func(t *testing.T) { + t.Parallel() + + var ( + stopCalled = false + + mConn = &mockMConn{ + flushFn: func() { + stopCalled = true + }, + } + + p = &peer{ + mConn: mConn, + } + ) + + p.BaseService = *service.NewBaseService(nil, "Peer", p) + + p.FlushStop() + + assert.True(t, stopCalled) + }) + + t.Run("node info fetch", func(t *testing.T) { + t.Parallel() + + var ( + info = NodeInfo{ + Network: "gnoland", + } + + p = &peer{ + nodeInfo: info, + } + ) + + assert.Equal(t, info, p.NodeInfo()) + }) + + t.Run("node status fetch", func(t *testing.T) { + t.Parallel() + + var ( + status = conn.ConnectionStatus{ + Duration: 5 * time.Second, + } + + mConn = &mockMConn{ + statusFn: func() conn.ConnectionStatus { + return status + }, + } + + p = &peer{ + mConn: mConn, + } + ) + + assert.Equal(t, status, p.Status()) + }) + + t.Run("string representation", func(t *testing.T) { + t.Parallel() + + testTable := []struct { + name string + outbound bool + }{ + { + "outbound", + true, + }, + { + "inbound", + false, + }, + } + + for _, testCase := range testTable { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + var ( + id = GenerateNodeKey().ID() + mConnStr = "description" + + p = &peer{ + mConn: &mockMConn{ + stringFn: func() string { + return mConnStr + }, + }, + nodeInfo: NodeInfo{ + NetAddress: &NetAddress{ + ID: id, + }, + }, + connInfo: &ConnInfo{ + Outbound: testCase.outbound, + }, + } + + direction = "in" + ) + + if testCase.outbound { + direction = "out" + } + + assert.Contains( + t, + p.String(), + fmt.Sprintf( + "Peer{%s %s %s}", + mConnStr, + id, + direction, + ), + ) + }) + } + }) + + t.Run("outbound information", func(t *testing.T) { + t.Parallel() + + p := &peer{ + connInfo: &ConnInfo{ + Outbound: true, + }, + } + + assert.True( + t, + p.IsOutbound(), + ) + }) + + t.Run("persistent information", func(t *testing.T) { + t.Parallel() + + p := &peer{ + connInfo: &ConnInfo{ + Persistent: true, + }, + } + + assert.True(t, p.IsPersistent()) + }) + + t.Run("initial conn close", func(t *testing.T) { + t.Parallel() + + var ( + closeErr = errors.New("close error") + + mockConn = &mockConn{ + closeFn: func() error { + return closeErr + }, + } + + p = &peer{ + connInfo: &ConnInfo{ + Conn: mockConn, + }, + } + ) + + assert.ErrorIs(t, p.CloseConn(), closeErr) + }) + }) +} + +func TestPeer_GetSet(t *testing.T) { + t.Parallel() + + var ( + key = "key" + data = []byte("random") + + p = &peer{ + data: cmap.NewCMap(), + } + ) + + assert.Nil(t, p.Get(key)) + + // Set the key + p.Set(key, data) + + assert.Equal(t, data, p.Get(key)) +} + +func TestPeer_Send(t *testing.T) { + t.Parallel() + + t.Run("peer not running", func(t *testing.T) { + t.Parallel() + + var ( + chID = byte(10) + data = []byte("random") + + capturedSendID byte + capturedSendData []byte + + mockConn = &mockMConn{ + sendFn: func(c byte, d []byte) bool { + capturedSendID = c + capturedSendData = d + + return true + }, + } + + p = &peer{ + nodeInfo: NodeInfo{ + Channels: []byte{ + chID, + }, + }, + mConn: mockConn, + } + ) + + p.BaseService = *service.NewBaseService(nil, "Peer", p) + + // Make sure the send fails + require.False(t, p.Send(chID, data)) + + assert.Empty(t, capturedSendID) + assert.Nil(t, capturedSendData) + }) + + t.Run("peer doesn't have channel", func(t *testing.T) { + t.Parallel() + + var ( + chID = byte(10) + data = []byte("random") + + capturedSendID byte + capturedSendData []byte + + mockConn = &mockMConn{ + sendFn: func(c byte, d []byte) bool { + capturedSendID = c + capturedSendData = d + + return true + }, + } + + p = &peer{ + nodeInfo: NodeInfo{ + Channels: []byte{}, + }, + mConn: mockConn, + } + ) + + p.BaseService = *service.NewBaseService(nil, "Peer", p) + + // Start the peer "multiplexing" + require.NoError(t, p.Start()) + t.Cleanup(func() { + require.NoError(t, p.Stop()) + }) + + // Make sure the send fails + require.False(t, p.Send(chID, data)) + + assert.Empty(t, capturedSendID) + assert.Nil(t, capturedSendData) + }) + + t.Run("valid peer data send", func(t *testing.T) { + t.Parallel() + + var ( + chID = byte(10) + data = []byte("random") + + capturedSendID byte + capturedSendData []byte + + mockConn = &mockMConn{ + sendFn: func(c byte, d []byte) bool { + capturedSendID = c + capturedSendData = d + + return true + }, + } + + p = &peer{ + nodeInfo: NodeInfo{ + Channels: []byte{ + chID, + }, + }, + mConn: mockConn, + } + ) + + p.BaseService = *service.NewBaseService(nil, "Peer", p) + + // Start the peer "multiplexing" + require.NoError(t, p.Start()) + t.Cleanup(func() { + require.NoError(t, p.Stop()) + }) + + // Make sure the send is valid + require.True(t, p.Send(chID, data)) + + assert.Equal(t, chID, capturedSendID) + assert.Equal(t, data, capturedSendData) + }) +} + +func TestPeer_TrySend(t *testing.T) { + t.Parallel() + + t.Run("peer not running", func(t *testing.T) { + t.Parallel() + + var ( + chID = byte(10) + data = []byte("random") + + capturedSendID byte + capturedSendData []byte + + mockConn = &mockMConn{ + trySendFn: func(c byte, d []byte) bool { + capturedSendID = c + capturedSendData = d + + return true + }, + } + + p = &peer{ + nodeInfo: NodeInfo{ + Channels: []byte{ + chID, + }, + }, + mConn: mockConn, + } + ) + + p.BaseService = *service.NewBaseService(nil, "Peer", p) + + // Make sure the send fails + require.False(t, p.TrySend(chID, data)) + + assert.Empty(t, capturedSendID) + assert.Nil(t, capturedSendData) + }) + + t.Run("peer doesn't have channel", func(t *testing.T) { + t.Parallel() + + var ( + chID = byte(10) + data = []byte("random") + + capturedSendID byte + capturedSendData []byte + + mockConn = &mockMConn{ + trySendFn: func(c byte, d []byte) bool { + capturedSendID = c + capturedSendData = d + + return true + }, + } + + p = &peer{ + nodeInfo: NodeInfo{ + Channels: []byte{}, + }, + mConn: mockConn, + } + ) + + p.BaseService = *service.NewBaseService(nil, "Peer", p) + + // Start the peer "multiplexing" + require.NoError(t, p.Start()) + t.Cleanup(func() { + require.NoError(t, p.Stop()) + }) + + // Make sure the send fails + require.False(t, p.TrySend(chID, data)) + + assert.Empty(t, capturedSendID) + assert.Nil(t, capturedSendData) + }) + + t.Run("valid peer data send", func(t *testing.T) { + t.Parallel() + + var ( + chID = byte(10) + data = []byte("random") + + capturedSendID byte + capturedSendData []byte + + mockConn = &mockMConn{ + trySendFn: func(c byte, d []byte) bool { + capturedSendID = c + capturedSendData = d + + return true + }, + } + + p = &peer{ + nodeInfo: NodeInfo{ + Channels: []byte{ + chID, + }, + }, + mConn: mockConn, + } + ) + + p.BaseService = *service.NewBaseService(nil, "Peer", p) + + // Start the peer "multiplexing" + require.NoError(t, p.Start()) + t.Cleanup(func() { + require.NoError(t, p.Stop()) + }) + + // Make sure the send is valid + require.True(t, p.TrySend(chID, data)) + + assert.Equal(t, chID, capturedSendID) + assert.Equal(t, data, capturedSendData) + }) +} + +func TestPeer_NewPeer(t *testing.T) { + t.Parallel() + + tcpAddr, err := net.ResolveTCPAddr("tcp", "localhost:8080") + require.NoError(t, err) + + netAddr, err := NewNetAddress(GenerateNodeKey().ID(), tcpAddr) + require.NoError(t, err) + + var ( + connInfo = &ConnInfo{ + Outbound: false, + Persistent: true, + Conn: &mockConn{}, + RemoteIP: tcpAddr.IP, + SocketAddr: netAddr, + } + + mConfig = &MultiplexConnConfig{ + MConfig: MConnConfig(config.DefaultP2PConfig()), + ReactorsByCh: make(map[byte]Reactor), + ChDescs: make([]*conn.ChannelDescriptor, 0), + OnPeerError: nil, + } + ) + + assert.NotPanics(t, func() { + _ = NewPeer(connInfo, NodeInfo{}, mConfig) + }) +} diff --git a/tm2/pkg/p2p/transport.go b/tm2/pkg/p2p/transport.go index 9168dafd4a7..ea9611846fe 100644 --- a/tm2/pkg/p2p/transport.go +++ b/tm2/pkg/p2p/transport.go @@ -517,16 +517,14 @@ func (mt *MultiplexTransport) wrapPeer( SocketAddr: socketAddr, } - p := New( - peerConn, - mt.mConfig, - ni, - cfg.reactorsByCh, - cfg.chDescs, - cfg.onPeerError, - ) + mConfig := &MultiplexConnConfig{ + MConfig: mt.mConfig, + ReactorsByCh: cfg.reactorsByCh, + ChDescs: cfg.chDescs, + OnPeerError: cfg.onPeerError, + } - return p + return NewPeer(peerConn, ni, mConfig) } func handshake( From 853607438d60ba539547633f18a73b1bc96b25bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Thu, 3 Oct 2024 18:27:46 +0200 Subject: [PATCH 10/43] Save progress on switch --- tm2/pkg/bft/blockchain/reactor.go | 5 +- tm2/pkg/bft/node/node.go | 9 +- tm2/pkg/p2p/config/config.go | 4 +- tm2/pkg/p2p/peer.go | 1 - tm2/pkg/p2p/peer_test.go | 2 +- tm2/pkg/p2p/set.go | 113 +++++-- tm2/pkg/p2p/switch.go | 448 +++++++++++---------------- tm2/pkg/p2p/switch_option.go | 22 ++ tm2/pkg/p2p/switch_test.go | 8 +- tm2/pkg/p2p/test_util.go | 4 +- tm2/pkg/p2p/transport.go | 13 +- tm2/pkg/p2p/transport_test.go | 2 +- tm2/pkg/p2p/types.go | 7 +- tm2/pkg/telemetry/metrics/metrics.go | 8 +- 14 files changed, 314 insertions(+), 332 deletions(-) create mode 100644 tm2/pkg/p2p/switch_option.go diff --git a/tm2/pkg/bft/blockchain/reactor.go b/tm2/pkg/bft/blockchain/reactor.go index 09e1225b717..0fd0cebfdbf 100644 --- a/tm2/pkg/bft/blockchain/reactor.go +++ b/tm2/pkg/bft/blockchain/reactor.go @@ -257,9 +257,8 @@ FOR_LOOP: select { case <-switchToConsensusTicker.C: height, numPending, lenRequesters := bcR.pool.GetStatus() - outbound, inbound, _ := bcR.Switch.NumPeers() - bcR.Logger.Debug("Consensus ticker", "numPending", numPending, "total", lenRequesters, - "outbound", outbound, "inbound", inbound) + + bcR.Logger.Debug("Consensus ticker", "numPending", numPending, "total", lenRequesters) if bcR.pool.IsCaughtUp() { bcR.Logger.Info("Time to switch to consensus reactor!", "height", height) bcR.pool.Stop() diff --git a/tm2/pkg/bft/node/node.go b/tm2/pkg/bft/node/node.go index 779998e1885..21bcd1223b2 100644 --- a/tm2/pkg/bft/node/node.go +++ b/tm2/pkg/bft/node/node.go @@ -333,7 +333,7 @@ func createConsensusReactor(config *cfg.Config, func createTransport(config *cfg.Config, nodeInfo p2p.NodeInfo, nodeKey *p2p.NodeKey, proxyApp appconn.AppConns) (*p2p.MultiplexTransport, []p2p.PeerFilterFunc) { var ( - mConnConfig = p2p.MConnConfig(config.P2P) + mConnConfig = p2p.MultiplexConfigFromP2P(config.P2P) transport = p2p.NewMultiplexTransport(nodeInfo, *nodeKey, mConnConfig) connFilters = []p2p.ConnFilterFunc{} peerFilters = []p2p.PeerFilterFunc{} @@ -400,16 +400,15 @@ func createSwitch(config *cfg.Config, sw := p2p.NewSwitch( config.P2P, transport, - p2p.SwitchPeerFilters(peerFilters...), + p2p.WithPeerFilters(peerFilters...), + p2p.WithNodeInfo(nodeInfo), + p2p.WithNodeKey(nodeKey), ) sw.SetLogger(p2pLogger) sw.AddReactor("MEMPOOL", mempoolReactor) sw.AddReactor("BLOCKCHAIN", bcReactor) sw.AddReactor("CONSENSUS", consensusReactor) - sw.SetNodeInfo(nodeInfo) - sw.SetNodeKey(nodeKey) - p2pLogger.Info("P2P Node ID", "ID", nodeKey.ID(), "file", config.NodeKeyFile()) return sw } diff --git a/tm2/pkg/p2p/config/config.go b/tm2/pkg/p2p/config/config.go index ce145fd2e73..d13ccca04a4 100644 --- a/tm2/pkg/p2p/config/config.go +++ b/tm2/pkg/p2p/config/config.go @@ -26,10 +26,10 @@ type P2PConfig struct { UPNP bool `json:"upnp" toml:"upnp" comment:"UPNP port forwarding"` // Maximum number of inbound peers - MaxNumInboundPeers int `json:"max_num_inbound_peers" toml:"max_num_inbound_peers" comment:"Maximum number of inbound peers"` + MaxNumInboundPeers uint64 `json:"max_num_inbound_peers" toml:"max_num_inbound_peers" comment:"Maximum number of inbound peers"` // Maximum number of outbound peers to connect to, excluding persistent peers - MaxNumOutboundPeers int `json:"max_num_outbound_peers" toml:"max_num_outbound_peers" comment:"Maximum number of outbound peers to connect to, excluding persistent peers"` + MaxNumOutboundPeers uint64 `json:"max_num_outbound_peers" toml:"max_num_outbound_peers" comment:"Maximum number of outbound peers to connect to, excluding persistent peers"` // Time to wait before flushing messages out on the connection FlushThrottleTimeout time.Duration `json:"flush_throttle_timeout" toml:"flush_throttle_timeout" comment:"Time to wait before flushing messages out on the connection"` diff --git a/tm2/pkg/p2p/peer.go b/tm2/pkg/p2p/peer.go index e227b5fd619..f43f39f0b65 100644 --- a/tm2/pkg/p2p/peer.go +++ b/tm2/pkg/p2p/peer.go @@ -210,7 +210,6 @@ func (p *peer) hasChannel(chID byte) bool { return false } -// TODO cover func (p *peer) createMConnection( conn net.Conn, config *MultiplexConnConfig, diff --git a/tm2/pkg/p2p/peer_test.go b/tm2/pkg/p2p/peer_test.go index 9fecced0b46..77a40862935 100644 --- a/tm2/pkg/p2p/peer_test.go +++ b/tm2/pkg/p2p/peer_test.go @@ -617,7 +617,7 @@ func TestPeer_NewPeer(t *testing.T) { } mConfig = &MultiplexConnConfig{ - MConfig: MConnConfig(config.DefaultP2PConfig()), + MConfig: MultiplexConfigFromP2P(config.DefaultP2PConfig()), ReactorsByCh: make(map[byte]Reactor), ChDescs: make([]*conn.ChannelDescriptor, 0), OnPeerError: nil, diff --git a/tm2/pkg/p2p/set.go b/tm2/pkg/p2p/set.go index 1b796d5ae72..53dc060a691 100644 --- a/tm2/pkg/p2p/set.go +++ b/tm2/pkg/p2p/set.go @@ -6,91 +6,138 @@ import ( ) type Set struct { - peers sync.Map // p2p.ID -> p2p.Peer + mux sync.RWMutex + + peers map[ID]Peer + outbound uint64 + inbound uint64 } // NewSet creates an empty peer set func NewSet() *Set { - return &Set{} + return &Set{ + peers: make(map[ID]Peer), + outbound: 0, + inbound: 0, + } } // Add adds the peer to the set func (s *Set) Add(peer Peer) { - s.peers.Store(peer.ID(), peer) + s.mux.Lock() + defer s.mux.Unlock() + + s.peers[peer.ID()] = peer + + if peer.IsOutbound() { + s.outbound += 1 + + return + } + + s.inbound += 1 } // Has returns true if the set contains the peer referred to by this // peerKey, otherwise false. func (s *Set) Has(peerKey ID) bool { - _, ok := s.peers.Load(peerKey) + s.mux.RLock() + defer s.mux.RUnlock() - return ok + _, exists := s.peers[peerKey] + + return exists } // HasIP returns true if the set contains the peer referred to by this IP // address, otherwise false. func (s *Set) HasIP(peerIP net.IP) bool { - hasIP := false - - s.peers.Range(func(_, value interface{}) bool { - peer := value.(Peer) + s.mux.RLock() + defer s.mux.RUnlock() - if peer.RemoteIP().Equal(peerIP) { - hasIP = true - - return false + for _, p := range s.peers { + if p.(Peer).RemoteIP().Equal(peerIP) { + return true } + } - return true - }) - - return hasIP + return false } // Get looks up a peer by the provided peerKey. Returns nil if peer is not // found. func (s *Set) Get(key ID) Peer { - peerRaw, found := s.peers.Load(key) + s.mux.RLock() + defer s.mux.RUnlock() + + p, found := s.peers[key] if !found { // TODO change this to an error, it doesn't make // sense to propagate an implementation detail like this return nil } - return peerRaw.(Peer) + return p.(Peer) } // Remove discards peer by its Key, if the peer was previously memoized. // Returns true if the peer was removed, and false if it was not found. // in the set. func (s *Set) Remove(key ID) bool { - _, existed := s.peers.LoadAndDelete(key) + s.mux.Lock() + defer s.mux.Unlock() + + p, found := s.peers[key] + if !found { + return false + } - return existed + delete(s.peers, key) + + if p.(Peer).IsOutbound() { + s.outbound -= 1 + + return true + } + + s.inbound -= 1 + + return true } // Size returns the number of unique peers in the peer table func (s *Set) Size() int { - size := 0 + s.mux.RLock() + defer s.mux.RUnlock() - s.peers.Range(func(_, _ any) bool { - size++ + return len(s.peers) +} - return true - }) +// NumInbound returns the number of inbound peers +func (s *Set) NumInbound() uint64 { + s.mux.RLock() + defer s.mux.RUnlock() - return size + return s.inbound +} + +// NumOutbound returns the number of outbound peers +func (s *Set) NumOutbound() uint64 { + s.mux.RLock() + defer s.mux.RUnlock() + + return s.outbound } // List returns the list of peers func (s *Set) List() []Peer { - peers := make([]Peer, 0) + s.mux.RLock() + defer s.mux.RUnlock() - s.peers.Range(func(_, value any) bool { - peers = append(peers, value.(Peer)) - - return true - }) + peers := make([]Peer, 0) + for _, p := range s.peers { + peers = append(peers, p.(Peer)) + } return peers } diff --git a/tm2/pkg/p2p/switch.go b/tm2/pkg/p2p/switch.go index e9c343d4e0a..11138524fbe 100644 --- a/tm2/pkg/p2p/switch.go +++ b/tm2/pkg/p2p/switch.go @@ -2,19 +2,20 @@ package p2p import ( "context" + "crypto/rand" "fmt" "math" + "math/big" "sync" "time" "github.com/gnolang/gno/tm2/pkg/cmap" - "github.com/gnolang/gno/tm2/pkg/errors" "github.com/gnolang/gno/tm2/pkg/p2p/config" "github.com/gnolang/gno/tm2/pkg/p2p/conn" - "github.com/gnolang/gno/tm2/pkg/random" "github.com/gnolang/gno/tm2/pkg/service" "github.com/gnolang/gno/tm2/pkg/telemetry" "github.com/gnolang/gno/tm2/pkg/telemetry/metrics" + "golang.org/x/sync/errgroup" ) const ( @@ -33,9 +34,9 @@ const ( reconnectBackOffBaseSeconds = 3 ) -// MConnConfig returns an MConnConfig with fields updated -// from the P2PConfig. -func MConnConfig(cfg *config.P2PConfig) conn.MConnConfig { +// MultiplexConfigFromP2P returns a multiplex connection configuration +// with fields updated from the P2PConfig +func MultiplexConfigFromP2P(cfg *config.P2PConfig) conn.MConnConfig { mConfig := conn.DefaultMConnConfig() mConfig.FlushThrottle = cfg.FlushThrottleTimeout mConfig.SendRate = cfg.SendRate @@ -48,8 +49,6 @@ func MConnConfig(cfg *config.P2PConfig) conn.MConnConfig { // fully setup. type PeerFilterFunc func(PeerSet, Peer) error -// ----------------------------------------------------------------------------- - // Switch handles peer connections and exposes an API to receive incoming messages // on `Reactors`. Each `Reactor` is responsible for handling incoming messages of one // or more `Channels`. So while sending outgoing messages is typically performed on the peer, @@ -57,29 +56,29 @@ type PeerFilterFunc func(PeerSet, Peer) error type Switch struct { service.BaseService - config *config.P2PConfig + config *config.P2PConfig // TODO remove this dependency reactors map[string]Reactor chDescs []*conn.ChannelDescriptor reactorsByCh map[byte]Reactor - peers PeerSet + dialing *cmap.CMap reconnecting *cmap.CMap - nodeInfo NodeInfo // our node info - nodeKey *NodeKey // our node privkey - // peers addresses with whom we'll maintain constant connection - persistentPeersAddrs []*NetAddress - transport Transport + nodeInfo NodeInfo // our node info + nodeKey *NodeKey // our node privkey + + peers PeerSet // currently active peer set + persistentPeers sync.Map // ID -> *NetAddress; peers whose connections are constant + transport Transport filterTimeout time.Duration peerFilters []PeerFilterFunc - - rng *random.Rand // seed for randomizing dial times and orders } // NetAddress returns the address the switch is listening on. func (sw *Switch) NetAddress() *NetAddress { addr := sw.transport.NetAddress() + return &addr } @@ -93,21 +92,17 @@ func NewSwitch( options ...SwitchOption, ) *Switch { sw := &Switch{ - config: cfg, - reactors: make(map[string]Reactor), - chDescs: make([]*conn.ChannelDescriptor, 0), - reactorsByCh: make(map[byte]Reactor), - peers: NewSet(), - dialing: cmap.NewCMap(), - reconnecting: cmap.NewCMap(), - transport: transport, - filterTimeout: defaultFilterTimeout, - persistentPeersAddrs: make([]*NetAddress, 0), + config: cfg, + reactors: make(map[string]Reactor), + chDescs: make([]*conn.ChannelDescriptor, 0), + reactorsByCh: make(map[byte]Reactor), + peers: NewSet(), + dialing: cmap.NewCMap(), + reconnecting: cmap.NewCMap(), + transport: transport, + filterTimeout: defaultFilterTimeout, } - // Ensure we have a completely undeterministic PRNG. - sw.rng = random.NewRand() - sw.BaseService = *service.NewBaseService(nil, "P2P Switch", sw) for _, option := range options { @@ -117,19 +112,6 @@ func NewSwitch( return sw } -// SwitchFilterTimeout sets the timeout used for peer filters. -func SwitchFilterTimeout(timeout time.Duration) SwitchOption { - return func(sw *Switch) { sw.filterTimeout = timeout } -} - -// SwitchPeerFilters sets the filters for rejection of new peers. -func SwitchPeerFilters(filters ...PeerFilterFunc) SwitchOption { - return func(sw *Switch) { sw.peerFilters = filters } -} - -// --------------------------------------------------------------------- -// Switch setup - // AddReactor adds the given reactor to the switch. // NOTE: Not goroutine safe. func (sw *Switch) AddReactor(name string, reactor Reactor) Reactor { @@ -139,11 +121,15 @@ func (sw *Switch) AddReactor(name string, reactor Reactor) Reactor { if sw.reactorsByCh[chID] != nil { panic(fmt.Sprintf("Channel %X has multiple reactors %v & %v", chID, sw.reactorsByCh[chID], reactor)) } + sw.chDescs = append(sw.chDescs, chDesc) sw.reactorsByCh[chID] = reactor } + sw.reactors[name] = reactor + reactor.SetSwitch(sw) + return reactor } @@ -158,16 +144,13 @@ func (sw *Switch) RemoveReactor(name string, reactor Reactor) { break } } + delete(sw.reactorsByCh, chDesc.ID) } + delete(sw.reactors, name) - reactor.SetSwitch(nil) -} -// Reactors returns a map of reactors registered on the switch. -// NOTE: Not goroutine safe. -func (sw *Switch) Reactors() map[string]Reactor { - return sw.reactors + reactor.SetSwitch(nil) } // Reactor returns the reactor with the given name. @@ -176,24 +159,12 @@ func (sw *Switch) Reactor(name string) Reactor { return sw.reactors[name] } -// SetNodeInfo sets the switch's NodeInfo for checking compatibility and handshaking with other nodes. -// NOTE: Not goroutine safe. -func (sw *Switch) SetNodeInfo(nodeInfo NodeInfo) { - sw.nodeInfo = nodeInfo -} - // NodeInfo returns the switch's NodeInfo. // NOTE: Not goroutine safe. func (sw *Switch) NodeInfo() NodeInfo { return sw.nodeInfo } -// SetNodeKey sets the switch's private key for authenticated encryption. -// NOTE: Not goroutine safe. -func (sw *Switch) SetNodeKey(nodeKey *NodeKey) { - sw.nodeKey = nodeKey -} - // --------------------------------------------------------------------- // Service start/stop @@ -201,14 +172,14 @@ func (sw *Switch) SetNodeKey(nodeKey *NodeKey) { func (sw *Switch) OnStart() error { // Start reactors for _, reactor := range sw.reactors { - err := reactor.Start() - if err != nil { - return errors.Wrap(err, "failed to start %v", reactor) + if err := reactor.Start(); err != nil { + return fmt.Errorf("unable to start reactor %w", err) } } - // Start accepting Peers. - go sw.acceptRoutine() + // Run the peer accept routine + // TODO propagate ctx down + go sw.runAcceptLoop(context.Background()) return nil } @@ -216,11 +187,8 @@ func (sw *Switch) OnStart() error { // OnStop implements BaseService. It stops all peers and reactors. func (sw *Switch) OnStop() { // Stop transport - if t, ok := sw.transport.(TransportLifecycle); ok { - err := t.Close() - if err != nil { - sw.Logger.Error("Error stopping transport on stop: ", "err", err) - } + if err := sw.transport.Close(); err != nil { + sw.Logger.Error("unable to gracefully close transport", "err", err) } // Stop peers @@ -229,71 +197,40 @@ func (sw *Switch) OnStop() { } // Stop reactors - sw.Logger.Debug("Switch: Stopping reactors") for _, reactor := range sw.reactors { - reactor.Stop() + if err := reactor.Stop(); err != nil { + sw.Logger.Error("unable to gracefully stop reactor", "err", err) + } } } -// --------------------------------------------------------------------- -// Peers - -// Broadcast runs a go routine for each attempted send, which will block trying -// to send for defaultSendTimeoutSeconds. Returns a channel which receives -// success values for each attempted send (false if times out). Channel will be -// closed once msg bytes are sent to all peers (or time out). -// -// NOTE: Broadcast uses goroutines, so order of broadcast may not be preserved. -func (sw *Switch) Broadcast(chID byte, msgBytes []byte) chan bool { - startTime := time.Now() - - sw.Logger.Debug( - "Broadcast", - "channel", chID, - "value", fmt.Sprintf("%X", msgBytes), - ) - - peers := sw.peers.List() +// Broadcast broadcasts the given data to the given channel, across the +// entire switch peer set +func (sw *Switch) Broadcast(chID byte, data []byte) { var wg sync.WaitGroup - wg.Add(len(peers)) - successChan := make(chan bool, len(peers)) - for _, peer := range peers { - go func(p Peer) { - defer wg.Done() - success := p.Send(chID, msgBytes) - successChan <- success - }(peer) - } - - go func() { - wg.Wait() - close(successChan) - if telemetry.MetricsEnabled() { - metrics.BroadcastTxTimer.Record(context.Background(), time.Since(startTime).Milliseconds()) - } - }() + for _, p := range sw.peers.List() { + wg.Add(1) - return successChan -} + go func() { + defer wg.Done() -// NumPeers returns the count of outbound/inbound and outbound-dialing peers. -func (sw *Switch) NumPeers() (outbound, inbound, dialing int) { - peers := sw.peers.List() - for _, peer := range peers { - if peer.IsOutbound() { - outbound++ - } else { - inbound++ - } + // TODO propagate the context, instead of relying + // on the underlying multiplex conn + if !p.Send(chID, data) { + sw.Logger.Error( + "unable to perform broadcast, channel ID %X, peer ID %s", + chID, p.ID(), + ) + } + }() } - dialing = sw.dialing.Size() - return -} -// MaxNumOutboundPeers returns a maximum number of outbound peers. -func (sw *Switch) MaxNumOutboundPeers() int { - return sw.config.MaxNumOutboundPeers + // Wait for all the sends to complete, + // at the mercy of the multiplex connection + // send routine :) + // TODO: I'm not sure Broadcast should be blocking, at all + wg.Wait() } // Peers returns the set of peers that are connected to the switch. @@ -319,13 +256,6 @@ func (sw *Switch) StopPeerForError(peer Peer, reason interface{}) { } } -// StopPeerGracefully disconnects from a peer gracefully. -// TODO: handle graceful disconnects. -func (sw *Switch) StopPeerGracefully(peer Peer) { - sw.Logger.Info("Stopping peer gracefully") - sw.stopAndRemovePeer(peer, nil) -} - func (sw *Switch) stopAndRemovePeer(peer Peer, reason interface{}) { sw.transport.Cleanup(peer) peer.Stop() @@ -361,7 +291,7 @@ func (sw *Switch) reconnectToPeer(addr *NetAddress) { return } - err := sw.DialPeerWithAddress(addr) + err := sw.dialPeerWithAddress(addr) if err == nil { return // success } else if _, ok := err.(CurrentlyDialingOrExistingAddressError); ok { @@ -385,7 +315,7 @@ func (sw *Switch) reconnectToPeer(addr *NetAddress) { sleepIntervalSeconds := math.Pow(reconnectBackOffBaseSeconds, float64(i)) sw.randomSleep(time.Duration(sleepIntervalSeconds) * time.Second) - err := sw.DialPeerWithAddress(addr) + err := sw.dialPeerWithAddress(addr) if err == nil { return // success } else if _, ok := err.(CurrentlyDialingOrExistingAddressError); ok { @@ -422,59 +352,72 @@ func (sw *Switch) DialPeersAsync(peers []string) error { } func (sw *Switch) dialPeersAsync(netAddrs []*NetAddress) { - ourAddr := sw.NetAddress() + var ( + ourAddr = sw.NetAddress() - // permute the list, dial them in random order. - perm := sw.rng.Perm(len(netAddrs)) - for i := 0; i < len(perm); i++ { - go func(i int) { - j := perm[i] - addr := netAddrs[j] + wg sync.WaitGroup + ) + + wg.Add(len(netAddrs)) + + for _, peerAddr := range netAddrs { + go func(addr *NetAddress) { + defer wg.Done() if addr.Same(ourAddr) { - sw.Logger.Debug("Ignore attempt to connect to ourselves", "addr", addr, "ourAddr", ourAddr) + sw.Logger.Debug( + "ignoring self-dial attempt", + "addr", + addr, + ) + return } - sw.randomSleep(0) + sw.randomSleep(0) // TODO remove this - err := sw.DialPeerWithAddress(addr) - if err != nil { - switch err.(type) { - case SwitchConnectToSelfError, SwitchDuplicatePeerIDError, CurrentlyDialingOrExistingAddressError: - sw.Logger.Debug("Error dialing peer", "err", err) - default: - sw.Logger.Error("Error dialing peer", "err", err) - } + if err := sw.dialPeerWithAddress(addr); err != nil { + sw.Logger.Debug("Error dialing peer", "err", err) } - }(i) + }(peerAddr) } + + wg.Wait() } -// DialPeerWithAddress dials the given peer and runs sw.addPeer if it connects +// dialPeerWithAddress dials the given peer and runs sw.addPeer if it connects // and authenticates successfully. // If we're currently dialing this address or it belongs to an existing peer, // CurrentlyDialingOrExistingAddressError is returned. -func (sw *Switch) DialPeerWithAddress(addr *NetAddress) error { - if sw.IsDialingOrExistingAddress(addr) { +func (sw *Switch) dialPeerWithAddress(addr *NetAddress) error { + if sw.isDialingOrExistingAddress(addr) { return CurrentlyDialingOrExistingAddressError{addr.String()} } + // TODO clean up sw.dialing.Set(addr.ID.String(), addr) defer sw.dialing.Delete(addr.ID.String()) - return sw.addOutboundPeerWithConfig(addr, sw.config) + return sw.addOutboundPeerWithConfig(addr) } // sleep for interval plus some random amount of ms on [0, dialRandomizerIntervalMilliseconds] func (sw *Switch) randomSleep(interval time.Duration) { - r := time.Duration(sw.rng.Int63n(dialRandomizerIntervalMilliseconds)) * time.Millisecond - time.Sleep(r + interval) + r, err := rand.Int(rand.Reader, big.NewInt(dialRandomizerIntervalMilliseconds)) + if err != nil { + sw.Logger.Error("unable to generate random sleep value", "err", err) + + return + } + + duration := time.Duration(r.Uint64()) * time.Millisecond + + time.Sleep(duration + interval) } -// IsDialingOrExistingAddress returns true if switch has a peer with the given +// isDialingOrExistingAddress returns true if switch has a peer with the given // address or dialing it at the moment. -func (sw *Switch) IsDialingOrExistingAddress(addr *NetAddress) bool { +func (sw *Switch) isDialingOrExistingAddress(addr *NetAddress) bool { return sw.dialing.Has(addr.ID.String()) || sw.peers.Has(addr.ID) || (!sw.config.AllowDuplicateIP && sw.peers.HasIP(addr.IP)) @@ -483,6 +426,8 @@ func (sw *Switch) IsDialingOrExistingAddress(addr *NetAddress) bool { // AddPersistentPeers allows you to set persistent peers. It ignores // NetAddressLookupError. However, if there are other errors, first encounter is // returned. +// TODO change to net addresses +// TODO make an option func (sw *Switch) AddPersistentPeers(addrs []string) error { sw.Logger.Info("Adding persistent peers", "addrs", addrs) netAddrs, errs := NewNetAddressFromStrings(addrs) @@ -497,97 +442,80 @@ func (sw *Switch) AddPersistentPeers(addrs []string) error { } return err } - sw.persistentPeersAddrs = netAddrs + + // Set the persistent peers + for _, peerAddr := range netAddrs { + sw.persistentPeers.Store(peerAddr.ID, peerAddr) + } + return nil } -func (sw *Switch) isPeerPersistentFn() func(*NetAddress) bool { - return func(na *NetAddress) bool { - for _, pa := range sw.persistentPeersAddrs { - if pa.Equals(na) { - return true - } - } - return false - } +// isPersistentPeer returns a flag indicating if a peer +// is present in the persistent peer set +func (sw *Switch) isPersistentPeer(id ID) bool { + _, persistent := sw.persistentPeers.Load(id) + + return persistent } -func (sw *Switch) acceptRoutine() { +// runAcceptLoop is the main powerhouse method +// for accepting incoming peer connections, filtering them, +// and persisting them +func (sw *Switch) runAcceptLoop(ctx context.Context) { for { - p, err := sw.transport.Accept(peerConfig{ - chDescs: sw.chDescs, - onPeerError: sw.StopPeerForError, - reactorsByCh: sw.reactorsByCh, - isPersistent: sw.isPeerPersistentFn(), - }) - if err != nil { - switch err := err.(type) { - case RejectedError: - if err.IsSelf() { - // TODO: warn? - } + select { + case <-ctx.Done(): + sw.Logger.Debug("switch context close received") - sw.Logger.Info( - "Inbound Peer rejected", - "err", err, - "numPeers", sw.peers.Size(), - ) + return + default: + p, err := sw.transport.Accept(peerConfig{ + chDescs: sw.chDescs, + onPeerError: sw.StopPeerForError, + reactorsByCh: sw.reactorsByCh, + isPersistent: func(address *NetAddress) bool { + return sw.isPersistentPeer(address.ID) + }, + }) - continue - case FilterTimeoutError: + if err != nil { sw.Logger.Error( - "Peer filter timed out", + "error encountered during peer connection accept", "err", err, ) continue - case TransportClosedError: - sw.Logger.Error( - "Stopped accept routine, as transport is closed", - "numPeers", sw.peers.Size(), - ) - default: - sw.Logger.Error( - "Accept on transport errored", - "err", err, - "numPeers", sw.peers.Size(), - ) - // We could instead have a retry loop around the acceptRoutine, - // but that would need to stop and let the node shutdown eventually. - // So might as well panic and let process managers restart the node. - // There's no point in letting the node run without the acceptRoutine, - // since it won't be able to accept new connections. - panic(fmt.Errorf("accept routine exited: %w", err)) } - break - } + // Ignore connection if we already have enough peers. + if in := sw.Peers().NumInbound(); in >= sw.config.MaxNumInboundPeers { + sw.Logger.Info( + "Ignoring inbound connection: already have enough inbound peers", + "address", p.SocketAddr(), + "have", in, + "max", sw.config.MaxNumInboundPeers, + ) - // Ignore connection if we already have enough peers. - _, in, _ := sw.NumPeers() - if in >= sw.config.MaxNumInboundPeers { - sw.Logger.Info( - "Ignoring inbound connection: already have enough inbound peers", - "address", p.SocketAddr(), - "have", in, - "max", sw.config.MaxNumInboundPeers, - ) + sw.transport.Cleanup(p) - sw.transport.Cleanup(p) + continue + } - continue - } + // There are open peer slots, add peers + if err := sw.addPeer(p); err != nil { + sw.transport.Cleanup(p) + + if p.IsRunning() { + _ = p.Stop() + } - if err := sw.addPeer(p); err != nil { - sw.transport.Cleanup(p) - if p.IsRunning() { - _ = p.Stop() + sw.Logger.Info( + "Ignoring inbound connection: error while adding peer", + "err", err, + "id", p.ID(), + ) } - sw.Logger.Info( - "Ignoring inbound connection: error while adding peer", - "err", err, - "id", p.ID(), - ) } } } @@ -597,16 +525,15 @@ func (sw *Switch) acceptRoutine() { // if dialing fails, start the reconnect loop. If handshake fails, it's over. // If peer is started successfully, reconnectLoop will start when // StopPeerForError is called. -func (sw *Switch) addOutboundPeerWithConfig( - addr *NetAddress, - cfg *config.P2PConfig, -) error { +func (sw *Switch) addOutboundPeerWithConfig(addr *NetAddress) error { sw.Logger.Info("Dialing peer", "address", addr) p, err := sw.transport.Dial(*addr, peerConfig{ - chDescs: sw.chDescs, - onPeerError: sw.StopPeerForError, - isPersistent: sw.isPeerPersistentFn(), + chDescs: sw.chDescs, + onPeerError: sw.StopPeerForError, + isPersistent: func(address *NetAddress) bool { + return sw.isPersistentPeer(address.ID) + }, reactorsByCh: sw.reactorsByCh, }) if err != nil { @@ -619,7 +546,7 @@ func (sw *Switch) addOutboundPeerWithConfig( // retry persistent peers after // any dial error besides IsSelf() - if sw.isPeerPersistentFn()(addr) { + if sw.isPersistentPeer(addr.ID) { go sw.reconnectToPeer(addr) } @@ -637,29 +564,29 @@ func (sw *Switch) addOutboundPeerWithConfig( return nil } +// TODO remove this entirely func (sw *Switch) filterPeer(p Peer) error { // Avoid duplicate if sw.peers.Has(p.ID()) { - return RejectedError{id: p.ID(), isDuplicate: true} + return RejectedError{ + id: p.ID(), + isDuplicate: true, + } } - errc := make(chan error, len(sw.peerFilters)) + ctx, cancelFn := context.WithTimeout(context.Background(), sw.filterTimeout) + defer cancelFn() + + g, _ := errgroup.WithContext(ctx) - for _, f := range sw.peerFilters { - go func(f PeerFilterFunc, p Peer, errc chan<- error) { - errc <- f(sw.peers, p) - }(f, p, errc) + for _, filterFn := range sw.peerFilters { + g.Go(func() error { + return filterFn(sw.peers, p) + }) } - for i := 0; i < cap(errc); i++ { - select { - case err := <-errc: - if err != nil { - return RejectedError{id: p.ID(), err: err, isFiltered: true} - } - case <-time.After(sw.filterTimeout): - return FilterTimeoutError{} - } + if err := g.Wait(); err != nil { + return RejectedError{id: p.ID(), err: err, isFiltered: true} } return nil @@ -674,7 +601,7 @@ func (sw *Switch) addPeer(p Peer) error { p.SetLogger(sw.Logger.With("peer", p.SocketAddr())) - // Handle the shut down case where the switch has stopped but we're + // Handle the shut down case where the switch has stopped, but we're // concurrently trying to add a peer. if !sw.IsRunning() { // XXX should this return an error or just log and terminate? @@ -690,14 +617,13 @@ func (sw *Switch) addPeer(p Peer) error { // Start the peer's send/recv routines. // Must start it before adding it to the peer set // to prevent Start and Stop from being called concurrently. - err := p.Start() - if err != nil { - // Should never happen + if err := p.Start(); err != nil { sw.Logger.Error("Error starting peer", "err", err, "peer", p) + return err } - // Add the peer to PeerSet. Do this before starting the reactors + // Add the peer to the peer set. Do this before starting the reactors // so that if Receive errors, we will find the peer and remove it. sw.peers.Add(p) @@ -723,7 +649,7 @@ func (sw *Switch) logTelemetry() { } // Fetch the number of peers - outbound, inbound, dialing := sw.NumPeers() + outbound, inbound := sw.peers.NumOutbound(), sw.peers.NumInbound() // Log the outbound peer count metrics.OutboundPeers.Record(context.Background(), int64(outbound)) @@ -732,5 +658,5 @@ func (sw *Switch) logTelemetry() { metrics.InboundPeers.Record(context.Background(), int64(inbound)) // Log the dialing peer count - metrics.DialingPeers.Record(context.Background(), int64(dialing)) + metrics.DialingPeers.Record(context.Background(), int64(sw.dialing.Size())) } diff --git a/tm2/pkg/p2p/switch_option.go b/tm2/pkg/p2p/switch_option.go new file mode 100644 index 00000000000..8b1e7060bb1 --- /dev/null +++ b/tm2/pkg/p2p/switch_option.go @@ -0,0 +1,22 @@ +package p2p + +// WithPeerFilters sets the filters for rejection of new peers. +func WithPeerFilters(filters ...PeerFilterFunc) SwitchOption { + return func(sw *Switch) { + sw.peerFilters = filters + } +} + +// WithNodeInfo sets the node info for the p2p switch +func WithNodeInfo(ni NodeInfo) SwitchOption { + return func(sw *Switch) { + sw.nodeInfo = ni + } +} + +// WithNodeKey sets the node p2p key, utilized by the switch +func WithNodeKey(key *NodeKey) SwitchOption { + return func(sw *Switch) { + sw.nodeKey = key + } +} diff --git a/tm2/pkg/p2p/switch_test.go b/tm2/pkg/p2p/switch_test.go index 9931622bcee..74c0e418cd9 100644 --- a/tm2/pkg/p2p/switch_test.go +++ b/tm2/pkg/p2p/switch_test.go @@ -142,7 +142,7 @@ package p2p // rp.Start() // // // addr should be rejected in addPeer based on the same ID -// err := s1.DialPeerWithAddress(rp.Addr()) +// err := s1.dialPeerWithAddress(rp.Addr()) // if assert.Error(t, err) { // if err, ok := err.(RejectedError); ok { // if !err.IsSelf() { @@ -173,7 +173,7 @@ package p2p // "testing", // "123.123.123", // initSwitchFunc, -// SwitchPeerFilters(filters...), +// WithPeerFilters(filters...), // ) // ) // defer sw.Stop() @@ -220,7 +220,7 @@ package p2p // "123.123.123", // initSwitchFunc, // SwitchFilterTimeout(5*time.Millisecond), -// SwitchPeerFilters(filters...), +// WithPeerFilters(filters...), // ) // ) // defer sw.Stop() @@ -368,7 +368,7 @@ package p2p // err = sw.AddPersistentPeers([]string{rp.Addr().String()}) // require.NoError(t, err) // -// err = sw.DialPeerWithAddress(rp.Addr()) +// err = sw.dialPeerWithAddress(rp.Addr()) // require.Nil(t, err) // require.NotNil(t, sw.Peers().Get(rp.ID())) // diff --git a/tm2/pkg/p2p/test_util.go b/tm2/pkg/p2p/test_util.go index a67b3731fe8..8950dd504f0 100644 --- a/tm2/pkg/p2p/test_util.go +++ b/tm2/pkg/p2p/test_util.go @@ -95,7 +95,7 @@ package p2p // // p := peer.newPeer( // pc, -// MConnConfig(sw.config), +// MultiplexConfigFromP2P(sw.config), // ni, // sw.reactorsByCh, // sw.chDescs, @@ -134,7 +134,7 @@ package p2p // } // nodeInfo := testNodeInfo(nodeKey.ID(), fmt.Sprintf("node%d", i)) // -// t := NewMultiplexTransport(nodeInfo, nodeKey, MConnConfig(cfg)) +// t := NewMultiplexTransport(nodeInfo, nodeKey, MultiplexConfigFromP2P(cfg)) // // if err := t.Listen(*nodeInfo.NetAddress); err != nil { // panic(err) diff --git a/tm2/pkg/p2p/transport.go b/tm2/pkg/p2p/transport.go index ea9611846fe..630fc17eb53 100644 --- a/tm2/pkg/p2p/transport.go +++ b/tm2/pkg/p2p/transport.go @@ -54,7 +54,7 @@ type peerConfig struct { // the transport. Each transport is also responsible to filter establishing // peers specific to its domain. type Transport interface { - // Listening address. + // NetAddress returns the associated net address of the transport NetAddress() NetAddress // Accept returns a newly connected Peer. @@ -65,13 +65,9 @@ type Transport interface { // Cleanup any resources associated with Peer. Cleanup(Peer) -} -// TransportLifecycle bundles the methods for callers to control start and stop -// behaviour. -type TransportLifecycle interface { + // Close closes the transport Close() error - Listen(NetAddress) error } // ConnFilterFunc to be implemented by filter hooks after a new connection has @@ -149,10 +145,7 @@ type MultiplexTransport struct { } // Test multiplexTransport for interface completeness. -var ( - _ Transport = (*MultiplexTransport)(nil) - _ TransportLifecycle = (*MultiplexTransport)(nil) -) +var _ Transport = (*MultiplexTransport)(nil) // NewMultiplexTransport returns a tcp connected multiplexed peer. func NewMultiplexTransport( diff --git a/tm2/pkg/p2p/transport_test.go b/tm2/pkg/p2p/transport_test.go index 959d83d1131..2419add987b 100644 --- a/tm2/pkg/p2p/transport_test.go +++ b/tm2/pkg/p2p/transport_test.go @@ -7,7 +7,7 @@ package p2p // } // // // newMultiplexTransport returns a tcp connected multiplexed peer -// // using the default MConnConfig. It's a convenience function used +// // using the default MultiplexConfigFromP2P. It's a convenience function used // // for testing. // func newMultiplexTransport( // nodeInfo NodeInfo, diff --git a/tm2/pkg/p2p/types.go b/tm2/pkg/p2p/types.go index 199dd4af6d3..d13252d66fe 100644 --- a/tm2/pkg/p2p/types.go +++ b/tm2/pkg/p2p/types.go @@ -49,6 +49,9 @@ type PeerSet interface { Has(key ID) bool HasIP(ip net.IP) bool Get(key ID) Peer - List() []Peer - Size() int + List() []Peer // TODO consider implementing an iterator + Size() int // TODO remove + + NumInbound() uint64 // returns the number of connected inbound nodes + NumOutbound() uint64 // returns the number of connected outbound nodes } diff --git a/tm2/pkg/telemetry/metrics/metrics.go b/tm2/pkg/telemetry/metrics/metrics.go index 2b04769fe0c..d54c289cf3b 100644 --- a/tm2/pkg/telemetry/metrics/metrics.go +++ b/tm2/pkg/telemetry/metrics/metrics.go @@ -16,8 +16,7 @@ import ( ) const ( - broadcastTxTimerKey = "broadcast_tx_hist" - buildBlockTimerKey = "build_block_hist" + buildBlockTimerKey = "build_block_hist" inboundPeersKey = "inbound_peers_hist" outboundPeersKey = "outbound_peers_hist" @@ -43,11 +42,6 @@ const ( ) var ( - // Misc // - - // BroadcastTxTimer measures the transaction broadcast duration - BroadcastTxTimer metric.Int64Histogram - // Networking // // InboundPeers measures the active number of inbound peers From 338c93bfebf6267891dfd8a0fb19f4a3a621f03d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Sun, 6 Oct 2024 16:01:29 +0200 Subject: [PATCH 11/43] Save switch progress --- go.mod | 1 + go.sum | 2 + tm2/pkg/bft/blockchain/reactor.go | 35 +- tm2/pkg/bft/blockchain/reactor_test.go | 2 +- tm2/pkg/bft/node/node.go | 113 +++--- tm2/pkg/bft/node/node_test.go | 37 -- tm2/pkg/p2p/conn/connection.go | 4 +- tm2/pkg/p2p/conn/connection_test.go | 2 +- tm2/pkg/p2p/dial/dial.go | 68 ++++ tm2/pkg/p2p/dial/doc.go | 2 + tm2/pkg/p2p/events/doc.go | 2 + tm2/pkg/p2p/events/events.go | 106 ++++++ tm2/pkg/p2p/events/types.go | 35 ++ tm2/pkg/p2p/mock/reactor.go | 23 -- tm2/pkg/p2p/peer.go | 4 +- tm2/pkg/p2p/switch.go | 465 +++++++++---------------- tm2/pkg/p2p/switch_option.go | 32 +- tm2/pkg/p2p/transport.go | 2 +- tm2/pkg/telemetry/metrics/metrics.go | 19 - 19 files changed, 472 insertions(+), 482 deletions(-) create mode 100644 tm2/pkg/p2p/dial/dial.go create mode 100644 tm2/pkg/p2p/dial/doc.go create mode 100644 tm2/pkg/p2p/events/doc.go create mode 100644 tm2/pkg/p2p/events/events.go create mode 100644 tm2/pkg/p2p/events/types.go delete mode 100644 tm2/pkg/p2p/mock/reactor.go diff --git a/go.mod b/go.mod index d890ab020a4..c7234fb287b 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,7 @@ require ( github.com/rogpeppe/go-internal v1.12.0 github.com/rs/cors v1.11.1 github.com/rs/xid v1.6.0 + github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b github.com/stretchr/testify v1.9.0 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 go.etcd.io/bbolt v1.3.11 diff --git a/go.sum b/go.sum index 9495dd5b451..102d828b5c8 100644 --- a/go.sum +++ b/go.sum @@ -145,6 +145,8 @@ github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b/go.mod h1:JprPCeMgYyLKJoAy9nxpVScm7NwFSwpibdrUKm4kcw0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/tm2/pkg/bft/blockchain/reactor.go b/tm2/pkg/bft/blockchain/reactor.go index 0fd0cebfdbf..1d8245c957c 100644 --- a/tm2/pkg/bft/blockchain/reactor.go +++ b/tm2/pkg/bft/blockchain/reactor.go @@ -37,11 +37,7 @@ const ( bcBlockResponseMessageFieldKeySize ) -type consensusReactor interface { - // for when we switch from blockchain reactor and fast sync to - // the consensus machine - SwitchToConsensus(sm.State, int) -} +type SwitchToConsensusFn func(sm.State, int) type peerError struct { err error @@ -66,11 +62,17 @@ type BlockchainReactor struct { requestsCh <-chan BlockRequest errorsCh <-chan peerError + + switchToConsensusFn SwitchToConsensusFn } // NewBlockchainReactor returns new reactor instance. -func NewBlockchainReactor(state sm.State, blockExec *sm.BlockExecutor, store *store.BlockStore, +func NewBlockchainReactor( + state sm.State, + blockExec *sm.BlockExecutor, + store *store.BlockStore, fastSync bool, + switchToConsensusFn SwitchToConsensusFn, ) *BlockchainReactor { if state.LastBlockHeight != store.Height() { panic(fmt.Sprintf("state (%v) and store (%v) height mismatch", state.LastBlockHeight, @@ -89,13 +91,14 @@ func NewBlockchainReactor(state sm.State, blockExec *sm.BlockExecutor, store *st ) bcR := &BlockchainReactor{ - initialState: state, - blockExec: blockExec, - store: store, - pool: pool, - fastSync: fastSync, - requestsCh: requestsCh, - errorsCh: errorsCh, + initialState: state, + blockExec: blockExec, + store: store, + pool: pool, + fastSync: fastSync, + requestsCh: requestsCh, + errorsCh: errorsCh, + switchToConsensusFn: switchToConsensusFn, } bcR.BaseReactor = *p2p.NewBaseReactor("BlockchainReactor", bcR) return bcR @@ -262,10 +265,8 @@ FOR_LOOP: if bcR.pool.IsCaughtUp() { bcR.Logger.Info("Time to switch to consensus reactor!", "height", height) bcR.pool.Stop() - conR, ok := bcR.Switch.Reactor("CONSENSUS").(consensusReactor) - if ok { - conR.SwitchToConsensus(state, blocksSynced) - } + + bcR.switchToConsensusFn(state, blocksSynced) // else { // should only happen during testing // } diff --git a/tm2/pkg/bft/blockchain/reactor_test.go b/tm2/pkg/bft/blockchain/reactor_test.go index a40dbc6376b..5b3cdd938da 100644 --- a/tm2/pkg/bft/blockchain/reactor_test.go +++ b/tm2/pkg/bft/blockchain/reactor_test.go @@ -110,7 +110,7 @@ func newBlockchainReactor(logger *slog.Logger, genDoc *types.GenesisDoc, privVal blockStore.SaveBlock(thisBlock, thisParts, lastCommit) } - bcReactor := NewBlockchainReactor(state.Copy(), blockExec, blockStore, fastSync) + bcReactor := NewBlockchainReactor(state.Copy(), blockExec, blockStore, fastSync, nil) bcReactor.SetLogger(logger.With("module", "blockchain")) return BlockchainReactorPair{bcReactor, proxyApp} diff --git a/tm2/pkg/bft/node/node.go b/tm2/pkg/bft/node/node.go index 21bcd1223b2..3622df2ff32 100644 --- a/tm2/pkg/bft/node/node.go +++ b/tm2/pkg/bft/node/node.go @@ -118,30 +118,6 @@ func DefaultNewNode( // Option sets a parameter for the node. type Option func(*Node) -// CustomReactors allows you to add custom reactors (name -> p2p.Reactor) to -// the node's Switch. -// -// WARNING: using any name from the below list of the existing reactors will -// result in replacing it with the custom one. -// -// - MEMPOOL -// - BLOCKCHAIN -// - CONSENSUS -// - EVIDENCE -// - PEX -func CustomReactors(reactors map[string]p2p.Reactor) Option { - return func(n *Node) { - for name, reactor := range reactors { - if existingReactor := n.sw.Reactor(name); existingReactor != nil { - n.sw.Logger.Info("Replacing existing reactor with a custom one", - "name", name, "existing", existingReactor, "custom", reactor) - n.sw.RemoveReactor(name, existingReactor) - } - n.sw.AddReactor(name, reactor) - } - } -} - // ------------------------------------------------------------------------------ // Node is the highest level interface to a full Tendermint node. @@ -289,14 +265,21 @@ func createMempoolAndMempoolReactor(config *cfg.Config, proxyApp appconn.AppConn return mempoolReactor, mempool } -func createBlockchainReactor(config *cfg.Config, +func createBlockchainReactor( state sm.State, blockExec *sm.BlockExecutor, blockStore *store.BlockStore, fastSync bool, + switchToConsensusFn bc.SwitchToConsensusFn, logger *slog.Logger, ) (bcReactor p2p.Reactor, err error) { - bcReactor = bc.NewBlockchainReactor(state.Copy(), blockExec, blockStore, fastSync) + bcReactor = bc.NewBlockchainReactor( + state.Copy(), + blockExec, + blockStore, + fastSync, + switchToConsensusFn, + ) bcReactor.SetLogger(logger.With("module", "blockchain")) return bcReactor, nil @@ -387,32 +370,6 @@ func createTransport(config *cfg.Config, nodeInfo p2p.NodeInfo, nodeKey *p2p.Nod return transport, peerFilters } -func createSwitch(config *cfg.Config, - transport *p2p.MultiplexTransport, - peerFilters []p2p.PeerFilterFunc, - mempoolReactor *mempl.Reactor, - bcReactor p2p.Reactor, - consensusReactor *cs.ConsensusReactor, - nodeInfo p2p.NodeInfo, - nodeKey *p2p.NodeKey, - p2pLogger *slog.Logger, -) *p2p.Switch { - sw := p2p.NewSwitch( - config.P2P, - transport, - p2p.WithPeerFilters(peerFilters...), - p2p.WithNodeInfo(nodeInfo), - p2p.WithNodeKey(nodeKey), - ) - sw.SetLogger(p2pLogger) - sw.AddReactor("MEMPOOL", mempoolReactor) - sw.AddReactor("BLOCKCHAIN", bcReactor) - sw.AddReactor("CONSENSUS", consensusReactor) - - p2pLogger.Info("P2P Node ID", "ID", nodeKey.ID(), "file", config.NodeKeyFile()) - return sw -} - // NewNode returns a new, ready to go, Tendermint Node. func NewNode(config *cfg.Config, privValidator types.PrivValidator, @@ -505,18 +462,25 @@ func NewNode(config *cfg.Config, mempool, ) - // Make BlockchainReactor - bcReactor, err := createBlockchainReactor(config, state, blockExec, blockStore, fastSync, logger) - if err != nil { - return nil, errors.Wrap(err, "could not create blockchain reactor") - } - // Make ConsensusReactor consensusReactor, consensusState := createConsensusReactor( config, state, blockExec, blockStore, mempool, privValidator, fastSync, evsw, consensusLogger, ) + // Make BlockchainReactor + bcReactor, err := createBlockchainReactor( + state, + blockExec, + blockStore, + fastSync, + consensusReactor.SwitchToConsensus, + logger, + ) + if err != nil { + return nil, errors.Wrap(err, "could not create blockchain reactor") + } + nodeInfo, err := makeNodeInfo(config, nodeKey, txEventStore, genDoc, state) if err != nil { return nil, errors.Wrap(err, "error making NodeInfo") @@ -527,16 +491,26 @@ func NewNode(config *cfg.Config, // Setup Switch. p2pLogger := logger.With("module", "p2p") - sw := createSwitch( - config, transport, peerFilters, mempoolReactor, bcReactor, - consensusReactor, nodeInfo, nodeKey, p2pLogger, - ) - err = sw.AddPersistentPeers(splitAndTrimEmpty(config.P2P.PersistentPeers, ",", " ")) - if err != nil { - return nil, errors.Wrap(err, "could not add peers from persistent_peers field") + peerAddrs, errs := p2p.NewNetAddressFromStrings(splitAndTrimEmpty(config.P2P.PersistentPeers, ",", " ")) + for _, err := range errs { + p2pLogger.Error("invalid persistent peer address", "err", err) } + sw := p2p.NewSwitch( + config.P2P, + transport, + p2p.WithPeerFilters(peerFilters...), + p2p.WithReactor("MEMPOOL", mempoolReactor), + p2p.WithReactor("BLOCKCHAIN", bcReactor), + p2p.WithReactor("CONSENSUS", consensusReactor), + p2p.WithPersistentPeers(peerAddrs), + ) + + sw.SetLogger(p2pLogger) + + p2pLogger.Info("P2P Node ID", "ID", nodeKey.ID(), "file", config.NodeKeyFile()) + if config.ProfListenAddress != "" { server := &http.Server{ Addr: config.ProfListenAddress, @@ -638,11 +612,14 @@ func (n *Node) OnStart() error { } // Always connect to persistent peers - err = n.sw.DialPeersAsync(splitAndTrimEmpty(n.config.P2P.PersistentPeers, ",", " ")) - if err != nil { - return errors.Wrap(err, "could not dial peers from persistent_peers field") + peerAddrs, errs := p2p.NewNetAddressFromStrings(splitAndTrimEmpty(n.config.P2P.PersistentPeers, ",", " ")) + for _, err := range errs { + n.Logger.Error("invalid persistent peer address", "err", err) } + // Dial the persistent peers + n.sw.DialPeers(peerAddrs...) + return nil } diff --git a/tm2/pkg/bft/node/node_test.go b/tm2/pkg/bft/node/node_test.go index e28464ff711..8ad9d95a88b 100644 --- a/tm2/pkg/bft/node/node_test.go +++ b/tm2/pkg/bft/node/node_test.go @@ -25,8 +25,6 @@ import ( "github.com/gnolang/gno/tm2/pkg/db/memdb" "github.com/gnolang/gno/tm2/pkg/events" "github.com/gnolang/gno/tm2/pkg/log" - "github.com/gnolang/gno/tm2/pkg/p2p" - p2pmock "github.com/gnolang/gno/tm2/pkg/p2p/mock" "github.com/gnolang/gno/tm2/pkg/random" ) @@ -40,8 +38,6 @@ func TestNodeStartStop(t *testing.T) { err = n.Start() require.NoError(t, err) - t.Logf("Started node %v", n.sw.NodeInfo()) - // wait for the node to produce a block blocksSub := events.SubscribeToEvent(n.EventSwitch(), "node_test", types.EventNewBlock{}) require.NoError(t, err) @@ -308,39 +304,6 @@ func TestCreateProposalBlock(t *testing.T) { assert.NoError(t, err) } -func TestNodeNewNodeCustomReactors(t *testing.T) { - config, genesisFile := cfg.ResetTestRoot("node_new_node_custom_reactors_test") - defer os.RemoveAll(config.RootDir) - - cr := p2pmock.NewReactor() - customBlockchainReactor := p2pmock.NewReactor() - - nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) - require.NoError(t, err) - - n, err := NewNode(config, - privval.LoadOrGenFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile()), - nodeKey, - proxy.DefaultClientCreator(nil, config.ProxyApp, config.ABCI, config.DBDir()), - DefaultGenesisDocProviderFunc(genesisFile), - DefaultDBProvider, - events.NewEventSwitch(), - log.NewTestingLogger(t), - CustomReactors(map[string]p2p.Reactor{"FOO": cr, "BLOCKCHAIN": customBlockchainReactor}), - ) - require.NoError(t, err) - - err = n.Start() - require.NoError(t, err) - defer n.Stop() - - assert.True(t, cr.IsRunning()) - assert.Equal(t, cr, n.Switch().Reactor("FOO")) - - assert.True(t, customBlockchainReactor.IsRunning()) - assert.Equal(t, customBlockchainReactor, n.Switch().Reactor("BLOCKCHAIN")) -} - func state(nVals int, height int64) (sm.State, dbm.DB) { vals := make([]types.GenesisValidator, nVals) for i := 0; i < nVals; i++ { diff --git a/tm2/pkg/p2p/conn/connection.go b/tm2/pkg/p2p/conn/connection.go index 6b7400600d3..b4e2881dd19 100644 --- a/tm2/pkg/p2p/conn/connection.go +++ b/tm2/pkg/p2p/conn/connection.go @@ -46,7 +46,7 @@ const ( type ( receiveCbFunc func(chID byte, msgBytes []byte) - errorCbFunc func(interface{}) + errorCbFunc func(error) ) /* @@ -323,7 +323,7 @@ func (c *MConnection) _recover() { } } -func (c *MConnection) stopForError(r interface{}) { +func (c *MConnection) stopForError(r error) { c.Stop() if atomic.CompareAndSwapUint32(&c.errored, 0, 1) { if c.onError != nil { diff --git a/tm2/pkg/p2p/conn/connection_test.go b/tm2/pkg/p2p/conn/connection_test.go index 7bbe88ded22..2b69ca3531d 100644 --- a/tm2/pkg/p2p/conn/connection_test.go +++ b/tm2/pkg/p2p/conn/connection_test.go @@ -32,7 +32,7 @@ func createMConnectionWithCallbacks( t *testing.T, conn net.Conn, onReceive func(chID byte, msgBytes []byte), - onError func(r interface{}), + onError func(r error), ) *MConnection { t.Helper() diff --git a/tm2/pkg/p2p/dial/dial.go b/tm2/pkg/p2p/dial/dial.go new file mode 100644 index 00000000000..20e70699884 --- /dev/null +++ b/tm2/pkg/p2p/dial/dial.go @@ -0,0 +1,68 @@ +package dial + +import ( + "sync" + "time" + + "github.com/gnolang/gno/tm2/pkg/p2p" + queue "github.com/sig-0/insertion-queue" +) + +// Item is a single dial queue item, wrapping +// the approximately appropriate dial time, and the +// peer dial address +type Item struct { + Time time.Time // appropriate dial time + Address *p2p.NetAddress // the dial address of the peer +} + +// Less is the comparison method for the dial queue Item (time ascending) +func (i Item) Less(item Item) bool { + return i.Time.Before(item.Time) +} + +// Queue is a time-sorted (ascending) dial queue +type Queue struct { + mux sync.RWMutex + + items queue.Queue[Item] // sorted dial queue (by time, ascending) +} + +// NewQueue creates a new dial queue +func NewQueue() *Queue { + return &Queue{ + items: queue.NewQueue[Item](), + } +} + +// Peek returns the first item in the dial queue, if any +func (q *Queue) Peek() *Item { + q.mux.RLock() + defer q.mux.RUnlock() + + if q.items.Len() == 0 { + return nil + } + + item := q.items.Index(0) + + return &item +} + +// Push adds new items to the dial queue +func (q *Queue) Push(items ...Item) { + q.mux.Lock() + defer q.mux.Unlock() + + for _, item := range items { + q.items.Push(item) + } +} + +// Pop removes an item from the dial queue, if any +func (q *Queue) Pop() *Item { + q.mux.Lock() + defer q.mux.Unlock() + + return q.items.PopFront() +} diff --git a/tm2/pkg/p2p/dial/doc.go b/tm2/pkg/p2p/dial/doc.go new file mode 100644 index 00000000000..2df1138c7c1 --- /dev/null +++ b/tm2/pkg/p2p/dial/doc.go @@ -0,0 +1,2 @@ +// TODO add coverage, documentation +package dial diff --git a/tm2/pkg/p2p/events/doc.go b/tm2/pkg/p2p/events/doc.go new file mode 100644 index 00000000000..c96faaa75c6 --- /dev/null +++ b/tm2/pkg/p2p/events/doc.go @@ -0,0 +1,2 @@ +// TODO add documentation, coverage +package events diff --git a/tm2/pkg/p2p/events/events.go b/tm2/pkg/p2p/events/events.go new file mode 100644 index 00000000000..e500db448ce --- /dev/null +++ b/tm2/pkg/p2p/events/events.go @@ -0,0 +1,106 @@ +package events + +import ( + "sync" + + "github.com/rs/xid" +) + +// EventFilter is the filter function used to +// filter incoming p2p events. A false flag will +// consider the event as irrelevant +type EventFilter func(Event) bool + +// Events is the p2p event switch +type Events struct { + subs subscriptions + subscriptionsMux sync.RWMutex +} + +func New() *Events { + return &Events{ + subs: make(subscriptions), + } +} + +// Subscribe registers a new filtered event listener +func (es *Events) Subscribe(filterFn EventFilter) (<-chan Event, func()) { + es.subscriptionsMux.Lock() + defer es.subscriptionsMux.Unlock() + + // Create a new subscription + id, ch := es.subs.add(filterFn) + + // Create the unsubscribe callback + unsubscribeFn := func() { + es.subscriptionsMux.Lock() + defer es.subscriptionsMux.Unlock() + + es.subs.remove(id) + } + + return ch, unsubscribeFn +} + +// Notify notifies all subscribers of an incoming event +func (es *Events) Notify(event Event) { + es.subscriptionsMux.RLock() + defer es.subscriptionsMux.RUnlock() + + es.subs.notify(event) +} + +type ( + // subscriptions holds the corresponding subscription information + subscriptions map[string]subscription // subscription ID -> subscription + + // subscription wraps the subscription notification channel, + // and the event filter + subscription struct { + ch chan Event + filterFn EventFilter + } +) + +// add adds a new subscription to the subscription map. +// Returns the subscription ID, and update channel +func (s *subscriptions) add(filterFn EventFilter) (string, chan Event) { + var ( + id = xid.New().String() + ch = make(chan Event, 1) + ) + + (*s)[id] = subscription{ + ch: ch, + filterFn: filterFn, + } + + return id, ch +} + +// remove removes the given subscription +func (s *subscriptions) remove(id string) { + if sub, exists := (*s)[id]; exists { + // Close the notification channel + close(sub.ch) + } + + // Delete the subscription + delete(*s, id) +} + +// notify notifies all subscription listeners, +// if their filters pass +func (s *subscriptions) notify(event Event) { + // Notify the listeners + for _, sub := range *s { + if !sub.filterFn(event) { + continue + } + + select { + case sub.ch <- event: + default: + } + } +} diff --git a/tm2/pkg/p2p/events/types.go b/tm2/pkg/p2p/events/types.go new file mode 100644 index 00000000000..7b21822df60 --- /dev/null +++ b/tm2/pkg/p2p/events/types.go @@ -0,0 +1,35 @@ +package events + +import "github.com/gnolang/gno/tm2/pkg/p2p" + +type EventType string + +const ( + PeerConnected EventType = "PeerConnected" // emitted when a fresh peer connects + PeerDisconnected EventType = "PeerDisconnected" // emitted when a peer disconnects +) + +// Event is a p2p event +type Event interface { + // Type returns the type information for the event + Type() EventType +} + +type PeerConnectedEvent struct { + PeerID p2p.ID // the ID of the peer + Address p2p.NetAddress // the dial address of the peer +} + +func (p *PeerConnectedEvent) Type() EventType { + return PeerConnected +} + +type PeerDisconnectedEvent struct { + PeerID p2p.ID // the ID of the peer + Address p2p.NetAddress // the dial address of the peer + Reason error // the disconnect reason, if any +} + +func (p *PeerDisconnectedEvent) Type() EventType { + return PeerDisconnected +} diff --git a/tm2/pkg/p2p/mock/reactor.go b/tm2/pkg/p2p/mock/reactor.go deleted file mode 100644 index fe123fdc0b2..00000000000 --- a/tm2/pkg/p2p/mock/reactor.go +++ /dev/null @@ -1,23 +0,0 @@ -package mock - -import ( - "github.com/gnolang/gno/tm2/pkg/log" - "github.com/gnolang/gno/tm2/pkg/p2p" - "github.com/gnolang/gno/tm2/pkg/p2p/conn" -) - -type Reactor struct { - p2p.BaseReactor -} - -func NewReactor() *Reactor { - r := &Reactor{} - r.BaseReactor = *p2p.NewBaseReactor("Reactor", r) - r.SetLogger(log.NewNoopLogger()) - return r -} - -func (r *Reactor) GetChannels() []*conn.ChannelDescriptor { return []*conn.ChannelDescriptor{} } -func (r *Reactor) AddPeer(peer p2p.Peer) {} -func (r *Reactor) RemovePeer(peer p2p.Peer, reason interface{}) {} -func (r *Reactor) Receive(chID byte, peer p2p.Peer, msgBytes []byte) {} diff --git a/tm2/pkg/p2p/peer.go b/tm2/pkg/p2p/peer.go index f43f39f0b65..314388cf56c 100644 --- a/tm2/pkg/p2p/peer.go +++ b/tm2/pkg/p2p/peer.go @@ -14,7 +14,7 @@ type MultiplexConnConfig struct { MConfig connm.MConnConfig ReactorsByCh map[byte]Reactor ChDescs []*connm.ChannelDescriptor - OnPeerError func(Peer, interface{}) + OnPeerError func(Peer, error) } // ConnInfo wraps the remote peer connection @@ -225,7 +225,7 @@ func (p *peer) createMConnection( reactor.Receive(chID, p, msgBytes) } - onError := func(r any) { + onError := func(r error) { config.OnPeerError(p, r) } diff --git a/tm2/pkg/p2p/switch.go b/tm2/pkg/p2p/switch.go index 11138524fbe..f51ec3133e8 100644 --- a/tm2/pkg/p2p/switch.go +++ b/tm2/pkg/p2p/switch.go @@ -2,38 +2,20 @@ package p2p import ( "context" - "crypto/rand" "fmt" - "math" - "math/big" "sync" "time" - "github.com/gnolang/gno/tm2/pkg/cmap" "github.com/gnolang/gno/tm2/pkg/p2p/config" "github.com/gnolang/gno/tm2/pkg/p2p/conn" + "github.com/gnolang/gno/tm2/pkg/p2p/dial" + "github.com/gnolang/gno/tm2/pkg/p2p/events" "github.com/gnolang/gno/tm2/pkg/service" "github.com/gnolang/gno/tm2/pkg/telemetry" "github.com/gnolang/gno/tm2/pkg/telemetry/metrics" "golang.org/x/sync/errgroup" ) -const ( - // wait a random amount of time from this interval - // before dialing peers or reconnecting to help prevent DoS - dialRandomizerIntervalMilliseconds = 3000 - - // repeatedly try to reconnect for a few minutes - // ie. 5 * 20 = 100s - reconnectAttempts = 20 - reconnectInterval = 5 * time.Second - - // then move into exponential backoff mode for ~1day - // ie. 3**10 = 16hrs - reconnectBackOffAttempts = 10 - reconnectBackOffBaseSeconds = 3 -) - // MultiplexConfigFromP2P returns a multiplex connection configuration // with fields updated from the P2PConfig func MultiplexConfigFromP2P(cfg *config.P2PConfig) conn.MConnConfig { @@ -56,16 +38,11 @@ type PeerFilterFunc func(PeerSet, Peer) error type Switch struct { service.BaseService - config *config.P2PConfig // TODO remove this dependency - reactors map[string]Reactor - chDescs []*conn.ChannelDescriptor - reactorsByCh map[byte]Reactor - - dialing *cmap.CMap - reconnecting *cmap.CMap + config *config.P2PConfig // TODO remove this dependency - nodeInfo NodeInfo // our node info - nodeKey *NodeKey // our node privkey + reactors map[string]Reactor // TODO wrap + chDescs []*conn.ChannelDescriptor // TODO wrap + reactorsByCh map[byte]Reactor // TODO wrap peers PeerSet // currently active peer set persistentPeers sync.Map // ID -> *NetAddress; peers whose connections are constant @@ -73,18 +50,11 @@ type Switch struct { filterTimeout time.Duration peerFilters []PeerFilterFunc -} - -// NetAddress returns the address the switch is listening on. -func (sw *Switch) NetAddress() *NetAddress { - addr := sw.transport.NetAddress() - return &addr + dialQueue *dial.Queue + events *events.Events } -// SwitchOption sets an optional parameter on the Switch. -type SwitchOption func(*Switch) - // NewSwitch creates a new Switch with the given config. func NewSwitch( cfg *config.P2PConfig, @@ -97,10 +67,10 @@ func NewSwitch( chDescs: make([]*conn.ChannelDescriptor, 0), reactorsByCh: make(map[byte]Reactor), peers: NewSet(), - dialing: cmap.NewCMap(), - reconnecting: cmap.NewCMap(), transport: transport, filterTimeout: defaultFilterTimeout, + dialQueue: dial.NewQueue(), + events: events.New(), } sw.BaseService = *service.NewBaseService(nil, "P2P Switch", sw) @@ -112,57 +82,17 @@ func NewSwitch( return sw } -// AddReactor adds the given reactor to the switch. -// NOTE: Not goroutine safe. -func (sw *Switch) AddReactor(name string, reactor Reactor) Reactor { - for _, chDesc := range reactor.GetChannels() { - chID := chDesc.ID - // No two reactors can share the same channel. - if sw.reactorsByCh[chID] != nil { - panic(fmt.Sprintf("Channel %X has multiple reactors %v & %v", chID, sw.reactorsByCh[chID], reactor)) - } - - sw.chDescs = append(sw.chDescs, chDesc) - sw.reactorsByCh[chID] = reactor - } - - sw.reactors[name] = reactor - - reactor.SetSwitch(sw) - - return reactor -} - -// RemoveReactor removes the given Reactor from the Switch. -// NOTE: Not goroutine safe. -func (sw *Switch) RemoveReactor(name string, reactor Reactor) { - for _, chDesc := range reactor.GetChannels() { - // remove channel description - for i := 0; i < len(sw.chDescs); i++ { - if chDesc.ID == sw.chDescs[i].ID { - sw.chDescs = append(sw.chDescs[:i], sw.chDescs[i+1:]...) - break - } - } - - delete(sw.reactorsByCh, chDesc.ID) - } - - delete(sw.reactors, name) - - reactor.SetSwitch(nil) -} +// NetAddress returns the address the switch is listening on. +func (sw *Switch) NetAddress() *NetAddress { + addr := sw.transport.NetAddress() -// Reactor returns the reactor with the given name. -// NOTE: Not goroutine safe. -func (sw *Switch) Reactor(name string) Reactor { - return sw.reactors[name] + return &addr } -// NodeInfo returns the switch's NodeInfo. -// NOTE: Not goroutine safe. -func (sw *Switch) NodeInfo() NodeInfo { - return sw.nodeInfo +// Subscribe registers to live events happening on the p2p Switch. +// Returns the notification channel, along with an unsubscribe method +func (sw *Switch) Subscribe(filterFn events.EventFilter) (<-chan events.Event, func()) { + return sw.events.Subscribe(filterFn) } // --------------------------------------------------------------------- @@ -181,6 +111,14 @@ func (sw *Switch) OnStart() error { // TODO propagate ctx down go sw.runAcceptLoop(context.Background()) + // Run the dial routine + // TODO propagate ctx down + go sw.runDialLoop(context.Background()) + + // Run the redial routine + // TODO propagate ctx down + go sw.runRedialLoop(context.Background()) + return nil } @@ -241,27 +179,43 @@ func (sw *Switch) Peers() PeerSet { // StopPeerForError disconnects from a peer due to external error. // If the peer is persistent, it will attempt to reconnect. // TODO: make record depending on reason. -func (sw *Switch) StopPeerForError(peer Peer, reason interface{}) { - sw.Logger.Error("Stopping peer for error", "peer", peer, "err", reason) - sw.stopAndRemovePeer(peer, reason) - - if peer.IsPersistent() { - var addr *NetAddress - if peer.IsOutbound() { // socket address for outbound peers - addr = peer.SocketAddr() - } else { // self-reported address for inbound peers - addr = peer.NodeInfo().NetAddress - } - go sw.reconnectToPeer(addr) +func (sw *Switch) StopPeerForError(peer Peer, err error) { + sw.Logger.Error("Stopping peer for error", "peer", peer, "err", err) + + sw.stopAndRemovePeer(peer, err) + + if !peer.IsPersistent() { + return + } + + // socket address for outbound peers + addr := peer.SocketAddr() + + if !peer.IsOutbound() { + // self-reported address for inbound peers + addr = peer.NodeInfo().NetAddress } + + // Add the peer to the dial queue + sw.DialPeers(addr) } -func (sw *Switch) stopAndRemovePeer(peer Peer, reason interface{}) { +func (sw *Switch) stopAndRemovePeer(peer Peer, err error) { + // Remove the peer from the transport sw.transport.Cleanup(peer) - peer.Stop() + // Stop the peer connection multiplexing + if stopErr := peer.Stop(); stopErr != nil { + sw.Logger.Error( + "unable to gracefully stop peer", + "peer", peer, + "err", err, + ) + } + + // Alert the reactors of a peer removal for _, reactor := range sw.reactors { - reactor.RemovePeer(peer, reason) + reactor.RemovePeer(peer, err) } // Removing a peer should go last to avoid a situation where a peer @@ -271,184 +225,136 @@ func (sw *Switch) stopAndRemovePeer(peer Peer, reason interface{}) { sw.peers.Remove(peer.ID()) } -// reconnectToPeer tries to reconnect to the addr, first repeatedly -// with a fixed interval, then with exponential backoff. -// If no success after all that, it stops trying. -// NOTE: this will keep trying even if the handshake or auth fails. -// TODO: be more explicit with error types so we only retry on certain failures -// - ie. if we're getting ErrDuplicatePeer we can stop -func (sw *Switch) reconnectToPeer(addr *NetAddress) { - if sw.reconnecting.Has(addr.ID.String()) { - return - } - sw.reconnecting.Set(addr.ID.String(), addr) - defer sw.reconnecting.Delete(addr.ID.String()) - - start := time.Now() - sw.Logger.Info("Reconnecting to peer", "addr", addr) - for i := 0; i < reconnectAttempts; i++ { - if !sw.IsRunning() { - return - } - - err := sw.dialPeerWithAddress(addr) - if err == nil { - return // success - } else if _, ok := err.(CurrentlyDialingOrExistingAddressError); ok { - return - } - - sw.Logger.Info("Error reconnecting to peer. Trying again", "tries", i, "err", err, "addr", addr) - // sleep a set amount - sw.randomSleep(reconnectInterval) - continue - } - - sw.Logger.Error("Failed to reconnect to peer. Beginning exponential backoff", - "addr", addr, "elapsed", time.Since(start)) - for i := 0; i < reconnectBackOffAttempts; i++ { - if !sw.IsRunning() { - return - } - - // sleep an exponentially increasing amount - sleepIntervalSeconds := math.Pow(reconnectBackOffBaseSeconds, float64(i)) - sw.randomSleep(time.Duration(sleepIntervalSeconds) * time.Second) - - err := sw.dialPeerWithAddress(addr) - if err == nil { - return // success - } else if _, ok := err.(CurrentlyDialingOrExistingAddressError); ok { - return - } - sw.Logger.Info("Error reconnecting to peer. Trying again", "tries", i, "err", err, "addr", addr) - } - sw.Logger.Error("Failed to reconnect to peer. Giving up", "addr", addr, "elapsed", time.Since(start)) -} - // --------------------------------------------------------------------- // Dialing -// DialPeersAsync dials a list of peers asynchronously in random order. -// Used to dial peers from config on startup or from unsafe-RPC (trusted sources). -// It ignores NetAddressLookupError. However, if there are other errors, first -// encounter is returned. -// Nop if there are no peers. -func (sw *Switch) DialPeersAsync(peers []string) error { - netAddrs, errs := NewNetAddressFromStrings(peers) - // report all the errors - for _, err := range errs { - sw.Logger.Error("Error in peer's address", "err", err) - } - // return first non-NetAddressLookupError error - for _, err := range errs { - if _, ok := err.(NetAddressLookupError); ok { - continue - } - return err - } - sw.dialPeersAsync(netAddrs) - return nil -} +func (sw *Switch) runDialLoop(ctx context.Context) { + for { + select { + case <-ctx.Done(): + sw.Logger.Debug("dial context canceled") -func (sw *Switch) dialPeersAsync(netAddrs []*NetAddress) { - var ( - ourAddr = sw.NetAddress() + return + default: + // Grab a dial item + item := sw.dialQueue.Peek() + if item == nil { + // Nothing to dial + continue + } - wg sync.WaitGroup - ) + // Check if the dial time is right + // for the item + if time.Now().Before(item.Time) { + // Nothing to dial + continue + } - wg.Add(len(netAddrs)) + // Dial the peer + sw.Logger.Info( + "dialing peer", + "address", item.Address.String(), + ) - for _, peerAddr := range netAddrs { - go func(addr *NetAddress) { - defer wg.Done() + peerAddr := item.Address - if addr.Same(ourAddr) { - sw.Logger.Debug( - "ignoring self-dial attempt", - "addr", - addr, + // TODO pass context to dial + p, err := sw.transport.Dial(*peerAddr, peerConfig{ + chDescs: sw.chDescs, + onPeerError: sw.StopPeerForError, + isPersistent: func(address *NetAddress) bool { + return sw.isPersistentPeer(address.ID) + }, + reactorsByCh: sw.reactorsByCh, + }) + if err != nil { + sw.Logger.Error( + "unable to dial peer", + "peer", peerAddr, + "err", err, ) - return + continue } - sw.randomSleep(0) // TODO remove this - - if err := sw.dialPeerWithAddress(addr); err != nil { - sw.Logger.Debug("Error dialing peer", "err", err) - } - }(peerAddr) - } + // Register the peer with the switch + if err = sw.addPeer(p); err != nil { + sw.Logger.Error( + "unable to add peer", + "peer", p, + "err", err, + ) - wg.Wait() -} + sw.transport.Cleanup(p) -// dialPeerWithAddress dials the given peer and runs sw.addPeer if it connects -// and authenticates successfully. -// If we're currently dialing this address or it belongs to an existing peer, -// CurrentlyDialingOrExistingAddressError is returned. -func (sw *Switch) dialPeerWithAddress(addr *NetAddress) error { - if sw.isDialingOrExistingAddress(addr) { - return CurrentlyDialingOrExistingAddressError{addr.String()} - } + if !p.IsRunning() { + // TODO check if this check is even required + continue + } - // TODO clean up - sw.dialing.Set(addr.ID.String(), addr) - defer sw.dialing.Delete(addr.ID.String()) + if stopErr := p.Stop(); stopErr != nil { + sw.Logger.Error( + "unable to gracefully stop peer", + "peer", p, + "err", stopErr, + ) + } + } - return sw.addOutboundPeerWithConfig(addr) + // Log the telemetry + sw.logTelemetry() + } + } } -// sleep for interval plus some random amount of ms on [0, dialRandomizerIntervalMilliseconds] -func (sw *Switch) randomSleep(interval time.Duration) { - r, err := rand.Int(rand.Reader, big.NewInt(dialRandomizerIntervalMilliseconds)) - if err != nil { - sw.Logger.Error("unable to generate random sleep value", "err", err) +// runRedialLoop starts the persistent peer redial loop +func (sw *Switch) runRedialLoop(ctx context.Context) { + // Set up the event subscription for persistent peer disconnects + subCh, unsubFn := sw.Subscribe(func(event events.Event) bool { + // Make sure the peer event relates to a peer disconnect + if event.Type() != events.PeerDisconnected { + return false + } - return - } + disconnectEv, ok := event.(*events.PeerDisconnectedEvent) + if !ok { + return false + } - duration := time.Duration(r.Uint64()) * time.Millisecond + return sw.isPersistentPeer(disconnectEv.PeerID) + }) + defer unsubFn() - time.Sleep(duration + interval) -} + for { + select { + case <-ctx.Done(): + sw.Logger.Debug("redial context canceled") -// isDialingOrExistingAddress returns true if switch has a peer with the given -// address or dialing it at the moment. -func (sw *Switch) isDialingOrExistingAddress(addr *NetAddress) bool { - return sw.dialing.Has(addr.ID.String()) || - sw.peers.Has(addr.ID) || - (!sw.config.AllowDuplicateIP && sw.peers.HasIP(addr.IP)) -} + return + case ev := <-subCh: + disconnectEv, ok := ev.(*events.PeerDisconnectedEvent) + if !ok { + continue + } -// AddPersistentPeers allows you to set persistent peers. It ignores -// NetAddressLookupError. However, if there are other errors, first encounter is -// returned. -// TODO change to net addresses -// TODO make an option -func (sw *Switch) AddPersistentPeers(addrs []string) error { - sw.Logger.Info("Adding persistent peers", "addrs", addrs) - netAddrs, errs := NewNetAddressFromStrings(addrs) - // report all the errors - for _, err := range errs { - sw.Logger.Error("Error in peer's address", "err", err) - } - // return first non-NetAddressLookupError error - for _, err := range errs { - if _, ok := err.(NetAddressLookupError); ok { - continue + // Dial the disconnected peer + // TODO add backoff mechanism + sw.DialPeers(&disconnectEv.Address) } - return err } +} - // Set the persistent peers - for _, peerAddr := range netAddrs { - sw.persistentPeers.Store(peerAddr.ID, peerAddr) - } +// DialPeers adds the peers to the dial queue for async dialing. +// To monitor dial progress, subscribe to adequate p2p Switch events +func (sw *Switch) DialPeers(peerAddrs ...*NetAddress) { + for _, peerAddr := range peerAddrs { + item := dial.Item{ + Time: time.Now(), + Address: peerAddr, + } - return nil + sw.dialQueue.Push(item) + } } // isPersistentPeer returns a flag indicating if a peer @@ -478,7 +384,6 @@ func (sw *Switch) runAcceptLoop(ctx context.Context) { return sw.isPersistentPeer(address.ID) }, }) - if err != nil { sw.Logger.Error( "error encountered during peer connection accept", @@ -520,50 +425,6 @@ func (sw *Switch) runAcceptLoop(ctx context.Context) { } } -// dial the peer; make secret connection; authenticate against the dialed ID; -// add the peer. -// if dialing fails, start the reconnect loop. If handshake fails, it's over. -// If peer is started successfully, reconnectLoop will start when -// StopPeerForError is called. -func (sw *Switch) addOutboundPeerWithConfig(addr *NetAddress) error { - sw.Logger.Info("Dialing peer", "address", addr) - - p, err := sw.transport.Dial(*addr, peerConfig{ - chDescs: sw.chDescs, - onPeerError: sw.StopPeerForError, - isPersistent: func(address *NetAddress) bool { - return sw.isPersistentPeer(address.ID) - }, - reactorsByCh: sw.reactorsByCh, - }) - if err != nil { - if e, ok := err.(RejectedError); ok { - if e.IsSelf() { - // TODO: warn? - return err - } - } - - // retry persistent peers after - // any dial error besides IsSelf() - if sw.isPersistentPeer(addr.ID) { - go sw.reconnectToPeer(addr) - } - - return err - } - - if err := sw.addPeer(p); err != nil { - sw.transport.Cleanup(p) - if p.IsRunning() { - _ = p.Stop() - } - return err - } - - return nil -} - // TODO remove this entirely func (sw *Switch) filterPeer(p Peer) error { // Avoid duplicate @@ -634,9 +495,6 @@ func (sw *Switch) addPeer(p Peer) error { sw.Logger.Info("Added peer", "peer", p) - // Update the telemetry data - sw.logTelemetry() - return nil } @@ -656,7 +514,4 @@ func (sw *Switch) logTelemetry() { // Log the inbound peer count metrics.InboundPeers.Record(context.Background(), int64(inbound)) - - // Log the dialing peer count - metrics.DialingPeers.Record(context.Background(), int64(sw.dialing.Size())) } diff --git a/tm2/pkg/p2p/switch_option.go b/tm2/pkg/p2p/switch_option.go index 8b1e7060bb1..35c57fb59e9 100644 --- a/tm2/pkg/p2p/switch_option.go +++ b/tm2/pkg/p2p/switch_option.go @@ -1,5 +1,10 @@ package p2p +import "fmt" + +// SwitchOption is a callback used for configuring the p2p Switch +type SwitchOption func(*Switch) + // WithPeerFilters sets the filters for rejection of new peers. func WithPeerFilters(filters ...PeerFilterFunc) SwitchOption { return func(sw *Switch) { @@ -7,16 +12,31 @@ func WithPeerFilters(filters ...PeerFilterFunc) SwitchOption { } } -// WithNodeInfo sets the node info for the p2p switch -func WithNodeInfo(ni NodeInfo) SwitchOption { +// WithReactor sets the p2p switch reactors +func WithReactor(name string, reactor Reactor) SwitchOption { return func(sw *Switch) { - sw.nodeInfo = ni + for _, chDesc := range reactor.GetChannels() { + chID := chDesc.ID + // No two reactors can share the same channel + if sw.reactorsByCh[chID] != nil { + panic(fmt.Sprintf("Channel %X has multiple reactors %v & %v", chID, sw.reactorsByCh[chID], reactor)) + } + + sw.chDescs = append(sw.chDescs, chDesc) + sw.reactorsByCh[chID] = reactor + } + + sw.reactors[name] = reactor + + reactor.SetSwitch(sw) } } -// WithNodeKey sets the node p2p key, utilized by the switch -func WithNodeKey(key *NodeKey) SwitchOption { +// WithPersistentPeers sets the p2p switch's persistent peer set +func WithPersistentPeers(peerAddrs []*NetAddress) SwitchOption { return func(sw *Switch) { - sw.nodeKey = key + for _, addr := range peerAddrs { + sw.persistentPeers.Store(addr.ID, addr) + } } } diff --git a/tm2/pkg/p2p/transport.go b/tm2/pkg/p2p/transport.go index 630fc17eb53..bcf8d26101a 100644 --- a/tm2/pkg/p2p/transport.go +++ b/tm2/pkg/p2p/transport.go @@ -41,7 +41,7 @@ type accept struct { // TODO(xla): Refactor out with more static Reactor setup and PeerBehaviour. type peerConfig struct { chDescs []*conn.ChannelDescriptor - onPeerError func(Peer, interface{}) + onPeerError func(Peer, error) outbound bool // isPersistent allows you to set a function, which, given socket address // (for outbound peers) OR self-reported address (for inbound peers), tells diff --git a/tm2/pkg/telemetry/metrics/metrics.go b/tm2/pkg/telemetry/metrics/metrics.go index d54c289cf3b..da73b846715 100644 --- a/tm2/pkg/telemetry/metrics/metrics.go +++ b/tm2/pkg/telemetry/metrics/metrics.go @@ -20,7 +20,6 @@ const ( inboundPeersKey = "inbound_peers_hist" outboundPeersKey = "outbound_peers_hist" - dialingPeersKey = "dialing_peers_hist" numMempoolTxsKey = "num_mempool_txs_hist" numCachedTxsKey = "num_cached_txs_hist" @@ -50,9 +49,6 @@ var ( // OutboundPeers measures the active number of outbound peers OutboundPeers metric.Int64Histogram - // DialingPeers measures the active number of peers in the dialing state - DialingPeers metric.Int64Histogram - // Mempool // // NumMempoolTxs measures the number of transaction inside the mempool @@ -154,14 +150,6 @@ func Init(config config.Config) error { otel.SetMeterProvider(provider) meter := provider.Meter(config.MeterName) - if BroadcastTxTimer, err = meter.Int64Histogram( - broadcastTxTimerKey, - metric.WithDescription("broadcast tx duration"), - metric.WithUnit("ms"), - ); err != nil { - return fmt.Errorf("unable to create histogram, %w", err) - } - if BuildBlockTimer, err = meter.Int64Histogram( buildBlockTimerKey, metric.WithDescription("block build duration"), @@ -185,13 +173,6 @@ func Init(config config.Config) error { return fmt.Errorf("unable to create histogram, %w", err) } - if DialingPeers, err = meter.Int64Histogram( - dialingPeersKey, - metric.WithDescription("dialing peer count"), - ); err != nil { - return fmt.Errorf("unable to create histogram, %w", err) - } - // Mempool // if NumMempoolTxs, err = meter.Int64Histogram( numMempoolTxsKey, From e5d7861eaaef091605b3cec7e8d559ccaf50d1d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Mon, 14 Oct 2024 10:45:57 +0200 Subject: [PATCH 12/43] Remove unnecessary peer filtering logic --- tm2/pkg/bft/node/node.go | 26 ++-------------- tm2/pkg/p2p/switch.go | 57 +++++------------------------------- tm2/pkg/p2p/switch_option.go | 7 ----- tm2/pkg/p2p/transport.go | 2 -- 4 files changed, 11 insertions(+), 81 deletions(-) diff --git a/tm2/pkg/bft/node/node.go b/tm2/pkg/bft/node/node.go index 3622df2ff32..789b84dea05 100644 --- a/tm2/pkg/bft/node/node.go +++ b/tm2/pkg/bft/node/node.go @@ -314,12 +314,11 @@ func createConsensusReactor(config *cfg.Config, return consensusReactor, consensusState } -func createTransport(config *cfg.Config, nodeInfo p2p.NodeInfo, nodeKey *p2p.NodeKey, proxyApp appconn.AppConns) (*p2p.MultiplexTransport, []p2p.PeerFilterFunc) { +func createTransport(config *cfg.Config, nodeInfo p2p.NodeInfo, nodeKey *p2p.NodeKey, proxyApp appconn.AppConns) *p2p.MultiplexTransport { var ( mConnConfig = p2p.MultiplexConfigFromP2P(config.P2P) transport = p2p.NewMultiplexTransport(nodeInfo, *nodeKey, mConnConfig) connFilters = []p2p.ConnFilterFunc{} - peerFilters = []p2p.PeerFilterFunc{} ) if !config.P2P.AllowDuplicateIP { @@ -346,28 +345,10 @@ func createTransport(config *cfg.Config, nodeInfo p2p.NodeInfo, nodeKey *p2p.Nod return nil }, ) - - peerFilters = append( - peerFilters, - // ABCI query for ID filtering. - func(_ p2p.PeerSet, p p2p.Peer) error { - res, err := proxyApp.Query().QuerySync(abci.RequestQuery{ - Path: fmt.Sprintf("/p2p/filter/id/%s", p.ID()), - }) - if err != nil { - return err - } - if res.IsErr() { - return fmt.Errorf("error querying abci app: %v", res) - } - - return nil - }, - ) } p2p.MultiplexTransportConnFilters(connFilters...)(transport) - return transport, peerFilters + return transport } // NewNode returns a new, ready to go, Tendermint Node. @@ -487,7 +468,7 @@ func NewNode(config *cfg.Config, } // Setup Transport. - transport, peerFilters := createTransport(config, nodeInfo, nodeKey, proxyApp) + transport := createTransport(config, nodeInfo, nodeKey, proxyApp) // Setup Switch. p2pLogger := logger.With("module", "p2p") @@ -500,7 +481,6 @@ func NewNode(config *cfg.Config, sw := p2p.NewSwitch( config.P2P, transport, - p2p.WithPeerFilters(peerFilters...), p2p.WithReactor("MEMPOOL", mempoolReactor), p2p.WithReactor("BLOCKCHAIN", bcReactor), p2p.WithReactor("CONSENSUS", consensusReactor), diff --git a/tm2/pkg/p2p/switch.go b/tm2/pkg/p2p/switch.go index f51ec3133e8..1a51894c129 100644 --- a/tm2/pkg/p2p/switch.go +++ b/tm2/pkg/p2p/switch.go @@ -13,7 +13,6 @@ import ( "github.com/gnolang/gno/tm2/pkg/service" "github.com/gnolang/gno/tm2/pkg/telemetry" "github.com/gnolang/gno/tm2/pkg/telemetry/metrics" - "golang.org/x/sync/errgroup" ) // MultiplexConfigFromP2P returns a multiplex connection configuration @@ -27,10 +26,6 @@ func MultiplexConfigFromP2P(cfg *config.P2PConfig) conn.MConnConfig { return mConfig } -// PeerFilterFunc to be implemented by filter hooks after a new Peer has been -// fully setup. -type PeerFilterFunc func(PeerSet, Peer) error - // Switch handles peer connections and exposes an API to receive incoming messages // on `Reactors`. Each `Reactor` is responsible for handling incoming messages of one // or more `Channels`. So while sending outgoing messages is typically performed on the peer, @@ -48,9 +43,6 @@ type Switch struct { persistentPeers sync.Map // ID -> *NetAddress; peers whose connections are constant transport Transport - filterTimeout time.Duration - peerFilters []PeerFilterFunc - dialQueue *dial.Queue events *events.Events } @@ -62,15 +54,14 @@ func NewSwitch( options ...SwitchOption, ) *Switch { sw := &Switch{ - config: cfg, - reactors: make(map[string]Reactor), - chDescs: make([]*conn.ChannelDescriptor, 0), - reactorsByCh: make(map[byte]Reactor), - peers: NewSet(), - transport: transport, - filterTimeout: defaultFilterTimeout, - dialQueue: dial.NewQueue(), - events: events.New(), + config: cfg, + reactors: make(map[string]Reactor), + chDescs: make([]*conn.ChannelDescriptor, 0), + reactorsByCh: make(map[byte]Reactor), + peers: NewSet(), + transport: transport, + dialQueue: dial.NewQueue(), + events: events.New(), } sw.BaseService = *service.NewBaseService(nil, "P2P Switch", sw) @@ -425,41 +416,9 @@ func (sw *Switch) runAcceptLoop(ctx context.Context) { } } -// TODO remove this entirely -func (sw *Switch) filterPeer(p Peer) error { - // Avoid duplicate - if sw.peers.Has(p.ID()) { - return RejectedError{ - id: p.ID(), - isDuplicate: true, - } - } - - ctx, cancelFn := context.WithTimeout(context.Background(), sw.filterTimeout) - defer cancelFn() - - g, _ := errgroup.WithContext(ctx) - - for _, filterFn := range sw.peerFilters { - g.Go(func() error { - return filterFn(sw.peers, p) - }) - } - - if err := g.Wait(); err != nil { - return RejectedError{id: p.ID(), err: err, isFiltered: true} - } - - return nil -} - // addPeer starts up the Peer and adds it to the Switch. Error is returned if // the peer is filtered out or failed to start or can't be added. func (sw *Switch) addPeer(p Peer) error { - if err := sw.filterPeer(p); err != nil { - return err - } - p.SetLogger(sw.Logger.With("peer", p.SocketAddr())) // Handle the shut down case where the switch has stopped, but we're diff --git a/tm2/pkg/p2p/switch_option.go b/tm2/pkg/p2p/switch_option.go index 35c57fb59e9..ba94a14bc86 100644 --- a/tm2/pkg/p2p/switch_option.go +++ b/tm2/pkg/p2p/switch_option.go @@ -5,13 +5,6 @@ import "fmt" // SwitchOption is a callback used for configuring the p2p Switch type SwitchOption func(*Switch) -// WithPeerFilters sets the filters for rejection of new peers. -func WithPeerFilters(filters ...PeerFilterFunc) SwitchOption { - return func(sw *Switch) { - sw.peerFilters = filters - } -} - // WithReactor sets the p2p switch reactors func WithReactor(name string, reactor Reactor) SwitchOption { return func(sw *Switch) { diff --git a/tm2/pkg/p2p/transport.go b/tm2/pkg/p2p/transport.go index bcf8d26101a..d837e29c631 100644 --- a/tm2/pkg/p2p/transport.go +++ b/tm2/pkg/p2p/transport.go @@ -15,7 +15,6 @@ import ( const ( defaultDialTimeout = time.Second - defaultFilterTimeout = 5 * time.Second defaultHandshakeTimeout = 3 * time.Second ) @@ -157,7 +156,6 @@ func NewMultiplexTransport( acceptc: make(chan accept), closec: make(chan struct{}), dialTimeout: defaultDialTimeout, - filterTimeout: defaultFilterTimeout, handshakeTimeout: defaultHandshakeTimeout, mConfig: mConfig, nodeInfo: nodeInfo, From fefba77e56f61f23f6ff1b82700628e51e22a8ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Mon, 14 Oct 2024 14:08:35 +0200 Subject: [PATCH 13/43] Remove unnecessary peer filtering logic from config --- gno.land/cmd/gnoland/config_get_test.go | 8 -------- gno.land/cmd/gnoland/config_set_test.go | 13 ------------- tm2/pkg/bft/config/config.go | 5 ----- tm2/pkg/bft/node/node.go | 23 ----------------------- 4 files changed, 49 deletions(-) diff --git a/gno.land/cmd/gnoland/config_get_test.go b/gno.land/cmd/gnoland/config_get_test.go index f2ddc5ca6d0..dbb8d474a31 100644 --- a/gno.land/cmd/gnoland/config_get_test.go +++ b/gno.land/cmd/gnoland/config_get_test.go @@ -289,14 +289,6 @@ func TestConfig_Get_Base(t *testing.T) { }, true, }, - { - "filter peers flag fetched", - "filter_peers", - func(loadedCfg *config.Config, value []byte) { - assert.Equal(t, loadedCfg.FilterPeers, unmarshalJSONCommon[bool](t, value)) - }, - false, - }, } verifyGetTestTableCommon(t, testTable) diff --git a/gno.land/cmd/gnoland/config_set_test.go b/gno.land/cmd/gnoland/config_set_test.go index cb831f0e502..3a99eed518e 100644 --- a/gno.land/cmd/gnoland/config_set_test.go +++ b/gno.land/cmd/gnoland/config_set_test.go @@ -244,19 +244,6 @@ func TestConfig_Set_Base(t *testing.T) { assert.Equal(t, value, loadedCfg.ProfListenAddress) }, }, - { - "filter peers flag updated", - []string{ - "filter_peers", - "true", - }, - func(loadedCfg *config.Config, value string) { - boolVal, err := strconv.ParseBool(value) - require.NoError(t, err) - - assert.Equal(t, boolVal, loadedCfg.FilterPeers) - }, - }, } verifySetTestTableCommon(t, testTable) diff --git a/tm2/pkg/bft/config/config.go b/tm2/pkg/bft/config/config.go index f9e9a0cd899..b89d456a9be 100644 --- a/tm2/pkg/bft/config/config.go +++ b/tm2/pkg/bft/config/config.go @@ -318,10 +318,6 @@ type BaseConfig struct { // TCP or UNIX socket address for the profiling server to listen on ProfListenAddress string `toml:"prof_laddr" comment:"TCP or UNIX socket address for the profiling server to listen on"` - - // If true, query the ABCI app on connecting to a new peer - // so the app can decide if we should keep the connection or not - FilterPeers bool `toml:"filter_peers" comment:"If true, query the ABCI app on connecting to a new peer\n so the app can decide if we should keep the connection or not"` // false } // DefaultBaseConfig returns a default base configuration for a Tendermint node @@ -335,7 +331,6 @@ func DefaultBaseConfig() BaseConfig { ABCI: SocketABCI, ProfListenAddress: "", FastSyncMode: true, - FilterPeers: false, DBBackend: db.GoLevelDBBackend.String(), DBPath: DefaultDBDir, } diff --git a/tm2/pkg/bft/node/node.go b/tm2/pkg/bft/node/node.go index 789b84dea05..3aebd4e0217 100644 --- a/tm2/pkg/bft/node/node.go +++ b/tm2/pkg/bft/node/node.go @@ -17,7 +17,6 @@ import ( "github.com/rs/cors" "github.com/gnolang/gno/tm2/pkg/amino" - abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" bc "github.com/gnolang/gno/tm2/pkg/bft/blockchain" cfg "github.com/gnolang/gno/tm2/pkg/bft/config" cs "github.com/gnolang/gno/tm2/pkg/bft/consensus" @@ -325,28 +324,6 @@ func createTransport(config *cfg.Config, nodeInfo p2p.NodeInfo, nodeKey *p2p.Nod connFilters = append(connFilters, p2p.ConnDuplicateIPFilter()) } - // Filter peers by addr or pubkey with an ABCI query. - // If the query return code is OK, add peer. - if config.FilterPeers { - connFilters = append( - connFilters, - // ABCI query for address filtering. - func(_ p2p.ConnSet, c net.Conn, _ []net.IP) error { - res, err := proxyApp.Query().QuerySync(abci.RequestQuery{ - Path: fmt.Sprintf("/p2p/filter/addr/%s", c.RemoteAddr().String()), - }) - if err != nil { - return err - } - if res.IsErr() { - return fmt.Errorf("error querying abci app: %v", res) - } - - return nil - }, - ) - } - p2p.MultiplexTransportConnFilters(connFilters...)(transport) return transport } From 2f11de2897aa782c7c7a60e005a9c47dad62924a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Tue, 15 Oct 2024 13:52:34 +0200 Subject: [PATCH 14/43] Save transport progress --- tm2/pkg/bft/node/node.go | 4 +- tm2/pkg/p2p/netaddress.go | 10 +- tm2/pkg/p2p/switch.go | 10 +- tm2/pkg/p2p/test_util.go | 6 +- tm2/pkg/p2p/transport.go | 404 +++++++++++++------------------------- tm2/pkg/p2p/types.go | 16 ++ 6 files changed, 162 insertions(+), 288 deletions(-) diff --git a/tm2/pkg/bft/node/node.go b/tm2/pkg/bft/node/node.go index 3aebd4e0217..b92f57d203c 100644 --- a/tm2/pkg/bft/node/node.go +++ b/tm2/pkg/bft/node/node.go @@ -313,7 +313,7 @@ func createConsensusReactor(config *cfg.Config, return consensusReactor, consensusState } -func createTransport(config *cfg.Config, nodeInfo p2p.NodeInfo, nodeKey *p2p.NodeKey, proxyApp appconn.AppConns) *p2p.MultiplexTransport { +func createTransport(config *cfg.Config, nodeInfo p2p.NodeInfo, nodeKey *p2p.NodeKey) *p2p.MultiplexTransport { var ( mConnConfig = p2p.MultiplexConfigFromP2P(config.P2P) transport = p2p.NewMultiplexTransport(nodeInfo, *nodeKey, mConnConfig) @@ -445,7 +445,7 @@ func NewNode(config *cfg.Config, } // Setup Transport. - transport := createTransport(config, nodeInfo, nodeKey, proxyApp) + transport := createTransport(config, nodeInfo, nodeKey) // Setup Switch. p2pLogger := logger.With("module", "p2p") diff --git a/tm2/pkg/p2p/netaddress.go b/tm2/pkg/p2p/netaddress.go index f22472375a4..c5ebc43961a 100644 --- a/tm2/pkg/p2p/netaddress.go +++ b/tm2/pkg/p2p/netaddress.go @@ -5,11 +5,11 @@ package p2p import ( + "context" "fmt" "net" "strconv" "strings" - "time" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/errors" @@ -223,9 +223,11 @@ func (na *NetAddress) DialString() string { ) } -// DialTimeout calls net.DialTimeout on the address. -func (na *NetAddress) DialTimeout(timeout time.Duration) (net.Conn, error) { - conn, err := net.DialTimeout("tcp", na.DialString(), timeout) +// DialContext dials the given NetAddress with a context +func (na *NetAddress) DialContext(ctx context.Context) (net.Conn, error) { + var d net.Dialer + + conn, err := d.DialContext(ctx, "tcp", na.DialString()) if err != nil { return nil, fmt.Errorf("unable to dial address, %w", err) } diff --git a/tm2/pkg/p2p/switch.go b/tm2/pkg/p2p/switch.go index 1a51894c129..0d47619dd29 100644 --- a/tm2/pkg/p2p/switch.go +++ b/tm2/pkg/p2p/switch.go @@ -73,13 +73,6 @@ func NewSwitch( return sw } -// NetAddress returns the address the switch is listening on. -func (sw *Switch) NetAddress() *NetAddress { - addr := sw.transport.NetAddress() - - return &addr -} - // Subscribe registers to live events happening on the p2p Switch. // Returns the notification channel, along with an unsubscribe method func (sw *Switch) Subscribe(filterFn events.EventFilter) (<-chan events.Event, func()) { @@ -241,6 +234,9 @@ func (sw *Switch) runDialLoop(ctx context.Context) { continue } + // Pop the item from the dial queue + item = sw.dialQueue.Pop() + // Dial the peer sw.Logger.Info( "dialing peer", diff --git a/tm2/pkg/p2p/test_util.go b/tm2/pkg/p2p/test_util.go index 8950dd504f0..0e7ac51b269 100644 --- a/tm2/pkg/p2p/test_util.go +++ b/tm2/pkg/p2p/test_util.go @@ -4,16 +4,16 @@ package p2p // // // ------------------------------------------------ // -// func CreateRoutableAddr() (addr string, netAddr *NetAddress) { +// func CreateRoutableAddr() (addr string, addr *NetAddress) { // for { // id := ed25519.GenPrivKey().PubKey().Address().ID() // var err error // addr = fmt.Sprintf("%s@%v.%v.%v.%v:26656", id, random.RandInt()%256, random.RandInt()%256, random.RandInt()%256, random.RandInt()%256) -// netAddr, err = NewNetAddressFromString(addr) +// addr, err = NewNetAddressFromString(addr) // if err != nil { // panic(err) // } -// if netAddr.Routable() { +// if addr.Routable() { // break // } // } diff --git a/tm2/pkg/p2p/transport.go b/tm2/pkg/p2p/transport.go index d837e29c631..ede928e1c68 100644 --- a/tm2/pkg/p2p/transport.go +++ b/tm2/pkg/p2p/transport.go @@ -4,13 +4,13 @@ import ( "context" "fmt" "net" - "strconv" "time" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/errors" "github.com/gnolang/gno/tm2/pkg/p2p/conn" + "golang.org/x/sync/errgroup" ) const ( @@ -18,18 +18,11 @@ const ( defaultHandshakeTimeout = 3 * time.Second ) -// IPResolver is a behaviour subset of net.Resolver. -type IPResolver interface { - LookupIPAddr(context.Context, string) ([]net.IPAddr, error) -} - -// accept is the container to carry the upgraded connection and NodeInfo from an -// asynchronously running routine to the Accept method. -type accept struct { - netAddr *NetAddress - conn net.Conn - nodeInfo NodeInfo - err error +// inboundPeer is a wrapper for incoming peer information +type inboundPeer struct { + addr *NetAddress // the dial address of the peer + conn net.Conn // the connection associated with the peer + nodeInfo NodeInfo // the relevant peer node info } // peerConfig is used to bundle data we need to fully setup a Peer with an @@ -49,93 +42,22 @@ type peerConfig struct { reactorsByCh map[byte]Reactor } -// Transport emits and connects to Peers. The implementation of Peer is left to -// the transport. Each transport is also responsible to filter establishing -// peers specific to its domain. -type Transport interface { - // NetAddress returns the associated net address of the transport - NetAddress() NetAddress - - // Accept returns a newly connected Peer. - Accept(peerConfig) (Peer, error) - - // Dial connects to the Peer for the address. - Dial(NetAddress, peerConfig) (Peer, error) - - // Cleanup any resources associated with Peer. - Cleanup(Peer) - - // Close closes the transport - Close() error -} - -// ConnFilterFunc to be implemented by filter hooks after a new connection has -// been established. The set of existing connections is passed along together -// with all resolved IPs for the new connection. -type ConnFilterFunc func(ConnSet, net.Conn, []net.IP) error - -// ConnDuplicateIPFilter resolves and keeps all ips for an incoming connection -// and refuses new ones if they come from a known ip. -func ConnDuplicateIPFilter() ConnFilterFunc { - return func(cs ConnSet, c net.Conn, ips []net.IP) error { - for _, ip := range ips { - if cs.HasIP(ip) { - return RejectedError{ - conn: c, - err: fmt.Errorf("IP<%v> already connected", ip), - isDuplicate: true, - } - } - } - - return nil - } -} - -// MultiplexTransportOption sets an optional parameter on the -// MultiplexTransport. -type MultiplexTransportOption func(*MultiplexTransport) - -// MultiplexTransportConnFilters sets the filters for rejection new connections. -func MultiplexTransportConnFilters( - filters ...ConnFilterFunc, -) MultiplexTransportOption { - return func(mt *MultiplexTransport) { mt.connFilters = filters } -} - -// MultiplexTransportFilterTimeout sets the timeout waited for filter calls to -// return. -func MultiplexTransportFilterTimeout( - timeout time.Duration, -) MultiplexTransportOption { - return func(mt *MultiplexTransport) { mt.filterTimeout = timeout } -} - -// MultiplexTransportResolver sets the Resolver used for ip lookups, defaults to -// net.DefaultResolver. -func MultiplexTransportResolver(resolver IPResolver) MultiplexTransportOption { - return func(mt *MultiplexTransport) { mt.resolver = resolver } -} - // MultiplexTransport accepts and dials tcp connections and upgrades them to // multiplexed peers. type MultiplexTransport struct { - netAddr NetAddress - listener net.Listener + ctx context.Context + cancelFn context.CancelFunc + + netAddr NetAddress // the node's P2P dial address, used for handshaking + nodeInfo NodeInfo // the node's P2P info, used for handshaking + nodeKey NodeKey // the node's private P2P key, used for handshaking - acceptc chan accept - closec chan struct{} + listener net.Listener // listener for inbound peer connections + peerCh chan inboundPeer // pipe for inbound peer connections - // Lookup table for duplicate ip and id checks. - conns ConnSet - connFilters []ConnFilterFunc + conns ConnSet // lookup - dialTimeout time.Duration - filterTimeout time.Duration handshakeTimeout time.Duration - nodeInfo NodeInfo - nodeKey NodeKey - resolver IPResolver // TODO(xla): This config is still needed as we parameterize peerConn and // peer currently. All relevant configuration should be refactored into options @@ -153,15 +75,12 @@ func NewMultiplexTransport( mConfig conn.MConnConfig, ) *MultiplexTransport { return &MultiplexTransport{ - acceptc: make(chan accept), - closec: make(chan struct{}), - dialTimeout: defaultDialTimeout, + peerCh: make(chan inboundPeer, 1), handshakeTimeout: defaultHandshakeTimeout, mConfig: mConfig, nodeInfo: nodeInfo, nodeKey: nodeKey, conns: NewConnSet(), - resolver: net.DefaultResolver, } } @@ -170,39 +89,43 @@ func (mt *MultiplexTransport) NetAddress() NetAddress { return mt.netAddr } -// Accept implements Transport. -func (mt *MultiplexTransport) Accept(cfg peerConfig) (Peer, error) { +// Accept waits for a verified inbound Peer to connect, and returns it [BLOCKING] +func (mt *MultiplexTransport) Accept(ctx context.Context, cfg peerConfig) (Peer, error) { select { - // This case should never have any side-effectful/blocking operations to - // ensure that quality peers are ready to be used. - case a := <-mt.acceptc: - if a.err != nil { - return nil, a.err + case <-ctx.Done(): + return nil, ctx.Err() + case info, ok := <-mt.peerCh: + if !ok { + return nil, errors.New("transport closed") // TODO make standard } - cfg.outbound = false + // cfg.outbound = false TODO integrate - return mt.wrapPeer(a.conn, a.nodeInfo, cfg, a.netAddr), nil - case <-mt.closec: - return nil, TransportClosedError{} + return mt.wrapPeer(info, cfg) } } -// Dial implements Transport. +// Dial creates an outbound verified Peer connection [BLOCKING] func (mt *MultiplexTransport) Dial( + ctx context.Context, addr NetAddress, cfg peerConfig, ) (Peer, error) { - c, err := addr.DialTimeout(mt.dialTimeout) + // Set a dial timeout for the connection + c, err := addr.DialContext(ctx) if err != nil { return nil, err } - // TODO(xla): Evaluate if we should apply filters if we explicitly dial. - if err := mt.filterConn(c); err != nil { - return nil, err + // Check if the connection is a duplicate one + if mt.conns.Has(c) { + // Close the connection + _ = c.Close() + + return nil, errors.New("duplicate peer connection") } + // Handshake with the peer secretConn, nodeInfo, err := mt.upgrade(c, &addr) if err != nil { return nil, err @@ -210,120 +133,121 @@ func (mt *MultiplexTransport) Dial( cfg.outbound = true - p := mt.wrapPeer(secretConn, nodeInfo, cfg, &addr) + info := inboundPeer{ + addr: &addr, + conn: secretConn, + nodeInfo: nodeInfo, + } - return p, nil + return mt.wrapPeer(info, cfg) } // Close implements TransportLifecycle. func (mt *MultiplexTransport) Close() error { - close(mt.closec) + mt.cancelFn() - if mt.listener != nil { - return mt.listener.Close() + if mt.listener == nil { + return nil } - return nil + return mt.listener.Close() } // Listen implements TransportLifecycle. func (mt *MultiplexTransport) Listen(addr NetAddress) error { ln, err := net.Listen("tcp", addr.DialString()) if err != nil { - return err + return fmt.Errorf("unable to listen on address, %w", err) } if addr.Port == 0 { // net.Listen on port 0 means the kernel will auto-allocate a port // - find out which one has been given to us. - _, p, err := net.SplitHostPort(ln.Addr().String()) - if err != nil { + tcpAddr, ok := ln.Addr().(*net.TCPAddr) + if !ok { return fmt.Errorf("error finding port (after listening on port 0): %w", err) } - pInt, _ := strconv.Atoi(p) - addr.Port = uint16(pInt) + + addr.Port = uint16(tcpAddr.Port) } mt.netAddr = addr mt.listener = ln - go mt.acceptPeers() + go mt.runAcceptLoop() return nil } -func (mt *MultiplexTransport) acceptPeers() { - for { - c, err := mt.listener.Accept() - if err != nil { - // If Close() has been called, silently exit. - select { - case _, ok := <-mt.closec: - if !ok { - return - } - default: - // Transport is not closed - } +// runAcceptLoop runs the loop where incoming peers are: +// - 1. accepted by the transport +// - 2. filtered +// - 3. upgraded (handshaked + verified) +func (mt *MultiplexTransport) runAcceptLoop() { + defer close(mt.peerCh) - mt.acceptc <- accept{err: err} + for { + select { + case <-mt.ctx.Done(): return - } + default: + // Accept an incoming peer connection + c, err := mt.listener.Accept() + if err != nil { + // TODO log accept error + continue + } - // Connection upgrade and filtering should be asynchronous to avoid - // Head-of-line blocking[0]. - // Reference: https://github.com/tendermint/classic/issues/2047 - // - // [0] https://en.wikipedia.org/wiki/Head-of-line_blocking - go func(c net.Conn) { - defer func() { - if r := recover(); r != nil { - err := RejectedError{ - conn: c, - err: errors.New("recovered from panic: %v", r), - isAuthFailure: true, - } - select { - case mt.acceptc <- accept{err: err}: - case <-mt.closec: - // Give up if the transport was closed. - _ = c.Close() - return - } - } - }() + // Check if the connection is a duplicate one + if mt.conns.Has(c) { + // TODO add warn log + continue + } - var ( - nodeInfo NodeInfo - secretConn *conn.SecretConnection - netAddr *NetAddress - ) + // Connection upgrade and filtering should be asynchronous to avoid + // Head-of-line blocking[0]. + // Reference: https://github.com/tendermint/classic/issues/2047 + // + // [0] https://en.wikipedia.org/wiki/Head-of-line_blocking + go func(c net.Conn) { + // TODO extract common logic with Dial() + var ( + nodeInfo NodeInfo + secretConn *conn.SecretConnection + ) - err := mt.filterConn(c) - if err == nil { secretConn, nodeInfo, err = mt.upgrade(c, nil) - if err == nil { - addr := c.RemoteAddr() - id := secretConn.RemotePubKey().Address().ID() + if err != nil { + // TODO add error log + return + } + + var ( + addr = c.RemoteAddr() + id = secretConn.RemotePubKey().Address().ID() netAddr, _ = NewNetAddress(id, addr) + ) + + p := inboundPeer{ + addr: netAddr, + conn: c, + nodeInfo: nodeInfo, } - } - select { - case mt.acceptc <- accept{netAddr, secretConn, nodeInfo, err}: - // Make the upgraded peer available. - case <-mt.closec: - // Give up if the transport was closed. - _ = c.Close() - return - } - }(c) + select { + case mt.peerCh <- p: + case <-mt.ctx.Done(): + // Give up if the transport was closed. + _ = c.Close() + } + }(c) + } } } -// Cleanup removes the given address from the connections set and +// Remove removes the given address from the connections set and // closes the connection. -func (mt *MultiplexTransport) Cleanup(p Peer) { +func (mt *MultiplexTransport) Remove(p Peer) { mt.conns.RemoveAddr(p.RemoteAddr()) _ = p.CloseConn() } @@ -334,48 +258,6 @@ func (mt *MultiplexTransport) cleanup(c net.Conn) error { return c.Close() } -func (mt *MultiplexTransport) filterConn(c net.Conn) (err error) { - defer func() { - if err != nil { - _ = c.Close() - } - }() - - // Reject if connection is already present. - if mt.conns.Has(c) { - return RejectedError{conn: c, isDuplicate: true} - } - - // Resolve ips for incoming conn. - ips, err := resolveIPs(mt.resolver, c) - if err != nil { - return err - } - - errc := make(chan error, len(mt.connFilters)) - - for _, f := range mt.connFilters { - go func(f ConnFilterFunc, c net.Conn, ips []net.IP, errc chan<- error) { - errc <- f(mt.conns, c, ips) - }(f, c, ips, errc) - } - - for i := 0; i < cap(errc); i++ { - select { - case err := <-errc: - if err != nil { - return RejectedError{conn: c, err: err, isFiltered: true} - } - case <-time.After(mt.filterTimeout): - return FilterTimeoutError{} - } - } - - mt.conns.Set(c, ips) - - return nil -} - func (mt *MultiplexTransport) upgrade( c net.Conn, dialedAddr *NetAddress, @@ -474,38 +356,36 @@ func (mt *MultiplexTransport) upgrade( } func (mt *MultiplexTransport) wrapPeer( - c net.Conn, - ni NodeInfo, + info inboundPeer, cfg peerConfig, - socketAddr *NetAddress, -) Peer { +) (Peer, error) { persistent := false if cfg.isPersistent != nil { if cfg.outbound { - persistent = cfg.isPersistent(socketAddr) + persistent = cfg.isPersistent(info.addr) } else { - selfReportedAddr := ni.NetAddress + selfReportedAddr := info.nodeInfo.NetAddress persistent = cfg.isPersistent(selfReportedAddr) } } // Extract the host - host, _, err := net.SplitHostPort(c.RemoteAddr().String()) + host, _, err := net.SplitHostPort(info.conn.RemoteAddr().String()) if err != nil { - panic(err) // TODO propagate + return nil, fmt.Errorf("unable to extract peer host, %w", err) } ips, err := net.LookupIP(host) if err != nil { - panic(err) // TODO propagate + return nil, fmt.Errorf("unable to lookup peer IPs, %w", err) } peerConn := &ConnInfo{ Outbound: cfg.outbound, Persistent: persistent, - Conn: c, + Conn: info.conn, RemoteIP: ips[0], - SocketAddr: socketAddr, + SocketAddr: info.addr, } mConfig := &MultiplexConnConfig{ @@ -515,7 +395,7 @@ func (mt *MultiplexTransport) wrapPeer( OnPeerError: cfg.onPeerError, } - return NewPeer(peerConn, ni, mConfig) + return NewPeer(peerConn, info.nodeInfo, mConfig), nil } func handshake( @@ -528,30 +408,30 @@ func handshake( } var ( - errc = make(chan error, 2) - peerNodeInfo NodeInfo ourNodeInfo = nodeInfo ) - go func(errc chan<- error, c net.Conn) { + g, _ := errgroup.WithContext(context.Background()) + + g.Go(func() error { _, err := amino.MarshalSizedWriter(c, ourNodeInfo) - errc <- err - }(errc, c) - go func(errc chan<- error, c net.Conn) { + + return err + }) + + g.Go(func() error { _, err := amino.UnmarshalSizedReader( c, &peerNodeInfo, - int64(MaxNodeInfoSize), + MaxNodeInfoSize, ) - errc <- err - }(errc, c) - for i := 0; i < cap(errc); i++ { - err := <-errc - if err != nil { - return NodeInfo{}, err - } + return err + }) + + if err := g.Wait(); err != nil { + return NodeInfo{}, err } return peerNodeInfo, c.SetDeadline(time.Time{}) @@ -573,23 +453,3 @@ func upgradeSecretConn( return sc, sc.SetDeadline(time.Time{}) } - -func resolveIPs(resolver IPResolver, c net.Conn) ([]net.IP, error) { - host, _, err := net.SplitHostPort(c.RemoteAddr().String()) - if err != nil { - return nil, err - } - - addrs, err := resolver.LookupIPAddr(context.Background(), host) - if err != nil { - return nil, err - } - - ips := []net.IP{} - - for _, addr := range addrs { - ips = append(ips, addr.IP) - } - - return ips, nil -} diff --git a/tm2/pkg/p2p/types.go b/tm2/pkg/p2p/types.go index d13252d66fe..e88512eeb79 100644 --- a/tm2/pkg/p2p/types.go +++ b/tm2/pkg/p2p/types.go @@ -1,6 +1,7 @@ package p2p import ( + "context" "net" "github.com/gnolang/gno/tm2/pkg/crypto" @@ -55,3 +56,18 @@ type PeerSet interface { NumInbound() uint64 // returns the number of connected inbound nodes NumOutbound() uint64 // returns the number of connected outbound nodes } + +// Transport handles peer dialing and connection acceptance. Additionally, +// it is also responsible for any custom connection mechanisms (like handshaking). +// Peers returned by the transport are considered to be verified and sound +type Transport interface { + // Accept returns a newly connected inbound peer + Accept(context.Context) (Peer, error) + + // Dial dials a peer, and returns it + Dial(context.Context, NetAddress) (Peer, error) + + // Remove drops any resources associated + // with the Peer in the transport + Remove(Peer) +} From 1c0152eccb4ed0073ce688ae8d698bff160bb169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Wed, 16 Oct 2024 16:59:35 +0200 Subject: [PATCH 15/43] Save progress on transport --- tm2/pkg/p2p/conn_set.go | 81 --------- tm2/pkg/p2p/netaddress.go | 4 +- tm2/pkg/p2p/switch.go | 26 ++- tm2/pkg/p2p/test_util.go | 2 +- tm2/pkg/p2p/transport.go | 330 ++++++++++++++-------------------- tm2/pkg/p2p/transport_test.go | 2 +- tm2/pkg/p2p/types.go | 27 ++- 7 files changed, 186 insertions(+), 286 deletions(-) delete mode 100644 tm2/pkg/p2p/conn_set.go diff --git a/tm2/pkg/p2p/conn_set.go b/tm2/pkg/p2p/conn_set.go deleted file mode 100644 index d646227831a..00000000000 --- a/tm2/pkg/p2p/conn_set.go +++ /dev/null @@ -1,81 +0,0 @@ -package p2p - -import ( - "net" - "sync" -) - -// ConnSet is a lookup table for connections and all their ips. -type ConnSet interface { - Has(net.Conn) bool - HasIP(net.IP) bool - Set(net.Conn, []net.IP) - Remove(net.Conn) - RemoveAddr(net.Addr) -} - -type connSetItem struct { - conn net.Conn - ips []net.IP -} - -type connSet struct { - sync.RWMutex - - conns map[string]connSetItem -} - -// NewConnSet returns a ConnSet implementation. -func NewConnSet() *connSet { - return &connSet{ - conns: map[string]connSetItem{}, - } -} - -func (cs *connSet) Has(c net.Conn) bool { - cs.RLock() - defer cs.RUnlock() - - _, ok := cs.conns[c.RemoteAddr().String()] - - return ok -} - -func (cs *connSet) HasIP(ip net.IP) bool { - cs.RLock() - defer cs.RUnlock() - - for _, c := range cs.conns { - for _, known := range c.ips { - if known.Equal(ip) { - return true - } - } - } - - return false -} - -func (cs *connSet) Remove(c net.Conn) { - cs.Lock() - defer cs.Unlock() - - delete(cs.conns, c.RemoteAddr().String()) -} - -func (cs *connSet) RemoveAddr(addr net.Addr) { - cs.Lock() - defer cs.Unlock() - - delete(cs.conns, addr.String()) -} - -func (cs *connSet) Set(c net.Conn, ips []net.IP) { - cs.Lock() - defer cs.Unlock() - - cs.conns[c.RemoteAddr().String()] = connSetItem{ - conn: c, - ips: ips, - } -} diff --git a/tm2/pkg/p2p/netaddress.go b/tm2/pkg/p2p/netaddress.go index c5ebc43961a..7c0f85f6a63 100644 --- a/tm2/pkg/p2p/netaddress.go +++ b/tm2/pkg/p2p/netaddress.go @@ -160,12 +160,12 @@ func NewNetAddressFromIPPort(ip net.IP, port uint16) *NetAddress { // Equals reports whether na and other are the same addresses, // including their ID, IP, and Port. -func (na *NetAddress) Equals(other *NetAddress) bool { +func (na *NetAddress) Equals(other NetAddress) bool { return na.String() == other.String() } // Same returns true is na has the same non-empty ID or DialString as other. -func (na *NetAddress) Same(other *NetAddress) bool { +func (na *NetAddress) Same(other NetAddress) bool { var ( dialsSame = na.DialString() == other.DialString() IDsSame = na.ID != "" && na.ID == other.ID diff --git a/tm2/pkg/p2p/switch.go b/tm2/pkg/p2p/switch.go index 0d47619dd29..893b9cebec1 100644 --- a/tm2/pkg/p2p/switch.go +++ b/tm2/pkg/p2p/switch.go @@ -109,7 +109,7 @@ func (sw *Switch) OnStart() error { // OnStop implements BaseService. It stops all peers and reactors. func (sw *Switch) OnStop() { // Stop transport - if err := sw.transport.Close(); err != nil { + if err := sw.transport.Close(); err != nil { // TODO move to node sw.Logger.Error("unable to gracefully close transport", "err", err) } @@ -186,7 +186,16 @@ func (sw *Switch) StopPeerForError(peer Peer, err error) { func (sw *Switch) stopAndRemovePeer(peer Peer, err error) { // Remove the peer from the transport - sw.transport.Cleanup(peer) + sw.transport.Remove(peer) + + // Close the (original) peer connection + if closeErr := peer.CloseConn(); closeErr != nil { + sw.Logger.Error( + "unable to gracefully close peer connection", + "peer", peer, + "err", err, + ) + } // Stop the peer connection multiplexing if stopErr := peer.Stop(); stopErr != nil { @@ -272,7 +281,7 @@ func (sw *Switch) runDialLoop(ctx context.Context) { "err", err, ) - sw.transport.Cleanup(p) + sw.transport.Remove(p) if !p.IsRunning() { // TODO check if this check is even required @@ -335,6 +344,13 @@ func (sw *Switch) runRedialLoop(ctx context.Context) { // To monitor dial progress, subscribe to adequate p2p Switch events func (sw *Switch) DialPeers(peerAddrs ...*NetAddress) { for _, peerAddr := range peerAddrs { + // Check if this is our address + if peerAddr.Same(sw.transport.NetAddress()) { + sw.Logger.Warn("ignoring request for self-dial") + + continue + } + item := dial.Item{ Time: time.Now(), Address: peerAddr, @@ -389,14 +405,14 @@ func (sw *Switch) runAcceptLoop(ctx context.Context) { "max", sw.config.MaxNumInboundPeers, ) - sw.transport.Cleanup(p) + sw.transport.Remove(p) continue } // There are open peer slots, add peers if err := sw.addPeer(p); err != nil { - sw.transport.Cleanup(p) + sw.transport.Remove(p) if p.IsRunning() { _ = p.Stop() diff --git a/tm2/pkg/p2p/test_util.go b/tm2/pkg/p2p/test_util.go index 0e7ac51b269..1d4fb3f5a3a 100644 --- a/tm2/pkg/p2p/test_util.go +++ b/tm2/pkg/p2p/test_util.go @@ -175,7 +175,7 @@ package p2p // conn := rawConn // // // Encrypt connection -// conn, err = upgradeSecretConn(conn, cfg.HandshakeTimeout, ourNodePrivKey) +// conn, err = upgradeToSecretConn(conn, cfg.HandshakeTimeout, ourNodePrivKey) // if err != nil { // return pc, errors.Wrap(err, "Error creating peer") // } diff --git a/tm2/pkg/p2p/transport.go b/tm2/pkg/p2p/transport.go index ede928e1c68..5f1c30cf10a 100644 --- a/tm2/pkg/p2p/transport.go +++ b/tm2/pkg/p2p/transport.go @@ -3,7 +3,9 @@ package p2p import ( "context" "fmt" + "log/slog" "net" + "sync" "time" "github.com/gnolang/gno/tm2/pkg/amino" @@ -13,49 +15,34 @@ import ( "golang.org/x/sync/errgroup" ) +// TODO make options const ( defaultDialTimeout = time.Second defaultHandshakeTimeout = 3 * time.Second ) -// inboundPeer is a wrapper for incoming peer information -type inboundPeer struct { +// peerInfo is a wrapper for an unverified peer connection +type peerInfo struct { addr *NetAddress // the dial address of the peer conn net.Conn // the connection associated with the peer nodeInfo NodeInfo // the relevant peer node info } -// peerConfig is used to bundle data we need to fully setup a Peer with an -// MConn, provided by the caller of Accept and Dial (currently the Switch). This -// a temporary measure until reactor setup is less dynamic and we introduce the -// concept of PeerBehaviour to communicate about significant Peer lifecycle -// events. -// TODO(xla): Refactor out with more static Reactor setup and PeerBehaviour. -type peerConfig struct { - chDescs []*conn.ChannelDescriptor - onPeerError func(Peer, error) - outbound bool - // isPersistent allows you to set a function, which, given socket address - // (for outbound peers) OR self-reported address (for inbound peers), tells - // if the peer is persistent or not. - isPersistent func(*NetAddress) bool - reactorsByCh map[byte]Reactor -} - // MultiplexTransport accepts and dials tcp connections and upgrades them to // multiplexed peers. type MultiplexTransport struct { ctx context.Context cancelFn context.CancelFunc + logger *slog.Logger + netAddr NetAddress // the node's P2P dial address, used for handshaking nodeInfo NodeInfo // the node's P2P info, used for handshaking nodeKey NodeKey // the node's private P2P key, used for handshaking - listener net.Listener // listener for inbound peer connections - peerCh chan inboundPeer // pipe for inbound peer connections - - conns ConnSet // lookup + listener net.Listener // listener for inbound peer connections + peerCh chan peerInfo // pipe for inbound peer connections + activeConns sync.Map // active peer connections (remote address -> nothing) handshakeTimeout time.Duration @@ -75,41 +62,39 @@ func NewMultiplexTransport( mConfig conn.MConnConfig, ) *MultiplexTransport { return &MultiplexTransport{ - peerCh: make(chan inboundPeer, 1), + peerCh: make(chan peerInfo, 1), handshakeTimeout: defaultHandshakeTimeout, mConfig: mConfig, nodeInfo: nodeInfo, nodeKey: nodeKey, - conns: NewConnSet(), } } -// NetAddress implements Transport. +// NetAddress returns the transport's listen address (for p2p connections) func (mt *MultiplexTransport) NetAddress() NetAddress { return mt.netAddr } // Accept waits for a verified inbound Peer to connect, and returns it [BLOCKING] -func (mt *MultiplexTransport) Accept(ctx context.Context, cfg peerConfig) (Peer, error) { +func (mt *MultiplexTransport) Accept(ctx context.Context, behavior PeerBehavior) (Peer, error) { select { case <-ctx.Done(): return nil, ctx.Err() case info, ok := <-mt.peerCh: if !ok { - return nil, errors.New("transport closed") // TODO make standard + return nil, errors.New("transport closed") // TODO make constant } - // cfg.outbound = false TODO integrate - - return mt.wrapPeer(info, cfg) + return mt.newMultiplexPeer(info, behavior, false) } } -// Dial creates an outbound verified Peer connection [BLOCKING] +// Dial creates an outbound Peer connection, and +// verifies it (performs handshaking) [BLOCKING] func (mt *MultiplexTransport) Dial( ctx context.Context, addr NetAddress, - cfg peerConfig, + behavior PeerBehavior, ) (Peer, error) { // Set a dial timeout for the connection c, err := addr.DialContext(ctx) @@ -117,32 +102,19 @@ func (mt *MultiplexTransport) Dial( return nil, err } - // Check if the connection is a duplicate one - if mt.conns.Has(c) { - // Close the connection + // Process the connection with expected ID + info, err := mt.processConn(c, addr.ID) + if err != nil { + // Close the net peer connection _ = c.Close() - return nil, errors.New("duplicate peer connection") - } - - // Handshake with the peer - secretConn, nodeInfo, err := mt.upgrade(c, &addr) - if err != nil { return nil, err } - cfg.outbound = true - - info := inboundPeer{ - addr: &addr, - conn: secretConn, - nodeInfo: nodeInfo, - } - - return mt.wrapPeer(info, cfg) + return mt.newMultiplexPeer(info, behavior, true) } -// Close implements TransportLifecycle. +// Close stops the multiplex transport func (mt *MultiplexTransport) Close() error { mt.cancelFn() @@ -153,8 +125,9 @@ func (mt *MultiplexTransport) Close() error { return mt.listener.Close() } -// Listen implements TransportLifecycle. +// Listen starts an active process of listening for incoming connections [NON-BLOCKING] func (mt *MultiplexTransport) Listen(addr NetAddress) error { + // Reserve a port, and start listening ln, err := net.Listen("tcp", addr.DialString()) if err != nil { return fmt.Errorf("unable to listen on address, %w", err) @@ -174,6 +147,8 @@ func (mt *MultiplexTransport) Listen(addr NetAddress) error { mt.netAddr = addr mt.listener = ln + // Run the routine for accepting + // incoming peer connections go mt.runAcceptLoop() return nil @@ -194,48 +169,31 @@ func (mt *MultiplexTransport) runAcceptLoop() { // Accept an incoming peer connection c, err := mt.listener.Accept() if err != nil { - // TODO log accept error - continue - } + mt.logger.Error( + "unable to accept p2p connection", + "err", err, + ) - // Check if the connection is a duplicate one - if mt.conns.Has(c) { - // TODO add warn log continue } - // Connection upgrade and filtering should be asynchronous to avoid - // Head-of-line blocking[0]. - // Reference: https://github.com/tendermint/classic/issues/2047 - // - // [0] https://en.wikipedia.org/wiki/Head-of-line_blocking + // Process the new connection asynchronously go func(c net.Conn) { - // TODO extract common logic with Dial() - var ( - nodeInfo NodeInfo - secretConn *conn.SecretConnection - ) - - secretConn, nodeInfo, err = mt.upgrade(c, nil) + info, err := mt.processConn(c, "") if err != nil { - // TODO add error log - return - } + mt.logger.Error( + "unable to process p2p connection", + "err", err, + ) - var ( - addr = c.RemoteAddr() - id = secretConn.RemotePubKey().Address().ID() - netAddr, _ = NewNetAddress(id, addr) - ) + // Close the connection + _ = c.Close() - p := inboundPeer{ - addr: netAddr, - conn: c, - nodeInfo: nodeInfo, + return } select { - case mt.peerCh <- p: + case mt.peerCh <- info: case <-mt.ctx.Done(): // Give up if the transport was closed. _ = c.Close() @@ -245,129 +203,99 @@ func (mt *MultiplexTransport) runAcceptLoop() { } } -// Remove removes the given address from the connections set and -// closes the connection. -func (mt *MultiplexTransport) Remove(p Peer) { - mt.conns.RemoveAddr(p.RemoteAddr()) - _ = p.CloseConn() -} - -func (mt *MultiplexTransport) cleanup(c net.Conn) error { - mt.conns.Remove(c) - - return c.Close() -} +// processConn handles the raw connection by upgrading it and verifying it +func (mt *MultiplexTransport) processConn(c net.Conn, expectedID ID) (peerInfo, error) { + dialAddr := c.RemoteAddr().String() -func (mt *MultiplexTransport) upgrade( - c net.Conn, - dialedAddr *NetAddress, -) (secretConn *conn.SecretConnection, nodeInfo NodeInfo, err error) { - defer func() { - if err != nil { - _ = mt.cleanup(c) - } - }() + // Check if the connection is a duplicate one + if _, exists := mt.activeConns.LoadOrStore(dialAddr, struct{}{}); exists { + return peerInfo{}, errors.New("duplicate peer connection") // TODO make constant + } - secretConn, err = upgradeSecretConn(c, mt.handshakeTimeout, mt.nodeKey.PrivKey) + // Handshake with the peer, through STS + secretConn, nodeInfo, err := mt.upgradeAndVerifyConn(c) if err != nil { - return nil, NodeInfo{}, RejectedError{ - conn: c, - err: fmt.Errorf("secret conn failed: %w", err), - isAuthFailure: true, - } - } + mt.activeConns.Delete(dialAddr) - // For outgoing conns, ensure connection key matches dialed key. - connID := secretConn.RemotePubKey().Address().ID() - if dialedAddr != nil { - if dialedID := dialedAddr.ID; connID.String() != dialedID.String() { - return nil, NodeInfo{}, RejectedError{ - conn: c, - id: connID, - err: fmt.Errorf( - "conn.ID (%v) dialed ID (%v) mismatch", - connID, - dialedID, - ), - isAuthFailure: true, - } - } + return peerInfo{}, fmt.Errorf("unable to upgrade connection: %w", err) } - nodeInfo, err = handshake(secretConn, mt.handshakeTimeout, mt.nodeInfo) - if err != nil { - return nil, NodeInfo{}, RejectedError{ - conn: c, - err: fmt.Errorf("handshake failed: %w", err), - isAuthFailure: true, - } + // Verify the connection ID + id := secretConn.RemotePubKey().Address().ID() + + if !expectedID.IsZero() && id.String() != expectedID.String() { + mt.activeConns.Delete(dialAddr) + + return peerInfo{}, fmt.Errorf( + "connection ID does not match dialed ID (expected %q got %q)", + expectedID, + id, + ) } - if err := nodeInfo.Validate(); err != nil { - return nil, NodeInfo{}, RejectedError{ - conn: c, - err: err, - isNodeInfoInvalid: true, - } + netAddr, _ := NewNetAddress(id, c.RemoteAddr()) + + return peerInfo{ + addr: netAddr, + conn: secretConn, + nodeInfo: nodeInfo, + }, nil +} + +// Remove removes the peer resources from the transport +func (mt *MultiplexTransport) Remove(p Peer) { + mt.activeConns.Delete(p.RemoteAddr().String()) +} + +// upgradeAndVerifyConn upgrades the connections (performs the handshaking process) +// and verifies that the connecting peer is valid +func (mt *MultiplexTransport) upgradeAndVerifyConn(c net.Conn) (*conn.SecretConnection, NodeInfo, error) { + // Upgrade to a secret connection + secretConn, err := upgradeToSecretConn( + c, + mt.handshakeTimeout, + mt.nodeKey.PrivKey, + ) + if err != nil { + return nil, NodeInfo{}, fmt.Errorf("unable to upgrade p2p connection, %w", err) } - // Ensure connection key matches self reported key. - if connID != nodeInfo.ID() { - return nil, NodeInfo{}, RejectedError{ - conn: c, - id: connID, - err: fmt.Errorf( - "conn.ID (%v) NodeInfo.ID (%v) mismatch", - connID, - nodeInfo.ID(), - ), - isAuthFailure: true, - } + // Exchange node information + nodeInfo, err := exchangeNodeInfo(secretConn, mt.handshakeTimeout, mt.nodeInfo) + if err != nil { + return nil, NodeInfo{}, fmt.Errorf("unable to exchange node information, %w", err) } - // Reject self. - if mt.nodeInfo.ID() == nodeInfo.ID() { - addr, err := NewNetAddress(nodeInfo.ID(), c.RemoteAddr()) - if err != nil { - return nil, NodeInfo{}, NetAddressInvalidError{ - Addr: c.RemoteAddr().String(), - Err: err, - } - } + // Ensure the connection ID matches the node's reported ID + connID := secretConn.RemotePubKey().Address().ID() - return nil, NodeInfo{}, RejectedError{ - addr: *addr, - conn: c, - id: nodeInfo.ID(), - isSelf: true, - } + if connID != nodeInfo.ID() { + return nil, NodeInfo{}, fmt.Errorf( + "connection ID does not match node info ID (expected %q got %q)", + connID.String(), + nodeInfo.ID().String(), + ) } - if err := mt.nodeInfo.CompatibleWith(nodeInfo); err != nil { - return nil, NodeInfo{}, RejectedError{ - conn: c, - err: err, - id: nodeInfo.ID(), - isIncompatible: true, - } + // Check compatibility with the node + if err = mt.nodeInfo.CompatibleWith(nodeInfo); err != nil { + return nil, NodeInfo{}, fmt.Errorf("incompatible node info, %w", err) } return secretConn, nodeInfo, nil } -func (mt *MultiplexTransport) wrapPeer( - info inboundPeer, - cfg peerConfig, +// newMultiplexPeer creates a new multiplex Peer, using +// the provided Peer behavior and info +func (mt *MultiplexTransport) newMultiplexPeer( + info peerInfo, + behavior PeerBehavior, + isOutbound bool, ) (Peer, error) { - persistent := false - if cfg.isPersistent != nil { - if cfg.outbound { - persistent = cfg.isPersistent(info.addr) - } else { - selfReportedAddr := info.nodeInfo.NetAddress - persistent = cfg.isPersistent(selfReportedAddr) - } - } + // Check for peer persistence using the dial address, + // as well as the self-reported address + persistent := behavior.IsPersistentPeer(info.addr) || + behavior.IsPersistentPeer(info.nodeInfo.NetAddress) // Extract the host host, _, err := net.SplitHostPort(info.conn.RemoteAddr().String()) @@ -375,30 +303,35 @@ func (mt *MultiplexTransport) wrapPeer( return nil, fmt.Errorf("unable to extract peer host, %w", err) } + // Look up the IPs ips, err := net.LookupIP(host) if err != nil { return nil, fmt.Errorf("unable to lookup peer IPs, %w", err) } + // Wrap the info related to the connection peerConn := &ConnInfo{ - Outbound: cfg.outbound, + Outbound: isOutbound, Persistent: persistent, Conn: info.conn, - RemoteIP: ips[0], + RemoteIP: ips[0], // IPv4 SocketAddr: info.addr, } + // Create the info related to the multiplex connection mConfig := &MultiplexConnConfig{ MConfig: mt.mConfig, - ReactorsByCh: cfg.reactorsByCh, - ChDescs: cfg.chDescs, - OnPeerError: cfg.onPeerError, + ReactorsByCh: behavior.Reactors(), + ChDescs: behavior.ReactorChDescriptors(), + OnPeerError: behavior.HandlePeerError, } return NewPeer(peerConn, info.nodeInfo, mConfig), nil } -func handshake( +// exchangeNodeInfo performs a "handshake", where node +// info is exchanged between the current node and a peer +func exchangeNodeInfo( c net.Conn, timeout time.Duration, nodeInfo NodeInfo, @@ -434,10 +367,18 @@ func handshake( return NodeInfo{}, err } - return peerNodeInfo, c.SetDeadline(time.Time{}) + // Validate the received node information + if err := nodeInfo.Validate(); err != nil { + return NodeInfo{}, fmt.Errorf("unable to validate node info, %w", err) + } + + return peerNodeInfo, nil } -func upgradeSecretConn( +// upgradeToSecretConn takes an active TCP connection, +// and upgrades it to a verified, handshaked connection through +// the STS protocol +func upgradeToSecretConn( c net.Conn, timeout time.Duration, privKey crypto.PrivKey, @@ -446,6 +387,7 @@ func upgradeSecretConn( return nil, err } + // Handshake (STS) sc, err := conn.MakeSecretConnection(c, privKey) if err != nil { return nil, err diff --git a/tm2/pkg/p2p/transport_test.go b/tm2/pkg/p2p/transport_test.go index 2419add987b..f2c63475f43 100644 --- a/tm2/pkg/p2p/transport_test.go +++ b/tm2/pkg/p2p/transport_test.go @@ -242,7 +242,7 @@ package p2p // errc <- fmt.Errorf("Fast peer timed out") // } // -// sc, err := upgradeSecretConn(c, 100*time.Millisecond, ed25519.GenPrivKey()) +// sc, err := upgradeToSecretConn(c, 100*time.Millisecond, ed25519.GenPrivKey()) // if err != nil { // errc <- err // return diff --git a/tm2/pkg/p2p/types.go b/tm2/pkg/p2p/types.go index e88512eeb79..51765352e96 100644 --- a/tm2/pkg/p2p/types.go +++ b/tm2/pkg/p2p/types.go @@ -61,13 +61,36 @@ type PeerSet interface { // it is also responsible for any custom connection mechanisms (like handshaking). // Peers returned by the transport are considered to be verified and sound type Transport interface { + // NetAddress returns the Transport's dial address + NetAddress() NetAddress + // Accept returns a newly connected inbound peer - Accept(context.Context) (Peer, error) + Accept(context.Context, PeerBehavior) (Peer, error) // Dial dials a peer, and returns it - Dial(context.Context, NetAddress) (Peer, error) + Dial(context.Context, NetAddress, PeerBehavior) (Peer, error) // Remove drops any resources associated // with the Peer in the transport Remove(Peer) } + +// PeerBehavior wraps the Reactor and Switch information a Transport would need when +// dialing or accepting new Peer connections. +// It is worth noting that the only reason why this information is required in the first place, +// is because Peers expose an API through which different TM modules can interact with them. +// In the future™, modules should not directly "Send" anything to Peers, but instead communicate through +// other mediums, such as the P2P module +type PeerBehavior interface { + // ReactorChDescriptors returns the Reactor channel descriptors + ReactorChDescriptors() []*conn.ChannelDescriptor + + // Reactors returns the node's active p2p Reactors (modules) + Reactors() map[byte]Reactor + + // HandlePeerError propagates a peer connection error for further processing + HandlePeerError(Peer, error) + + // IsPersistentPeer returns a flag indicating if the given peer is persistent + IsPersistentPeer(*NetAddress) bool +} From 1653571681e02b3eb84ca2b590f452e8f225bf8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Thu, 17 Oct 2024 17:40:30 +0200 Subject: [PATCH 16/43] Save reorg progress --- gno.land/cmd/gnoland/config_get_test.go | 42 +--- gno.land/cmd/gnoland/config_set_test.go | 61 +---- gno.land/cmd/gnoland/secrets_common.go | 6 +- gno.land/cmd/gnoland/secrets_common_test.go | 8 +- gno.land/cmd/gnoland/secrets_get.go | 3 +- gno.land/cmd/gnoland/secrets_get_test.go | 4 +- gno.land/cmd/gnoland/secrets_init.go | 6 +- gno.land/cmd/gnoland/secrets_init_test.go | 4 +- gno.land/cmd/gnoland/secrets_verify.go | 4 +- gno.land/cmd/gnoland/secrets_verify_test.go | 4 +- gno.land/pkg/gnoland/node_inmemory.go | 4 +- tm2/pkg/bft/blockchain/reactor.go | 5 +- tm2/pkg/bft/consensus/reactor.go | 5 +- tm2/pkg/bft/mempool/reactor.go | 5 +- tm2/pkg/bft/node/node.go | 66 +++--- tm2/pkg/bft/rpc/client/batch_test.go | 2 +- tm2/pkg/bft/rpc/client/client_test.go | 2 +- tm2/pkg/bft/rpc/client/e2e_test.go | 2 +- tm2/pkg/bft/rpc/core/pipe.go | 3 +- tm2/pkg/bft/rpc/core/types/responses.go | 9 +- tm2/pkg/bft/rpc/core/types/responses_test.go | 14 +- tm2/pkg/p2p/config/config.go | 30 +-- tm2/pkg/p2p/conn/connection.go | 13 ++ tm2/pkg/p2p/conn/connection_test.go | 18 +- tm2/pkg/p2p/dial/dial.go | 6 +- tm2/pkg/p2p/errors.go | 184 --------------- tm2/pkg/p2p/events/types.go | 14 +- tm2/pkg/p2p/mock/peer.go | 30 +-- tm2/pkg/p2p/mock_test.go | 15 +- tm2/pkg/p2p/peer.go | 41 ++-- tm2/pkg/p2p/peer_test.go | 31 +-- tm2/pkg/p2p/set.go | 34 +-- tm2/pkg/p2p/set_test.go | 21 +- tm2/pkg/p2p/switch.go | 141 ++++++------ tm2/pkg/p2p/switch_option.go | 38 +++- tm2/pkg/p2p/switch_test.go | 2 +- tm2/pkg/p2p/test_util.go | 221 ------------------- tm2/pkg/p2p/transport.go | 101 +++++---- tm2/pkg/p2p/types.go | 25 +-- tm2/pkg/p2p/{ => types}/key.go | 5 +- tm2/pkg/p2p/{ => types}/key_test.go | 2 +- tm2/pkg/p2p/{ => types}/netaddress.go | 19 +- tm2/pkg/p2p/{ => types}/netaddress_test.go | 2 +- tm2/pkg/p2p/{ => types}/node_info.go | 2 +- tm2/pkg/p2p/{ => types}/node_info_test.go | 2 +- 45 files changed, 402 insertions(+), 854 deletions(-) delete mode 100644 tm2/pkg/p2p/errors.go delete mode 100644 tm2/pkg/p2p/test_util.go rename tm2/pkg/p2p/{ => types}/key.go (96%) rename tm2/pkg/p2p/{ => types}/key_test.go (99%) rename tm2/pkg/p2p/{ => types}/netaddress.go (94%) rename tm2/pkg/p2p/{ => types}/netaddress_test.go (99%) rename tm2/pkg/p2p/{ => types}/node_info.go (99%) rename tm2/pkg/p2p/{ => types}/node_info_test.go (99%) diff --git a/gno.land/cmd/gnoland/config_get_test.go b/gno.land/cmd/gnoland/config_get_test.go index dbb8d474a31..b546d7d4cea 100644 --- a/gno.land/cmd/gnoland/config_get_test.go +++ b/gno.land/cmd/gnoland/config_get_test.go @@ -608,14 +608,6 @@ func TestConfig_Get_P2P(t *testing.T) { }, true, }, - { - "upnp toggle", - "p2p.upnp", - func(loadedCfg *config.Config, value []byte) { - assert.Equal(t, loadedCfg.P2P.UPNP, unmarshalJSONCommon[bool](t, value)) - }, - false, - }, { "max inbound peers", "p2p.max_num_inbound_peers", @@ -668,15 +660,7 @@ func TestConfig_Get_P2P(t *testing.T) { "pex reactor toggle", "p2p.pex", func(loadedCfg *config.Config, value []byte) { - assert.Equal(t, loadedCfg.P2P.PexReactor, unmarshalJSONCommon[bool](t, value)) - }, - false, - }, - { - "seed mode", - "p2p.seed_mode", - func(loadedCfg *config.Config, value []byte) { - assert.Equal(t, loadedCfg.P2P.SeedMode, unmarshalJSONCommon[bool](t, value)) + assert.Equal(t, loadedCfg.P2P.PeerExchange, unmarshalJSONCommon[bool](t, value)) }, false, }, @@ -696,30 +680,6 @@ func TestConfig_Get_P2P(t *testing.T) { }, true, }, - { - "allow duplicate IP", - "p2p.allow_duplicate_ip", - func(loadedCfg *config.Config, value []byte) { - assert.Equal(t, loadedCfg.P2P.AllowDuplicateIP, unmarshalJSONCommon[bool](t, value)) - }, - false, - }, - { - "handshake timeout", - "p2p.handshake_timeout", - func(loadedCfg *config.Config, value []byte) { - assert.Equal(t, loadedCfg.P2P.HandshakeTimeout, unmarshalJSONCommon[time.Duration](t, value)) - }, - false, - }, - { - "dial timeout", - "p2p.dial_timeout", - func(loadedCfg *config.Config, value []byte) { - assert.Equal(t, loadedCfg.P2P.DialTimeout, unmarshalJSONCommon[time.Duration](t, value)) - }, - false, - }, } verifyGetTestTableCommon(t, testTable) diff --git a/gno.land/cmd/gnoland/config_set_test.go b/gno.land/cmd/gnoland/config_set_test.go index 3a99eed518e..39880313043 100644 --- a/gno.land/cmd/gnoland/config_set_test.go +++ b/gno.land/cmd/gnoland/config_set_test.go @@ -492,19 +492,6 @@ func TestConfig_Set_P2P(t *testing.T) { assert.Equal(t, value, loadedCfg.P2P.PersistentPeers) }, }, - { - "upnp toggle updated", - []string{ - "p2p.upnp", - "false", - }, - func(loadedCfg *config.Config, value string) { - boolVal, err := strconv.ParseBool(value) - require.NoError(t, err) - - assert.Equal(t, boolVal, loadedCfg.P2P.UPNP) - }, - }, { "max inbound peers updated", []string{ @@ -575,20 +562,7 @@ func TestConfig_Set_P2P(t *testing.T) { boolVal, err := strconv.ParseBool(value) require.NoError(t, err) - assert.Equal(t, boolVal, loadedCfg.P2P.PexReactor) - }, - }, - { - "seed mode updated", - []string{ - "p2p.seed_mode", - "false", - }, - func(loadedCfg *config.Config, value string) { - boolVal, err := strconv.ParseBool(value) - require.NoError(t, err) - - assert.Equal(t, boolVal, loadedCfg.P2P.SeedMode) + assert.Equal(t, boolVal, loadedCfg.P2P.PeerExchange) }, }, { @@ -601,39 +575,6 @@ func TestConfig_Set_P2P(t *testing.T) { assert.Equal(t, value, loadedCfg.P2P.PrivatePeerIDs) }, }, - { - "allow duplicate IPs updated", - []string{ - "p2p.allow_duplicate_ip", - "false", - }, - func(loadedCfg *config.Config, value string) { - boolVal, err := strconv.ParseBool(value) - require.NoError(t, err) - - assert.Equal(t, boolVal, loadedCfg.P2P.AllowDuplicateIP) - }, - }, - { - "handshake timeout updated", - []string{ - "p2p.handshake_timeout", - "1s", - }, - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.P2P.HandshakeTimeout.String()) - }, - }, - { - "dial timeout updated", - []string{ - "p2p.dial_timeout", - "1s", - }, - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.P2P.DialTimeout.String()) - }, - }, } verifySetTestTableCommon(t, testTable) diff --git a/gno.land/cmd/gnoland/secrets_common.go b/gno.land/cmd/gnoland/secrets_common.go index d40e90f6b48..500336e3489 100644 --- a/gno.land/cmd/gnoland/secrets_common.go +++ b/gno.land/cmd/gnoland/secrets_common.go @@ -8,7 +8,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/bft/privval" "github.com/gnolang/gno/tm2/pkg/crypto" - "github.com/gnolang/gno/tm2/pkg/p2p" + "github.com/gnolang/gno/tm2/pkg/p2p/types" ) var ( @@ -54,7 +54,7 @@ func isValidDirectory(dirPath string) bool { } type secretData interface { - privval.FilePVKey | privval.FilePVLastSignState | p2p.NodeKey + privval.FilePVKey | privval.FilePVLastSignState | types.NodeKey } // readSecretData reads the secret data from the given path @@ -145,7 +145,7 @@ func validateValidatorStateSignature( } // validateNodeKey validates the node's p2p key -func validateNodeKey(key *p2p.NodeKey) error { +func validateNodeKey(key *types.NodeKey) error { if key.PrivKey == nil { return errInvalidNodeKey } diff --git a/gno.land/cmd/gnoland/secrets_common_test.go b/gno.land/cmd/gnoland/secrets_common_test.go index 34592c3bd8f..38c4772c705 100644 --- a/gno.land/cmd/gnoland/secrets_common_test.go +++ b/gno.land/cmd/gnoland/secrets_common_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/gnolang/gno/tm2/pkg/crypto" - "github.com/gnolang/gno/tm2/pkg/p2p" + "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -26,7 +26,7 @@ func TestCommon_SaveReadData(t *testing.T) { t.Run("invalid data read path", func(t *testing.T) { t.Parallel() - readData, err := readSecretData[p2p.NodeKey]("") + readData, err := readSecretData[types.NodeKey]("") assert.Nil(t, readData) assert.ErrorContains( @@ -44,7 +44,7 @@ func TestCommon_SaveReadData(t *testing.T) { require.NoError(t, saveSecretData("totally valid key", path)) - readData, err := readSecretData[p2p.NodeKey](path) + readData, err := readSecretData[types.NodeKey](path) require.Nil(t, readData) assert.ErrorContains(t, err, "unable to unmarshal data") @@ -59,7 +59,7 @@ func TestCommon_SaveReadData(t *testing.T) { require.NoError(t, saveSecretData(key, path)) - readKey, err := readSecretData[p2p.NodeKey](path) + readKey, err := readSecretData[types.NodeKey](path) require.NoError(t, err) assert.Equal(t, key, readKey) diff --git a/gno.land/cmd/gnoland/secrets_get.go b/gno.land/cmd/gnoland/secrets_get.go index 8d111516816..64eb48f7d27 100644 --- a/gno.land/cmd/gnoland/secrets_get.go +++ b/gno.land/cmd/gnoland/secrets_get.go @@ -13,6 +13,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/commands" osm "github.com/gnolang/gno/tm2/pkg/os" "github.com/gnolang/gno/tm2/pkg/p2p" + "github.com/gnolang/gno/tm2/pkg/p2p/types" ) var errInvalidSecretsGetArgs = errors.New("invalid number of secrets get arguments provided") @@ -169,7 +170,7 @@ func readValidatorState(path string) (*validatorStateInfo, error) { // readNodeID reads the node p2p info from the given path func readNodeID(path string) (*nodeIDInfo, error) { - nodeKey, err := readSecretData[p2p.NodeKey](path) + nodeKey, err := readSecretData[types.NodeKey](path) if err != nil { return nil, fmt.Errorf("unable to read node key, %w", err) } diff --git a/gno.land/cmd/gnoland/secrets_get_test.go b/gno.land/cmd/gnoland/secrets_get_test.go index 66e6e3509fc..3dfe0c727dd 100644 --- a/gno.land/cmd/gnoland/secrets_get_test.go +++ b/gno.land/cmd/gnoland/secrets_get_test.go @@ -13,7 +13,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/config" "github.com/gnolang/gno/tm2/pkg/bft/privval" "github.com/gnolang/gno/tm2/pkg/commands" - "github.com/gnolang/gno/tm2/pkg/p2p" + "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -66,7 +66,7 @@ func TestSecrets_Get_All(t *testing.T) { // Get the node key nodeKeyPath := filepath.Join(tempDir, defaultNodeKeyName) - nodeKey, err := readSecretData[p2p.NodeKey](nodeKeyPath) + nodeKey, err := readSecretData[types.NodeKey](nodeKeyPath) require.NoError(t, err) // Get the validator private key diff --git a/gno.land/cmd/gnoland/secrets_init.go b/gno.land/cmd/gnoland/secrets_init.go index 7a368255834..9a7ddd106c3 100644 --- a/gno.land/cmd/gnoland/secrets_init.go +++ b/gno.land/cmd/gnoland/secrets_init.go @@ -12,7 +12,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" osm "github.com/gnolang/gno/tm2/pkg/os" - "github.com/gnolang/gno/tm2/pkg/p2p" + "github.com/gnolang/gno/tm2/pkg/p2p/types" ) var errOverwriteNotEnabled = errors.New("overwrite not enabled") @@ -200,6 +200,6 @@ func generateLastSignValidatorState() *privval.FilePVLastSignState { } // generateNodeKey generates the p2p node key -func generateNodeKey() *p2p.NodeKey { - return p2p.GenerateNodeKey() +func generateNodeKey() *types.NodeKey { + return types.GenerateNodeKey() } diff --git a/gno.land/cmd/gnoland/secrets_init_test.go b/gno.land/cmd/gnoland/secrets_init_test.go index 20e061447f5..7be3650fb4b 100644 --- a/gno.land/cmd/gnoland/secrets_init_test.go +++ b/gno.land/cmd/gnoland/secrets_init_test.go @@ -7,7 +7,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/privval" "github.com/gnolang/gno/tm2/pkg/commands" - "github.com/gnolang/gno/tm2/pkg/p2p" + "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -37,7 +37,7 @@ func verifyValidatorState(t *testing.T, path string) { func verifyNodeKey(t *testing.T, path string) { t.Helper() - nodeKey, err := readSecretData[p2p.NodeKey](path) + nodeKey, err := readSecretData[types.NodeKey](path) require.NoError(t, err) assert.NoError(t, validateNodeKey(nodeKey)) diff --git a/gno.land/cmd/gnoland/secrets_verify.go b/gno.land/cmd/gnoland/secrets_verify.go index 32e563c1c6f..15fef6649ec 100644 --- a/gno.land/cmd/gnoland/secrets_verify.go +++ b/gno.land/cmd/gnoland/secrets_verify.go @@ -8,7 +8,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/privval" "github.com/gnolang/gno/tm2/pkg/commands" - "github.com/gnolang/gno/tm2/pkg/p2p" + "github.com/gnolang/gno/tm2/pkg/p2p/types" ) type secretsVerifyCfg struct { @@ -146,7 +146,7 @@ func readAndVerifyValidatorState(path string, io commands.IO) (*privval.FilePVLa // readAndVerifyNodeKey reads the node p2p key from the given path and verifies it func readAndVerifyNodeKey(path string, io commands.IO) error { - nodeKey, err := readSecretData[p2p.NodeKey](path) + nodeKey, err := readSecretData[types.NodeKey](path) if err != nil { return fmt.Errorf("unable to read node p2p key, %w", err) } diff --git a/gno.land/cmd/gnoland/secrets_verify_test.go b/gno.land/cmd/gnoland/secrets_verify_test.go index 513d7c8b503..67630aaaa4a 100644 --- a/gno.land/cmd/gnoland/secrets_verify_test.go +++ b/gno.land/cmd/gnoland/secrets_verify_test.go @@ -8,7 +8,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/privval" "github.com/gnolang/gno/tm2/pkg/commands" - "github.com/gnolang/gno/tm2/pkg/p2p" + "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -347,7 +347,7 @@ func TestSecrets_Verify_Single(t *testing.T) { dirPath := t.TempDir() path := filepath.Join(dirPath, defaultNodeKeyName) - invalidNodeKey := &p2p.NodeKey{ + invalidNodeKey := &types.NodeKey{ PrivKey: nil, // invalid } diff --git a/gno.land/pkg/gnoland/node_inmemory.go b/gno.land/pkg/gnoland/node_inmemory.go index d168c955607..fec561665f6 100644 --- a/gno.land/pkg/gnoland/node_inmemory.go +++ b/gno.land/pkg/gnoland/node_inmemory.go @@ -15,7 +15,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/db" "github.com/gnolang/gno/tm2/pkg/db/memdb" "github.com/gnolang/gno/tm2/pkg/events" - "github.com/gnolang/gno/tm2/pkg/p2p" + "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/gnolang/gno/tm2/pkg/std" ) @@ -131,7 +131,7 @@ func NewInMemoryNode(logger *slog.Logger, cfg *InMemoryNodeConfig) (*node.Node, dbProvider := func(*node.DBContext) (db.DB, error) { return cfg.DB, nil } // Generate p2p node identity - nodekey := &p2p.NodeKey{PrivKey: ed25519.GenPrivKey()} + nodekey := &types.NodeKey{PrivKey: ed25519.GenPrivKey()} // Create and return the in-memory node instance return node.NewNode(cfg.TMConfig, diff --git a/tm2/pkg/bft/blockchain/reactor.go b/tm2/pkg/bft/blockchain/reactor.go index 1d8245c957c..2c681b21823 100644 --- a/tm2/pkg/bft/blockchain/reactor.go +++ b/tm2/pkg/bft/blockchain/reactor.go @@ -12,6 +12,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/store" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/p2p" + types2 "github.com/gnolang/gno/tm2/pkg/p2p/types" ) const ( @@ -128,8 +129,8 @@ func (bcR *BlockchainReactor) OnStop() { } // GetChannels implements Reactor -func (bcR *BlockchainReactor) GetChannels() []*p2p.ChannelDescriptor { - return []*p2p.ChannelDescriptor{ +func (bcR *BlockchainReactor) GetChannels() []*types2.ChannelDescriptor { + return []*types2.ChannelDescriptor{ { ID: BlockchainChannel, Priority: 10, diff --git a/tm2/pkg/bft/consensus/reactor.go b/tm2/pkg/bft/consensus/reactor.go index aee695114f8..e778525152b 100644 --- a/tm2/pkg/bft/consensus/reactor.go +++ b/tm2/pkg/bft/consensus/reactor.go @@ -17,6 +17,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/events" "github.com/gnolang/gno/tm2/pkg/log" "github.com/gnolang/gno/tm2/pkg/p2p" + types2 "github.com/gnolang/gno/tm2/pkg/p2p/types" ) const ( @@ -123,9 +124,9 @@ conR: } // GetChannels implements Reactor -func (conR *ConsensusReactor) GetChannels() []*p2p.ChannelDescriptor { +func (conR *ConsensusReactor) GetChannels() []*types2.ChannelDescriptor { // TODO optimize - return []*p2p.ChannelDescriptor{ + return []*types2.ChannelDescriptor{ { ID: StateChannel, Priority: 5, diff --git a/tm2/pkg/bft/mempool/reactor.go b/tm2/pkg/bft/mempool/reactor.go index 3ef85b80a21..099e66f74bf 100644 --- a/tm2/pkg/bft/mempool/reactor.go +++ b/tm2/pkg/bft/mempool/reactor.go @@ -13,6 +13,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/clist" "github.com/gnolang/gno/tm2/pkg/p2p" + types2 "github.com/gnolang/gno/tm2/pkg/p2p/types" ) const ( @@ -127,8 +128,8 @@ func (memR *Reactor) OnStart() error { // GetChannels implements Reactor. // It returns the list of channels for this reactor. -func (memR *Reactor) GetChannels() []*p2p.ChannelDescriptor { - return []*p2p.ChannelDescriptor{ +func (memR *Reactor) GetChannels() []*types2.ChannelDescriptor { + return []*types2.ChannelDescriptor{ { ID: MempoolChannel, Priority: 5, diff --git a/tm2/pkg/bft/node/node.go b/tm2/pkg/bft/node/node.go index b92f57d203c..b9a36d2ea88 100644 --- a/tm2/pkg/bft/node/node.go +++ b/tm2/pkg/bft/node/node.go @@ -14,6 +14,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/appconn" "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/file" + types2 "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/rs/cors" "github.com/gnolang/gno/tm2/pkg/amino" @@ -86,7 +87,7 @@ func DefaultNewNode( logger *slog.Logger, ) (*Node, error) { // Generate node PrivKey - nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) + nodeKey, err := types2.LoadOrGenNodeKey(config.NodeKeyFile()) if err != nil { return nil, err } @@ -130,10 +131,10 @@ type Node struct { privValidator types.PrivValidator // local node's validator key // network - transport *p2p.MultiplexTransport + transport *p2p.Transport sw *p2p.Switch // p2p connections - nodeInfo p2p.NodeInfo - nodeKey *p2p.NodeKey // our node privkey + nodeInfo types2.NodeInfo + nodeKey *types2.NodeKey // our node privkey isListening bool // services @@ -313,25 +314,10 @@ func createConsensusReactor(config *cfg.Config, return consensusReactor, consensusState } -func createTransport(config *cfg.Config, nodeInfo p2p.NodeInfo, nodeKey *p2p.NodeKey) *p2p.MultiplexTransport { - var ( - mConnConfig = p2p.MultiplexConfigFromP2P(config.P2P) - transport = p2p.NewMultiplexTransport(nodeInfo, *nodeKey, mConnConfig) - connFilters = []p2p.ConnFilterFunc{} - ) - - if !config.P2P.AllowDuplicateIP { - connFilters = append(connFilters, p2p.ConnDuplicateIPFilter()) - } - - p2p.MultiplexTransportConnFilters(connFilters...)(transport) - return transport -} - // NewNode returns a new, ready to go, Tendermint Node. func NewNode(config *cfg.Config, privValidator types.PrivValidator, - nodeKey *p2p.NodeKey, + nodeKey *types2.NodeKey, clientCreator appconn.ClientCreator, genesisDocProvider GenesisDocProvider, dbProvider DBProvider, @@ -444,24 +430,31 @@ func NewNode(config *cfg.Config, return nil, errors.Wrap(err, "error making NodeInfo") } - // Setup Transport. - transport := createTransport(config, nodeInfo, nodeKey) - - // Setup Switch. p2pLogger := logger.With("module", "p2p") - peerAddrs, errs := p2p.NewNetAddressFromStrings(splitAndTrimEmpty(config.P2P.PersistentPeers, ",", " ")) + // Setup the multiplex transport, used by the P2P switch + // TODO cleanup + transport := p2p.NewMultiplexTransport( + nodeInfo, + *nodeKey, + conn.MConfigFromP2P(config.P2P), + p2pLogger.With("transport", "multiplex"), + ) + + // Setup Switch. + peerAddrs, errs := types2.NewNetAddressFromStrings(splitAndTrimEmpty(config.P2P.PersistentPeers, ",", " ")) for _, err := range errs { p2pLogger.Error("invalid persistent peer address", "err", err) } sw := p2p.NewSwitch( - config.P2P, transport, p2p.WithReactor("MEMPOOL", mempoolReactor), p2p.WithReactor("BLOCKCHAIN", bcReactor), p2p.WithReactor("CONSENSUS", consensusReactor), p2p.WithPersistentPeers(peerAddrs), + p2p.WithMaxInboundPeers(config.P2P.MaxNumInboundPeers), + p2p.WithMaxOutboundPeers(config.P2P.MaxNumOutboundPeers), ) sw.SetLogger(p2pLogger) @@ -541,7 +534,7 @@ func (n *Node) OnStart() error { } // Start the transport. - addr, err := p2p.NewNetAddressFromString(p2p.NetAddressString(n.nodeKey.ID(), n.config.P2P.ListenAddress)) + addr, err := types2.NewNetAddressFromString(types2.NetAddressString(n.nodeKey.ID(), n.config.P2P.ListenAddress)) if err != nil { return err } @@ -569,7 +562,7 @@ func (n *Node) OnStart() error { } // Always connect to persistent peers - peerAddrs, errs := p2p.NewNetAddressFromStrings(splitAndTrimEmpty(n.config.P2P.PersistentPeers, ",", " ")) + peerAddrs, errs := types2.NewNetAddressFromStrings(splitAndTrimEmpty(n.config.P2P.PersistentPeers, ",", " ")) for _, err := range errs { n.Logger.Error("invalid persistent peer address", "err", err) } @@ -590,6 +583,11 @@ func (n *Node) OnStop() { n.evsw.Stop() n.eventStoreService.Stop() + // Stop the node p2p transport + if err := n.transport.Close(); err != nil { + n.Logger.Error("unable to gracefully close transport", "err", err) + } + // now stop the reactors n.sw.Stop() @@ -792,17 +790,17 @@ func (n *Node) IsListening() bool { } // NodeInfo returns the Node's Info from the Switch. -func (n *Node) NodeInfo() p2p.NodeInfo { +func (n *Node) NodeInfo() types2.NodeInfo { return n.nodeInfo } func makeNodeInfo( config *cfg.Config, - nodeKey *p2p.NodeKey, + nodeKey *types2.NodeKey, txEventStore eventstore.TxEventStore, genDoc *types.GenesisDoc, state sm.State, -) (p2p.NodeInfo, error) { +) (types2.NodeInfo, error) { txIndexerStatus := eventstore.StatusOff if txEventStore.GetType() != null.EventStoreType { txIndexerStatus = eventstore.StatusOn @@ -815,7 +813,7 @@ func makeNodeInfo( Version: state.AppVersion, }) - nodeInfo := p2p.NodeInfo{ + nodeInfo := types2.NodeInfo{ VersionSet: vset, Network: genDoc.ChainID, Version: version.Version, @@ -825,7 +823,7 @@ func makeNodeInfo( mempl.MempoolChannel, }, Moniker: config.Moniker, - Other: p2p.NodeInfoOther{ + Other: types2.NodeInfoOther{ TxIndex: txIndexerStatus, RPCAddress: config.RPC.ListenAddress, }, @@ -835,7 +833,7 @@ func makeNodeInfo( if lAddr == "" { lAddr = config.P2P.ListenAddress } - addr, err := p2p.NewNetAddressFromString(p2p.NetAddressString(nodeKey.ID(), lAddr)) + addr, err := types2.NewNetAddressFromString(types2.NetAddressString(nodeKey.ID(), lAddr)) if err != nil { return nodeInfo, errors.Wrap(err, "invalid (local) node net address") } diff --git a/tm2/pkg/bft/rpc/client/batch_test.go b/tm2/pkg/bft/rpc/client/batch_test.go index 2f8e04e4bf1..d4a6c54a14b 100644 --- a/tm2/pkg/bft/rpc/client/batch_test.go +++ b/tm2/pkg/bft/rpc/client/batch_test.go @@ -10,7 +10,7 @@ import ( ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" types "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/types" bfttypes "github.com/gnolang/gno/tm2/pkg/bft/types" - types2 "github.com/gnolang/gno/tm2/pkg/p2p" + types2 "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/tm2/pkg/bft/rpc/client/client_test.go b/tm2/pkg/bft/rpc/client/client_test.go index f0a509d31b6..68b4db095bf 100644 --- a/tm2/pkg/bft/rpc/client/client_test.go +++ b/tm2/pkg/bft/rpc/client/client_test.go @@ -14,7 +14,7 @@ import ( ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" types "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/types" bfttypes "github.com/gnolang/gno/tm2/pkg/bft/types" - types2 "github.com/gnolang/gno/tm2/pkg/p2p" + types2 "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/tm2/pkg/bft/rpc/client/e2e_test.go b/tm2/pkg/bft/rpc/client/e2e_test.go index 5bbe286a9da..d70f2583e48 100644 --- a/tm2/pkg/bft/rpc/client/e2e_test.go +++ b/tm2/pkg/bft/rpc/client/e2e_test.go @@ -13,7 +13,7 @@ import ( ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" types "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/types" bfttypes "github.com/gnolang/gno/tm2/pkg/bft/types" - types2 "github.com/gnolang/gno/tm2/pkg/p2p" + types2 "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/tm2/pkg/bft/rpc/core/pipe.go b/tm2/pkg/bft/rpc/core/pipe.go index f0de0b75d2b..49df94e444b 100644 --- a/tm2/pkg/bft/rpc/core/pipe.go +++ b/tm2/pkg/bft/rpc/core/pipe.go @@ -15,6 +15,7 @@ import ( dbm "github.com/gnolang/gno/tm2/pkg/db" "github.com/gnolang/gno/tm2/pkg/events" "github.com/gnolang/gno/tm2/pkg/p2p" + types2 "github.com/gnolang/gno/tm2/pkg/p2p/types" ) const ( @@ -38,7 +39,7 @@ type Consensus interface { type transport interface { Listeners() []string IsListening() bool - NodeInfo() p2p.NodeInfo + NodeInfo() types2.NodeInfo } type peers interface { diff --git a/tm2/pkg/bft/rpc/core/types/responses.go b/tm2/pkg/bft/rpc/core/types/responses.go index 2874517147d..6b17592199e 100644 --- a/tm2/pkg/bft/rpc/core/types/responses.go +++ b/tm2/pkg/bft/rpc/core/types/responses.go @@ -11,6 +11,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/p2p" + types2 "github.com/gnolang/gno/tm2/pkg/p2p/types" ) // List of blocks @@ -74,9 +75,9 @@ type ValidatorInfo struct { // Node Status type ResultStatus struct { - NodeInfo p2p.NodeInfo `json:"node_info"` - SyncInfo SyncInfo `json:"sync_info"` - ValidatorInfo ValidatorInfo `json:"validator_info"` + NodeInfo types2.NodeInfo `json:"node_info"` + SyncInfo SyncInfo `json:"sync_info"` + ValidatorInfo ValidatorInfo `json:"validator_info"` } // Is TxIndexing enabled @@ -107,7 +108,7 @@ type ResultDialPeers struct { // A peer type Peer struct { - NodeInfo p2p.NodeInfo `json:"node_info"` + NodeInfo types2.NodeInfo `json:"node_info"` IsOutbound bool `json:"is_outbound"` ConnectionStatus p2p.ConnectionStatus `json:"connection_status"` RemoteIP string `json:"remote_ip"` diff --git a/tm2/pkg/bft/rpc/core/types/responses_test.go b/tm2/pkg/bft/rpc/core/types/responses_test.go index 619eab80780..7d03addc546 100644 --- a/tm2/pkg/bft/rpc/core/types/responses_test.go +++ b/tm2/pkg/bft/rpc/core/types/responses_test.go @@ -3,7 +3,7 @@ package core_types import ( "testing" - "github.com/gnolang/gno/tm2/pkg/p2p" + "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/stretchr/testify/assert" ) @@ -16,17 +16,17 @@ func TestStatusIndexer(t *testing.T) { status = &ResultStatus{} assert.False(t, status.TxIndexEnabled()) - status.NodeInfo = p2p.NodeInfo{} + status.NodeInfo = types.NodeInfo{} assert.False(t, status.TxIndexEnabled()) cases := []struct { expected bool - other p2p.NodeInfoOther + other types.NodeInfoOther }{ - {false, p2p.NodeInfoOther{}}, - {false, p2p.NodeInfoOther{TxIndex: "aa"}}, - {false, p2p.NodeInfoOther{TxIndex: "off"}}, - {true, p2p.NodeInfoOther{TxIndex: "on"}}, + {false, types.NodeInfoOther{}}, + {false, types.NodeInfoOther{TxIndex: "aa"}}, + {false, types.NodeInfoOther{TxIndex: "off"}}, + {true, types.NodeInfoOther{TxIndex: "on"}}, } for _, tc := range cases { diff --git a/tm2/pkg/p2p/config/config.go b/tm2/pkg/p2p/config/config.go index d13ccca04a4..1206e7beaca 100644 --- a/tm2/pkg/p2p/config/config.go +++ b/tm2/pkg/p2p/config/config.go @@ -22,9 +22,6 @@ type P2PConfig struct { // Comma separated list of nodes to keep persistent connections to PersistentPeers string `json:"persistent_peers" toml:"persistent_peers" comment:"Comma separated list of nodes to keep persistent connections to"` - // UPNP port forwarding - UPNP bool `json:"upnp" toml:"upnp" comment:"UPNP port forwarding"` - // Maximum number of inbound peers MaxNumInboundPeers uint64 `json:"max_num_inbound_peers" toml:"max_num_inbound_peers" comment:"Maximum number of inbound peers"` @@ -44,24 +41,10 @@ type P2PConfig struct { RecvRate int64 `json:"recv_rate" toml:"recv_rate" comment:"Rate at which packets can be received, in bytes/second"` // Set true to enable the peer-exchange reactor - PexReactor bool `json:"pex" toml:"pex" comment:"Set true to enable the peer-exchange reactor"` - - // Seed mode, in which node constantly crawls the network and looks for - // peers. If another node asks it for addresses, it responds and disconnects. - // - // Does not work if the peer-exchange reactor is disabled. - SeedMode bool `json:"seed_mode" toml:"seed_mode" comment:"Seed mode, in which node constantly crawls the network and looks for\n peers. If another node asks it for addresses, it responds and disconnects.\n\n Does not work if the peer-exchange reactor is disabled."` + PeerExchange bool `json:"pex" toml:"pex" comment:"Set true to enable the peer-exchange reactor"` - // Comma separated list of peer IDs to keep private (will not be gossiped to - // other peers) + // Comma separated list of peer IDs to keep private (will not be gossiped to other peers) PrivatePeerIDs string `json:"private_peer_ids" toml:"private_peer_ids" comment:"Comma separated list of peer IDs to keep private (will not be gossiped to other peers)"` - - // Toggle to disable guard against peers connecting from the same ip. - AllowDuplicateIP bool `json:"allow_duplicate_ip" toml:"allow_duplicate_ip" comment:"Toggle to disable guard against peers connecting from the same ip."` - - // Peer connection configuration. - HandshakeTimeout time.Duration `json:"handshake_timeout" toml:"handshake_timeout" comment:"Peer connection configuration."` - DialTimeout time.Duration `json:"dial_timeout" toml:"dial_timeout"` } // DefaultP2PConfig returns a default configuration for the peer-to-peer layer @@ -69,18 +52,13 @@ func DefaultP2PConfig() *P2PConfig { return &P2PConfig{ ListenAddress: "tcp://0.0.0.0:26656", ExternalAddress: "", - UPNP: false, MaxNumInboundPeers: 40, MaxNumOutboundPeers: 10, FlushThrottleTimeout: 100 * time.Millisecond, MaxPacketMsgPayloadSize: 1024, // 1 kB SendRate: 5120000, // 5 mB/s RecvRate: 5120000, // 5 mB/s - PexReactor: true, - SeedMode: false, - AllowDuplicateIP: false, - HandshakeTimeout: 20 * time.Second, - DialTimeout: 3 * time.Second, + PeerExchange: true, } } @@ -89,7 +67,7 @@ func TestP2PConfig() *P2PConfig { cfg := DefaultP2PConfig() cfg.ListenAddress = "tcp://0.0.0.0:26656" cfg.FlushThrottleTimeout = 10 * time.Millisecond - cfg.AllowDuplicateIP = true + return cfg } diff --git a/tm2/pkg/p2p/conn/connection.go b/tm2/pkg/p2p/conn/connection.go index b4e2881dd19..678bf0b8fab 100644 --- a/tm2/pkg/p2p/conn/connection.go +++ b/tm2/pkg/p2p/conn/connection.go @@ -17,6 +17,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/errors" "github.com/gnolang/gno/tm2/pkg/flow" + "github.com/gnolang/gno/tm2/pkg/p2p/config" "github.com/gnolang/gno/tm2/pkg/service" "github.com/gnolang/gno/tm2/pkg/timer" ) @@ -147,6 +148,18 @@ func DefaultMConnConfig() MConnConfig { } } +// MConfigFromP2P returns a multiplex connection configuration +// with fields updated from the P2PConfig +func MConfigFromP2P(cfg *config.P2PConfig) MConnConfig { + mConfig := DefaultMConnConfig() + mConfig.FlushThrottle = cfg.FlushThrottleTimeout + mConfig.SendRate = cfg.SendRate + mConfig.RecvRate = cfg.RecvRate + mConfig.MaxPacketMsgPayloadSize = cfg.MaxPacketMsgPayloadSize + + return mConfig +} + // NewMConnection wraps net.Conn and creates multiplex connection func NewMConnection(conn net.Conn, chDescs []*ChannelDescriptor, onReceive receiveCbFunc, onError errorCbFunc) *MConnection { return NewMConnectionWithConfig( diff --git a/tm2/pkg/p2p/conn/connection_test.go b/tm2/pkg/p2p/conn/connection_test.go index 2b69ca3531d..58b363b7b78 100644 --- a/tm2/pkg/p2p/conn/connection_test.go +++ b/tm2/pkg/p2p/conn/connection_test.go @@ -21,7 +21,7 @@ func createTestMConnection(t *testing.T, conn net.Conn) *MConnection { onReceive := func(chID byte, msgBytes []byte) { } - onError := func(r interface{}) { + onError := func(r error) { } c := createMConnectionWithCallbacks(t, conn, onReceive, onError) c.SetLogger(log.NewTestingLogger(t)) @@ -137,7 +137,7 @@ func TestMConnectionReceive(t *testing.T) { onReceive := func(chID byte, msgBytes []byte) { receivedCh <- msgBytes } - onError := func(r interface{}) { + onError := func(r error) { errorsCh <- r } mconn1 := createMConnectionWithCallbacks(t, client, onReceive, onError) @@ -192,7 +192,7 @@ func TestMConnectionPongTimeoutResultsInError(t *testing.T) { onReceive := func(chID byte, msgBytes []byte) { receivedCh <- msgBytes } - onError := func(r interface{}) { + onError := func(r error) { errorsCh <- r } mconn := createMConnectionWithCallbacks(t, client, onReceive, onError) @@ -233,7 +233,7 @@ func TestMConnectionMultiplePongsInTheBeginning(t *testing.T) { onReceive := func(chID byte, msgBytes []byte) { receivedCh <- msgBytes } - onError := func(r interface{}) { + onError := func(r error) { errorsCh <- r } mconn := createMConnectionWithCallbacks(t, client, onReceive, onError) @@ -288,7 +288,7 @@ func TestMConnectionMultiplePings(t *testing.T) { onReceive := func(chID byte, msgBytes []byte) { receivedCh <- msgBytes } - onError := func(r interface{}) { + onError := func(r error) { errorsCh <- r } mconn := createMConnectionWithCallbacks(t, client, onReceive, onError) @@ -331,7 +331,7 @@ func TestMConnectionPingPongs(t *testing.T) { onReceive := func(chID byte, msgBytes []byte) { receivedCh <- msgBytes } - onError := func(r interface{}) { + onError := func(r error) { errorsCh <- r } mconn := createMConnectionWithCallbacks(t, client, onReceive, onError) @@ -384,7 +384,7 @@ func TestMConnectionStopsAndReturnsError(t *testing.T) { onReceive := func(chID byte, msgBytes []byte) { receivedCh <- msgBytes } - onError := func(r interface{}) { + onError := func(r error) { errorsCh <- r } mconn := createMConnectionWithCallbacks(t, client, onReceive, onError) @@ -413,7 +413,7 @@ func newClientAndServerConnsForReadErrors(t *testing.T, chOnErr chan struct{}) ( server, client := NetPipe() onReceive := func(chID byte, msgBytes []byte) {} - onError := func(r interface{}) {} + onError := func(r error) {} // create client conn with two channels chDescs := []*ChannelDescriptor{ @@ -428,7 +428,7 @@ func newClientAndServerConnsForReadErrors(t *testing.T, chOnErr chan struct{}) ( // create server conn with 1 channel // it fires on chOnErr when there's an error serverLogger := log.NewNoopLogger().With("module", "server") - onError = func(r interface{}) { + onError = func(_ error) { chOnErr <- struct{}{} } mconnServer := createMConnectionWithCallbacks(t, server, onReceive, onError) diff --git a/tm2/pkg/p2p/dial/dial.go b/tm2/pkg/p2p/dial/dial.go index 20e70699884..5dc7b8c9219 100644 --- a/tm2/pkg/p2p/dial/dial.go +++ b/tm2/pkg/p2p/dial/dial.go @@ -4,7 +4,7 @@ import ( "sync" "time" - "github.com/gnolang/gno/tm2/pkg/p2p" + "github.com/gnolang/gno/tm2/pkg/p2p/types" queue "github.com/sig-0/insertion-queue" ) @@ -12,8 +12,8 @@ import ( // the approximately appropriate dial time, and the // peer dial address type Item struct { - Time time.Time // appropriate dial time - Address *p2p.NetAddress // the dial address of the peer + Time time.Time // appropriate dial time + Address *types.NetAddress // the dial address of the peer } // Less is the comparison method for the dial queue Item (time ascending) diff --git a/tm2/pkg/p2p/errors.go b/tm2/pkg/p2p/errors.go deleted file mode 100644 index d4ad58e8ab5..00000000000 --- a/tm2/pkg/p2p/errors.go +++ /dev/null @@ -1,184 +0,0 @@ -package p2p - -import ( - "fmt" - "net" -) - -// FilterTimeoutError indicates that a filter operation timed out. -type FilterTimeoutError struct{} - -func (e FilterTimeoutError) Error() string { - return "filter timed out" -} - -// RejectedError indicates that a Peer was rejected carrying additional -// information as to the reason. -type RejectedError struct { - addr NetAddress - conn net.Conn - err error - id ID - isAuthFailure bool - isDuplicate bool - isFiltered bool - isIncompatible bool - isNodeInfoInvalid bool - isSelf bool -} - -// Addr returns the NetAddress for the rejected Peer. -func (e RejectedError) Addr() NetAddress { - return e.addr -} - -func (e RejectedError) Error() string { - if e.isAuthFailure { - return fmt.Sprintf("auth failure: %s", e.err) - } - - if e.isDuplicate { - if e.conn != nil { - return fmt.Sprintf( - "duplicate CONN<%s>", - e.conn.RemoteAddr().String(), - ) - } - if !e.id.IsZero() { - return fmt.Sprintf("duplicate ID<%v>", e.id) - } - } - - if e.isFiltered { - if e.conn != nil { - return fmt.Sprintf( - "filtered CONN<%s>: %s", - e.conn.RemoteAddr().String(), - e.err, - ) - } - - if !e.id.IsZero() { - return fmt.Sprintf("filtered ID<%v>: %s", e.id, e.err) - } - } - - if e.isIncompatible { - return fmt.Sprintf("incompatible: %s", e.err) - } - - if e.isNodeInfoInvalid { - return fmt.Sprintf("invalid NodeInfo: %s", e.err) - } - - if e.isSelf { - return fmt.Sprintf("self ID<%v>", e.id) - } - - return fmt.Sprintf("%s", e.err) -} - -// IsAuthFailure when Peer authentication was unsuccessful. -func (e RejectedError) IsAuthFailure() bool { return e.isAuthFailure } - -// IsDuplicate when Peer ID or IP are present already. -func (e RejectedError) IsDuplicate() bool { return e.isDuplicate } - -// IsFiltered when Peer ID or IP was filtered. -func (e RejectedError) IsFiltered() bool { return e.isFiltered } - -// IsIncompatible when Peer NodeInfo is not compatible with our own. -func (e RejectedError) IsIncompatible() bool { return e.isIncompatible } - -// IsNodeInfoInvalid when the sent NodeInfo is not valid. -func (e RejectedError) IsNodeInfoInvalid() bool { return e.isNodeInfoInvalid } - -// IsSelf when Peer is our own node. -func (e RejectedError) IsSelf() bool { return e.isSelf } - -// SwitchDuplicatePeerIDError to be raised when a peer is connecting with a known -// ID. -type SwitchDuplicatePeerIDError struct { - ID ID -} - -func (e SwitchDuplicatePeerIDError) Error() string { - return fmt.Sprintf("duplicate peer ID %v", e.ID) -} - -// SwitchDuplicatePeerIPError to be raised when a peer is connecting with a known -// IP. -type SwitchDuplicatePeerIPError struct { - IP net.IP -} - -func (e SwitchDuplicatePeerIPError) Error() string { - return fmt.Sprintf("duplicate peer IP %v", e.IP.String()) -} - -// SwitchConnectToSelfError to be raised when trying to connect to itself. -type SwitchConnectToSelfError struct { - Addr *NetAddress -} - -func (e SwitchConnectToSelfError) Error() string { - return fmt.Sprintf("connect to self: %v", e.Addr) -} - -type SwitchAuthenticationFailureError struct { - Dialed *NetAddress - Got ID -} - -func (e SwitchAuthenticationFailureError) Error() string { - return fmt.Sprintf( - "failed to authenticate peer. Dialed %v, but got peer with ID %s", - e.Dialed, - e.Got, - ) -} - -// TransportClosedError is raised when the Transport has been closed. -type TransportClosedError struct{} - -func (e TransportClosedError) Error() string { - return "transport has been closed" -} - -// ------------------------------------------------------------------- - -type NetAddressNoIDError struct { - Addr string -} - -func (e NetAddressNoIDError) Error() string { - return fmt.Sprintf("address (%s) does not contain ID", e.Addr) -} - -type NetAddressInvalidError struct { - Addr string - Err error -} - -func (e NetAddressInvalidError) Error() string { - return fmt.Sprintf("invalid address (%s): %v", e.Addr, e.Err) -} - -type NetAddressLookupError struct { - Addr string - Err error -} - -func (e NetAddressLookupError) Error() string { - return fmt.Sprintf("error looking up host (%s): %v", e.Addr, e.Err) -} - -// CurrentlyDialingOrExistingAddressError indicates that we're currently -// dialing this address or it belongs to an existing peer. -type CurrentlyDialingOrExistingAddressError struct { - Addr string -} - -func (e CurrentlyDialingOrExistingAddressError) Error() string { - return fmt.Sprintf("connection with %s has been established or dialed", e.Addr) -} diff --git a/tm2/pkg/p2p/events/types.go b/tm2/pkg/p2p/events/types.go index 7b21822df60..b01334298cc 100644 --- a/tm2/pkg/p2p/events/types.go +++ b/tm2/pkg/p2p/events/types.go @@ -1,6 +1,8 @@ package events -import "github.com/gnolang/gno/tm2/pkg/p2p" +import ( + "github.com/gnolang/gno/tm2/pkg/p2p/types" +) type EventType string @@ -16,8 +18,8 @@ type Event interface { } type PeerConnectedEvent struct { - PeerID p2p.ID // the ID of the peer - Address p2p.NetAddress // the dial address of the peer + PeerID types.ID // the ID of the peer + Address types.NetAddress // the dial address of the peer } func (p *PeerConnectedEvent) Type() EventType { @@ -25,9 +27,9 @@ func (p *PeerConnectedEvent) Type() EventType { } type PeerDisconnectedEvent struct { - PeerID p2p.ID // the ID of the peer - Address p2p.NetAddress // the dial address of the peer - Reason error // the disconnect reason, if any + PeerID types.ID // the ID of the peer + Address types.NetAddress // the dial address of the peer + Reason error // the disconnect reason, if any } func (p *PeerDisconnectedEvent) Type() EventType { diff --git a/tm2/pkg/p2p/mock/peer.go b/tm2/pkg/p2p/mock/peer.go index 7274369459d..0dca571ee22 100644 --- a/tm2/pkg/p2p/mock/peer.go +++ b/tm2/pkg/p2p/mock/peer.go @@ -5,7 +5,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" "github.com/gnolang/gno/tm2/pkg/p2p" - "github.com/gnolang/gno/tm2/pkg/p2p/conn" + "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/gnolang/gno/tm2/pkg/service" ) @@ -13,7 +13,7 @@ type Peer struct { *service.BaseService ip net.IP id p2p.ID - addr *p2p.NetAddress + addr *types.NetAddress kv map[string]interface{} Outbound, Persistent bool } @@ -21,13 +21,13 @@ type Peer struct { // NewPeer creates and starts a new mock peer. If the ip // is nil, random routable address is used. func NewPeer(ip net.IP) *Peer { - var netAddr *p2p.NetAddress + var netAddr *types.NetAddress if ip == nil { _, netAddr = p2p.CreateRoutableAddr() } else { - netAddr = p2p.NewNetAddressFromIPPort(ip, 26656) + netAddr = types.NewNetAddressFromIPPort(ip, 26656) } - nodeKey := p2p.NodeKey{PrivKey: ed25519.GenPrivKey()} + nodeKey := types.NodeKey{PrivKey: ed25519.GenPrivKey()} netAddr.ID = nodeKey.ID() mp := &Peer{ ip: ip, @@ -43,15 +43,15 @@ func NewPeer(ip net.IP) *Peer { func (mp *Peer) FlushStop() { mp.Stop() } func (mp *Peer) TrySend(chID byte, msgBytes []byte) bool { return true } func (mp *Peer) Send(chID byte, msgBytes []byte) bool { return true } -func (mp *Peer) NodeInfo() p2p.NodeInfo { - return p2p.NodeInfo{ +func (mp *Peer) NodeInfo() types.NodeInfo { + return types.NodeInfo{ NetAddress: mp.addr, } } -func (mp *Peer) Status() conn.ConnectionStatus { return conn.ConnectionStatus{} } -func (mp *Peer) ID() p2p.ID { return mp.id } -func (mp *Peer) IsOutbound() bool { return mp.Outbound } -func (mp *Peer) IsPersistent() bool { return mp.Persistent } +func (mp *Peer) Status() multiplex.ConnectionStatus { return multiplex.ConnectionStatus{} } +func (mp *Peer) ID() p2p.ID { return mp.id } +func (mp *Peer) IsOutbound() bool { return mp.Outbound } +func (mp *Peer) IsPersistent() bool { return mp.Persistent } func (mp *Peer) Get(key string) interface{} { if value, ok := mp.kv[key]; ok { return value @@ -62,7 +62,7 @@ func (mp *Peer) Get(key string) interface{} { func (mp *Peer) Set(key string, value interface{}) { mp.kv[key] = value } -func (mp *Peer) RemoteIP() net.IP { return mp.ip } -func (mp *Peer) SocketAddr() *p2p.NetAddress { return mp.addr } -func (mp *Peer) RemoteAddr() net.Addr { return &net.TCPAddr{IP: mp.ip, Port: 8800} } -func (mp *Peer) CloseConn() error { return nil } +func (mp *Peer) RemoteIP() net.IP { return mp.ip } +func (mp *Peer) SocketAddr() *types.NetAddress { return mp.addr } +func (mp *Peer) RemoteAddr() net.Addr { return &net.TCPAddr{IP: mp.ip, Port: 8800} } +func (mp *Peer) CloseConn() error { return nil } diff --git a/tm2/pkg/p2p/mock_test.go b/tm2/pkg/p2p/mock_test.go index d97090b8a97..376108a692a 100644 --- a/tm2/pkg/p2p/mock_test.go +++ b/tm2/pkg/p2p/mock_test.go @@ -6,20 +6,21 @@ import ( "time" "github.com/gnolang/gno/tm2/pkg/p2p/conn" + "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/gnolang/gno/tm2/pkg/service" ) type ( flushStopDelegate func() - idDelegate func() ID + idDelegate func() types.ID remoteIPDelegate func() net.IP remoteAddrDelegate func() net.Addr isOutboundDelegate func() bool isPersistentDelegate func() bool closeConnDelegate func() error - nodeInfoDelegate func() NodeInfo + nodeInfoDelegate func() types.NodeInfo statusDelegate func() conn.ConnectionStatus - socketAddrDelegate func() *NetAddress + socketAddrDelegate func() *types.NetAddress sendDelegate func(byte, []byte) bool trySendDelegate func(byte, []byte) bool setDelegate func(string, any) @@ -51,7 +52,7 @@ func (m *mockPeer) FlushStop() { } } -func (m *mockPeer) ID() ID { +func (m *mockPeer) ID() types.ID { if m.idFn != nil { return m.idFn() } @@ -99,12 +100,12 @@ func (m *mockPeer) CloseConn() error { return nil } -func (m *mockPeer) NodeInfo() NodeInfo { +func (m *mockPeer) NodeInfo() types.NodeInfo { if m.nodeInfoFn != nil { return m.nodeInfoFn() } - return NodeInfo{} + return types.NodeInfo{} } func (m *mockPeer) Status() conn.ConnectionStatus { @@ -115,7 +116,7 @@ func (m *mockPeer) Status() conn.ConnectionStatus { return conn.ConnectionStatus{} } -func (m *mockPeer) SocketAddr() *NetAddress { +func (m *mockPeer) SocketAddr() *types.NetAddress { if m.socketAddrFn != nil { return m.socketAddrFn() } diff --git a/tm2/pkg/p2p/peer.go b/tm2/pkg/p2p/peer.go index 314388cf56c..4a4e7cabffa 100644 --- a/tm2/pkg/p2p/peer.go +++ b/tm2/pkg/p2p/peer.go @@ -6,14 +6,15 @@ import ( "net" "github.com/gnolang/gno/tm2/pkg/cmap" - connm "github.com/gnolang/gno/tm2/pkg/p2p/conn" + "github.com/gnolang/gno/tm2/pkg/p2p/conn" + "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/gnolang/gno/tm2/pkg/service" ) -type MultiplexConnConfig struct { - MConfig connm.MConnConfig +type ConnConfig struct { + MConfig conn.MConnConfig ReactorsByCh map[byte]Reactor - ChDescs []*connm.ChannelDescriptor + ChDescs []*conn.ChannelDescriptor OnPeerError func(Peer, error) } @@ -23,7 +24,7 @@ type ConnInfo struct { Persistent bool // flag indicating if the connection is persistent Conn net.Conn // the source connection RemoteIP net.IP // the remote IP of the peer - SocketAddr *NetAddress + SocketAddr *types.NetAddress } type multiplexConn interface { @@ -33,7 +34,7 @@ type multiplexConn interface { Send(byte, []byte) bool TrySend(byte, []byte) bool SetLogger(*slog.Logger) - Status() connm.ConnectionStatus + Status() conn.ConnectionStatus String() string } @@ -42,9 +43,9 @@ type multiplexConn interface { type peer struct { service.BaseService - connInfo *ConnInfo // Metadata about the connection - nodeInfo NodeInfo // Information about the peer's node - mConn multiplexConn // The multiplexed connection + connInfo *ConnInfo // Metadata about the connection + nodeInfo types.NodeInfo // Information about the peer's node + mConn multiplexConn // The multiplexed connection data *cmap.CMap // Arbitrary data store associated with the peer } @@ -52,8 +53,8 @@ type peer struct { // NewPeer creates an uninitialized peer instance func NewPeer( connInfo *ConnInfo, - nodeInfo NodeInfo, - mConfig *MultiplexConnConfig, + nodeInfo types.NodeInfo, + mConfig *ConnConfig, ) Peer { p := &peer{ connInfo: connInfo, @@ -103,7 +104,7 @@ func (p *peer) IsPersistent() bool { // For outbound peers, it's the address dialed (after DNS resolution). // For inbound peers, it's the address returned by the underlying connection // (not what's reported in the peer's NodeInfo). -func (p *peer) SocketAddr() *NetAddress { +func (p *peer) SocketAddr() *types.NetAddress { return p.connInfo.SocketAddr } @@ -152,17 +153,17 @@ func (p *peer) OnStop() { } // ID returns the peer's ID - the hex encoded hash of its pubkey. -func (p *peer) ID() ID { +func (p *peer) ID() types.ID { return p.nodeInfo.NetAddress.ID } // NodeInfo returns a copy of the peer's NodeInfo. -func (p *peer) NodeInfo() NodeInfo { +func (p *peer) NodeInfo() types.NodeInfo { return p.nodeInfo } // Status returns the peer's ConnectionStatus. -func (p *peer) Status() connm.ConnectionStatus { +func (p *peer) Status() conn.ConnectionStatus { return p.mConn.Status() } @@ -211,9 +212,9 @@ func (p *peer) hasChannel(chID byte) bool { } func (p *peer) createMConnection( - conn net.Conn, - config *MultiplexConnConfig, -) *connm.MConnection { + c net.Conn, + config *ConnConfig, +) *conn.MConnection { onReceive := func(chID byte, msgBytes []byte) { reactor := config.ReactorsByCh[chID] if reactor == nil { @@ -229,8 +230,8 @@ func (p *peer) createMConnection( config.OnPeerError(p, r) } - return connm.NewMConnectionWithConfig( - conn, + return conn.NewMConnectionWithConfig( + c, config.ChDescs, onReceive, onError, diff --git a/tm2/pkg/p2p/peer_test.go b/tm2/pkg/p2p/peer_test.go index 77a40862935..ee51e9c4421 100644 --- a/tm2/pkg/p2p/peer_test.go +++ b/tm2/pkg/p2p/peer_test.go @@ -12,6 +12,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/cmap" "github.com/gnolang/gno/tm2/pkg/p2p/config" "github.com/gnolang/gno/tm2/pkg/p2p/conn" + "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/gnolang/gno/tm2/pkg/service" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -68,7 +69,7 @@ func TestPeer_Properties(t *testing.T) { tcpAddr, err := net.ResolveTCPAddr("tcp", "localhost:8080") require.NoError(t, err) - netAddr, err := NewNetAddress(GenerateNodeKey().ID(), tcpAddr) + netAddr, err := types.NewNetAddress(types.GenerateNodeKey().ID(), tcpAddr) require.NoError(t, err) var ( @@ -175,7 +176,7 @@ func TestPeer_Properties(t *testing.T) { t.Parallel() var ( - info = NodeInfo{ + info = types.NodeInfo{ Network: "gnoland", } @@ -231,7 +232,7 @@ func TestPeer_Properties(t *testing.T) { t.Parallel() var ( - id = GenerateNodeKey().ID() + id = types.GenerateNodeKey().ID() mConnStr = "description" p = &peer{ @@ -240,8 +241,8 @@ func TestPeer_Properties(t *testing.T) { return mConnStr }, }, - nodeInfo: NodeInfo{ - NetAddress: &NetAddress{ + nodeInfo: types.NodeInfo{ + NetAddress: &types.NetAddress{ ID: id, }, }, @@ -365,7 +366,7 @@ func TestPeer_Send(t *testing.T) { } p = &peer{ - nodeInfo: NodeInfo{ + nodeInfo: types.NodeInfo{ Channels: []byte{ chID, }, @@ -403,7 +404,7 @@ func TestPeer_Send(t *testing.T) { } p = &peer{ - nodeInfo: NodeInfo{ + nodeInfo: types.NodeInfo{ Channels: []byte{}, }, mConn: mockConn, @@ -445,7 +446,7 @@ func TestPeer_Send(t *testing.T) { } p = &peer{ - nodeInfo: NodeInfo{ + nodeInfo: types.NodeInfo{ Channels: []byte{ chID, }, @@ -493,7 +494,7 @@ func TestPeer_TrySend(t *testing.T) { } p = &peer{ - nodeInfo: NodeInfo{ + nodeInfo: types.NodeInfo{ Channels: []byte{ chID, }, @@ -531,7 +532,7 @@ func TestPeer_TrySend(t *testing.T) { } p = &peer{ - nodeInfo: NodeInfo{ + nodeInfo: types.NodeInfo{ Channels: []byte{}, }, mConn: mockConn, @@ -573,7 +574,7 @@ func TestPeer_TrySend(t *testing.T) { } p = &peer{ - nodeInfo: NodeInfo{ + nodeInfo: types.NodeInfo{ Channels: []byte{ chID, }, @@ -604,7 +605,7 @@ func TestPeer_NewPeer(t *testing.T) { tcpAddr, err := net.ResolveTCPAddr("tcp", "localhost:8080") require.NoError(t, err) - netAddr, err := NewNetAddress(GenerateNodeKey().ID(), tcpAddr) + netAddr, err := types.NewNetAddress(types.GenerateNodeKey().ID(), tcpAddr) require.NoError(t, err) var ( @@ -616,8 +617,8 @@ func TestPeer_NewPeer(t *testing.T) { SocketAddr: netAddr, } - mConfig = &MultiplexConnConfig{ - MConfig: MultiplexConfigFromP2P(config.DefaultP2PConfig()), + mConfig = &ConnConfig{ + MConfig: conn.MConfigFromP2P(config.DefaultP2PConfig()), ReactorsByCh: make(map[byte]Reactor), ChDescs: make([]*conn.ChannelDescriptor, 0), OnPeerError: nil, @@ -625,6 +626,6 @@ func TestPeer_NewPeer(t *testing.T) { ) assert.NotPanics(t, func() { - _ = NewPeer(connInfo, NodeInfo{}, mConfig) + _ = NewPeer(connInfo, types.NodeInfo{}, mConfig) }) } diff --git a/tm2/pkg/p2p/set.go b/tm2/pkg/p2p/set.go index 53dc060a691..57100015c32 100644 --- a/tm2/pkg/p2p/set.go +++ b/tm2/pkg/p2p/set.go @@ -3,27 +3,29 @@ package p2p import ( "net" "sync" + + "github.com/gnolang/gno/tm2/pkg/p2p/types" ) -type Set struct { +type set struct { mux sync.RWMutex - peers map[ID]Peer + peers map[types.ID]Peer outbound uint64 inbound uint64 } -// NewSet creates an empty peer set -func NewSet() *Set { - return &Set{ - peers: make(map[ID]Peer), +// newSet creates an empty peer set +func newSet() *set { + return &set{ + peers: make(map[types.ID]Peer), outbound: 0, inbound: 0, } } // Add adds the peer to the set -func (s *Set) Add(peer Peer) { +func (s *set) Add(peer Peer) { s.mux.Lock() defer s.mux.Unlock() @@ -40,7 +42,7 @@ func (s *Set) Add(peer Peer) { // Has returns true if the set contains the peer referred to by this // peerKey, otherwise false. -func (s *Set) Has(peerKey ID) bool { +func (s *set) Has(peerKey types.ID) bool { s.mux.RLock() defer s.mux.RUnlock() @@ -51,7 +53,7 @@ func (s *Set) Has(peerKey ID) bool { // HasIP returns true if the set contains the peer referred to by this IP // address, otherwise false. -func (s *Set) HasIP(peerIP net.IP) bool { +func (s *set) HasIP(peerIP net.IP) bool { s.mux.RLock() defer s.mux.RUnlock() @@ -64,9 +66,9 @@ func (s *Set) HasIP(peerIP net.IP) bool { return false } -// Get looks up a peer by the provided peerKey. Returns nil if peer is not +// Get looks up a peer by the provtypes.IDed peerKey. Returns nil if peer is not // found. -func (s *Set) Get(key ID) Peer { +func (s *set) Get(key types.ID) Peer { s.mux.RLock() defer s.mux.RUnlock() @@ -83,7 +85,7 @@ func (s *Set) Get(key ID) Peer { // Remove discards peer by its Key, if the peer was previously memoized. // Returns true if the peer was removed, and false if it was not found. // in the set. -func (s *Set) Remove(key ID) bool { +func (s *set) Remove(key types.ID) bool { s.mux.Lock() defer s.mux.Unlock() @@ -106,7 +108,7 @@ func (s *Set) Remove(key ID) bool { } // Size returns the number of unique peers in the peer table -func (s *Set) Size() int { +func (s *set) Size() int { s.mux.RLock() defer s.mux.RUnlock() @@ -114,7 +116,7 @@ func (s *Set) Size() int { } // NumInbound returns the number of inbound peers -func (s *Set) NumInbound() uint64 { +func (s *set) NumInbound() uint64 { s.mux.RLock() defer s.mux.RUnlock() @@ -122,7 +124,7 @@ func (s *Set) NumInbound() uint64 { } // NumOutbound returns the number of outbound peers -func (s *Set) NumOutbound() uint64 { +func (s *set) NumOutbound() uint64 { s.mux.RLock() defer s.mux.RUnlock() @@ -130,7 +132,7 @@ func (s *Set) NumOutbound() uint64 { } // List returns the list of peers -func (s *Set) List() []Peer { +func (s *set) List() []Peer { s.mux.RLock() defer s.mux.RUnlock() diff --git a/tm2/pkg/p2p/set_test.go b/tm2/pkg/p2p/set_test.go index 307c783b16d..047601c7eb5 100644 --- a/tm2/pkg/p2p/set_test.go +++ b/tm2/pkg/p2p/set_test.go @@ -5,6 +5,7 @@ import ( "sort" "testing" + "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -16,9 +17,9 @@ func generatePeers(t *testing.T, count int) []*mockPeer { peers := make([]*mockPeer, count) for i := 0; i < count; i++ { - id := GenerateNodeKey().ID() + id := types.GenerateNodeKey().ID() peers[i] = &mockPeer{ - idFn: func() ID { + idFn: func() types.ID { return id }, } @@ -34,7 +35,7 @@ func TestSet_Add(t *testing.T) { numPeers = 100 peers = generatePeers(t, numPeers) - s = NewSet() + s = newSet() ) for _, peer := range peers { @@ -55,7 +56,7 @@ func TestSet_Remove(t *testing.T) { numPeers = 100 peers = generatePeers(t, numPeers) - s = NewSet() + s = newSet() ) // Add the initial peers @@ -90,7 +91,7 @@ func TestSet_HasIP(t *testing.T) { peers = generatePeers(t, 100) ip = net.ParseIP("0.0.0.0") - s = NewSet() + s = newSet() ) // Make sure at least one peer has the set IP @@ -114,7 +115,7 @@ func TestSet_HasIP(t *testing.T) { peers = generatePeers(t, 100) ip = net.ParseIP("0.0.0.0") - s = NewSet() + s = newSet() ) // Add the peers @@ -135,7 +136,7 @@ func TestSet_Get(t *testing.T) { var ( peers = generatePeers(t, 100) - s = NewSet() + s = newSet() ) for _, peer := range peers { @@ -151,7 +152,7 @@ func TestSet_Get(t *testing.T) { var ( peers = generatePeers(t, 100) - s = NewSet() + s = newSet() ) for _, peer := range peers { @@ -170,7 +171,7 @@ func TestSet_List(t *testing.T) { t.Parallel() // Empty set - s := NewSet() + s := newSet() // Linearize the set assert.Len(t, s.List(), 0) @@ -181,7 +182,7 @@ func TestSet_List(t *testing.T) { var ( peers = generatePeers(t, 100) - s = NewSet() + s = newSet() ) for _, peer := range peers { diff --git a/tm2/pkg/p2p/switch.go b/tm2/pkg/p2p/switch.go index 893b9cebec1..1f132875e03 100644 --- a/tm2/pkg/p2p/switch.go +++ b/tm2/pkg/p2p/switch.go @@ -10,20 +10,34 @@ import ( "github.com/gnolang/gno/tm2/pkg/p2p/conn" "github.com/gnolang/gno/tm2/pkg/p2p/dial" "github.com/gnolang/gno/tm2/pkg/p2p/events" + "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/gnolang/gno/tm2/pkg/service" "github.com/gnolang/gno/tm2/pkg/telemetry" "github.com/gnolang/gno/tm2/pkg/telemetry/metrics" ) -// MultiplexConfigFromP2P returns a multiplex connection configuration -// with fields updated from the P2PConfig -func MultiplexConfigFromP2P(cfg *config.P2PConfig) conn.MConnConfig { - mConfig := conn.DefaultMConnConfig() - mConfig.FlushThrottle = cfg.FlushThrottleTimeout - mConfig.SendRate = cfg.SendRate - mConfig.RecvRate = cfg.RecvRate - mConfig.MaxPacketMsgPayloadSize = cfg.MaxPacketMsgPayloadSize - return mConfig +type reactorPeerBehavior struct { + chDescs []*conn.ChannelDescriptor + reactorsByCh map[byte]Reactor + + handlePeerErrFn func(Peer, error) + isPersistentPeerFn func(*types.NetAddress) bool +} + +func (r *reactorPeerBehavior) ReactorChDescriptors() []*conn.ChannelDescriptor { + return r.chDescs +} + +func (r *reactorPeerBehavior) Reactors() map[byte]Reactor { + return r.reactorsByCh +} + +func (r *reactorPeerBehavior) HandlePeerError(p Peer, err error) { + r.handlePeerErrFn(p, err) +} + +func (r *reactorPeerBehavior) IsPersistentPeer(address *types.NetAddress) bool { + return r.isPersistentPeerFn(address) } // Switch handles peer connections and exposes an API to receive incoming messages @@ -33,11 +47,11 @@ func MultiplexConfigFromP2P(cfg *config.P2PConfig) conn.MConnConfig { type Switch struct { service.BaseService - config *config.P2PConfig // TODO remove this dependency + maxInboundPeers uint64 + maxOutboundPeers uint64 - reactors map[string]Reactor // TODO wrap - chDescs []*conn.ChannelDescriptor // TODO wrap - reactorsByCh map[byte]Reactor // TODO wrap + reactors map[string]Reactor + peerBehavior *reactorPeerBehavior peers PeerSet // currently active peer set persistentPeers sync.Map // ID -> *NetAddress; peers whose connections are constant @@ -49,19 +63,29 @@ type Switch struct { // NewSwitch creates a new Switch with the given config. func NewSwitch( - cfg *config.P2PConfig, transport Transport, options ...SwitchOption, ) *Switch { + defaultCfg := config.DefaultP2PConfig() + sw := &Switch{ - config: cfg, - reactors: make(map[string]Reactor), - chDescs: make([]*conn.ChannelDescriptor, 0), - reactorsByCh: make(map[byte]Reactor), - peers: NewSet(), - transport: transport, - dialQueue: dial.NewQueue(), - events: events.New(), + reactors: make(map[string]Reactor), + peers: newSet(), + transport: transport, + dialQueue: dial.NewQueue(), + events: events.New(), + maxInboundPeers: defaultCfg.MaxNumInboundPeers, + maxOutboundPeers: defaultCfg.MaxNumOutboundPeers, + } + + // Set up the peer dial behavior + sw.peerBehavior = &reactorPeerBehavior{ + chDescs: make([]*conn.ChannelDescriptor, 0), + reactorsByCh: make(map[byte]Reactor), + handlePeerErrFn: sw.StopPeerForError, + isPersistentPeerFn: func(peer *types.NetAddress) bool { + return sw.isPersistentPeer(peer.ID) + }, } sw.BaseService = *service.NewBaseService(nil, "P2P Switch", sw) @@ -108,11 +132,6 @@ func (sw *Switch) OnStart() error { // OnStop implements BaseService. It stops all peers and reactors. func (sw *Switch) OnStop() { - // Stop transport - if err := sw.transport.Close(); err != nil { // TODO move to node - sw.Logger.Error("unable to gracefully close transport", "err", err) - } - // Stop peers for _, p := range sw.peers.List() { sw.stopAndRemovePeer(p, nil) @@ -126,33 +145,22 @@ func (sw *Switch) OnStop() { } } -// Broadcast broadcasts the given data to the given channel, across the -// entire switch peer set +// Broadcast broadcasts the given data to the given channel, +// across the entire switch peer set, without blocking func (sw *Switch) Broadcast(chID byte, data []byte) { - var wg sync.WaitGroup - for _, p := range sw.peers.List() { - wg.Add(1) - go func() { - defer wg.Done() - - // TODO propagate the context, instead of relying - // on the underlying multiplex conn + // This send context is managed internally + // by the Peer's underlying connection implementation if !p.Send(chID, data) { sw.Logger.Error( - "unable to perform broadcast, channel ID %X, peer ID %s", - chID, p.ID(), + "unable to perform broadcast", + "chID", chID, + "peerID", p.ID(), ) } }() } - - // Wait for all the sends to complete, - // at the mercy of the multiplex connection - // send routine :) - // TODO: I'm not sure Broadcast should be blocking, at all - wg.Wait() } // Peers returns the set of peers that are connected to the switch. @@ -161,14 +169,15 @@ func (sw *Switch) Peers() PeerSet { } // StopPeerForError disconnects from a peer due to external error. -// If the peer is persistent, it will attempt to reconnect. -// TODO: make record depending on reason. +// If the peer is persistent, it will attempt to reconnect func (sw *Switch) StopPeerForError(peer Peer, err error) { sw.Logger.Error("Stopping peer for error", "peer", peer, "err", err) sw.stopAndRemovePeer(peer, err) if !peer.IsPersistent() { + // Peer is not a persistent peer, + // no need to initiate a redial return } @@ -254,15 +263,7 @@ func (sw *Switch) runDialLoop(ctx context.Context) { peerAddr := item.Address - // TODO pass context to dial - p, err := sw.transport.Dial(*peerAddr, peerConfig{ - chDescs: sw.chDescs, - onPeerError: sw.StopPeerForError, - isPersistent: func(address *NetAddress) bool { - return sw.isPersistentPeer(address.ID) - }, - reactorsByCh: sw.reactorsByCh, - }) + p, err := sw.transport.Dial(ctx, *peerAddr, sw.peerBehavior) if err != nil { sw.Logger.Error( "unable to dial peer", @@ -342,7 +343,7 @@ func (sw *Switch) runRedialLoop(ctx context.Context) { // DialPeers adds the peers to the dial queue for async dialing. // To monitor dial progress, subscribe to adequate p2p Switch events -func (sw *Switch) DialPeers(peerAddrs ...*NetAddress) { +func (sw *Switch) DialPeers(peerAddrs ...*types.NetAddress) { for _, peerAddr := range peerAddrs { // Check if this is our address if peerAddr.Same(sw.transport.NetAddress()) { @@ -351,6 +352,17 @@ func (sw *Switch) DialPeers(peerAddrs ...*NetAddress) { continue } + // Ignore dial if the limit is reached + if out := sw.Peers().NumOutbound(); out >= sw.maxOutboundPeers { + sw.Logger.Warn( + "ignoring dial request: already have max outbound peers", + "have", out, + "max", sw.maxOutboundPeers, + ) + + continue + } + item := dial.Item{ Time: time.Now(), Address: peerAddr, @@ -362,7 +374,7 @@ func (sw *Switch) DialPeers(peerAddrs ...*NetAddress) { // isPersistentPeer returns a flag indicating if a peer // is present in the persistent peer set -func (sw *Switch) isPersistentPeer(id ID) bool { +func (sw *Switch) isPersistentPeer(id types.ID) bool { _, persistent := sw.persistentPeers.Load(id) return persistent @@ -379,14 +391,7 @@ func (sw *Switch) runAcceptLoop(ctx context.Context) { return default: - p, err := sw.transport.Accept(peerConfig{ - chDescs: sw.chDescs, - onPeerError: sw.StopPeerForError, - reactorsByCh: sw.reactorsByCh, - isPersistent: func(address *NetAddress) bool { - return sw.isPersistentPeer(address.ID) - }, - }) + p, err := sw.transport.Accept(ctx, sw.peerBehavior) if err != nil { sw.Logger.Error( "error encountered during peer connection accept", @@ -397,12 +402,12 @@ func (sw *Switch) runAcceptLoop(ctx context.Context) { } // Ignore connection if we already have enough peers. - if in := sw.Peers().NumInbound(); in >= sw.config.MaxNumInboundPeers { + if in := sw.Peers().NumInbound(); in >= sw.maxInboundPeers { sw.Logger.Info( "Ignoring inbound connection: already have enough inbound peers", "address", p.SocketAddr(), "have", in, - "max", sw.config.MaxNumInboundPeers, + "max", sw.maxInboundPeers, ) sw.transport.Remove(p) diff --git a/tm2/pkg/p2p/switch_option.go b/tm2/pkg/p2p/switch_option.go index ba94a14bc86..6b94b6a632a 100644 --- a/tm2/pkg/p2p/switch_option.go +++ b/tm2/pkg/p2p/switch_option.go @@ -1,6 +1,10 @@ package p2p -import "fmt" +import ( + "fmt" + + "github.com/gnolang/gno/tm2/pkg/p2p/types" +) // SwitchOption is a callback used for configuring the p2p Switch type SwitchOption func(*Switch) @@ -10,13 +14,21 @@ func WithReactor(name string, reactor Reactor) SwitchOption { return func(sw *Switch) { for _, chDesc := range reactor.GetChannels() { chID := chDesc.ID + // No two reactors can share the same channel - if sw.reactorsByCh[chID] != nil { - panic(fmt.Sprintf("Channel %X has multiple reactors %v & %v", chID, sw.reactorsByCh[chID], reactor)) + if sw.peerBehavior.reactorsByCh[chID] != nil { + panic( + fmt.Sprintf( + "Channel %X has multiple reactors %v & %v", + chID, + sw.peerBehavior.reactorsByCh[chID], + reactor, + ), + ) } - sw.chDescs = append(sw.chDescs, chDesc) - sw.reactorsByCh[chID] = reactor + sw.peerBehavior.chDescs = append(sw.peerBehavior.chDescs, chDesc) + sw.peerBehavior.reactorsByCh[chID] = reactor } sw.reactors[name] = reactor @@ -26,10 +38,24 @@ func WithReactor(name string, reactor Reactor) SwitchOption { } // WithPersistentPeers sets the p2p switch's persistent peer set -func WithPersistentPeers(peerAddrs []*NetAddress) SwitchOption { +func WithPersistentPeers(peerAddrs []*types.NetAddress) SwitchOption { return func(sw *Switch) { for _, addr := range peerAddrs { sw.persistentPeers.Store(addr.ID, addr) } } } + +// WithMaxInboundPeers sets the p2p switch's maximum inbound peer limit +func WithMaxInboundPeers(maxInbound uint64) SwitchOption { + return func(sw *Switch) { + sw.maxInboundPeers = maxInbound + } +} + +// WithMaxOutboundPeers sets the p2p switch's maximum outbound peer limit +func WithMaxOutboundPeers(maxOutbound uint64) SwitchOption { + return func(sw *Switch) { + sw.maxOutboundPeers = maxOutbound + } +} diff --git a/tm2/pkg/p2p/switch_test.go b/tm2/pkg/p2p/switch_test.go index 74c0e418cd9..696334d2ae4 100644 --- a/tm2/pkg/p2p/switch_test.go +++ b/tm2/pkg/p2p/switch_test.go @@ -4,7 +4,7 @@ package p2p // // func init() { // cfg = config.DefaultP2PConfig() -// cfg.PexReactor = true +// cfg.PeerExchange = true // cfg.AllowDuplicateIP = true // } // diff --git a/tm2/pkg/p2p/test_util.go b/tm2/pkg/p2p/test_util.go deleted file mode 100644 index 1d4fb3f5a3a..00000000000 --- a/tm2/pkg/p2p/test_util.go +++ /dev/null @@ -1,221 +0,0 @@ -package p2p - -// const testCh = 0x01 -// -// // ------------------------------------------------ -// -// func CreateRoutableAddr() (addr string, addr *NetAddress) { -// for { -// id := ed25519.GenPrivKey().PubKey().Address().ID() -// var err error -// addr = fmt.Sprintf("%s@%v.%v.%v.%v:26656", id, random.RandInt()%256, random.RandInt()%256, random.RandInt()%256, random.RandInt()%256) -// addr, err = NewNetAddressFromString(addr) -// if err != nil { -// panic(err) -// } -// if addr.Routable() { -// break -// } -// } -// return -// } -// -// // ------------------------------------------------------------------ -// // Connects switches via arbitrary net.Conn. Used for testing. -// -// const TEST_HOST = "localhost" -// -// // MakeConnectedSwitches returns n switches, connected according to the connect func. -// // If connect==Connect2Switches, the switches will be fully connected. -// // initSwitch defines how the i'th switch should be initialized (ie. with what reactors). -// // NOTE: panics if any switch fails to start. -// func MakeConnectedSwitches(cfg *config.P2PConfig, n int, initSwitch func(int, *Switch) *Switch, connect func([]*Switch, int, int)) []*Switch { -// switches := make([]*Switch, n) -// for i := 0; i < n; i++ { -// switches[i] = MakeSwitch(cfg, i, TEST_HOST, "123.123.123", initSwitch) -// } -// -// if err := StartSwitches(switches); err != nil { -// panic(err) -// } -// -// for i := 0; i < n; i++ { -// for j := i + 1; j < n; j++ { -// connect(switches, i, j) -// } -// } -// -// return switches -// } -// -// // Connect2Switches will connect switches i and j via net.Pipe(). -// // Blocks until a connection is established. -// // NOTE: caller ensures i and j are within bounds. -// func Connect2Switches(switches []*Switch, i, j int) { -// switchI := switches[i] -// switchJ := switches[j] -// -// c1, c2 := conn.NetPipe() -// -// doneCh := make(chan struct{}) -// go func() { -// err := switchI.addPeerWithConnection(c1) -// if err != nil { -// panic(err) -// } -// doneCh <- struct{}{} -// }() -// go func() { -// err := switchJ.addPeerWithConnection(c2) -// if err != nil { -// panic(err) -// } -// doneCh <- struct{}{} -// }() -// <-doneCh -// <-doneCh -// } -// -// func (sw *Switch) addPeerWithConnection(conn net.Conn) error { -// pc, err := testInboundPeerConn(conn, sw.config, sw.nodeKey.PrivKey) -// if err != nil { -// if err := conn.Close(); err != nil { -// sw.Logger.Error("Error closing connection", "err", err) -// } -// return err -// } -// -// ni, err := handshake(conn, time.Second, sw.nodeInfo) -// if err != nil { -// if err := conn.Close(); err != nil { -// sw.Logger.Error("Error closing connection", "err", err) -// } -// return err -// } -// -// p := peer.newPeer( -// pc, -// MultiplexConfigFromP2P(sw.config), -// ni, -// sw.reactorsByCh, -// sw.chDescs, -// sw.StopPeerForError, -// ) -// -// if err = sw.addPeer(p); err != nil { -// pc.CloseConn() -// return err -// } -// -// return nil -// } -// -// // StartSwitches calls sw.Start() for each given switch. -// // It returns the first encountered error. -// func StartSwitches(switches []*Switch) error { -// for _, s := range switches { -// err := s.Start() // start switch and reactors -// if err != nil { -// return err -// } -// } -// return nil -// } -// -// func MakeSwitch( -// cfg *config.P2PConfig, -// i int, -// network, version string, -// initSwitch func(int, *Switch) *Switch, -// opts ...SwitchOption, -// ) *Switch { -// nodeKey := NodeKey{ -// PrivKey: ed25519.GenPrivKey(), -// } -// nodeInfo := testNodeInfo(nodeKey.ID(), fmt.Sprintf("node%d", i)) -// -// t := NewMultiplexTransport(nodeInfo, nodeKey, MultiplexConfigFromP2P(cfg)) -// -// if err := t.Listen(*nodeInfo.NetAddress); err != nil { -// panic(err) -// } -// -// // TODO: let the config be passed in? -// sw := initSwitch(i, NewSwitch(cfg, t, opts...)) -// sw.SetLogger(log.NewNoopLogger().With("switch", i)) -// sw.SetNodeKey(&nodeKey) -// -// for ch := range sw.reactorsByCh { -// nodeInfo.Channels = append(nodeInfo.Channels, ch) -// } -// -// // TODO: We need to setup reactors ahead of time so the NodeInfo is properly -// // populated and we don't have to do those awkward overrides and setters. -// t.nodeInfo = nodeInfo -// sw.SetNodeInfo(nodeInfo) -// -// return sw -// } -// -// func testInboundPeerConn( -// conn net.Conn, -// config *config.P2PConfig, -// ourNodePrivKey crypto.PrivKey, -// ) (peer.peerConn, error) { -// return testPeerConn(conn, config, false, false, ourNodePrivKey, nil) -// } -// -// func testPeerConn( -// rawConn net.Conn, -// cfg *config.P2PConfig, -// outbound, persistent bool, -// ourNodePrivKey crypto.PrivKey, -// socketAddr *NetAddress, -// ) (pc peer.peerConn, err error) { -// conn := rawConn -// -// // Encrypt connection -// conn, err = upgradeToSecretConn(conn, cfg.HandshakeTimeout, ourNodePrivKey) -// if err != nil { -// return pc, errors.Wrap(err, "Error creating peer") -// } -// -// // Only the information we already have -// return peer.NewPeerConn(outbound, persistent, conn, socketAddr), nil -// } -// -// // ---------------------------------------------------------------- -// // rand node info -// -// func testNodeInfo(id ID, name string) NodeInfo { -// return testNodeInfoWithNetwork(id, name, "testing") -// } -// -// func testVersionSet() versionset.VersionSet { -// return versionset.VersionSet{ -// versionset.VersionInfo{ -// Name: "p2p", -// Version: "v0.0.0", // dontcare -// }, -// } -// } -// -// func testNodeInfoWithNetwork(id ID, name, network string) NodeInfo { -// info := NodeInfo{ -// VersionSet: testVersionSet(), -// NetAddress: NewNetAddressFromIPPort(net.ParseIP("127.0.0.1"), 0), -// Network: network, -// Software: "p2ptest", -// Version: "v1.2.3-rc.0-deadbeef", -// Channels: []byte{testCh}, -// Moniker: name, -// Other: NodeInfoOther{ -// TxIndex: "on", -// RPCAddress: fmt.Sprintf("127.0.0.1:%d", 0), -// }, -// } -// -// info.NetAddress.ID = id -// -// return info -// } diff --git a/tm2/pkg/p2p/transport.go b/tm2/pkg/p2p/transport.go index 5f1c30cf10a..25e156dccb0 100644 --- a/tm2/pkg/p2p/transport.go +++ b/tm2/pkg/p2p/transport.go @@ -12,20 +12,24 @@ import ( "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/errors" "github.com/gnolang/gno/tm2/pkg/p2p/conn" + "github.com/gnolang/gno/tm2/pkg/p2p/types" "golang.org/x/sync/errgroup" ) -// TODO make options -const ( - defaultDialTimeout = time.Second - defaultHandshakeTimeout = 3 * time.Second +// defaultHandshakeTimeout is the timeout for the STS handshaking protocol +const defaultHandshakeTimeout = 3 * time.Second + +var ( + errTransportClosed = errors.New("transport is closed") + errTransportInactive = errors.New("transport is inactive") + errDuplicateConnection = errors.New("duplicate peer connection") ) // peerInfo is a wrapper for an unverified peer connection type peerInfo struct { - addr *NetAddress // the dial address of the peer - conn net.Conn // the connection associated with the peer - nodeInfo NodeInfo // the relevant peer node info + addr *types.NetAddress // the dial address of the peer + conn net.Conn // the connection associated with the peer + nodeInfo types.NodeInfo // the relevant peer node info } // MultiplexTransport accepts and dials tcp connections and upgrades them to @@ -36,9 +40,9 @@ type MultiplexTransport struct { logger *slog.Logger - netAddr NetAddress // the node's P2P dial address, used for handshaking - nodeInfo NodeInfo // the node's P2P info, used for handshaking - nodeKey NodeKey // the node's private P2P key, used for handshaking + netAddr types.NetAddress // the node's P2P dial address, used for handshaking + nodeInfo types.NodeInfo // the node's P2P info, used for handshaking + nodeKey types.NodeKey // the node's private P2P key, used for handshaking listener net.Listener // listener for inbound peer connections peerCh chan peerInfo // pipe for inbound peer connections @@ -52,14 +56,12 @@ type MultiplexTransport struct { mConfig conn.MConnConfig } -// Test multiplexTransport for interface completeness. -var _ Transport = (*MultiplexTransport)(nil) - // NewMultiplexTransport returns a tcp connected multiplexed peer. func NewMultiplexTransport( - nodeInfo NodeInfo, - nodeKey NodeKey, + nodeInfo types.NodeInfo, + nodeKey types.NodeKey, mConfig conn.MConnConfig, + logger *slog.Logger, ) *MultiplexTransport { return &MultiplexTransport{ peerCh: make(chan peerInfo, 1), @@ -67,22 +69,29 @@ func NewMultiplexTransport( mConfig: mConfig, nodeInfo: nodeInfo, nodeKey: nodeKey, + logger: logger, } } // NetAddress returns the transport's listen address (for p2p connections) -func (mt *MultiplexTransport) NetAddress() NetAddress { +func (mt *MultiplexTransport) NetAddress() types.NetAddress { return mt.netAddr } // Accept waits for a verified inbound Peer to connect, and returns it [BLOCKING] func (mt *MultiplexTransport) Accept(ctx context.Context, behavior PeerBehavior) (Peer, error) { + // Sanity check, no need to wait + // on an inactive transport + if mt.listener == nil { + return nil, errTransportInactive + } + select { case <-ctx.Done(): return nil, ctx.Err() case info, ok := <-mt.peerCh: if !ok { - return nil, errors.New("transport closed") // TODO make constant + return nil, errTransportClosed } return mt.newMultiplexPeer(info, behavior, false) @@ -93,7 +102,7 @@ func (mt *MultiplexTransport) Accept(ctx context.Context, behavior PeerBehavior) // verifies it (performs handshaking) [BLOCKING] func (mt *MultiplexTransport) Dial( ctx context.Context, - addr NetAddress, + addr types.NetAddress, behavior PeerBehavior, ) (Peer, error) { // Set a dial timeout for the connection @@ -108,7 +117,7 @@ func (mt *MultiplexTransport) Dial( // Close the net peer connection _ = c.Close() - return nil, err + return nil, fmt.Errorf("unable to process connection, %w", err) } return mt.newMultiplexPeer(info, behavior, true) @@ -126,7 +135,7 @@ func (mt *MultiplexTransport) Close() error { } // Listen starts an active process of listening for incoming connections [NON-BLOCKING] -func (mt *MultiplexTransport) Listen(addr NetAddress) error { +func (mt *MultiplexTransport) Listen(addr types.NetAddress) error { // Reserve a port, and start listening ln, err := net.Listen("tcp", addr.DialString()) if err != nil { @@ -164,6 +173,8 @@ func (mt *MultiplexTransport) runAcceptLoop() { for { select { case <-mt.ctx.Done(): + mt.logger.Debug("transport accept context closed") + return default: // Accept an incoming peer connection @@ -204,12 +215,12 @@ func (mt *MultiplexTransport) runAcceptLoop() { } // processConn handles the raw connection by upgrading it and verifying it -func (mt *MultiplexTransport) processConn(c net.Conn, expectedID ID) (peerInfo, error) { +func (mt *MultiplexTransport) processConn(c net.Conn, expectedID types.ID) (peerInfo, error) { dialAddr := c.RemoteAddr().String() // Check if the connection is a duplicate one if _, exists := mt.activeConns.LoadOrStore(dialAddr, struct{}{}); exists { - return peerInfo{}, errors.New("duplicate peer connection") // TODO make constant + return peerInfo{}, errDuplicateConnection } // Handshake with the peer, through STS @@ -217,12 +228,17 @@ func (mt *MultiplexTransport) processConn(c net.Conn, expectedID ID) (peerInfo, if err != nil { mt.activeConns.Delete(dialAddr) - return peerInfo{}, fmt.Errorf("unable to upgrade connection: %w", err) + return peerInfo{}, fmt.Errorf("unable to upgrade connection, %w", err) } // Verify the connection ID id := secretConn.RemotePubKey().Address().ID() + // The reason the dial ID needs to be verified is because + // for outbound peers (peers the node dials), there is an expected peer ID + // when initializing the outbound connection, that can differ from the exchanged one. + // For inbound peers, the ID is whatever the peer exchanges during the + // handshaking process, and is verified separately if !expectedID.IsZero() && id.String() != expectedID.String() { mt.activeConns.Delete(dialAddr) @@ -233,7 +249,7 @@ func (mt *MultiplexTransport) processConn(c net.Conn, expectedID ID) (peerInfo, ) } - netAddr, _ := NewNetAddress(id, c.RemoteAddr()) + netAddr, _ := types.NewNetAddress(id, c.RemoteAddr()) return peerInfo{ addr: netAddr, @@ -249,28 +265,31 @@ func (mt *MultiplexTransport) Remove(p Peer) { // upgradeAndVerifyConn upgrades the connections (performs the handshaking process) // and verifies that the connecting peer is valid -func (mt *MultiplexTransport) upgradeAndVerifyConn(c net.Conn) (*conn.SecretConnection, NodeInfo, error) { - // Upgrade to a secret connection +func (mt *MultiplexTransport) upgradeAndVerifyConn(c net.Conn) (*conn.SecretConnection, types.NodeInfo, error) { + // Upgrade to a secret connection. + // A secret connection is a connection that has passed + // an initial handshaking process, as defined by the STS + // protocol, and is considered to be secure and authentic secretConn, err := upgradeToSecretConn( c, mt.handshakeTimeout, mt.nodeKey.PrivKey, ) if err != nil { - return nil, NodeInfo{}, fmt.Errorf("unable to upgrade p2p connection, %w", err) + return nil, types.NodeInfo{}, fmt.Errorf("unable to upgrade p2p connection, %w", err) } // Exchange node information nodeInfo, err := exchangeNodeInfo(secretConn, mt.handshakeTimeout, mt.nodeInfo) if err != nil { - return nil, NodeInfo{}, fmt.Errorf("unable to exchange node information, %w", err) + return nil, types.NodeInfo{}, fmt.Errorf("unable to exchange node information, %w", err) } // Ensure the connection ID matches the node's reported ID connID := secretConn.RemotePubKey().Address().ID() if connID != nodeInfo.ID() { - return nil, NodeInfo{}, fmt.Errorf( + return nil, types.NodeInfo{}, fmt.Errorf( "connection ID does not match node info ID (expected %q got %q)", connID.String(), nodeInfo.ID().String(), @@ -279,7 +298,7 @@ func (mt *MultiplexTransport) upgradeAndVerifyConn(c net.Conn) (*conn.SecretConn // Check compatibility with the node if err = mt.nodeInfo.CompatibleWith(nodeInfo); err != nil { - return nil, NodeInfo{}, fmt.Errorf("incompatible node info, %w", err) + return nil, types.NodeInfo{}, fmt.Errorf("incompatible node info, %w", err) } return secretConn, nodeInfo, nil @@ -319,7 +338,7 @@ func (mt *MultiplexTransport) newMultiplexPeer( } // Create the info related to the multiplex connection - mConfig := &MultiplexConnConfig{ + mConfig := &ConnConfig{ MConfig: mt.mConfig, ReactorsByCh: behavior.Reactors(), ChDescs: behavior.ReactorChDescriptors(), @@ -329,19 +348,19 @@ func (mt *MultiplexTransport) newMultiplexPeer( return NewPeer(peerConn, info.nodeInfo, mConfig), nil } -// exchangeNodeInfo performs a "handshake", where node -// info is exchanged between the current node and a peer +// exchangeNodeInfo performs a data swap, where node +// info is exchanged between the current node and a peer async func exchangeNodeInfo( c net.Conn, timeout time.Duration, - nodeInfo NodeInfo, -) (NodeInfo, error) { + nodeInfo types.NodeInfo, +) (types.NodeInfo, error) { if err := c.SetDeadline(time.Now().Add(timeout)); err != nil { - return NodeInfo{}, err + return types.NodeInfo{}, err } var ( - peerNodeInfo NodeInfo + peerNodeInfo types.NodeInfo ourNodeInfo = nodeInfo ) @@ -357,22 +376,22 @@ func exchangeNodeInfo( _, err := amino.UnmarshalSizedReader( c, &peerNodeInfo, - MaxNodeInfoSize, + types.MaxNodeInfoSize, ) return err }) if err := g.Wait(); err != nil { - return NodeInfo{}, err + return types.NodeInfo{}, err } // Validate the received node information if err := nodeInfo.Validate(); err != nil { - return NodeInfo{}, fmt.Errorf("unable to validate node info, %w", err) + return types.NodeInfo{}, fmt.Errorf("unable to validate node info, %w", err) } - return peerNodeInfo, nil + return peerNodeInfo, c.SetDeadline(time.Time{}) } // upgradeToSecretConn takes an active TCP connection, diff --git a/tm2/pkg/p2p/types.go b/tm2/pkg/p2p/types.go index 51765352e96..30400afed9f 100644 --- a/tm2/pkg/p2p/types.go +++ b/tm2/pkg/p2p/types.go @@ -4,9 +4,8 @@ import ( "context" "net" - "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/p2p/conn" - connm "github.com/gnolang/gno/tm2/pkg/p2p/conn" + "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/gnolang/gno/tm2/pkg/service" ) @@ -15,15 +14,13 @@ type ( ConnectionStatus = conn.ConnectionStatus ) -type ID = crypto.ID - // Peer is a wrapper for a connected peer type Peer interface { service.Service FlushStop() - ID() ID // peer's cryptographic ID + ID() types.ID // peer's cryptographic ID RemoteIP() net.IP // remote IP of the connection RemoteAddr() net.Addr // remote address of the connection @@ -32,9 +29,9 @@ type Peer interface { CloseConn() error // close original connection - NodeInfo() NodeInfo // peer's info - Status() connm.ConnectionStatus - SocketAddr() *NetAddress // actual address of the socket + NodeInfo() types.NodeInfo // peer's info + Status() ConnectionStatus + SocketAddr() *types.NetAddress // actual address of the socket Send(byte, []byte) bool TrySend(byte, []byte) bool @@ -46,10 +43,10 @@ type Peer interface { // PeerSet has a (immutable) subset of the methods of PeerSet. type PeerSet interface { Add(peer Peer) - Remove(key ID) bool - Has(key ID) bool + Remove(key types.ID) bool + Has(key types.ID) bool HasIP(ip net.IP) bool - Get(key ID) Peer + Get(key types.ID) Peer List() []Peer // TODO consider implementing an iterator Size() int // TODO remove @@ -62,13 +59,13 @@ type PeerSet interface { // Peers returned by the transport are considered to be verified and sound type Transport interface { // NetAddress returns the Transport's dial address - NetAddress() NetAddress + NetAddress() types.NetAddress // Accept returns a newly connected inbound peer Accept(context.Context, PeerBehavior) (Peer, error) // Dial dials a peer, and returns it - Dial(context.Context, NetAddress, PeerBehavior) (Peer, error) + Dial(context.Context, types.NetAddress, PeerBehavior) (Peer, error) // Remove drops any resources associated // with the Peer in the transport @@ -92,5 +89,5 @@ type PeerBehavior interface { HandlePeerError(Peer, error) // IsPersistentPeer returns a flag indicating if the given peer is persistent - IsPersistentPeer(*NetAddress) bool + IsPersistentPeer(*types.NetAddress) bool } diff --git a/tm2/pkg/p2p/key.go b/tm2/pkg/p2p/types/key.go similarity index 96% rename from tm2/pkg/p2p/key.go rename to tm2/pkg/p2p/types/key.go index 733ec288c97..f6074dbc2e3 100644 --- a/tm2/pkg/p2p/key.go +++ b/tm2/pkg/p2p/types/key.go @@ -1,4 +1,4 @@ -package p2p +package types import ( "fmt" @@ -10,6 +10,9 @@ import ( osm "github.com/gnolang/gno/tm2/pkg/os" ) +// ID represents the cryptographically unique Peer ID +type ID = crypto.ID + // NodeKey is the persistent peer key. // It contains the nodes private key for authentication. // NOTE: keep in sync with gno.land/cmd/gnoland/secrets.go diff --git a/tm2/pkg/p2p/key_test.go b/tm2/pkg/p2p/types/key_test.go similarity index 99% rename from tm2/pkg/p2p/key_test.go rename to tm2/pkg/p2p/types/key_test.go index d8b8ff36188..5dc153b08c0 100644 --- a/tm2/pkg/p2p/key_test.go +++ b/tm2/pkg/p2p/types/key_test.go @@ -1,4 +1,4 @@ -package p2p +package types import ( "encoding/json" diff --git a/tm2/pkg/p2p/netaddress.go b/tm2/pkg/p2p/types/netaddress.go similarity index 94% rename from tm2/pkg/p2p/netaddress.go rename to tm2/pkg/p2p/types/netaddress.go index 7c0f85f6a63..746a287067b 100644 --- a/tm2/pkg/p2p/netaddress.go +++ b/tm2/pkg/p2p/types/netaddress.go @@ -2,7 +2,7 @@ // Originally Copyright (c) 2013-2014 Conformal Systems LLC. // https://github.com/conformal/btcd/blob/master/LICENSE -package p2p +package types import ( "context" @@ -25,6 +25,8 @@ var ( errUnsetIPAddress = errors.New("unset IP address") errInvalidIP = errors.New("invalid IP address") errUnspecifiedIP = errors.New("unspecified IP address") + errInvalidNetAddress = errors.New("invalid net address") + errEmptyHost = errors.New("empty host address") ) // NetAddress defines information about a peer on the network @@ -80,7 +82,7 @@ func NewNetAddressFromString(idaddr string) (*NetAddress, error) { ) if len(spl) != 2 { - return nil, NetAddressNoIDError{prunedAddr} + return nil, errInvalidNetAddress } var ( @@ -90,27 +92,24 @@ func NewNetAddressFromString(idaddr string) (*NetAddress, error) { // Validate the ID if err := id.Validate(); err != nil { - return nil, NetAddressInvalidError{prunedAddr, err} + return nil, fmt.Errorf("unable to verify address ID, %w", err) } // Extract the host and port host, portStr, err := net.SplitHostPort(addr) if err != nil { - return nil, NetAddressInvalidError{addr, err} + return nil, fmt.Errorf("unable to split host and port, %w", err) } if host == "" { - return nil, NetAddressInvalidError{ - addr, - errors.New("host is empty"), - } + return nil, errEmptyHost } ip := net.ParseIP(host) if ip == nil { ips, err := net.LookupIP(host) if err != nil { - return nil, NetAddressLookupError{host, err} + return nil, fmt.Errorf("unable to look up IP, %w", err) } ip = ips[0] @@ -118,7 +117,7 @@ func NewNetAddressFromString(idaddr string) (*NetAddress, error) { port, err := strconv.ParseUint(portStr, 10, 16) if err != nil { - return nil, NetAddressInvalidError{portStr, err} + return nil, fmt.Errorf("unable to parse port %s, %w", portStr, err) } na := NewNetAddressFromIPPort(ip, uint16(port)) diff --git a/tm2/pkg/p2p/netaddress_test.go b/tm2/pkg/p2p/types/netaddress_test.go similarity index 99% rename from tm2/pkg/p2p/netaddress_test.go rename to tm2/pkg/p2p/types/netaddress_test.go index c3fe01a66a7..1f8f0229b99 100644 --- a/tm2/pkg/p2p/netaddress_test.go +++ b/tm2/pkg/p2p/types/netaddress_test.go @@ -1,4 +1,4 @@ -package p2p +package types import ( "fmt" diff --git a/tm2/pkg/p2p/node_info.go b/tm2/pkg/p2p/types/node_info.go similarity index 99% rename from tm2/pkg/p2p/node_info.go rename to tm2/pkg/p2p/types/node_info.go index 0b99851edf3..c511eab6545 100644 --- a/tm2/pkg/p2p/node_info.go +++ b/tm2/pkg/p2p/types/node_info.go @@ -1,4 +1,4 @@ -package p2p +package types import ( "errors" diff --git a/tm2/pkg/p2p/node_info_test.go b/tm2/pkg/p2p/types/node_info_test.go similarity index 99% rename from tm2/pkg/p2p/node_info_test.go rename to tm2/pkg/p2p/types/node_info_test.go index c0ad97fb1ab..d44c72bb20b 100644 --- a/tm2/pkg/p2p/node_info_test.go +++ b/tm2/pkg/p2p/types/node_info_test.go @@ -1,4 +1,4 @@ -package p2p +package types import ( "fmt" From 989ea8dbc878f069b62d948d3ff346ca63a1702e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Fri, 18 Oct 2024 12:59:31 +0200 Subject: [PATCH 17/43] Add unit tests for the config, dial queue --- tm2/pkg/bft/config/config.go | 12 ++- tm2/pkg/bft/mempool/reactor_test.go | 17 +++- tm2/pkg/p2p/base_reactor.go | 10 +- tm2/pkg/p2p/config/config.go | 37 +++---- tm2/pkg/p2p/config/config_test.go | 59 +++++++++++ tm2/pkg/p2p/dial/dial_test.go | 147 ++++++++++++++++++++++++++++ tm2/pkg/p2p/dial/doc.go | 10 +- 7 files changed, 260 insertions(+), 32 deletions(-) create mode 100644 tm2/pkg/p2p/config/config_test.go create mode 100644 tm2/pkg/p2p/dial/dial_test.go diff --git a/tm2/pkg/bft/config/config.go b/tm2/pkg/bft/config/config.go index b89d456a9be..b4f2948b12a 100644 --- a/tm2/pkg/bft/config/config.go +++ b/tm2/pkg/bft/config/config.go @@ -6,6 +6,7 @@ import ( "path/filepath" "regexp" "slices" + "time" "dario.cat/mergo" abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" @@ -163,12 +164,21 @@ func LoadOrMakeConfigWithOptions(root string, opts ...Option) (*Config, error) { return cfg, nil } +// testP2PConfig returns a configuration for testing the peer-to-peer layer +func testP2PConfig() *p2p.P2PConfig { + cfg := p2p.DefaultP2PConfig() + cfg.ListenAddress = "tcp://0.0.0.0:26656" + cfg.FlushThrottleTimeout = 10 * time.Millisecond + + return cfg +} + // TestConfig returns a configuration that can be used for testing func TestConfig() *Config { return &Config{ BaseConfig: testBaseConfig(), RPC: rpc.TestRPCConfig(), - P2P: p2p.TestP2PConfig(), + P2P: testP2PConfig(), Mempool: mem.TestMempoolConfig(), Consensus: cns.TestConsensusConfig(), TxEventStore: eventstore.DefaultEventStoreConfig(), diff --git a/tm2/pkg/bft/mempool/reactor_test.go b/tm2/pkg/bft/mempool/reactor_test.go index e7a3c43a6b9..d2a5e9d1e25 100644 --- a/tm2/pkg/bft/mempool/reactor_test.go +++ b/tm2/pkg/bft/mempool/reactor_test.go @@ -21,6 +21,15 @@ import ( "github.com/gnolang/gno/tm2/pkg/testutils" ) +// testP2PConfig returns a configuration for testing the peer-to-peer layer +func testP2PConfig() *p2pcfg.P2PConfig { + cfg := p2pcfg.DefaultP2PConfig() + cfg.ListenAddress = "tcp://0.0.0.0:26656" + cfg.FlushThrottleTimeout = 10 * time.Millisecond + + return cfg +} + type peerState struct { height int64 } @@ -109,7 +118,7 @@ func TestReactorBroadcastTxMessage(t *testing.T) { t.Parallel() mconfig := memcfg.TestMempoolConfig() - pconfig := p2pcfg.TestP2PConfig() + pconfig := testP2PConfig() const N = 4 reactors := makeAndConnectReactors(mconfig, pconfig, N) defer func() { @@ -133,7 +142,7 @@ func TestReactorNoBroadcastToSender(t *testing.T) { t.Parallel() mconfig := memcfg.TestMempoolConfig() - pconfig := p2pcfg.TestP2PConfig() + pconfig := testP2PConfig() const N = 2 reactors := makeAndConnectReactors(mconfig, pconfig, N) defer func() { @@ -158,7 +167,7 @@ func TestFlappyBroadcastTxForPeerStopsWhenPeerStops(t *testing.T) { } mconfig := memcfg.TestMempoolConfig() - pconfig := p2pcfg.TestP2PConfig() + pconfig := testP2PConfig() const N = 2 reactors := makeAndConnectReactors(mconfig, pconfig, N) defer func() { @@ -186,7 +195,7 @@ func TestFlappyBroadcastTxForPeerStopsWhenReactorStops(t *testing.T) { } mconfig := memcfg.TestMempoolConfig() - pconfig := p2pcfg.TestP2PConfig() + pconfig := testP2PConfig() const N = 2 reactors := makeAndConnectReactors(mconfig, pconfig, N) diff --git a/tm2/pkg/p2p/base_reactor.go b/tm2/pkg/p2p/base_reactor.go index 09596035fce..41756b984ce 100644 --- a/tm2/pkg/p2p/base_reactor.go +++ b/tm2/pkg/p2p/base_reactor.go @@ -64,8 +64,8 @@ func NewBaseReactor(name string, impl Reactor) *BaseReactor { func (br *BaseReactor) SetSwitch(sw *Switch) { br.Switch = sw } -func (*BaseReactor) GetChannels() []*conn.ChannelDescriptor { return nil } -func (*BaseReactor) AddPeer(peer Peer) {} -func (*BaseReactor) RemovePeer(peer Peer, reason interface{}) {} -func (*BaseReactor) Receive(chID byte, peer Peer, msgBytes []byte) {} -func (*BaseReactor) InitPeer(peer Peer) Peer { return peer } +func (*BaseReactor) GetChannels() []*conn.ChannelDescriptor { return nil } +func (*BaseReactor) AddPeer(_ Peer) {} +func (*BaseReactor) RemovePeer(_ Peer, _ any) {} +func (*BaseReactor) Receive(_ byte, _ Peer, _ []byte) {} +func (*BaseReactor) InitPeer(peer Peer) Peer { return peer } diff --git a/tm2/pkg/p2p/config/config.go b/tm2/pkg/p2p/config/config.go index 1206e7beaca..6185231af98 100644 --- a/tm2/pkg/p2p/config/config.go +++ b/tm2/pkg/p2p/config/config.go @@ -1,9 +1,15 @@ package config import ( + "errors" "time" +) - "github.com/gnolang/gno/tm2/pkg/errors" +var ( + errInvalidFlushThrottleTimeout = errors.New("invalid flush throttle timeout") + errInvalidMaxPayloadSize = errors.New("invalid message payload size") + errInvalidSendRate = errors.New("invalid packet send rate") + errInvalidReceiveRate = errors.New("invalid packet receive rate") ) // P2PConfig defines the configuration options for the Tendermint peer-to-peer networking layer @@ -51,7 +57,7 @@ type P2PConfig struct { func DefaultP2PConfig() *P2PConfig { return &P2PConfig{ ListenAddress: "tcp://0.0.0.0:26656", - ExternalAddress: "", + ExternalAddress: "", // nothing is advertised differently MaxNumInboundPeers: 40, MaxNumOutboundPeers: 10, FlushThrottleTimeout: 100 * time.Millisecond, @@ -62,35 +68,24 @@ func DefaultP2PConfig() *P2PConfig { } } -// TestP2PConfig returns a configuration for testing the peer-to-peer layer -func TestP2PConfig() *P2PConfig { - cfg := DefaultP2PConfig() - cfg.ListenAddress = "tcp://0.0.0.0:26656" - cfg.FlushThrottleTimeout = 10 * time.Millisecond - - return cfg -} - // ValidateBasic performs basic validation (checking param bounds, etc.) and // returns an error if any check fails. func (cfg *P2PConfig) ValidateBasic() error { - if cfg.MaxNumInboundPeers < 0 { - return errors.New("max_num_inbound_peers can't be negative") - } - if cfg.MaxNumOutboundPeers < 0 { - return errors.New("max_num_outbound_peers can't be negative") - } if cfg.FlushThrottleTimeout < 0 { - return errors.New("flush_throttle_timeout can't be negative") + return errInvalidFlushThrottleTimeout } + if cfg.MaxPacketMsgPayloadSize < 0 { - return errors.New("max_packet_msg_payload_size can't be negative") + return errInvalidMaxPayloadSize } + if cfg.SendRate < 0 { - return errors.New("send_rate can't be negative") + return errInvalidSendRate } + if cfg.RecvRate < 0 { - return errors.New("recv_rate can't be negative") + return errInvalidReceiveRate } + return nil } diff --git a/tm2/pkg/p2p/config/config_test.go b/tm2/pkg/p2p/config/config_test.go new file mode 100644 index 00000000000..6528c2d7315 --- /dev/null +++ b/tm2/pkg/p2p/config/config_test.go @@ -0,0 +1,59 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestP2PConfig_ValidateBasic(t *testing.T) { + t.Parallel() + + t.Run("invalid flush throttle timeout", func(t *testing.T) { + t.Parallel() + + cfg := DefaultP2PConfig() + + cfg.FlushThrottleTimeout = -1 + + assert.ErrorIs(t, cfg.ValidateBasic(), errInvalidFlushThrottleTimeout) + }) + + t.Run("invalid max packet payload size", func(t *testing.T) { + t.Parallel() + + cfg := DefaultP2PConfig() + + cfg.MaxPacketMsgPayloadSize = -1 + + assert.ErrorIs(t, cfg.ValidateBasic(), errInvalidMaxPayloadSize) + }) + + t.Run("invalid send rate", func(t *testing.T) { + t.Parallel() + + cfg := DefaultP2PConfig() + + cfg.SendRate = -1 + + assert.ErrorIs(t, cfg.ValidateBasic(), errInvalidSendRate) + }) + + t.Run("invalid receive rate", func(t *testing.T) { + t.Parallel() + + cfg := DefaultP2PConfig() + + cfg.RecvRate = -1 + + assert.ErrorIs(t, cfg.ValidateBasic(), errInvalidReceiveRate) + }) + + t.Run("valid configuration", func(t *testing.T) { + t.Parallel() + + cfg := DefaultP2PConfig() + + assert.NoError(t, cfg.ValidateBasic()) + }) +} diff --git a/tm2/pkg/p2p/dial/dial_test.go b/tm2/pkg/p2p/dial/dial_test.go new file mode 100644 index 00000000000..5e85ec1f95e --- /dev/null +++ b/tm2/pkg/p2p/dial/dial_test.go @@ -0,0 +1,147 @@ +package dial + +import ( + "crypto/rand" + "math/big" + "slices" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// generateRandomTimes generates random time intervals +func generateRandomTimes(t *testing.T, count int) []time.Time { + t.Helper() + + const timeRange = 94608000 // 3 years + + var ( + maxRange = big.NewInt(time.Now().Unix() - timeRange) + times = make([]time.Time, 0, count) + ) + + for range count { + n, err := rand.Int(rand.Reader, maxRange) + require.NoError(t, err) + + randTime := time.Unix(n.Int64()+timeRange, 0) + + times = append(times, randTime) + } + + return times +} + +func TestQueue_Push(t *testing.T) { + t.Parallel() + + var ( + timestamps = generateRandomTimes(t, 10) + q = NewQueue() + ) + + // Add the dial items + for _, timestamp := range timestamps { + q.Push(Item{ + Time: timestamp, + }) + } + + assert.Len(t, q.items, len(timestamps)) +} + +func TestQueue_Peek(t *testing.T) { + t.Parallel() + + t.Run("empty queue", func(t *testing.T) { + t.Parallel() + + q := NewQueue() + + assert.Nil(t, q.Peek()) + }) + + t.Run("existing item", func(t *testing.T) { + t.Parallel() + + var ( + timestamps = generateRandomTimes(t, 100) + q = NewQueue() + ) + + // Add the dial items + for _, timestamp := range timestamps { + q.Push(Item{ + Time: timestamp, + }) + } + + // Sort the initial list to find the best timestamp + slices.SortFunc(timestamps, func(a, b time.Time) int { + if a.Before(b) { + return -1 + } + + if a.After(b) { + return 1 + } + + return 0 + }) + + assert.Equal(t, q.Peek().Time.Unix(), timestamps[0].Unix()) + }) +} + +func TestQueue_Pop(t *testing.T) { + t.Parallel() + + t.Run("empty queue", func(t *testing.T) { + t.Parallel() + + q := NewQueue() + + assert.Nil(t, q.Pop()) + }) + + t.Run("existing item", func(t *testing.T) { + t.Parallel() + + var ( + timestamps = generateRandomTimes(t, 100) + q = NewQueue() + ) + + // Add the dial items + for _, timestamp := range timestamps { + q.Push(Item{ + Time: timestamp, + }) + } + + assert.Len(t, q.items, len(timestamps)) + + // Sort the initial list to find the best timestamp + slices.SortFunc(timestamps, func(a, b time.Time) int { + if a.Before(b) { + return -1 + } + + if a.After(b) { + return 1 + } + + return 0 + }) + + for index, timestamp := range timestamps { + item := q.Pop() + + require.Len(t, q.items, len(timestamps)-1-index) + + assert.Equal(t, item.Time.Unix(), timestamp.Unix()) + } + }) +} diff --git a/tm2/pkg/p2p/dial/doc.go b/tm2/pkg/p2p/dial/doc.go index 2df1138c7c1..069160e73e6 100644 --- a/tm2/pkg/p2p/dial/doc.go +++ b/tm2/pkg/p2p/dial/doc.go @@ -1,2 +1,10 @@ -// TODO add coverage, documentation +// Package dial contains an implementation of a thread-safe priority dial queue. The queue is sorted by +// dial items, time ascending. +// The behavior of the dial queue is the following: +// +// - Peeking the dial queue will return the most urgent dial item, or nil if the queue is empty. +// +// - Popping the dial queue will return the most urgent dial item or nil if the queue is empty. Popping removes the dial item. +// +// - Push will push a new item to the dial queue, upon which the queue will find an adequate place for it. package dial From a0e1f28d4c591765b6ac1d18bd7831089577e140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Fri, 18 Oct 2024 13:45:41 +0200 Subject: [PATCH 18/43] Add unit tests for events --- tm2/pkg/p2p/events/doc.go | 3 +- tm2/pkg/p2p/events/events.go | 8 +-- tm2/pkg/p2p/events/events_test.go | 94 +++++++++++++++++++++++++++++++ tm2/pkg/p2p/events/types.go | 6 +- tm2/pkg/p2p/mock/peer.go | 68 ---------------------- 5 files changed, 102 insertions(+), 77 deletions(-) create mode 100644 tm2/pkg/p2p/events/events_test.go delete mode 100644 tm2/pkg/p2p/mock/peer.go diff --git a/tm2/pkg/p2p/events/doc.go b/tm2/pkg/p2p/events/doc.go index c96faaa75c6..a624102379e 100644 --- a/tm2/pkg/p2p/events/doc.go +++ b/tm2/pkg/p2p/events/doc.go @@ -1,2 +1,3 @@ -// TODO add documentation, coverage +// Package events contains a simple p2p event system implementation, that simplifies asynchronous event flows in the +// p2p module. The event subscriptions allow for event filtering, which eases the load on the event notification flow. package events diff --git a/tm2/pkg/p2p/events/events.go b/tm2/pkg/p2p/events/events.go index e500db448ce..bf76e27d91e 100644 --- a/tm2/pkg/p2p/events/events.go +++ b/tm2/pkg/p2p/events/events.go @@ -17,6 +17,7 @@ type Events struct { subscriptionsMux sync.RWMutex } +// New creates a new event subscription manager func New() *Events { return &Events{ subs: make(subscriptions), @@ -42,7 +43,7 @@ func (es *Events) Subscribe(filterFn EventFilter) (<-chan Event, func()) { return ch, unsubscribeFn } -// Notify notifies all subscribers of an incoming event +// Notify notifies all subscribers of an incoming event [BLOCKING] func (es *Events) Notify(event Event) { es.subscriptionsMux.RLock() defer es.subscriptionsMux.RUnlock() @@ -98,9 +99,6 @@ func (s *subscriptions) notify(event Event) { continue } - select { - case sub.ch <- event: - default: - } + sub.ch <- event } } diff --git a/tm2/pkg/p2p/events/events_test.go b/tm2/pkg/p2p/events/events_test.go new file mode 100644 index 00000000000..a0feafceddb --- /dev/null +++ b/tm2/pkg/p2p/events/events_test.go @@ -0,0 +1,94 @@ +package events + +import ( + "fmt" + "sync" + "testing" + "time" + + "github.com/gnolang/gno/tm2/pkg/p2p/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// generateEvents generates p2p events +func generateEvents(count int) []Event { + events := make([]Event, 0, count) + + for i := range count { + var event Event + + if i%2 == 0 { + event = PeerConnectedEvent{ + PeerID: types.ID(fmt.Sprintf("peer-%d", i)), + } + } else { + event = PeerDisconnectedEvent{ + PeerID: types.ID(fmt.Sprintf("peer-%d", i)), + } + } + + events = append(events, event) + } + + return events +} + +func TestEvents_Subscribe(t *testing.T) { + t.Parallel() + + var ( + capturedEvents []Event + + events = generateEvents(10) + subFn = func(e Event) bool { + return e.Type() == PeerDisconnected + } + ) + + // Create the events manager + e := New() + + // Subscribe to events + ch, unsubFn := e.Subscribe(subFn) + defer unsubFn() + + // Listen for the events + var wg sync.WaitGroup + + wg.Add(1) + + go func() { + defer wg.Done() + + timeout := time.After(5 * time.Second) + + for { + select { + case ev := <-ch: + capturedEvents = append(capturedEvents, ev) + + if len(capturedEvents) == len(events)/2 { + return + } + case <-timeout: + return + } + } + }() + + // Send out the events + for _, ev := range events { + e.Notify(ev) + } + + wg.Wait() + + // Make sure the events were captured + // and filtered properly + require.Len(t, capturedEvents, len(events)/2) + + for _, ev := range capturedEvents { + assert.Equal(t, ev.Type(), PeerDisconnected) + } +} diff --git a/tm2/pkg/p2p/events/types.go b/tm2/pkg/p2p/events/types.go index b01334298cc..6778c3cd36d 100644 --- a/tm2/pkg/p2p/events/types.go +++ b/tm2/pkg/p2p/events/types.go @@ -11,7 +11,7 @@ const ( PeerDisconnected EventType = "PeerDisconnected" // emitted when a peer disconnects ) -// Event is a p2p event +// Event is a generic p2p event type Event interface { // Type returns the type information for the event Type() EventType @@ -22,7 +22,7 @@ type PeerConnectedEvent struct { Address types.NetAddress // the dial address of the peer } -func (p *PeerConnectedEvent) Type() EventType { +func (p PeerConnectedEvent) Type() EventType { return PeerConnected } @@ -32,6 +32,6 @@ type PeerDisconnectedEvent struct { Reason error // the disconnect reason, if any } -func (p *PeerDisconnectedEvent) Type() EventType { +func (p PeerDisconnectedEvent) Type() EventType { return PeerDisconnected } diff --git a/tm2/pkg/p2p/mock/peer.go b/tm2/pkg/p2p/mock/peer.go deleted file mode 100644 index 0dca571ee22..00000000000 --- a/tm2/pkg/p2p/mock/peer.go +++ /dev/null @@ -1,68 +0,0 @@ -package mock - -import ( - "net" - - "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" - "github.com/gnolang/gno/tm2/pkg/p2p" - "github.com/gnolang/gno/tm2/pkg/p2p/types" - "github.com/gnolang/gno/tm2/pkg/service" -) - -type Peer struct { - *service.BaseService - ip net.IP - id p2p.ID - addr *types.NetAddress - kv map[string]interface{} - Outbound, Persistent bool -} - -// NewPeer creates and starts a new mock peer. If the ip -// is nil, random routable address is used. -func NewPeer(ip net.IP) *Peer { - var netAddr *types.NetAddress - if ip == nil { - _, netAddr = p2p.CreateRoutableAddr() - } else { - netAddr = types.NewNetAddressFromIPPort(ip, 26656) - } - nodeKey := types.NodeKey{PrivKey: ed25519.GenPrivKey()} - netAddr.ID = nodeKey.ID() - mp := &Peer{ - ip: ip, - id: nodeKey.ID(), - addr: netAddr, - kv: make(map[string]interface{}), - } - mp.BaseService = service.NewBaseService(nil, "MockPeer", mp) - mp.Start() - return mp -} - -func (mp *Peer) FlushStop() { mp.Stop() } -func (mp *Peer) TrySend(chID byte, msgBytes []byte) bool { return true } -func (mp *Peer) Send(chID byte, msgBytes []byte) bool { return true } -func (mp *Peer) NodeInfo() types.NodeInfo { - return types.NodeInfo{ - NetAddress: mp.addr, - } -} -func (mp *Peer) Status() multiplex.ConnectionStatus { return multiplex.ConnectionStatus{} } -func (mp *Peer) ID() p2p.ID { return mp.id } -func (mp *Peer) IsOutbound() bool { return mp.Outbound } -func (mp *Peer) IsPersistent() bool { return mp.Persistent } -func (mp *Peer) Get(key string) interface{} { - if value, ok := mp.kv[key]; ok { - return value - } - return nil -} - -func (mp *Peer) Set(key string, value interface{}) { - mp.kv[key] = value -} -func (mp *Peer) RemoteIP() net.IP { return mp.ip } -func (mp *Peer) SocketAddr() *types.NetAddress { return mp.addr } -func (mp *Peer) RemoteAddr() net.Addr { return &net.TCPAddr{IP: mp.ip, Port: 8800} } -func (mp *Peer) CloseConn() error { return nil } From eb2a7a1ea84340087c96bbd58ecf5044d6206aa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Mon, 21 Oct 2024 11:35:15 -0500 Subject: [PATCH 19/43] Add ctx to switch --- tm2/pkg/p2p/switch.go | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/tm2/pkg/p2p/switch.go b/tm2/pkg/p2p/switch.go index 1f132875e03..e3f61328c25 100644 --- a/tm2/pkg/p2p/switch.go +++ b/tm2/pkg/p2p/switch.go @@ -47,6 +47,9 @@ func (r *reactorPeerBehavior) IsPersistentPeer(address *types.NetAddress) bool { type Switch struct { service.BaseService + ctx context.Context + cancelFn context.CancelFunc + maxInboundPeers uint64 maxOutboundPeers uint64 @@ -90,6 +93,9 @@ func NewSwitch( sw.BaseService = *service.NewBaseService(nil, "P2P Switch", sw) + // Set up the context + sw.ctx, sw.cancelFn = context.WithCancel(context.Background()) + for _, option := range options { option(sw) } @@ -115,23 +121,30 @@ func (sw *Switch) OnStart() error { } } - // Run the peer accept routine - // TODO propagate ctx down - go sw.runAcceptLoop(context.Background()) + // Run the peer accept routine. + // The accept routine asynchronously accepts + // and processes incoming peer connections + go sw.runAcceptLoop(sw.ctx) - // Run the dial routine - // TODO propagate ctx down - go sw.runDialLoop(context.Background()) + // Run the dial routine. + // The dial routine parses items in the dial queue + // and initiates outbound peer connections + go sw.runDialLoop(sw.ctx) - // Run the redial routine - // TODO propagate ctx down - go sw.runRedialLoop(context.Background()) + // Run the redial routine. + // The redial routine monitors for important + // peer disconnects, and attempts to reconnect + // to them + go sw.runRedialLoop(sw.ctx) return nil } // OnStop implements BaseService. It stops all peers and reactors. func (sw *Switch) OnStop() { + // Close all hanging threads + sw.cancelFn() + // Stop peers for _, p := range sw.peers.List() { sw.stopAndRemovePeer(p, nil) @@ -329,10 +342,7 @@ func (sw *Switch) runRedialLoop(ctx context.Context) { return case ev := <-subCh: - disconnectEv, ok := ev.(*events.PeerDisconnectedEvent) - if !ok { - continue - } + disconnectEv := ev.(*events.PeerDisconnectedEvent) // Dial the disconnected peer // TODO add backoff mechanism From b238bf3e4cba69e5f41002feb730117d7e1af145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Wed, 23 Oct 2024 09:44:41 -0500 Subject: [PATCH 20/43] Add initial discovery code --- tm2/pkg/bft/blockchain/pool.go | 40 ++-- tm2/pkg/bft/blockchain/pool_test.go | 16 +- tm2/pkg/bft/blockchain/reactor.go | 8 +- tm2/pkg/bft/consensus/reactor.go | 5 +- tm2/pkg/bft/consensus/state.go | 18 +- .../bft/consensus/types/height_vote_set.go | 14 +- tm2/pkg/bft/mempool/reactor.go | 10 +- tm2/pkg/bft/node/node.go | 29 +-- tm2/pkg/bft/rpc/client/batch_test.go | 6 +- tm2/pkg/bft/rpc/client/client_test.go | 10 +- tm2/pkg/bft/rpc/client/e2e_test.go | 4 +- tm2/pkg/bft/rpc/core/pipe.go | 4 +- tm2/pkg/bft/rpc/core/types/responses.go | 10 +- tm2/pkg/p2p/discovery/discovery.go | 225 ++++++++++++++++++ tm2/pkg/p2p/discovery/package.go | 29 +++ tm2/pkg/p2p/discovery/types.go | 44 ++++ tm2/pkg/p2p/discovery/types_test.go | 80 +++++++ tm2/pkg/p2p/peer.go | 4 +- tm2/pkg/p2p/peer_test.go | 2 +- tm2/pkg/p2p/switch.go | 2 - tm2/pkg/p2p/transport.go | 8 +- 21 files changed, 475 insertions(+), 93 deletions(-) create mode 100644 tm2/pkg/p2p/discovery/discovery.go create mode 100644 tm2/pkg/p2p/discovery/package.go create mode 100644 tm2/pkg/p2p/discovery/types.go create mode 100644 tm2/pkg/p2p/discovery/types_test.go diff --git a/tm2/pkg/bft/blockchain/pool.go b/tm2/pkg/bft/blockchain/pool.go index f29476cca28..ded9282b7ec 100644 --- a/tm2/pkg/bft/blockchain/pool.go +++ b/tm2/pkg/bft/blockchain/pool.go @@ -12,7 +12,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/flow" "github.com/gnolang/gno/tm2/pkg/log" - types2 "github.com/gnolang/gno/tm2/pkg/p2p" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/gnolang/gno/tm2/pkg/service" ) @@ -69,7 +69,7 @@ type BlockPool struct { requesters map[int64]*bpRequester height int64 // the lowest key in requesters. // peers - peers map[types2.ID]*bpPeer + peers map[p2pTypes.ID]*bpPeer maxPeerHeight int64 // the biggest reported height // atomic @@ -83,7 +83,7 @@ type BlockPool struct { // requests and errors will be sent to requestsCh and errorsCh accordingly. func NewBlockPool(start int64, requestsCh chan<- BlockRequest, errorsCh chan<- peerError) *BlockPool { bp := &BlockPool{ - peers: make(map[types2.ID]*bpPeer), + peers: make(map[p2pTypes.ID]*bpPeer), requesters: make(map[int64]*bpRequester), height: start, @@ -226,13 +226,13 @@ func (pool *BlockPool) PopRequest() { // RedoRequest invalidates the block at pool.height, // Remove the peer and redo request from others. // Returns the ID of the removed peer. -func (pool *BlockPool) RedoRequest(height int64) types2.ID { +func (pool *BlockPool) RedoRequest(height int64) p2pTypes.ID { pool.mtx.Lock() defer pool.mtx.Unlock() request := pool.requesters[height] peerID := request.getPeerID() - if peerID != types2.ID("") { + if peerID != p2pTypes.ID("") { // RemovePeer will redo all requesters associated with this peer. pool.removePeer(peerID) } @@ -241,7 +241,7 @@ func (pool *BlockPool) RedoRequest(height int64) types2.ID { // AddBlock validates that the block comes from the peer it was expected from and calls the requester to store it. // TODO: ensure that blocks come in order for each peer. -func (pool *BlockPool) AddBlock(peerID types2.ID, block *types.Block, blockSize int) { +func (pool *BlockPool) AddBlock(peerID p2pTypes.ID, block *types.Block, blockSize int) { pool.mtx.Lock() defer pool.mtx.Unlock() @@ -278,7 +278,7 @@ func (pool *BlockPool) MaxPeerHeight() int64 { } // SetPeerHeight sets the peer's alleged blockchain height. -func (pool *BlockPool) SetPeerHeight(peerID types2.ID, height int64) { +func (pool *BlockPool) SetPeerHeight(peerID p2pTypes.ID, height int64) { pool.mtx.Lock() defer pool.mtx.Unlock() @@ -298,14 +298,14 @@ func (pool *BlockPool) SetPeerHeight(peerID types2.ID, height int64) { // RemovePeer removes the peer with peerID from the pool. If there's no peer // with peerID, function is a no-op. -func (pool *BlockPool) RemovePeer(peerID types2.ID) { +func (pool *BlockPool) RemovePeer(peerID p2pTypes.ID) { pool.mtx.Lock() defer pool.mtx.Unlock() pool.removePeer(peerID) } -func (pool *BlockPool) removePeer(peerID types2.ID) { +func (pool *BlockPool) removePeer(peerID p2pTypes.ID) { for _, requester := range pool.requesters { if requester.getPeerID() == peerID { requester.redo(peerID) @@ -386,14 +386,14 @@ func (pool *BlockPool) requestersLen() int64 { return int64(len(pool.requesters)) } -func (pool *BlockPool) sendRequest(height int64, peerID types2.ID) { +func (pool *BlockPool) sendRequest(height int64, peerID p2pTypes.ID) { if !pool.IsRunning() { return } pool.requestsCh <- BlockRequest{height, peerID} } -func (pool *BlockPool) sendError(err error, peerID types2.ID) { +func (pool *BlockPool) sendError(err error, peerID p2pTypes.ID) { if !pool.IsRunning() { return } @@ -424,7 +424,7 @@ func (pool *BlockPool) debug() string { type bpPeer struct { pool *BlockPool - id types2.ID + id p2pTypes.ID recvMonitor *flow.Monitor height int64 @@ -435,7 +435,7 @@ type bpPeer struct { logger *slog.Logger } -func newBPPeer(pool *BlockPool, peerID types2.ID, height int64) *bpPeer { +func newBPPeer(pool *BlockPool, peerID p2pTypes.ID, height int64) *bpPeer { peer := &bpPeer{ pool: pool, id: peerID, @@ -499,10 +499,10 @@ type bpRequester struct { pool *BlockPool height int64 gotBlockCh chan struct{} - redoCh chan types2.ID // redo may send multitime, add peerId to identify repeat + redoCh chan p2pTypes.ID // redo may send multitime, add peerId to identify repeat mtx sync.Mutex - peerID types2.ID + peerID p2pTypes.ID block *types.Block } @@ -511,7 +511,7 @@ func newBPRequester(pool *BlockPool, height int64) *bpRequester { pool: pool, height: height, gotBlockCh: make(chan struct{}, 1), - redoCh: make(chan types2.ID, 1), + redoCh: make(chan p2pTypes.ID, 1), peerID: "", block: nil, @@ -526,7 +526,7 @@ func (bpr *bpRequester) OnStart() error { } // Returns true if the peer matches and block doesn't already exist. -func (bpr *bpRequester) setBlock(block *types.Block, peerID types2.ID) bool { +func (bpr *bpRequester) setBlock(block *types.Block, peerID p2pTypes.ID) bool { bpr.mtx.Lock() if bpr.block != nil || bpr.peerID != peerID { bpr.mtx.Unlock() @@ -548,7 +548,7 @@ func (bpr *bpRequester) getBlock() *types.Block { return bpr.block } -func (bpr *bpRequester) getPeerID() types2.ID { +func (bpr *bpRequester) getPeerID() p2pTypes.ID { bpr.mtx.Lock() defer bpr.mtx.Unlock() return bpr.peerID @@ -570,7 +570,7 @@ func (bpr *bpRequester) reset() { // Tells bpRequester to pick another peer and try again. // NOTE: Nonblocking, and does nothing if another redo // was already requested. -func (bpr *bpRequester) redo(peerID types2.ID) { +func (bpr *bpRequester) redo(peerID p2pTypes.ID) { select { case bpr.redoCh <- peerID: default: @@ -631,5 +631,5 @@ OUTER_LOOP: // delivering the block type BlockRequest struct { Height int64 - PeerID types2.ID + PeerID p2pTypes.ID } diff --git a/tm2/pkg/bft/blockchain/pool_test.go b/tm2/pkg/bft/blockchain/pool_test.go index 96455744f23..ee58d672e75 100644 --- a/tm2/pkg/bft/blockchain/pool_test.go +++ b/tm2/pkg/bft/blockchain/pool_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - types2 "github.com/gnolang/gno/tm2/pkg/p2p" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -19,7 +19,7 @@ func init() { } type testPeer struct { - id types2.ID + id p2pTypes.ID height int64 inputChan chan inputData // make sure each peer's data is sequential } @@ -47,7 +47,7 @@ func (p testPeer) simulateInput(input inputData) { // input.t.Logf("Added block from peer %v (height: %v)", input.request.PeerID, input.request.Height) } -type testPeers map[types2.ID]testPeer +type testPeers map[p2pTypes.ID]testPeer func (ps testPeers) start() { for _, v := range ps { @@ -64,7 +64,7 @@ func (ps testPeers) stop() { func makePeers(numPeers int, minHeight, maxHeight int64) testPeers { peers := make(testPeers, numPeers) for i := 0; i < numPeers; i++ { - peerID := types2.ID(random.RandStr(12)) + peerID := p2pTypes.ID(random.RandStr(12)) height := minHeight + random.RandInt63n(maxHeight-minHeight) peers[peerID] = testPeer{peerID, height, make(chan inputData, 10)} } @@ -172,7 +172,7 @@ func TestBlockPoolTimeout(t *testing.T) { // Pull from channels counter := 0 - timedOut := map[types2.ID]struct{}{} + timedOut := map[p2pTypes.ID]struct{}{} for { select { case err := <-errorsCh: @@ -195,7 +195,7 @@ func TestBlockPoolRemovePeer(t *testing.T) { peers := make(testPeers, 10) for i := 0; i < 10; i++ { - peerID := types2.ID(fmt.Sprintf("%d", i+1)) + peerID := p2pTypes.ID(fmt.Sprintf("%d", i+1)) height := int64(i + 1) peers[peerID] = testPeer{peerID, height, make(chan inputData)} } @@ -215,10 +215,10 @@ func TestBlockPoolRemovePeer(t *testing.T) { assert.EqualValues(t, 10, pool.MaxPeerHeight()) // remove not-existing peer - assert.NotPanics(t, func() { pool.RemovePeer(types2.ID("Superman")) }) + assert.NotPanics(t, func() { pool.RemovePeer(p2pTypes.ID("Superman")) }) // remove peer with biggest height - pool.RemovePeer(types2.ID("10")) + pool.RemovePeer(p2pTypes.ID("10")) assert.EqualValues(t, 9, pool.MaxPeerHeight()) // remove all peers diff --git a/tm2/pkg/bft/blockchain/reactor.go b/tm2/pkg/bft/blockchain/reactor.go index 2c681b21823..bac27f3959e 100644 --- a/tm2/pkg/bft/blockchain/reactor.go +++ b/tm2/pkg/bft/blockchain/reactor.go @@ -12,7 +12,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/store" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/p2p" - types2 "github.com/gnolang/gno/tm2/pkg/p2p/types" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" ) const ( @@ -42,7 +42,7 @@ type SwitchToConsensusFn func(sm.State, int) type peerError struct { err error - peerID p2p.ID + peerID p2pTypes.ID } func (e peerError) Error() string { @@ -129,8 +129,8 @@ func (bcR *BlockchainReactor) OnStop() { } // GetChannels implements Reactor -func (bcR *BlockchainReactor) GetChannels() []*types2.ChannelDescriptor { - return []*types2.ChannelDescriptor{ +func (bcR *BlockchainReactor) GetChannels() []*p2p.ChannelDescriptor { + return []*p2p.ChannelDescriptor{ { ID: BlockchainChannel, Priority: 10, diff --git a/tm2/pkg/bft/consensus/reactor.go b/tm2/pkg/bft/consensus/reactor.go index e778525152b..aee695114f8 100644 --- a/tm2/pkg/bft/consensus/reactor.go +++ b/tm2/pkg/bft/consensus/reactor.go @@ -17,7 +17,6 @@ import ( "github.com/gnolang/gno/tm2/pkg/events" "github.com/gnolang/gno/tm2/pkg/log" "github.com/gnolang/gno/tm2/pkg/p2p" - types2 "github.com/gnolang/gno/tm2/pkg/p2p/types" ) const ( @@ -124,9 +123,9 @@ conR: } // GetChannels implements Reactor -func (conR *ConsensusReactor) GetChannels() []*types2.ChannelDescriptor { +func (conR *ConsensusReactor) GetChannels() []*p2p.ChannelDescriptor { // TODO optimize - return []*types2.ChannelDescriptor{ + return []*p2p.ChannelDescriptor{ { ID: StateChannel, Priority: 5, diff --git a/tm2/pkg/bft/consensus/state.go b/tm2/pkg/bft/consensus/state.go index eec01564b32..36bcbc338bd 100644 --- a/tm2/pkg/bft/consensus/state.go +++ b/tm2/pkg/bft/consensus/state.go @@ -23,7 +23,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/errors" "github.com/gnolang/gno/tm2/pkg/events" osm "github.com/gnolang/gno/tm2/pkg/os" - types2 "github.com/gnolang/gno/tm2/pkg/p2p" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/gnolang/gno/tm2/pkg/service" "github.com/gnolang/gno/tm2/pkg/telemetry" "github.com/gnolang/gno/tm2/pkg/telemetry/metrics" @@ -53,7 +53,7 @@ type newRoundStepInfo struct { // msgs from the reactor which may update the state type msgInfo struct { Msg ConsensusMessage `json:"msg"` - PeerID types2.ID `json:"peer_key"` + PeerID p2pTypes.ID `json:"peer_key"` } // WAL message. @@ -399,7 +399,7 @@ func (cs *ConsensusState) OpenWAL(walFile string) (walm.WAL, error) { // TODO: should these return anything or let callers just use events? // AddVote inputs a vote. -func (cs *ConsensusState) AddVote(vote *types.Vote, peerID types2.ID) (added bool, err error) { +func (cs *ConsensusState) AddVote(vote *types.Vote, peerID p2pTypes.ID) (added bool, err error) { if peerID == "" { cs.internalMsgQueue <- msgInfo{&VoteMessage{vote}, ""} } else { @@ -411,7 +411,7 @@ func (cs *ConsensusState) AddVote(vote *types.Vote, peerID types2.ID) (added boo } // SetProposal inputs a proposal. -func (cs *ConsensusState) SetProposal(proposal *types.Proposal, peerID types2.ID) error { +func (cs *ConsensusState) SetProposal(proposal *types.Proposal, peerID p2pTypes.ID) error { if peerID == "" { cs.internalMsgQueue <- msgInfo{&ProposalMessage{proposal}, ""} } else { @@ -423,7 +423,7 @@ func (cs *ConsensusState) SetProposal(proposal *types.Proposal, peerID types2.ID } // AddProposalBlockPart inputs a part of the proposal block. -func (cs *ConsensusState) AddProposalBlockPart(height int64, round int, part *types.Part, peerID types2.ID) error { +func (cs *ConsensusState) AddProposalBlockPart(height int64, round int, part *types.Part, peerID p2pTypes.ID) error { if peerID == "" { cs.internalMsgQueue <- msgInfo{&BlockPartMessage{height, round, part}, ""} } else { @@ -435,7 +435,7 @@ func (cs *ConsensusState) AddProposalBlockPart(height int64, round int, part *ty } // SetProposalAndBlock inputs the proposal and all block parts. -func (cs *ConsensusState) SetProposalAndBlock(proposal *types.Proposal, block *types.Block, parts *types.PartSet, peerID types2.ID) error { +func (cs *ConsensusState) SetProposalAndBlock(proposal *types.Proposal, block *types.Block, parts *types.PartSet, peerID p2pTypes.ID) error { if err := cs.SetProposal(proposal, peerID); err != nil { return err } @@ -1444,7 +1444,7 @@ func (cs *ConsensusState) defaultSetProposal(proposal *types.Proposal) error { // NOTE: block is not necessarily valid. // Asynchronously triggers either enterPrevote (before we timeout of propose) or tryFinalizeCommit, once we have the full block. -func (cs *ConsensusState) addProposalBlockPart(msg *BlockPartMessage, peerID types2.ID) (added bool, err error) { +func (cs *ConsensusState) addProposalBlockPart(msg *BlockPartMessage, peerID p2pTypes.ID) (added bool, err error) { height, round, part := msg.Height, msg.Round, msg.Part // Blocks might be reused, so round mismatch is OK @@ -1514,7 +1514,7 @@ func (cs *ConsensusState) addProposalBlockPart(msg *BlockPartMessage, peerID typ } // Attempt to add the vote. if its a duplicate signature, dupeout the validator -func (cs *ConsensusState) tryAddVote(vote *types.Vote, peerID types2.ID) (bool, error) { +func (cs *ConsensusState) tryAddVote(vote *types.Vote, peerID p2pTypes.ID) (bool, error) { added, err := cs.addVote(vote, peerID) if err != nil { // If the vote height is off, we'll just ignore it, @@ -1547,7 +1547,7 @@ func (cs *ConsensusState) tryAddVote(vote *types.Vote, peerID types2.ID) (bool, // ----------------------------------------------------------------------------- -func (cs *ConsensusState) addVote(vote *types.Vote, peerID types2.ID) (added bool, err error) { +func (cs *ConsensusState) addVote(vote *types.Vote, peerID p2pTypes.ID) (added bool, err error) { cs.Logger.Debug("addVote", "voteHeight", vote.Height, "voteType", vote.Type, "valIndex", vote.ValidatorIndex, "csHeight", cs.Height) // A precommit for the previous height? diff --git a/tm2/pkg/bft/consensus/types/height_vote_set.go b/tm2/pkg/bft/consensus/types/height_vote_set.go index 854ac34f946..7f3d52022ad 100644 --- a/tm2/pkg/bft/consensus/types/height_vote_set.go +++ b/tm2/pkg/bft/consensus/types/height_vote_set.go @@ -8,7 +8,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/bft/types" - types2 "github.com/gnolang/gno/tm2/pkg/p2p" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" ) type RoundVoteSet struct { @@ -39,9 +39,9 @@ type HeightVoteSet struct { valSet *types.ValidatorSet mtx sync.Mutex - round int // max tracked round - roundVoteSets map[int]RoundVoteSet // keys: [0...round] - peerCatchupRounds map[types2.ID][]int // keys: peer.ID; values: at most 2 rounds + round int // max tracked round + roundVoteSets map[int]RoundVoteSet // keys: [0...round] + peerCatchupRounds map[p2pTypes.ID][]int // keys: peer.ID; values: at most 2 rounds } func NewHeightVoteSet(chainID string, height int64, valSet *types.ValidatorSet) *HeightVoteSet { @@ -59,7 +59,7 @@ func (hvs *HeightVoteSet) Reset(height int64, valSet *types.ValidatorSet) { hvs.height = height hvs.valSet = valSet hvs.roundVoteSets = make(map[int]RoundVoteSet) - hvs.peerCatchupRounds = make(map[types2.ID][]int) + hvs.peerCatchupRounds = make(map[p2pTypes.ID][]int) hvs.addRound(0) hvs.round = 0 @@ -108,7 +108,7 @@ func (hvs *HeightVoteSet) addRound(round int) { // Duplicate votes return added=false, err=nil. // By convention, peerID is "" if origin is self. -func (hvs *HeightVoteSet) AddVote(vote *types.Vote, peerID types2.ID) (added bool, err error) { +func (hvs *HeightVoteSet) AddVote(vote *types.Vote, peerID p2pTypes.ID) (added bool, err error) { hvs.mtx.Lock() defer hvs.mtx.Unlock() if !types.IsVoteTypeValid(vote.Type) { @@ -176,7 +176,7 @@ func (hvs *HeightVoteSet) getVoteSet(round int, type_ types.SignedMsgType) *type // NOTE: if there are too many peers, or too much peer churn, // this can cause memory issues. // TODO: implement ability to remove peers too -func (hvs *HeightVoteSet) SetPeerMaj23(round int, type_ types.SignedMsgType, peerID types2.ID, blockID types.BlockID) error { +func (hvs *HeightVoteSet) SetPeerMaj23(round int, type_ types.SignedMsgType, peerID p2pTypes.ID, blockID types.BlockID) error { hvs.mtx.Lock() defer hvs.mtx.Unlock() if !types.IsVoteTypeValid(type_) { diff --git a/tm2/pkg/bft/mempool/reactor.go b/tm2/pkg/bft/mempool/reactor.go index 099e66f74bf..3e0fc55b0f6 100644 --- a/tm2/pkg/bft/mempool/reactor.go +++ b/tm2/pkg/bft/mempool/reactor.go @@ -13,7 +13,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/clist" "github.com/gnolang/gno/tm2/pkg/p2p" - types2 "github.com/gnolang/gno/tm2/pkg/p2p/types" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" ) const ( @@ -40,7 +40,7 @@ type Reactor struct { type mempoolIDs struct { mtx sync.RWMutex - peerMap map[p2p.ID]uint16 + peerMap map[p2pTypes.ID]uint16 nextID uint16 // assumes that a node will never have over 65536 active peers activeIDs map[uint16]struct{} // used to check if a given peerID key is used, the value doesn't matter } @@ -95,7 +95,7 @@ func (ids *mempoolIDs) GetForPeer(peer p2p.Peer) uint16 { func newMempoolIDs() *mempoolIDs { return &mempoolIDs{ - peerMap: make(map[p2p.ID]uint16), + peerMap: make(map[p2pTypes.ID]uint16), activeIDs: map[uint16]struct{}{0: {}}, nextID: 1, // reserve unknownPeerID(0) for mempoolReactor.BroadcastTx } @@ -128,8 +128,8 @@ func (memR *Reactor) OnStart() error { // GetChannels implements Reactor. // It returns the list of channels for this reactor. -func (memR *Reactor) GetChannels() []*types2.ChannelDescriptor { - return []*types2.ChannelDescriptor{ +func (memR *Reactor) GetChannels() []*p2p.ChannelDescriptor { + return []*p2p.ChannelDescriptor{ { ID: MempoolChannel, Priority: 5, diff --git a/tm2/pkg/bft/node/node.go b/tm2/pkg/bft/node/node.go index b9a36d2ea88..d83f9b97e8a 100644 --- a/tm2/pkg/bft/node/node.go +++ b/tm2/pkg/bft/node/node.go @@ -14,7 +14,8 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/appconn" "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/file" - types2 "github.com/gnolang/gno/tm2/pkg/p2p/types" + "github.com/gnolang/gno/tm2/pkg/p2p/conn" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/rs/cors" "github.com/gnolang/gno/tm2/pkg/amino" @@ -87,7 +88,7 @@ func DefaultNewNode( logger *slog.Logger, ) (*Node, error) { // Generate node PrivKey - nodeKey, err := types2.LoadOrGenNodeKey(config.NodeKeyFile()) + nodeKey, err := p2pTypes.LoadOrGenNodeKey(config.NodeKeyFile()) if err != nil { return nil, err } @@ -133,8 +134,8 @@ type Node struct { // network transport *p2p.Transport sw *p2p.Switch // p2p connections - nodeInfo types2.NodeInfo - nodeKey *types2.NodeKey // our node privkey + nodeInfo p2pTypes.NodeInfo + nodeKey *p2pTypes.NodeKey // our node privkey isListening bool // services @@ -317,7 +318,7 @@ func createConsensusReactor(config *cfg.Config, // NewNode returns a new, ready to go, Tendermint Node. func NewNode(config *cfg.Config, privValidator types.PrivValidator, - nodeKey *types2.NodeKey, + nodeKey *p2pTypes.NodeKey, clientCreator appconn.ClientCreator, genesisDocProvider GenesisDocProvider, dbProvider DBProvider, @@ -442,7 +443,7 @@ func NewNode(config *cfg.Config, ) // Setup Switch. - peerAddrs, errs := types2.NewNetAddressFromStrings(splitAndTrimEmpty(config.P2P.PersistentPeers, ",", " ")) + peerAddrs, errs := p2pTypes.NewNetAddressFromStrings(splitAndTrimEmpty(config.P2P.PersistentPeers, ",", " ")) for _, err := range errs { p2pLogger.Error("invalid persistent peer address", "err", err) } @@ -534,7 +535,7 @@ func (n *Node) OnStart() error { } // Start the transport. - addr, err := types2.NewNetAddressFromString(types2.NetAddressString(n.nodeKey.ID(), n.config.P2P.ListenAddress)) + addr, err := p2pTypes.NewNetAddressFromString(p2pTypes.NetAddressString(n.nodeKey.ID(), n.config.P2P.ListenAddress)) if err != nil { return err } @@ -562,7 +563,7 @@ func (n *Node) OnStart() error { } // Always connect to persistent peers - peerAddrs, errs := types2.NewNetAddressFromStrings(splitAndTrimEmpty(n.config.P2P.PersistentPeers, ",", " ")) + peerAddrs, errs := p2pTypes.NewNetAddressFromStrings(splitAndTrimEmpty(n.config.P2P.PersistentPeers, ",", " ")) for _, err := range errs { n.Logger.Error("invalid persistent peer address", "err", err) } @@ -790,17 +791,17 @@ func (n *Node) IsListening() bool { } // NodeInfo returns the Node's Info from the Switch. -func (n *Node) NodeInfo() types2.NodeInfo { +func (n *Node) NodeInfo() p2pTypes.NodeInfo { return n.nodeInfo } func makeNodeInfo( config *cfg.Config, - nodeKey *types2.NodeKey, + nodeKey *p2pTypes.NodeKey, txEventStore eventstore.TxEventStore, genDoc *types.GenesisDoc, state sm.State, -) (types2.NodeInfo, error) { +) (p2pTypes.NodeInfo, error) { txIndexerStatus := eventstore.StatusOff if txEventStore.GetType() != null.EventStoreType { txIndexerStatus = eventstore.StatusOn @@ -813,7 +814,7 @@ func makeNodeInfo( Version: state.AppVersion, }) - nodeInfo := types2.NodeInfo{ + nodeInfo := p2pTypes.NodeInfo{ VersionSet: vset, Network: genDoc.ChainID, Version: version.Version, @@ -823,7 +824,7 @@ func makeNodeInfo( mempl.MempoolChannel, }, Moniker: config.Moniker, - Other: types2.NodeInfoOther{ + Other: p2pTypes.NodeInfoOther{ TxIndex: txIndexerStatus, RPCAddress: config.RPC.ListenAddress, }, @@ -833,7 +834,7 @@ func makeNodeInfo( if lAddr == "" { lAddr = config.P2P.ListenAddress } - addr, err := types2.NewNetAddressFromString(types2.NetAddressString(nodeKey.ID(), lAddr)) + addr, err := p2pTypes.NewNetAddressFromString(p2pTypes.NetAddressString(nodeKey.ID(), lAddr)) if err != nil { return nodeInfo, errors.Wrap(err, "invalid (local) node net address") } diff --git a/tm2/pkg/bft/rpc/client/batch_test.go b/tm2/pkg/bft/rpc/client/batch_test.go index d4a6c54a14b..fcd0f3f834d 100644 --- a/tm2/pkg/bft/rpc/client/batch_test.go +++ b/tm2/pkg/bft/rpc/client/batch_test.go @@ -10,7 +10,7 @@ import ( ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" types "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/types" bfttypes "github.com/gnolang/gno/tm2/pkg/bft/types" - types2 "github.com/gnolang/gno/tm2/pkg/p2p/types" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -116,7 +116,7 @@ func TestRPCBatch_Send(t *testing.T) { var ( numRequests = 10 expectedStatus = &ctypes.ResultStatus{ - NodeInfo: types2.NodeInfo{ + NodeInfo: p2pTypes.NodeInfo{ Moniker: "dummy", }, } @@ -160,7 +160,7 @@ func TestRPCBatch_Endpoints(t *testing.T) { { statusMethod, &ctypes.ResultStatus{ - NodeInfo: types2.NodeInfo{ + NodeInfo: p2pTypes.NodeInfo{ Moniker: "dummy", }, }, diff --git a/tm2/pkg/bft/rpc/client/client_test.go b/tm2/pkg/bft/rpc/client/client_test.go index 68b4db095bf..31889f59883 100644 --- a/tm2/pkg/bft/rpc/client/client_test.go +++ b/tm2/pkg/bft/rpc/client/client_test.go @@ -14,7 +14,7 @@ import ( ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" types "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/types" bfttypes "github.com/gnolang/gno/tm2/pkg/bft/types" - types2 "github.com/gnolang/gno/tm2/pkg/p2p/types" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -114,7 +114,7 @@ func TestRPCClient_Status(t *testing.T) { var ( expectedStatus = &ctypes.ResultStatus{ - NodeInfo: types2.NodeInfo{ + NodeInfo: p2pTypes.NodeInfo{ Moniker: "dummy", }, } @@ -811,17 +811,17 @@ func TestRPCClient_Batch(t *testing.T) { var ( expectedStatuses = []*ctypes.ResultStatus{ { - NodeInfo: types2.NodeInfo{ + NodeInfo: p2pTypes.NodeInfo{ Moniker: "dummy", }, }, { - NodeInfo: types2.NodeInfo{ + NodeInfo: p2pTypes.NodeInfo{ Moniker: "dummy", }, }, { - NodeInfo: types2.NodeInfo{ + NodeInfo: p2pTypes.NodeInfo{ Moniker: "dummy", }, }, diff --git a/tm2/pkg/bft/rpc/client/e2e_test.go b/tm2/pkg/bft/rpc/client/e2e_test.go index d70f2583e48..358c66b0b26 100644 --- a/tm2/pkg/bft/rpc/client/e2e_test.go +++ b/tm2/pkg/bft/rpc/client/e2e_test.go @@ -13,7 +13,7 @@ import ( ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" types "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/types" bfttypes "github.com/gnolang/gno/tm2/pkg/bft/types" - types2 "github.com/gnolang/gno/tm2/pkg/p2p/types" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -170,7 +170,7 @@ func TestRPCClient_E2E_Endpoints(t *testing.T) { { statusMethod, &ctypes.ResultStatus{ - NodeInfo: types2.NodeInfo{ + NodeInfo: p2pTypes.NodeInfo{ Moniker: "dummy", }, }, diff --git a/tm2/pkg/bft/rpc/core/pipe.go b/tm2/pkg/bft/rpc/core/pipe.go index 49df94e444b..d5cdf2c268c 100644 --- a/tm2/pkg/bft/rpc/core/pipe.go +++ b/tm2/pkg/bft/rpc/core/pipe.go @@ -15,7 +15,7 @@ import ( dbm "github.com/gnolang/gno/tm2/pkg/db" "github.com/gnolang/gno/tm2/pkg/events" "github.com/gnolang/gno/tm2/pkg/p2p" - types2 "github.com/gnolang/gno/tm2/pkg/p2p/types" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" ) const ( @@ -39,7 +39,7 @@ type Consensus interface { type transport interface { Listeners() []string IsListening() bool - NodeInfo() types2.NodeInfo + NodeInfo() p2pTypes.NodeInfo } type peers interface { diff --git a/tm2/pkg/bft/rpc/core/types/responses.go b/tm2/pkg/bft/rpc/core/types/responses.go index 6b17592199e..76474867b27 100644 --- a/tm2/pkg/bft/rpc/core/types/responses.go +++ b/tm2/pkg/bft/rpc/core/types/responses.go @@ -11,7 +11,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/p2p" - types2 "github.com/gnolang/gno/tm2/pkg/p2p/types" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" ) // List of blocks @@ -75,9 +75,9 @@ type ValidatorInfo struct { // Node Status type ResultStatus struct { - NodeInfo types2.NodeInfo `json:"node_info"` - SyncInfo SyncInfo `json:"sync_info"` - ValidatorInfo ValidatorInfo `json:"validator_info"` + NodeInfo p2pTypes.NodeInfo `json:"node_info"` + SyncInfo SyncInfo `json:"sync_info"` + ValidatorInfo ValidatorInfo `json:"validator_info"` } // Is TxIndexing enabled @@ -108,7 +108,7 @@ type ResultDialPeers struct { // A peer type Peer struct { - NodeInfo types2.NodeInfo `json:"node_info"` + NodeInfo p2pTypes.NodeInfo `json:"node_info"` IsOutbound bool `json:"is_outbound"` ConnectionStatus p2p.ConnectionStatus `json:"connection_status"` RemoteIP string `json:"remote_ip"` diff --git a/tm2/pkg/p2p/discovery/discovery.go b/tm2/pkg/p2p/discovery/discovery.go new file mode 100644 index 00000000000..eb2d0d55b63 --- /dev/null +++ b/tm2/pkg/p2p/discovery/discovery.go @@ -0,0 +1,225 @@ +package discovery + +import ( + "context" + "crypto/rand" + "fmt" + "math/big" + "time" + + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/p2p" + "github.com/gnolang/gno/tm2/pkg/p2p/conn" + "github.com/gnolang/gno/tm2/pkg/p2p/types" +) + +const ( + // Channel is the unique channel for the peer discovery protocol + Channel = byte(0x50) + + // discoveryInterval is the peer discovery interval, for random peers + discoveryInterval = time.Second * 3 + + // maxPeersShared is the maximum number of peers shared in the discovery request + maxPeersShared = 30 +) + +// descriptor is the constant peer discovery protocol descriptor +var descriptor = &conn.ChannelDescriptor{ + ID: Channel, + Priority: 1, // peer discovery is high priority + SendQueueCapacity: 20, // more than enough active conns + RecvMessageCapacity: 5242880, // 5MB +} + +// Reactor wraps the logic for the peer exchange protocol +type Reactor struct { + // This embed and the usage of "services" + // like the peer discovery reactor highlight the + // flipped design of the p2p package. + // The peer exchange service needs to be instantiated _outside_ + // the p2p module, because of this flipped design. + // Peers communicate with each other through Reactor channels, + // which are instantiated outside the p2p module + p2p.BaseReactor + + ctx context.Context + cancelFn context.CancelFunc +} + +// NewReactor creates a new peer discovery reactor +func NewReactor() *Reactor { + ctx, cancelFn := context.WithCancel(context.Background()) + + return &Reactor{ + ctx: ctx, + cancelFn: cancelFn, + } +} + +// StartDiscovery runs the peer discovery protocol +func (r *Reactor) StartDiscovery() { + go func() { + ticker := time.NewTicker(discoveryInterval) + defer ticker.Stop() + + for { + select { + case <-r.ctx.Done(): + r.Logger.Debug("discovery service stopped") + + return + case <-ticker.C: + // Run the discovery protocol + + // Grab a random peer, and engage + // them for peer discovery + peers := r.Switch.Peers().List() + + // Generate a random peer index + randomPeer, err := rand.Int( + rand.Reader, + big.NewInt(int64(len(peers))), + ) + if err != nil { + r.Logger.Error( + "unable to generate random peer index", + "err", err, + ) + + return + } + + // Request peers, async + go r.requestPeers(peers[randomPeer.Int64()]) + } + } + }() +} + +// requestPeers requests the peer set from the given peer +func (r *Reactor) requestPeers(peer p2p.Peer) { + // Initiate peer discovery + r.Logger.Debug("running peer discovery", "peer", peer.ID()) + + // Prepare the request + // (empty, as it's a notification) + req := &Request{} + + reqBytes, err := amino.MarshalAny(req) + if err != nil { + r.Logger.Error("unable to marshal discovery request", "err", err) + + return + } + + // Send the request + if !peer.Send(Channel, reqBytes) { + r.Logger.Warn("unable to send discovery request", "peer", peer.ID()) + } +} + +// StopDiscovery stops the peer discovery protocol +func (r *Reactor) StopDiscovery() { + r.cancelFn() +} + +// GetChannels returns the channels associated with peer discovery +func (r *Reactor) GetChannels() []*conn.ChannelDescriptor { + return []*conn.ChannelDescriptor{descriptor} +} + +// Receive handles incoming messages for the peer discovery reactor +func (r *Reactor) Receive(chID byte, peer p2p.Peer, msgBytes []byte) { + r.Logger.Debug( + "received message", + "peerID", peer.ID(), + "chID", chID, + ) + + // Unmarshal the message + var msg Message + + if err := amino.Unmarshal(msgBytes, &msg); err != nil { + r.Logger.Error("unable to unmarshal discovery message", "err", err) + + return + } + + // Validate the message + if err := msg.ValidateBasic(); err != nil { + r.Logger.Error("unable to validate discovery message", "err", err) + + return + } + + switch msg := msg.(type) { + case *Request: + if err := r.handleDiscoveryRequest(peer); err != nil { + r.Logger.Error("unable to handle discovery request", "err", err) + } + case *Response: + // Make the peers available for dialing on the switch + r.Switch.DialPeers(msg.Peers...) + default: + r.Logger.Warn("invalid message received", "msg", msgBytes) + } +} + +// handleDiscoveryRequest prepares a peer list that can be shared +// with the peer requesting discovery +func (r *Reactor) handleDiscoveryRequest(peer p2p.Peer) error { + // Check if there is anything to share, + // to avoid useless traffic + if r.Switch.Peers().Size() == 0 { + r.Logger.Warn("no peers to share in discovery request") + + return nil + } + + var ( + localPeers = r.Switch.Peers().List() + peers = make([]*types.NetAddress, 0, len(localPeers)) + ) + + // Shuffle and limit the peers shared + shufflePeers(localPeers) + + if len(localPeers) > maxPeersShared { + localPeers = localPeers[:maxPeersShared] + } + + for _, p := range localPeers { + peers = append(peers, p.NodeInfo().NetAddress) + } + + // Create the response, and marshal + // it to Amino binary + resp := &Response{ + Peers: peers, + } + + preparedResp, err := amino.MarshalAny(resp) + if err != nil { + return fmt.Errorf("unable to marshal discovery response, %w", err) + } + + // Send the response to the peer + if !peer.Send(Channel, preparedResp) { + return fmt.Errorf("unable to send discovery response to peer %s", peer.ID()) + } + + return nil +} + +// shufflePeers shuffles the peer list in-place +func shufflePeers(peers []p2p.Peer) { + for i := len(peers) - 1; i > 0; i-- { + jBig, _ := rand.Int(rand.Reader, big.NewInt(int64(i+1))) + + j := int(jBig.Int64()) + + // Swap elements + peers[i], peers[j] = peers[j], peers[i] + } +} diff --git a/tm2/pkg/p2p/discovery/package.go b/tm2/pkg/p2p/discovery/package.go new file mode 100644 index 00000000000..3cdeac8d76b --- /dev/null +++ b/tm2/pkg/p2p/discovery/package.go @@ -0,0 +1,29 @@ +package discovery + +import ( + "reflect" + + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/amino/pkg" +) + +var Package = amino.RegisterPackage(amino.NewPackage( + "github.com/gnolang/gno/tm2/pkg/p2p/discovery", + "p2p", + amino.GetCallersDirname(), +). + WithDependencies( + // NA + ). + WithTypes( + // NOTE: Keep the names short. + pkg.Type{ + Type: reflect.TypeOf(Request{}), + Name: "Request", + }, + pkg.Type{ + Type: reflect.TypeOf(Response{}), + Name: "Response", + }, + ), +) diff --git a/tm2/pkg/p2p/discovery/types.go b/tm2/pkg/p2p/discovery/types.go new file mode 100644 index 00000000000..87ea936ebb5 --- /dev/null +++ b/tm2/pkg/p2p/discovery/types.go @@ -0,0 +1,44 @@ +package discovery + +import ( + "github.com/gnolang/gno/tm2/pkg/errors" + "github.com/gnolang/gno/tm2/pkg/p2p/types" +) + +var errNoPeers = errors.New("no peers received") + +// Message is the wrapper for the discovery message +type Message interface { + ValidateBasic() error +} + +// Request is the peer discovery request. +// It is empty by design, since it's used as +// a notification type +type Request struct{} + +func (r *Request) ValidateBasic() error { + return nil +} + +// Response is the peer discovery response +type Response struct { + Peers []*types.NetAddress // the peer set returned by the peer +} + +func (r *Response) ValidateBasic() error { + // Make sure at least some peers were received + if len(r.Peers) == 0 { + return errNoPeers + } + + // Make sure the returned peer dial + // addresses are valid + for _, peer := range r.Peers { + if err := peer.Validate(); err != nil { + return err + } + } + + return nil +} diff --git a/tm2/pkg/p2p/discovery/types_test.go b/tm2/pkg/p2p/discovery/types_test.go new file mode 100644 index 00000000000..0ac2c16f4e5 --- /dev/null +++ b/tm2/pkg/p2p/discovery/types_test.go @@ -0,0 +1,80 @@ +package discovery + +import ( + "net" + "testing" + + "github.com/gnolang/gno/tm2/pkg/p2p/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// generateNetAddrs generates random net addresses +func generateNetAddrs(t *testing.T, count int) []*types.NetAddress { + t.Helper() + + addrs := make([]*types.NetAddress, count) + + for i := 0; i < count; i++ { + var ( + key = types.GenerateNodeKey() + address = "127.0.0.1:8080" + ) + + tcpAddr, err := net.ResolveTCPAddr("tcp", address) + require.NoError(t, err) + + addr, err := types.NewNetAddress(key.ID(), tcpAddr) + require.NoError(t, err) + + addrs[i] = addr + } + + return addrs +} + +func TestRequest_ValidateBasic(t *testing.T) { + t.Parallel() + + r := &Request{} + + assert.NoError(t, r.ValidateBasic()) +} + +func TestResponse_ValidateBasic(t *testing.T) { + t.Parallel() + + t.Run("empty peer set", func(t *testing.T) { + t.Parallel() + + r := &Response{ + Peers: make([]*types.NetAddress, 0), + } + + assert.ErrorIs(t, r.ValidateBasic(), errNoPeers) + }) + + t.Run("invalid peer dial address", func(t *testing.T) { + t.Parallel() + + r := &Response{ + Peers: []*types.NetAddress{ + { + ID: "", // invalid ID + }, + }, + } + + assert.Error(t, r.ValidateBasic()) + }) + + t.Run("valid peer set", func(t *testing.T) { + t.Parallel() + + r := &Response{ + Peers: generateNetAddrs(t, 10), + } + + assert.NoError(t, r.ValidateBasic()) + }) +} diff --git a/tm2/pkg/p2p/peer.go b/tm2/pkg/p2p/peer.go index 4a4e7cabffa..83f47b71cc1 100644 --- a/tm2/pkg/p2p/peer.go +++ b/tm2/pkg/p2p/peer.go @@ -50,8 +50,8 @@ type peer struct { data *cmap.CMap // Arbitrary data store associated with the peer } -// NewPeer creates an uninitialized peer instance -func NewPeer( +// newPeer creates an uninitialized peer instance +func newPeer( connInfo *ConnInfo, nodeInfo types.NodeInfo, mConfig *ConnConfig, diff --git a/tm2/pkg/p2p/peer_test.go b/tm2/pkg/p2p/peer_test.go index ee51e9c4421..9b4ec2f8777 100644 --- a/tm2/pkg/p2p/peer_test.go +++ b/tm2/pkg/p2p/peer_test.go @@ -626,6 +626,6 @@ func TestPeer_NewPeer(t *testing.T) { ) assert.NotPanics(t, func() { - _ = NewPeer(connInfo, types.NodeInfo{}, mConfig) + _ = newPeer(connInfo, types.NodeInfo{}, mConfig) }) } diff --git a/tm2/pkg/p2p/switch.go b/tm2/pkg/p2p/switch.go index e3f61328c25..28cc1d4ae25 100644 --- a/tm2/pkg/p2p/switch.go +++ b/tm2/pkg/p2p/switch.go @@ -357,8 +357,6 @@ func (sw *Switch) DialPeers(peerAddrs ...*types.NetAddress) { for _, peerAddr := range peerAddrs { // Check if this is our address if peerAddr.Same(sw.transport.NetAddress()) { - sw.Logger.Warn("ignoring request for self-dial") - continue } diff --git a/tm2/pkg/p2p/transport.go b/tm2/pkg/p2p/transport.go index 25e156dccb0..3e1a735abc0 100644 --- a/tm2/pkg/p2p/transport.go +++ b/tm2/pkg/p2p/transport.go @@ -234,6 +234,12 @@ func (mt *MultiplexTransport) processConn(c net.Conn, expectedID types.ID) (peer // Verify the connection ID id := secretConn.RemotePubKey().Address().ID() + if err = id.Validate(); err != nil { + mt.activeConns.Delete(dialAddr) + + return peerInfo{}, fmt.Errorf("unable to validate connection ID, %w", err) + } + // The reason the dial ID needs to be verified is because // for outbound peers (peers the node dials), there is an expected peer ID // when initializing the outbound connection, that can differ from the exchanged one. @@ -345,7 +351,7 @@ func (mt *MultiplexTransport) newMultiplexPeer( OnPeerError: behavior.HandlePeerError, } - return NewPeer(peerConn, info.nodeInfo, mConfig), nil + return newPeer(peerConn, info.nodeInfo, mConfig), nil } // exchangeNodeInfo performs a data swap, where node From 728742da29af393d470142270f1f1a6a99e4b3a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Wed, 23 Oct 2024 09:53:12 -0500 Subject: [PATCH 21/43] Make the switch modifiable in the base reactor --- tm2/pkg/bft/blockchain/reactor_test.go | 6 ++-- tm2/pkg/bft/consensus/reactor.go | 8 ++--- tm2/pkg/bft/consensus/reactor_test.go | 8 ++--- tm2/pkg/bft/mempool/reactor.go | 2 +- tm2/pkg/bft/mempool/reactor_test.go | 2 +- tm2/pkg/bft/node/node.go | 6 ++-- tm2/pkg/bft/rpc/core/net_test.go | 4 +-- tm2/pkg/p2p/base_reactor.go | 8 ++--- tm2/pkg/p2p/peer.go | 2 +- tm2/pkg/p2p/switch.go | 46 +++++++++++++------------- tm2/pkg/p2p/switch_option.go | 12 +++---- tm2/pkg/p2p/switch_test.go | 14 ++++---- tm2/pkg/p2p/types.go | 22 +++++++++++- 13 files changed, 80 insertions(+), 60 deletions(-) diff --git a/tm2/pkg/bft/blockchain/reactor_test.go b/tm2/pkg/bft/blockchain/reactor_test.go index 5b3cdd938da..c2fd66712db 100644 --- a/tm2/pkg/bft/blockchain/reactor_test.go +++ b/tm2/pkg/bft/blockchain/reactor_test.go @@ -130,7 +130,7 @@ func TestNoBlockResponse(t *testing.T) { reactorPairs[0] = newBlockchainReactor(log.NewTestingLogger(t), genDoc, privVals, maxBlockHeight) reactorPairs[1] = newBlockchainReactor(log.NewTestingLogger(t), genDoc, privVals, 0) - p2p.MakeConnectedSwitches(config.P2P, 2, func(i int, s *p2p.Switch) *p2p.Switch { + p2p.MakeConnectedSwitches(config.P2P, 2, func(i int, s *p2p.MultiplexSwitch) *p2p.MultiplexSwitch { s.AddReactor("BLOCKCHAIN", reactorPairs[i].reactor) return s }, p2p.Connect2Switches) @@ -201,7 +201,7 @@ func TestFlappyBadBlockStopsPeer(t *testing.T) { reactorPairs[2] = newBlockchainReactor(log.NewNoopLogger(), genDoc, privVals, 0) reactorPairs[3] = newBlockchainReactor(log.NewNoopLogger(), genDoc, privVals, 0) - switches := p2p.MakeConnectedSwitches(config.P2P, 4, func(i int, s *p2p.Switch) *p2p.Switch { + switches := p2p.MakeConnectedSwitches(config.P2P, 4, func(i int, s *p2p.MultiplexSwitch) *p2p.MultiplexSwitch { s.AddReactor("BLOCKCHAIN", reactorPairs[i].reactor) return s }, p2p.Connect2Switches) @@ -230,7 +230,7 @@ func TestFlappyBadBlockStopsPeer(t *testing.T) { lastReactorPair := newBlockchainReactor(log.NewNoopLogger(), genDoc, privVals, 0) reactorPairs = append(reactorPairs, lastReactorPair) - switches = append(switches, p2p.MakeConnectedSwitches(config.P2P, 1, func(i int, s *p2p.Switch) *p2p.Switch { + switches = append(switches, p2p.MakeConnectedSwitches(config.P2P, 1, func(i int, s *p2p.MultiplexSwitch) *p2p.MultiplexSwitch { s.AddReactor("BLOCKCHAIN", reactorPairs[len(reactorPairs)-1].reactor) return s }, p2p.Connect2Switches)...) diff --git a/tm2/pkg/bft/consensus/reactor.go b/tm2/pkg/bft/consensus/reactor.go index aee695114f8..8deca2b1e7a 100644 --- a/tm2/pkg/bft/consensus/reactor.go +++ b/tm2/pkg/bft/consensus/reactor.go @@ -35,7 +35,7 @@ const ( // ConsensusReactor defines a reactor for the consensus service. type ConsensusReactor struct { - p2p.BaseReactor // BaseService + p2p.Switch + p2p.BaseReactor // BaseService + p2p.MultiplexSwitch conS *ConsensusState @@ -417,7 +417,7 @@ func (conR *ConsensusReactor) broadcastHasVoteMessage(vote *types.Vote) { conR.Switch.Broadcast(StateChannel, amino.MustMarshalAny(msg)) /* // TODO: Make this broadcast more selective. - for _, peer := range conR.Switch.Peers().List() { + for _, peer := range conR.MultiplexSwitch.Peers().List() { ps, ok := peer.Get(PeerStateKey).(*PeerState) if !ok { panic(fmt.Sprintf("Peer %v has no state", peer)) @@ -826,12 +826,12 @@ func (conR *ConsensusReactor) peerStatsRoutine() { case *VoteMessage: if numVotes := ps.RecordVote(); numVotes%votesToContributeToBecomeGoodPeer == 0 { // TODO: peer metrics. - // conR.Switch.MarkPeerAsGood(peer) + // conR.MultiplexSwitch.MarkPeerAsGood(peer) } case *BlockPartMessage: if numParts := ps.RecordBlockPart(); numParts%blocksToContributeToBecomeGoodPeer == 0 { // TODO: peer metrics. - // conR.Switch.MarkPeerAsGood(peer) + // conR.MultiplexSwitch.MarkPeerAsGood(peer) } } case <-conR.conS.Quit(): diff --git a/tm2/pkg/bft/consensus/reactor_test.go b/tm2/pkg/bft/consensus/reactor_test.go index 42f944b7481..a9fb1968212 100644 --- a/tm2/pkg/bft/consensus/reactor_test.go +++ b/tm2/pkg/bft/consensus/reactor_test.go @@ -28,11 +28,11 @@ import ( // ---------------------------------------------- // in-process testnets -func startConsensusNet(css []*ConsensusState, n int) ([]*ConsensusReactor, []<-chan events.Event, []events.EventSwitch, []*p2p.Switch) { +func startConsensusNet(css []*ConsensusState, n int) ([]*ConsensusReactor, []<-chan events.Event, []events.EventSwitch, []*p2p.MultiplexSwitch) { reactors := make([]*ConsensusReactor, n) blocksSubs := make([]<-chan events.Event, 0) eventSwitches := make([]events.EventSwitch, n) - p2pSwitches := ([]*p2p.Switch)(nil) + p2pSwitches := ([]*p2p.MultiplexSwitch)(nil) for i := 0; i < n; i++ { /*logger, err := tmflags.ParseLogLevel("consensus:info,*:error", logger, "info") if err != nil { t.Fatal(err)}*/ @@ -51,7 +51,7 @@ func startConsensusNet(css []*ConsensusState, n int) ([]*ConsensusReactor, []<-c } } // make connected switches and start all reactors - p2pSwitches = p2p.MakeConnectedSwitches(config.P2P, n, func(i int, s *p2p.Switch) *p2p.Switch { + p2pSwitches = p2p.MakeConnectedSwitches(config.P2P, n, func(i int, s *p2p.MultiplexSwitch) *p2p.MultiplexSwitch { s.AddReactor("CONSENSUS", reactors[i]) s.SetLogger(reactors[i].conS.Logger.With("module", "p2p")) return s @@ -68,7 +68,7 @@ func startConsensusNet(css []*ConsensusState, n int) ([]*ConsensusReactor, []<-c return reactors, blocksSubs, eventSwitches, p2pSwitches } -func stopConsensusNet(logger *slog.Logger, reactors []*ConsensusReactor, eventSwitches []events.EventSwitch, p2pSwitches []*p2p.Switch) { +func stopConsensusNet(logger *slog.Logger, reactors []*ConsensusReactor, eventSwitches []events.EventSwitch, p2pSwitches []*p2p.MultiplexSwitch) { logger.Info("stopConsensusNet", "n", len(reactors)) for i, r := range reactors { logger.Info("stopConsensusNet: Stopping ConsensusReactor", "i", i) diff --git a/tm2/pkg/bft/mempool/reactor.go b/tm2/pkg/bft/mempool/reactor.go index 3e0fc55b0f6..634d92277c2 100644 --- a/tm2/pkg/bft/mempool/reactor.go +++ b/tm2/pkg/bft/mempool/reactor.go @@ -214,7 +214,7 @@ func (memR *Reactor) broadcastTxRoutine(peer p2p.Peer) { peerState, ok := peer.Get(types.PeerStateKey).(PeerState) if !ok { // Peer does not have a state yet. We set it in the consensus reactor, but - // when we add peer in Switch, the order we call reactors#AddPeer is + // when we add peer in MultiplexSwitch, the order we call reactors#AddPeer is // different every time due to us using a map. Sometimes other reactors // will be initialized before the consensus reactor. We should wait a few // milliseconds and retry. diff --git a/tm2/pkg/bft/mempool/reactor_test.go b/tm2/pkg/bft/mempool/reactor_test.go index d2a5e9d1e25..19403d09b76 100644 --- a/tm2/pkg/bft/mempool/reactor_test.go +++ b/tm2/pkg/bft/mempool/reactor_test.go @@ -52,7 +52,7 @@ func makeAndConnectReactors(mconfig *memcfg.MempoolConfig, pconfig *p2pcfg.P2PCo reactors[i].SetLogger(logger.With("validator", i)) } - p2p.MakeConnectedSwitches(pconfig, n, func(i int, s *p2p.Switch) *p2p.Switch { + p2p.MakeConnectedSwitches(pconfig, n, func(i int, s *p2p.MultiplexSwitch) *p2p.MultiplexSwitch { s.AddReactor("MEMPOOL", reactors[i]) return s }, p2p.Connect2Switches) diff --git a/tm2/pkg/bft/node/node.go b/tm2/pkg/bft/node/node.go index d83f9b97e8a..0a38df6a000 100644 --- a/tm2/pkg/bft/node/node.go +++ b/tm2/pkg/bft/node/node.go @@ -133,7 +133,7 @@ type Node struct { // network transport *p2p.Transport - sw *p2p.Switch // p2p connections + sw *p2p.MultiplexSwitch // p2p connections nodeInfo p2pTypes.NodeInfo nodeKey *p2pTypes.NodeKey // our node privkey isListening bool @@ -442,7 +442,7 @@ func NewNode(config *cfg.Config, p2pLogger.With("transport", "multiplex"), ) - // Setup Switch. + // Setup MultiplexSwitch. peerAddrs, errs := p2pTypes.NewNetAddressFromStrings(splitAndTrimEmpty(config.P2P.PersistentPeers, ",", " ")) for _, err := range errs { p2pLogger.Error("invalid persistent peer address", "err", err) @@ -723,7 +723,7 @@ func joinListenerAddresses(ll []net.Listener) string { } // Switch returns the Node's Switch. -func (n *Node) Switch() *p2p.Switch { +func (n *Node) Switch() *p2p.MultiplexSwitch { return n.sw } diff --git a/tm2/pkg/bft/rpc/core/net_test.go b/tm2/pkg/bft/rpc/core/net_test.go index 3273837b6ce..163312e2ed9 100644 --- a/tm2/pkg/bft/rpc/core/net_test.go +++ b/tm2/pkg/bft/rpc/core/net_test.go @@ -16,7 +16,7 @@ func TestUnsafeDialSeeds(t *testing.T) { t.Parallel() sw := p2p.MakeSwitch(p2pcfg.DefaultP2PConfig(), 1, "testing", "123.123.123", - func(n int, sw *p2p.Switch) *p2p.Switch { return sw }) + func(n int, sw *p2p.MultiplexSwitch) *p2p.MultiplexSwitch { return sw }) err := sw.Start() require.NoError(t, err) defer sw.Stop() @@ -48,7 +48,7 @@ func TestUnsafeDialPeers(t *testing.T) { t.Parallel() sw := p2p.MakeSwitch(p2pcfg.DefaultP2PConfig(), 1, "testing", "123.123.123", - func(n int, sw *p2p.Switch) *p2p.Switch { return sw }) + func(n int, sw *p2p.MultiplexSwitch) *p2p.MultiplexSwitch { return sw }) err := sw.Start() require.NoError(t, err) defer sw.Stop() diff --git a/tm2/pkg/p2p/base_reactor.go b/tm2/pkg/p2p/base_reactor.go index 41756b984ce..09d5ff593ab 100644 --- a/tm2/pkg/p2p/base_reactor.go +++ b/tm2/pkg/p2p/base_reactor.go @@ -6,7 +6,7 @@ import ( ) // Reactor is responsible for handling incoming messages on one or more -// Channel. Switch calls GetChannels when reactor is added to it. When a new +// Channel. MultiplexSwitch calls GetChannels when reactor is added to it. When a new // peer joins our node, InitPeer and AddPeer are called. RemovePeer is called // when the peer is stopped. Receive is called when a message is received on a // channel associated with this reactor. @@ -16,7 +16,7 @@ type Reactor interface { service.Service // Start, Stop // SetSwitch allows setting a switch. - SetSwitch(*Switch) + SetSwitch(Switch) // GetChannels returns the list of MConnection.ChannelDescriptor. Make sure // that each ID is unique across all the reactors added to the switch. @@ -51,7 +51,7 @@ type Reactor interface { type BaseReactor struct { service.BaseService // Provides Start, Stop, Quit - Switch *Switch + Switch Switch } func NewBaseReactor(name string, impl Reactor) *BaseReactor { @@ -61,7 +61,7 @@ func NewBaseReactor(name string, impl Reactor) *BaseReactor { } } -func (br *BaseReactor) SetSwitch(sw *Switch) { +func (br *BaseReactor) SetSwitch(sw Switch) { br.Switch = sw } func (*BaseReactor) GetChannels() []*conn.ChannelDescriptor { return nil } diff --git a/tm2/pkg/p2p/peer.go b/tm2/pkg/p2p/peer.go index 83f47b71cc1..62375d4fe35 100644 --- a/tm2/pkg/p2p/peer.go +++ b/tm2/pkg/p2p/peer.go @@ -171,7 +171,7 @@ func (p *peer) Status() conn.ConnectionStatus { // send queue is full after timeout, specified by MConnection. func (p *peer) Send(chID byte, msgBytes []byte) bool { if !p.IsRunning() || !p.hasChannel(chID) { - // see Switch#Broadcast, where we fetch the list of peers and loop over + // see MultiplexSwitch#Broadcast, where we fetch the list of peers and loop over // them - while we're looping, one peer may be removed and stopped. return false } diff --git a/tm2/pkg/p2p/switch.go b/tm2/pkg/p2p/switch.go index 28cc1d4ae25..d786751d2f0 100644 --- a/tm2/pkg/p2p/switch.go +++ b/tm2/pkg/p2p/switch.go @@ -40,11 +40,11 @@ func (r *reactorPeerBehavior) IsPersistentPeer(address *types.NetAddress) bool { return r.isPersistentPeerFn(address) } -// Switch handles peer connections and exposes an API to receive incoming messages +// MultiplexSwitch handles peer connections and exposes an API to receive incoming messages // on `Reactors`. Each `Reactor` is responsible for handling incoming messages of one // or more `Channels`. So while sending outgoing messages is typically performed on the peer, // incoming messages are received on the reactor. -type Switch struct { +type MultiplexSwitch struct { service.BaseService ctx context.Context @@ -64,14 +64,14 @@ type Switch struct { events *events.Events } -// NewSwitch creates a new Switch with the given config. +// NewSwitch creates a new MultiplexSwitch with the given config. func NewSwitch( transport Transport, options ...SwitchOption, -) *Switch { +) *MultiplexSwitch { defaultCfg := config.DefaultP2PConfig() - sw := &Switch{ + sw := &MultiplexSwitch{ reactors: make(map[string]Reactor), peers: newSet(), transport: transport, @@ -91,7 +91,7 @@ func NewSwitch( }, } - sw.BaseService = *service.NewBaseService(nil, "P2P Switch", sw) + sw.BaseService = *service.NewBaseService(nil, "P2P MultiplexSwitch", sw) // Set up the context sw.ctx, sw.cancelFn = context.WithCancel(context.Background()) @@ -103,9 +103,9 @@ func NewSwitch( return sw } -// Subscribe registers to live events happening on the p2p Switch. +// Subscribe registers to live events happening on the p2p MultiplexSwitch. // Returns the notification channel, along with an unsubscribe method -func (sw *Switch) Subscribe(filterFn events.EventFilter) (<-chan events.Event, func()) { +func (sw *MultiplexSwitch) Subscribe(filterFn events.EventFilter) (<-chan events.Event, func()) { return sw.events.Subscribe(filterFn) } @@ -113,7 +113,7 @@ func (sw *Switch) Subscribe(filterFn events.EventFilter) (<-chan events.Event, f // Service start/stop // OnStart implements BaseService. It starts all the reactors and peers. -func (sw *Switch) OnStart() error { +func (sw *MultiplexSwitch) OnStart() error { // Start reactors for _, reactor := range sw.reactors { if err := reactor.Start(); err != nil { @@ -141,7 +141,7 @@ func (sw *Switch) OnStart() error { } // OnStop implements BaseService. It stops all peers and reactors. -func (sw *Switch) OnStop() { +func (sw *MultiplexSwitch) OnStop() { // Close all hanging threads sw.cancelFn() @@ -160,7 +160,7 @@ func (sw *Switch) OnStop() { // Broadcast broadcasts the given data to the given channel, // across the entire switch peer set, without blocking -func (sw *Switch) Broadcast(chID byte, data []byte) { +func (sw *MultiplexSwitch) Broadcast(chID byte, data []byte) { for _, p := range sw.peers.List() { go func() { // This send context is managed internally @@ -177,13 +177,13 @@ func (sw *Switch) Broadcast(chID byte, data []byte) { } // Peers returns the set of peers that are connected to the switch. -func (sw *Switch) Peers() PeerSet { +func (sw *MultiplexSwitch) Peers() PeerSet { return sw.peers } // StopPeerForError disconnects from a peer due to external error. // If the peer is persistent, it will attempt to reconnect -func (sw *Switch) StopPeerForError(peer Peer, err error) { +func (sw *MultiplexSwitch) StopPeerForError(peer Peer, err error) { sw.Logger.Error("Stopping peer for error", "peer", peer, "err", err) sw.stopAndRemovePeer(peer, err) @@ -206,7 +206,7 @@ func (sw *Switch) StopPeerForError(peer Peer, err error) { sw.DialPeers(addr) } -func (sw *Switch) stopAndRemovePeer(peer Peer, err error) { +func (sw *MultiplexSwitch) stopAndRemovePeer(peer Peer, err error) { // Remove the peer from the transport sw.transport.Remove(peer) @@ -243,7 +243,7 @@ func (sw *Switch) stopAndRemovePeer(peer Peer, err error) { // --------------------------------------------------------------------- // Dialing -func (sw *Switch) runDialLoop(ctx context.Context) { +func (sw *MultiplexSwitch) runDialLoop(ctx context.Context) { for { select { case <-ctx.Done(): @@ -318,7 +318,7 @@ func (sw *Switch) runDialLoop(ctx context.Context) { } // runRedialLoop starts the persistent peer redial loop -func (sw *Switch) runRedialLoop(ctx context.Context) { +func (sw *MultiplexSwitch) runRedialLoop(ctx context.Context) { // Set up the event subscription for persistent peer disconnects subCh, unsubFn := sw.Subscribe(func(event events.Event) bool { // Make sure the peer event relates to a peer disconnect @@ -352,8 +352,8 @@ func (sw *Switch) runRedialLoop(ctx context.Context) { } // DialPeers adds the peers to the dial queue for async dialing. -// To monitor dial progress, subscribe to adequate p2p Switch events -func (sw *Switch) DialPeers(peerAddrs ...*types.NetAddress) { +// To monitor dial progress, subscribe to adequate p2p MultiplexSwitch events +func (sw *MultiplexSwitch) DialPeers(peerAddrs ...*types.NetAddress) { for _, peerAddr := range peerAddrs { // Check if this is our address if peerAddr.Same(sw.transport.NetAddress()) { @@ -382,7 +382,7 @@ func (sw *Switch) DialPeers(peerAddrs ...*types.NetAddress) { // isPersistentPeer returns a flag indicating if a peer // is present in the persistent peer set -func (sw *Switch) isPersistentPeer(id types.ID) bool { +func (sw *MultiplexSwitch) isPersistentPeer(id types.ID) bool { _, persistent := sw.persistentPeers.Load(id) return persistent @@ -391,7 +391,7 @@ func (sw *Switch) isPersistentPeer(id types.ID) bool { // runAcceptLoop is the main powerhouse method // for accepting incoming peer connections, filtering them, // and persisting them -func (sw *Switch) runAcceptLoop(ctx context.Context) { +func (sw *MultiplexSwitch) runAcceptLoop(ctx context.Context) { for { select { case <-ctx.Done(): @@ -441,9 +441,9 @@ func (sw *Switch) runAcceptLoop(ctx context.Context) { } } -// addPeer starts up the Peer and adds it to the Switch. Error is returned if +// addPeer starts up the Peer and adds it to the MultiplexSwitch. Error is returned if // the peer is filtered out or failed to start or can't be added. -func (sw *Switch) addPeer(p Peer) error { +func (sw *MultiplexSwitch) addPeer(p Peer) error { p.SetLogger(sw.Logger.With("peer", p.SocketAddr())) // Handle the shut down case where the switch has stopped, but we're @@ -484,7 +484,7 @@ func (sw *Switch) addPeer(p Peer) error { // logTelemetry logs the switch telemetry data // to global metrics funnels -func (sw *Switch) logTelemetry() { +func (sw *MultiplexSwitch) logTelemetry() { // Update the telemetry data if !telemetry.MetricsEnabled() { return diff --git a/tm2/pkg/p2p/switch_option.go b/tm2/pkg/p2p/switch_option.go index 6b94b6a632a..54356d83c1b 100644 --- a/tm2/pkg/p2p/switch_option.go +++ b/tm2/pkg/p2p/switch_option.go @@ -6,12 +6,12 @@ import ( "github.com/gnolang/gno/tm2/pkg/p2p/types" ) -// SwitchOption is a callback used for configuring the p2p Switch -type SwitchOption func(*Switch) +// SwitchOption is a callback used for configuring the p2p MultiplexSwitch +type SwitchOption func(*MultiplexSwitch) // WithReactor sets the p2p switch reactors func WithReactor(name string, reactor Reactor) SwitchOption { - return func(sw *Switch) { + return func(sw *MultiplexSwitch) { for _, chDesc := range reactor.GetChannels() { chID := chDesc.ID @@ -39,7 +39,7 @@ func WithReactor(name string, reactor Reactor) SwitchOption { // WithPersistentPeers sets the p2p switch's persistent peer set func WithPersistentPeers(peerAddrs []*types.NetAddress) SwitchOption { - return func(sw *Switch) { + return func(sw *MultiplexSwitch) { for _, addr := range peerAddrs { sw.persistentPeers.Store(addr.ID, addr) } @@ -48,14 +48,14 @@ func WithPersistentPeers(peerAddrs []*types.NetAddress) SwitchOption { // WithMaxInboundPeers sets the p2p switch's maximum inbound peer limit func WithMaxInboundPeers(maxInbound uint64) SwitchOption { - return func(sw *Switch) { + return func(sw *MultiplexSwitch) { sw.maxInboundPeers = maxInbound } } // WithMaxOutboundPeers sets the p2p switch's maximum outbound peer limit func WithMaxOutboundPeers(maxOutbound uint64) SwitchOption { - return func(sw *Switch) { + return func(sw *MultiplexSwitch) { sw.maxOutboundPeers = maxOutbound } } diff --git a/tm2/pkg/p2p/switch_test.go b/tm2/pkg/p2p/switch_test.go index 696334d2ae4..81cb0f83845 100644 --- a/tm2/pkg/p2p/switch_test.go +++ b/tm2/pkg/p2p/switch_test.go @@ -63,13 +63,13 @@ package p2p // // // convenience method for creating two switches connected to each other. // // XXX: note this uses net.Pipe and not a proper TCP conn -// func MakeSwitchPair(_ testing.TB, initSwitch func(int, *Switch) *Switch) (*Switch, *Switch) { +// func MakeSwitchPair(_ testing.TB, initSwitch func(int, *MultiplexSwitch) *MultiplexSwitch) (*MultiplexSwitch, *MultiplexSwitch) { // // Create two switches that will be interconnected. // switches := MakeConnectedSwitches(cfg, 2, initSwitch, Connect2Switches) // return switches[0], switches[1] // } // -// func initSwitchFunc(i int, sw *Switch) *Switch { +// func initSwitchFunc(i int, sw *MultiplexSwitch) *MultiplexSwitch { // // Make two reactors of two channels each // sw.AddReactor("foo", NewTestReactor([]*conn.ChannelDescriptor{ // {ID: byte(0x00), Priority: 10}, @@ -282,7 +282,7 @@ package p2p // } // } // -// func assertNoPeersAfterTimeout(t *testing.T, sw *Switch, timeout time.Duration) { +// func assertNoPeersAfterTimeout(t *testing.T, sw *MultiplexSwitch, timeout time.Duration) { // t.Helper() // // time.Sleep(timeout) @@ -332,7 +332,7 @@ package p2p // t.Parallel() // // // make two connected switches -// sw1, sw2 := MakeSwitchPair(t, func(i int, sw *Switch) *Switch { +// sw1, sw2 := MakeSwitchPair(t, func(i int, sw *MultiplexSwitch) *MultiplexSwitch { // return initSwitchFunc(i, sw) // }) // @@ -448,7 +448,7 @@ package p2p // require.NotNil(t, sw.Peers().Get(rp.ID())) // } // -// func waitUntilSwitchHasAtLeastNPeers(sw *Switch, n int) { +// func waitUntilSwitchHasAtLeastNPeers(sw *MultiplexSwitch, n int) { // for i := 0; i < 20; i++ { // time.Sleep(250 * time.Millisecond) // has := sw.Peers().Size() @@ -613,7 +613,7 @@ package p2p // reactor.BaseReactor = NewBaseReactor("mockReactor", reactor) // // // make switch -// sw := MakeSwitch(cfg, 1, "testing", "123.123.123", func(i int, sw *Switch) *Switch { +// sw := MakeSwitch(cfg, 1, "testing", "123.123.123", func(i int, sw *MultiplexSwitch) *MultiplexSwitch { // sw.AddReactor("mock", reactor) // return sw // }) @@ -644,7 +644,7 @@ package p2p // } // // func BenchmarkSwitchBroadcast(b *testing.B) { -// s1, s2 := MakeSwitchPair(b, func(i int, sw *Switch) *Switch { +// s1, s2 := MakeSwitchPair(b, func(i int, sw *MultiplexSwitch) *MultiplexSwitch { // // Make bar reactors of bar channels each // sw.AddReactor("foo", NewTestReactor([]*conn.ChannelDescriptor{ // {ID: byte(0x00), Priority: 10}, diff --git a/tm2/pkg/p2p/types.go b/tm2/pkg/p2p/types.go index 30400afed9f..209df4d1ff3 100644 --- a/tm2/pkg/p2p/types.go +++ b/tm2/pkg/p2p/types.go @@ -5,6 +5,7 @@ import ( "net" "github.com/gnolang/gno/tm2/pkg/p2p/conn" + "github.com/gnolang/gno/tm2/pkg/p2p/events" "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/gnolang/gno/tm2/pkg/service" ) @@ -72,7 +73,26 @@ type Transport interface { Remove(Peer) } -// PeerBehavior wraps the Reactor and Switch information a Transport would need when +// Switch is the abstraction in the p2p module that handles +// and manages peer connections thorough the transport +type Switch interface { + // Subscribe subscribes to peer events on the switch + Subscribe(filterFn events.EventFilter) (<-chan events.Event, func()) + + // Broadcast publishes data on the given channel, to all peers + Broadcast(chID byte, data []byte) + + // Peers returns the latest peer set + Peers() PeerSet + + // StopPeerForError stops the peer with the given reason + StopPeerForError(peer Peer, err error) + + // DialPeers marks the given peers as ready for async dialing + DialPeers(peerAddrs ...*types.NetAddress) +} + +// PeerBehavior wraps the Reactor and MultiplexSwitch information a Transport would need when // dialing or accepting new Peer connections. // It is worth noting that the only reason why this information is required in the first place, // is because Peers expose an API through which different TM modules can interact with them. From 8af018a79122fff684f5a94929554a8418c1a978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Wed, 23 Oct 2024 14:48:11 -0500 Subject: [PATCH 22/43] Add coverage for discovery --- tm2/pkg/p2p/discovery/discovery.go | 34 ++- tm2/pkg/p2p/discovery/discovery_test.go | 366 ++++++++++++++++++++++++ tm2/pkg/p2p/discovery/mock_test.go | 153 ++++++++++ tm2/pkg/p2p/discovery/option.go | 12 + tm2/pkg/p2p/discovery/package.go | 17 +- tm2/pkg/p2p/mock/peer.go | 310 ++++++++++++++++++++ tm2/pkg/p2p/mock_test.go | 310 -------------------- tm2/pkg/p2p/peer_test.go | 57 ++-- tm2/pkg/p2p/set_test.go | 11 +- tm2/pkg/p2p/types.go | 2 +- 10 files changed, 898 insertions(+), 374 deletions(-) create mode 100644 tm2/pkg/p2p/discovery/discovery_test.go create mode 100644 tm2/pkg/p2p/discovery/mock_test.go create mode 100644 tm2/pkg/p2p/discovery/option.go create mode 100644 tm2/pkg/p2p/mock/peer.go delete mode 100644 tm2/pkg/p2p/mock_test.go diff --git a/tm2/pkg/p2p/discovery/discovery.go b/tm2/pkg/p2p/discovery/discovery.go index eb2d0d55b63..724478252a7 100644 --- a/tm2/pkg/p2p/discovery/discovery.go +++ b/tm2/pkg/p2p/discovery/discovery.go @@ -45,22 +45,34 @@ type Reactor struct { ctx context.Context cancelFn context.CancelFunc + + discoveryInterval time.Duration } // NewReactor creates a new peer discovery reactor -func NewReactor() *Reactor { +func NewReactor(opts ...Option) *Reactor { ctx, cancelFn := context.WithCancel(context.Background()) - return &Reactor{ - ctx: ctx, - cancelFn: cancelFn, + r := &Reactor{ + ctx: ctx, + cancelFn: cancelFn, + discoveryInterval: discoveryInterval, + } + + r.BaseReactor = *p2p.NewBaseReactor("Reactor", r) + + // Apply the options + for _, opt := range opts { + opt(r) } + + return r } // StartDiscovery runs the peer discovery protocol func (r *Reactor) StartDiscovery() { go func() { - ticker := time.NewTicker(discoveryInterval) + ticker := time.NewTicker(r.discoveryInterval) defer ticker.Stop() for { @@ -77,18 +89,10 @@ func (r *Reactor) StartDiscovery() { peers := r.Switch.Peers().List() // Generate a random peer index - randomPeer, err := rand.Int( + randomPeer, _ := rand.Int( rand.Reader, big.NewInt(int64(len(peers))), ) - if err != nil { - r.Logger.Error( - "unable to generate random peer index", - "err", err, - ) - - return - } // Request peers, async go r.requestPeers(peers[randomPeer.Int64()]) @@ -140,7 +144,7 @@ func (r *Reactor) Receive(chID byte, peer p2p.Peer, msgBytes []byte) { // Unmarshal the message var msg Message - if err := amino.Unmarshal(msgBytes, &msg); err != nil { + if err := amino.UnmarshalAny(msgBytes, &msg); err != nil { r.Logger.Error("unable to unmarshal discovery message", "err", err) return diff --git a/tm2/pkg/p2p/discovery/discovery_test.go b/tm2/pkg/p2p/discovery/discovery_test.go new file mode 100644 index 00000000000..dba2f2e719c --- /dev/null +++ b/tm2/pkg/p2p/discovery/discovery_test.go @@ -0,0 +1,366 @@ +package discovery + +import ( + "net" + "slices" + "testing" + "time" + + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/p2p" + "github.com/gnolang/gno/tm2/pkg/p2p/mock" + "github.com/gnolang/gno/tm2/pkg/p2p/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// generatePeers generates random peers +func generatePeers(t *testing.T, count int) []p2p.Peer { + peers := make([]p2p.Peer, count) + + for i := range count { + var ( + key = types.GenerateNodeKey() + address = "127.0.0.1:8080" + ) + + tcpAddr, err := net.ResolveTCPAddr("tcp", address) + require.NoError(t, err) + + addr, err := types.NewNetAddress(key.ID(), tcpAddr) + require.NoError(t, err) + + peers[i] = &mock.Peer{ + IDFn: func() types.ID { + return key.ID() + }, + NodeInfoFn: func() types.NodeInfo { + return types.NodeInfo{ + NetAddress: addr, + } + }, + } + } + + return peers +} + +func TestReactor_DiscoveryRequest(t *testing.T) { + t.Parallel() + + var ( + notifCh = make(chan struct{}, 1) + + capturedSend []byte + + mockPeer = &mock.Peer{ + SendFn: func(chID byte, data []byte) bool { + require.Equal(t, Channel, chID) + + capturedSend = data + + notifCh <- struct{}{} + + return true + }, + } + + ps = &mockPeerSet{ + listFn: func() []p2p.Peer { + return []p2p.Peer{mockPeer} + }, + } + + mockSwitch = &mockSwitch{ + peersFn: func() p2p.PeerSet { + return ps + }, + } + ) + + r := NewReactor( + WithDiscoveryInterval(10 * time.Millisecond), + ) + + // Set the mock switch + r.SetSwitch(mockSwitch) + + // Start the discovery service + r.StartDiscovery() + defer r.StopDiscovery() + + select { + case <-notifCh: + case <-time.After(5 * time.Second): + } + + // Make sure the adequate message was captured + require.NotNil(t, capturedSend) + + // Parse the message + var msg Message + + require.NoError(t, amino.Unmarshal(capturedSend, &msg)) + + // Make sure the base message is valid + require.NoError(t, msg.ValidateBasic()) + + _, ok := msg.(*Request) + + require.True(t, ok) +} + +func TestReactor_DiscoveryResponse(t *testing.T) { + t.Parallel() + + t.Run("discovery request received", func(t *testing.T) { + t.Parallel() + + var ( + peers = generatePeers(t, 50) + notifCh = make(chan struct{}, 1) + + capturedSend []byte + + mockPeer = &mock.Peer{ + SendFn: func(chID byte, data []byte) bool { + require.Equal(t, Channel, chID) + + capturedSend = data + + notifCh <- struct{}{} + + return true + }, + } + + ps = &mockPeerSet{ + listFn: func() []p2p.Peer { + return peers + }, + sizeFn: func() int { + return len(peers) + }, + } + + mockSwitch = &mockSwitch{ + peersFn: func() p2p.PeerSet { + return ps + }, + } + ) + + r := NewReactor( + WithDiscoveryInterval(10 * time.Millisecond), + ) + + // Set the mock switch + r.SetSwitch(mockSwitch) + + // Prepare the message + req := &Request{} + + preparedReq, err := amino.MarshalAny(req) + require.NoError(t, err) + + // Receive the message + r.Receive(Channel, mockPeer, preparedReq) + + select { + case <-notifCh: + case <-time.After(5 * time.Second): + } + + // Make sure the adequate message was captured + require.NotNil(t, capturedSend) + + // Parse the message + var msg Message + + require.NoError(t, amino.Unmarshal(capturedSend, &msg)) + + // Make sure the base message is valid + require.NoError(t, msg.ValidateBasic()) + + resp, ok := msg.(*Response) + require.True(t, ok) + + // Make sure the peers are valid + require.Len(t, resp.Peers, maxPeersShared) + + slices.ContainsFunc(resp.Peers, func(addr *types.NetAddress) bool { + for _, localP := range peers { + if localP.NodeInfo().NetAddress.Equals(*addr) { + return true + } + } + + return false + }) + }) + + t.Run("empty peers on discover", func(t *testing.T) { + t.Parallel() + + var ( + capturedSend []byte + + mockPeer = &mock.Peer{ + SendFn: func(chID byte, data []byte) bool { + require.Equal(t, Channel, chID) + + capturedSend = data + + return true + }, + } + + ps = &mockPeerSet{ + listFn: func() []p2p.Peer { + return make([]p2p.Peer, 0) + }, + sizeFn: func() int { + return 0 + }, + } + + mockSwitch = &mockSwitch{ + peersFn: func() p2p.PeerSet { + return ps + }, + } + ) + + r := NewReactor( + WithDiscoveryInterval(10 * time.Millisecond), + ) + + // Set the mock switch + r.SetSwitch(mockSwitch) + + // Prepare the message + req := &Request{} + + preparedReq, err := amino.MarshalAny(req) + require.NoError(t, err) + + // Receive the message + r.Receive(Channel, mockPeer, preparedReq) + + // Make sure no message was captured + assert.Nil(t, capturedSend) + }) + + t.Run("peer response received", func(t *testing.T) { + t.Parallel() + + var ( + peers = generatePeers(t, 50) + notifCh = make(chan struct{}, 1) + + capturedDials []*types.NetAddress + + ps = &mockPeerSet{ + listFn: func() []p2p.Peer { + return peers + }, + sizeFn: func() int { + return len(peers) + }, + } + + mockSwitch = &mockSwitch{ + peersFn: func() p2p.PeerSet { + return ps + }, + dialPeersFn: func(addresses ...*types.NetAddress) { + capturedDials = append(capturedDials, addresses...) + + notifCh <- struct{}{} + }, + } + ) + + r := NewReactor( + WithDiscoveryInterval(10 * time.Millisecond), + ) + + // Set the mock switch + r.SetSwitch(mockSwitch) + + // Prepare the addresses + peerAddrs := make([]*types.NetAddress, 0, len(peers)) + + for _, p := range peers { + peerAddrs = append(peerAddrs, p.NodeInfo().NetAddress) + } + + // Prepare the message + req := &Response{ + Peers: peerAddrs, + } + + preparedReq, err := amino.MarshalAny(req) + require.NoError(t, err) + + // Receive the message + r.Receive(Channel, &mock.Peer{}, preparedReq) + + select { + case <-notifCh: + case <-time.After(5 * time.Second): + } + + // Make sure the correct peers were dialed + assert.Equal(t, capturedDials, peerAddrs) + }) + + t.Run("invalid peer response received", func(t *testing.T) { + t.Parallel() + + var ( + peers = generatePeers(t, 50) + + capturedDials []*types.NetAddress + + ps = &mockPeerSet{ + listFn: func() []p2p.Peer { + return peers + }, + sizeFn: func() int { + return len(peers) + }, + } + + mockSwitch = &mockSwitch{ + peersFn: func() p2p.PeerSet { + return ps + }, + dialPeersFn: func(addresses ...*types.NetAddress) { + capturedDials = append(capturedDials, addresses...) + }, + } + ) + + r := NewReactor( + WithDiscoveryInterval(10 * time.Millisecond), + ) + + // Set the mock switch + r.SetSwitch(mockSwitch) + + // Prepare the message + req := &Response{ + Peers: make([]*types.NetAddress, 0), // empty + } + + preparedReq, err := amino.MarshalAny(req) + require.NoError(t, err) + + // Receive the message + r.Receive(Channel, &mock.Peer{}, preparedReq) + + // Make sure no peers were dialed + assert.Empty(t, capturedDials) + }) +} diff --git a/tm2/pkg/p2p/discovery/mock_test.go b/tm2/pkg/p2p/discovery/mock_test.go new file mode 100644 index 00000000000..1a369667c73 --- /dev/null +++ b/tm2/pkg/p2p/discovery/mock_test.go @@ -0,0 +1,153 @@ +package discovery + +import ( + "net" + + "github.com/gnolang/gno/tm2/pkg/p2p" + "github.com/gnolang/gno/tm2/pkg/p2p/events" + "github.com/gnolang/gno/tm2/pkg/p2p/types" +) + +type ( + subscribeDelegate func(events.EventFilter) (<-chan events.Event, func()) + broadcastDelegate func(byte, []byte) + peersDelegate func() p2p.PeerSet + stopPeerForErrorDelegate func(p2p.Peer, error) + dialPeersDelegate func(...*types.NetAddress) +) + +type mockSwitch struct { + subscribeFn subscribeDelegate + broadcastFn broadcastDelegate + peersFn peersDelegate + stopPeerForErrorFn stopPeerForErrorDelegate + dialPeersFn dialPeersDelegate +} + +func (m *mockSwitch) Subscribe(filterFn events.EventFilter) (<-chan events.Event, func()) { + if m.subscribeFn != nil { + return m.subscribeFn(filterFn) + } + + return nil, nil +} + +func (m *mockSwitch) Broadcast(chID byte, data []byte) { + if m.broadcastFn != nil { + m.broadcastFn(chID, data) + } +} + +func (m *mockSwitch) Peers() p2p.PeerSet { + if m.peersFn != nil { + return m.peersFn() + } + + return nil +} + +func (m *mockSwitch) StopPeerForError(peer p2p.Peer, err error) { + if m.stopPeerForErrorFn != nil { + m.stopPeerForErrorFn(peer, err) + } +} + +func (m *mockSwitch) DialPeers(peerAddrs ...*types.NetAddress) { + if m.dialPeersFn != nil { + m.dialPeersFn(peerAddrs...) + } +} + +type ( + addDelegate func(p2p.Peer) + removeDelegate func(types.ID) bool + hasDelegate func(types.ID) bool + hasIPDelegate func(net.IP) bool + getPeerDelegate func(types.ID) p2p.Peer + listDelegate func() []p2p.Peer + sizeDelegate func() int + numInboundDelegate func() uint64 + numOutboundDelegate func() uint64 +) + +type mockPeerSet struct { + addFn addDelegate + removeFn removeDelegate + hasFn hasDelegate + hasIPFn hasIPDelegate + getFn getPeerDelegate + listFn listDelegate + sizeFn sizeDelegate + numInboundFn numInboundDelegate + numOutboundFn numOutboundDelegate +} + +func (m *mockPeerSet) Add(peer p2p.Peer) { + if m.addFn != nil { + m.addFn(peer) + } +} + +func (m *mockPeerSet) Remove(key types.ID) bool { + if m.removeFn != nil { + m.removeFn(key) + } + + return false +} + +func (m *mockPeerSet) Has(key types.ID) bool { + if m.hasFn != nil { + return m.hasFn(key) + } + + return false +} + +func (m *mockPeerSet) HasIP(ip net.IP) bool { + if m.hasIPFn != nil { + return m.hasIPFn(ip) + } + + return false +} + +func (m *mockPeerSet) Get(key types.ID) p2p.Peer { + if m.getFn != nil { + return m.getFn(key) + } + + return nil +} + +func (m *mockPeerSet) List() []p2p.Peer { + if m.listFn != nil { + return m.listFn() + } + + return nil +} + +func (m *mockPeerSet) Size() int { + if m.sizeFn != nil { + return m.sizeFn() + } + + return 0 +} + +func (m *mockPeerSet) NumInbound() uint64 { + if m.numInboundFn != nil { + return m.numInboundFn() + } + + return 0 +} + +func (m *mockPeerSet) NumOutbound() uint64 { + if m.numOutboundFn != nil { + return m.numOutboundFn() + } + + return 0 +} diff --git a/tm2/pkg/p2p/discovery/option.go b/tm2/pkg/p2p/discovery/option.go new file mode 100644 index 00000000000..dc0fb95b109 --- /dev/null +++ b/tm2/pkg/p2p/discovery/option.go @@ -0,0 +1,12 @@ +package discovery + +import "time" + +type Option func(*Reactor) + +// WithDiscoveryInterval sets the discovery crawl interval +func WithDiscoveryInterval(interval time.Duration) Option { + return func(r *Reactor) { + r.discoveryInterval = interval + } +} diff --git a/tm2/pkg/p2p/discovery/package.go b/tm2/pkg/p2p/discovery/package.go index 3cdeac8d76b..a3865fdf5d2 100644 --- a/tm2/pkg/p2p/discovery/package.go +++ b/tm2/pkg/p2p/discovery/package.go @@ -1,10 +1,7 @@ package discovery import ( - "reflect" - "github.com/gnolang/gno/tm2/pkg/amino" - "github.com/gnolang/gno/tm2/pkg/amino/pkg" ) var Package = amino.RegisterPackage(amino.NewPackage( @@ -12,18 +9,8 @@ var Package = amino.RegisterPackage(amino.NewPackage( "p2p", amino.GetCallersDirname(), ). - WithDependencies( - // NA - ). WithTypes( - // NOTE: Keep the names short. - pkg.Type{ - Type: reflect.TypeOf(Request{}), - Name: "Request", - }, - pkg.Type{ - Type: reflect.TypeOf(Response{}), - Name: "Response", - }, + &Request{}, + &Response{}, ), ) diff --git a/tm2/pkg/p2p/mock/peer.go b/tm2/pkg/p2p/mock/peer.go new file mode 100644 index 00000000000..8734f1ae2e2 --- /dev/null +++ b/tm2/pkg/p2p/mock/peer.go @@ -0,0 +1,310 @@ +package mock + +import ( + "log/slog" + "net" + "time" + + "github.com/gnolang/gno/tm2/pkg/p2p/conn" + "github.com/gnolang/gno/tm2/pkg/p2p/types" + "github.com/gnolang/gno/tm2/pkg/service" +) + +type ( + flushStopDelegate func() + idDelegate func() types.ID + remoteIPDelegate func() net.IP + remoteAddrDelegate func() net.Addr + isOutboundDelegate func() bool + isPersistentDelegate func() bool + closeConnDelegate func() error + nodeInfoDelegate func() types.NodeInfo + statusDelegate func() conn.ConnectionStatus + socketAddrDelegate func() *types.NetAddress + sendDelegate func(byte, []byte) bool + trySendDelegate func(byte, []byte) bool + setDelegate func(string, any) + getDelegate func(string) any +) + +type Peer struct { + service.BaseService + + FlushStopFn flushStopDelegate + IDFn idDelegate + RemoteIPFn remoteIPDelegate + RemoteAddrFn remoteAddrDelegate + IsOutboundFn isOutboundDelegate + IsPersistentFn isPersistentDelegate + CloseConnFn closeConnDelegate + NodeInfoFn nodeInfoDelegate + StatusFn statusDelegate + SocketAddrFn socketAddrDelegate + SendFn sendDelegate + TrySendFn trySendDelegate + SetFn setDelegate + GetFn getDelegate +} + +func (m *Peer) FlushStop() { + if m.FlushStopFn != nil { + m.FlushStopFn() + } +} + +func (m *Peer) ID() types.ID { + if m.IDFn != nil { + return m.IDFn() + } + + return "" +} + +func (m *Peer) RemoteIP() net.IP { + if m.RemoteIPFn != nil { + return m.RemoteIPFn() + } + + return nil +} + +func (m *Peer) RemoteAddr() net.Addr { + if m.RemoteAddrFn != nil { + return m.RemoteAddrFn() + } + + return nil +} + +func (m *Peer) IsOutbound() bool { + if m.IsOutboundFn != nil { + return m.IsOutboundFn() + } + + return false +} + +func (m *Peer) IsPersistent() bool { + if m.IsPersistentFn != nil { + return m.IsPersistentFn() + } + + return false +} + +func (m *Peer) CloseConn() error { + if m.CloseConnFn != nil { + return m.CloseConnFn() + } + + return nil +} + +func (m *Peer) NodeInfo() types.NodeInfo { + if m.NodeInfoFn != nil { + return m.NodeInfoFn() + } + + return types.NodeInfo{} +} + +func (m *Peer) Status() conn.ConnectionStatus { + if m.StatusFn != nil { + return m.StatusFn() + } + + return conn.ConnectionStatus{} +} + +func (m *Peer) SocketAddr() *types.NetAddress { + if m.SocketAddrFn != nil { + return m.SocketAddrFn() + } + + return nil +} + +func (m *Peer) Send(classifier byte, data []byte) bool { + if m.SendFn != nil { + return m.SendFn(classifier, data) + } + + return false +} + +func (m *Peer) TrySend(classifier byte, data []byte) bool { + if m.TrySendFn != nil { + return m.TrySendFn(classifier, data) + } + + return false +} + +func (m *Peer) Set(key string, data any) { + if m.SetFn != nil { + m.SetFn(key, data) + } +} + +func (m *Peer) Get(key string) any { + if m.GetFn != nil { + return m.GetFn(key) + } + + return nil +} + +type ( + readDelegate func([]byte) (int, error) + writeDelegate func([]byte) (int, error) + closeDelegate func() error + localAddrDelegate func() net.Addr + setDeadlineDelegate func(time.Time) error +) + +type MockConn struct { + ReadFn readDelegate + WriteFn writeDelegate + CloseFn closeDelegate + LocalAddrFn localAddrDelegate + RemoteAddrFn remoteAddrDelegate + SetDeadlineFn setDeadlineDelegate + SetReadDeadlineFn setDeadlineDelegate + SetWriteDeadlineFn setDeadlineDelegate +} + +func (m *MockConn) Read(b []byte) (int, error) { + if m.ReadFn != nil { + return m.ReadFn(b) + } + + return 0, nil +} + +func (m *MockConn) Write(b []byte) (int, error) { + if m.WriteFn != nil { + return m.WriteFn(b) + } + + return 0, nil +} + +func (m *MockConn) Close() error { + if m.CloseFn != nil { + return m.CloseFn() + } + + return nil +} + +func (m *MockConn) LocalAddr() net.Addr { + if m.LocalAddrFn != nil { + return m.LocalAddrFn() + } + + return nil +} + +func (m *MockConn) RemoteAddr() net.Addr { + if m.RemoteAddrFn != nil { + return m.RemoteAddrFn() + } + + return nil +} + +func (m *MockConn) SetDeadline(t time.Time) error { + if m.SetDeadlineFn != nil { + return m.SetDeadlineFn(t) + } + + return nil +} + +func (m *MockConn) SetReadDeadline(t time.Time) error { + if m.SetReadDeadlineFn != nil { + return m.SetReadDeadlineFn(t) + } + + return nil +} + +func (m *MockConn) SetWriteDeadline(t time.Time) error { + if m.SetWriteDeadlineFn != nil { + return m.SetWriteDeadlineFn(t) + } + + return nil +} + +type ( + startDelegate func() error + stopDelegate func() error + stringDelegate func() string +) + +type MockMConn struct { + FlushFn flushStopDelegate + StartFn startDelegate + StopFn stopDelegate + SendFn sendDelegate + TrySendFn trySendDelegate + StatusFn statusDelegate + StringFn stringDelegate +} + +func (m *MockMConn) FlushStop() { + if m.FlushFn != nil { + m.FlushFn() + } +} + +func (m *MockMConn) Start() error { + if m.StartFn != nil { + return m.StartFn() + } + + return nil +} + +func (m *MockMConn) Stop() error { + if m.StopFn != nil { + return m.StopFn() + } + + return nil +} + +func (m *MockMConn) Send(ch byte, data []byte) bool { + if m.SendFn != nil { + return m.SendFn(ch, data) + } + + return false +} + +func (m *MockMConn) TrySend(ch byte, data []byte) bool { + if m.TrySendFn != nil { + return m.TrySendFn(ch, data) + } + + return false +} + +func (m *MockMConn) SetLogger(_ *slog.Logger) {} + +func (m *MockMConn) Status() conn.ConnectionStatus { + if m.StatusFn != nil { + return m.StatusFn() + } + + return conn.ConnectionStatus{} +} + +func (m *MockMConn) String() string { + if m.StringFn != nil { + return m.StringFn() + } + + return "" +} diff --git a/tm2/pkg/p2p/mock_test.go b/tm2/pkg/p2p/mock_test.go deleted file mode 100644 index 376108a692a..00000000000 --- a/tm2/pkg/p2p/mock_test.go +++ /dev/null @@ -1,310 +0,0 @@ -package p2p - -import ( - "log/slog" - "net" - "time" - - "github.com/gnolang/gno/tm2/pkg/p2p/conn" - "github.com/gnolang/gno/tm2/pkg/p2p/types" - "github.com/gnolang/gno/tm2/pkg/service" -) - -type ( - flushStopDelegate func() - idDelegate func() types.ID - remoteIPDelegate func() net.IP - remoteAddrDelegate func() net.Addr - isOutboundDelegate func() bool - isPersistentDelegate func() bool - closeConnDelegate func() error - nodeInfoDelegate func() types.NodeInfo - statusDelegate func() conn.ConnectionStatus - socketAddrDelegate func() *types.NetAddress - sendDelegate func(byte, []byte) bool - trySendDelegate func(byte, []byte) bool - setDelegate func(string, any) - getDelegate func(string) any -) - -type mockPeer struct { - service.BaseService - - flushStopFn flushStopDelegate - idFn idDelegate - remoteIPFn remoteIPDelegate - remoteAddrFn remoteAddrDelegate - isOutboundFn isOutboundDelegate - isPersistentFn isPersistentDelegate - closeConnFn closeConnDelegate - nodeInfoFn nodeInfoDelegate - statusFn statusDelegate - socketAddrFn socketAddrDelegate - sendFn sendDelegate - trySendFn trySendDelegate - setFn setDelegate - getFn getDelegate -} - -func (m *mockPeer) FlushStop() { - if m.flushStopFn != nil { - m.flushStopFn() - } -} - -func (m *mockPeer) ID() types.ID { - if m.idFn != nil { - return m.idFn() - } - - return "" -} - -func (m *mockPeer) RemoteIP() net.IP { - if m.remoteIPFn != nil { - return m.remoteIPFn() - } - - return nil -} - -func (m *mockPeer) RemoteAddr() net.Addr { - if m.remoteAddrFn != nil { - return m.remoteAddrFn() - } - - return nil -} - -func (m *mockPeer) IsOutbound() bool { - if m.isOutboundFn != nil { - return m.isOutboundFn() - } - - return false -} - -func (m *mockPeer) IsPersistent() bool { - if m.isPersistentFn != nil { - return m.isPersistentFn() - } - - return false -} - -func (m *mockPeer) CloseConn() error { - if m.closeConnFn != nil { - return m.closeConnFn() - } - - return nil -} - -func (m *mockPeer) NodeInfo() types.NodeInfo { - if m.nodeInfoFn != nil { - return m.nodeInfoFn() - } - - return types.NodeInfo{} -} - -func (m *mockPeer) Status() conn.ConnectionStatus { - if m.statusFn != nil { - return m.statusFn() - } - - return conn.ConnectionStatus{} -} - -func (m *mockPeer) SocketAddr() *types.NetAddress { - if m.socketAddrFn != nil { - return m.socketAddrFn() - } - - return nil -} - -func (m *mockPeer) Send(classifier byte, data []byte) bool { - if m.sendFn != nil { - return m.sendFn(classifier, data) - } - - return false -} - -func (m *mockPeer) TrySend(classifier byte, data []byte) bool { - if m.trySendFn != nil { - return m.trySendFn(classifier, data) - } - - return false -} - -func (m *mockPeer) Set(key string, data any) { - if m.setFn != nil { - m.setFn(key, data) - } -} - -func (m *mockPeer) Get(key string) any { - if m.getFn != nil { - return m.getFn(key) - } - - return nil -} - -type ( - readDelegate func([]byte) (int, error) - writeDelegate func([]byte) (int, error) - closeDelegate func() error - localAddrDelegate func() net.Addr - setDeadlineDelegate func(time.Time) error -) - -type mockConn struct { - readFn readDelegate - writeFn writeDelegate - closeFn closeDelegate - localAddrFn localAddrDelegate - remoteAddrFn remoteAddrDelegate - setDeadlineFn setDeadlineDelegate - setReadDeadlineFn setDeadlineDelegate - setWriteDeadlineFn setDeadlineDelegate -} - -func (m *mockConn) Read(b []byte) (int, error) { - if m.readFn != nil { - return m.readFn(b) - } - - return 0, nil -} - -func (m *mockConn) Write(b []byte) (int, error) { - if m.writeFn != nil { - return m.writeFn(b) - } - - return 0, nil -} - -func (m *mockConn) Close() error { - if m.closeFn != nil { - return m.closeFn() - } - - return nil -} - -func (m *mockConn) LocalAddr() net.Addr { - if m.localAddrFn != nil { - return m.localAddrFn() - } - - return nil -} - -func (m *mockConn) RemoteAddr() net.Addr { - if m.remoteAddrFn != nil { - return m.remoteAddrFn() - } - - return nil -} - -func (m *mockConn) SetDeadline(t time.Time) error { - if m.setDeadlineFn != nil { - return m.setDeadlineFn(t) - } - - return nil -} - -func (m *mockConn) SetReadDeadline(t time.Time) error { - if m.setReadDeadlineFn != nil { - return m.setReadDeadlineFn(t) - } - - return nil -} - -func (m *mockConn) SetWriteDeadline(t time.Time) error { - if m.setWriteDeadlineFn != nil { - return m.setWriteDeadlineFn(t) - } - - return nil -} - -type ( - startDelegate func() error - stopDelegate func() error - stringDelegate func() string -) - -type mockMConn struct { - flushFn flushStopDelegate - startFn startDelegate - stopFn stopDelegate - sendFn sendDelegate - trySendFn trySendDelegate - statusFn statusDelegate - stringFn stringDelegate -} - -func (m *mockMConn) FlushStop() { - if m.flushFn != nil { - m.flushFn() - } -} - -func (m *mockMConn) Start() error { - if m.startFn != nil { - return m.startFn() - } - - return nil -} - -func (m *mockMConn) Stop() error { - if m.stopFn != nil { - return m.stopFn() - } - - return nil -} - -func (m *mockMConn) Send(ch byte, data []byte) bool { - if m.sendFn != nil { - return m.sendFn(ch, data) - } - - return false -} - -func (m *mockMConn) TrySend(ch byte, data []byte) bool { - if m.trySendFn != nil { - return m.trySendFn(ch, data) - } - - return false -} - -func (m *mockMConn) SetLogger(_ *slog.Logger) {} - -func (m *mockMConn) Status() conn.ConnectionStatus { - if m.statusFn != nil { - return m.statusFn() - } - - return conn.ConnectionStatus{} -} - -func (m *mockMConn) String() string { - if m.stringFn != nil { - return m.stringFn() - } - - return "" -} diff --git a/tm2/pkg/p2p/peer_test.go b/tm2/pkg/p2p/peer_test.go index 9b4ec2f8777..c3da539d4fa 100644 --- a/tm2/pkg/p2p/peer_test.go +++ b/tm2/pkg/p2p/peer_test.go @@ -12,6 +12,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/cmap" "github.com/gnolang/gno/tm2/pkg/p2p/config" "github.com/gnolang/gno/tm2/pkg/p2p/conn" + "github.com/gnolang/gno/tm2/pkg/p2p/mock" "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/gnolang/gno/tm2/pkg/service" "github.com/stretchr/testify/assert" @@ -48,8 +49,8 @@ func TestPeer_Properties(t *testing.T) { var ( info = &ConnInfo{ - Conn: &mockConn{ - remoteAddrFn: func() net.Addr { + Conn: &mock.MockConn{ + RemoteAddrFn: func() net.Addr { return tcpAddr }, }, @@ -92,7 +93,7 @@ func TestPeer_Properties(t *testing.T) { l = slog.New(slog.NewTextHandler(io.Discard, nil)) p = &peer{ - mConn: &mockMConn{}, + mConn: &mock.MockMConn{}, } ) @@ -107,8 +108,8 @@ func TestPeer_Properties(t *testing.T) { var ( expectedErr = errors.New("some error") - mConn = &mockMConn{ - startFn: func() error { + mConn = &mock.MockMConn{ + StartFn: func() error { return expectedErr }, } @@ -128,8 +129,8 @@ func TestPeer_Properties(t *testing.T) { stopCalled = false expectedErr = errors.New("some error") - mConn = &mockMConn{ - stopFn: func() error { + mConn = &mock.MockMConn{ + StopFn: func() error { stopCalled = true return expectedErr @@ -154,8 +155,8 @@ func TestPeer_Properties(t *testing.T) { var ( stopCalled = false - mConn = &mockMConn{ - flushFn: func() { + mConn = &mock.MockMConn{ + FlushFn: func() { stopCalled = true }, } @@ -196,8 +197,8 @@ func TestPeer_Properties(t *testing.T) { Duration: 5 * time.Second, } - mConn = &mockMConn{ - statusFn: func() conn.ConnectionStatus { + mConn = &mock.MockMConn{ + StatusFn: func() conn.ConnectionStatus { return status }, } @@ -236,8 +237,8 @@ func TestPeer_Properties(t *testing.T) { mConnStr = "description" p = &peer{ - mConn: &mockMConn{ - stringFn: func() string { + mConn: &mock.MockMConn{ + StringFn: func() string { return mConnStr }, }, @@ -305,8 +306,8 @@ func TestPeer_Properties(t *testing.T) { var ( closeErr = errors.New("close error") - mockConn = &mockConn{ - closeFn: func() error { + mockConn = &mock.MockConn{ + CloseFn: func() error { return closeErr }, } @@ -356,8 +357,8 @@ func TestPeer_Send(t *testing.T) { capturedSendID byte capturedSendData []byte - mockConn = &mockMConn{ - sendFn: func(c byte, d []byte) bool { + mockConn = &mock.MockMConn{ + SendFn: func(c byte, d []byte) bool { capturedSendID = c capturedSendData = d @@ -394,8 +395,8 @@ func TestPeer_Send(t *testing.T) { capturedSendID byte capturedSendData []byte - mockConn = &mockMConn{ - sendFn: func(c byte, d []byte) bool { + mockConn = &mock.MockMConn{ + SendFn: func(c byte, d []byte) bool { capturedSendID = c capturedSendData = d @@ -436,8 +437,8 @@ func TestPeer_Send(t *testing.T) { capturedSendID byte capturedSendData []byte - mockConn = &mockMConn{ - sendFn: func(c byte, d []byte) bool { + mockConn = &mock.MockMConn{ + SendFn: func(c byte, d []byte) bool { capturedSendID = c capturedSendData = d @@ -484,8 +485,8 @@ func TestPeer_TrySend(t *testing.T) { capturedSendID byte capturedSendData []byte - mockConn = &mockMConn{ - trySendFn: func(c byte, d []byte) bool { + mockConn = &mock.MockMConn{ + TrySendFn: func(c byte, d []byte) bool { capturedSendID = c capturedSendData = d @@ -522,8 +523,8 @@ func TestPeer_TrySend(t *testing.T) { capturedSendID byte capturedSendData []byte - mockConn = &mockMConn{ - trySendFn: func(c byte, d []byte) bool { + mockConn = &mock.MockMConn{ + TrySendFn: func(c byte, d []byte) bool { capturedSendID = c capturedSendData = d @@ -564,8 +565,8 @@ func TestPeer_TrySend(t *testing.T) { capturedSendID byte capturedSendData []byte - mockConn = &mockMConn{ - trySendFn: func(c byte, d []byte) bool { + mockConn = &mock.MockMConn{ + TrySendFn: func(c byte, d []byte) bool { capturedSendID = c capturedSendData = d @@ -612,7 +613,7 @@ func TestPeer_NewPeer(t *testing.T) { connInfo = &ConnInfo{ Outbound: false, Persistent: true, - Conn: &mockConn{}, + Conn: &mock.MockConn{}, RemoteIP: tcpAddr.IP, SocketAddr: netAddr, } diff --git a/tm2/pkg/p2p/set_test.go b/tm2/pkg/p2p/set_test.go index 047601c7eb5..00c18e90187 100644 --- a/tm2/pkg/p2p/set_test.go +++ b/tm2/pkg/p2p/set_test.go @@ -5,21 +5,22 @@ import ( "sort" "testing" + "github.com/gnolang/gno/tm2/pkg/p2p/mock" "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // generatePeers generates random node peers -func generatePeers(t *testing.T, count int) []*mockPeer { +func generatePeers(t *testing.T, count int) []*mock.Peer { t.Helper() - peers := make([]*mockPeer, count) + peers := make([]*mock.Peer, count) for i := 0; i < count; i++ { id := types.GenerateNodeKey().ID() - peers[i] = &mockPeer{ - idFn: func() types.ID { + peers[i] = &mock.Peer{ + IDFn: func() types.ID { return id }, } @@ -95,7 +96,7 @@ func TestSet_HasIP(t *testing.T) { ) // Make sure at least one peer has the set IP - peers[len(peers)/2].remoteIPFn = func() net.IP { + peers[len(peers)/2].RemoteIPFn = func() net.IP { return ip } diff --git a/tm2/pkg/p2p/types.go b/tm2/pkg/p2p/types.go index 209df4d1ff3..885bd3432cd 100644 --- a/tm2/pkg/p2p/types.go +++ b/tm2/pkg/p2p/types.go @@ -74,7 +74,7 @@ type Transport interface { } // Switch is the abstraction in the p2p module that handles -// and manages peer connections thorough the transport +// and manages peer connections thorough a Transport type Switch interface { // Subscribe subscribes to peer events on the switch Subscribe(filterFn events.EventFilter) (<-chan events.Event, func()) From 06433eca975df296525a8c1c4882367e5fe9bd73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Wed, 23 Oct 2024 15:20:57 -0500 Subject: [PATCH 23/43] Improve redial logic --- tm2/pkg/p2p/switch.go | 94 ++++++++++++++++++++++++++++++++----------- 1 file changed, 70 insertions(+), 24 deletions(-) diff --git a/tm2/pkg/p2p/switch.go b/tm2/pkg/p2p/switch.go index d786751d2f0..c51d1db313f 100644 --- a/tm2/pkg/p2p/switch.go +++ b/tm2/pkg/p2p/switch.go @@ -298,7 +298,6 @@ func (sw *MultiplexSwitch) runDialLoop(ctx context.Context) { sw.transport.Remove(p) if !p.IsRunning() { - // TODO check if this check is even required continue } @@ -319,36 +318,83 @@ func (sw *MultiplexSwitch) runDialLoop(ctx context.Context) { // runRedialLoop starts the persistent peer redial loop func (sw *MultiplexSwitch) runRedialLoop(ctx context.Context) { - // Set up the event subscription for persistent peer disconnects - subCh, unsubFn := sw.Subscribe(func(event events.Event) bool { - // Make sure the peer event relates to a peer disconnect - if event.Type() != events.PeerDisconnected { - return false - } + var wg sync.WaitGroup + + wg.Add(2) + + go func() { + defer wg.Done() + + ticker := time.NewTicker(time.Second * 5) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + sw.Logger.Debug("redial crawl context canceled") + + return + case <-ticker.C: + peers := sw.Peers() + + peersToDial := make([]*types.NetAddress, 0) - disconnectEv, ok := event.(*events.PeerDisconnectedEvent) - if !ok { - return false + sw.persistentPeers.Range(func(key, value any) bool { + var ( + id = key.(types.ID) + addr = value.(*types.NetAddress) + ) + + if !peers.Has(id) { + peersToDial = append(peersToDial, addr) + } + + return true + }) + } } + }() - return sw.isPersistentPeer(disconnectEv.PeerID) - }) - defer unsubFn() + go func() { + defer wg.Done() - for { - select { - case <-ctx.Done(): - sw.Logger.Debug("redial context canceled") + // Set up the event subscription for persistent peer disconnects + subCh, unsubFn := sw.Subscribe(func(event events.Event) bool { + // Make sure the peer event relates to a peer disconnect + if event.Type() != events.PeerDisconnected { + return false + } - return - case ev := <-subCh: - disconnectEv := ev.(*events.PeerDisconnectedEvent) + disconnectEv, ok := event.(*events.PeerDisconnectedEvent) + if !ok { + return false + } + + return sw.isPersistentPeer(disconnectEv.PeerID) + }) + defer unsubFn() + + for { + select { + case <-ctx.Done(): + sw.Logger.Debug("redial event context canceled") - // Dial the disconnected peer - // TODO add backoff mechanism - sw.DialPeers(&disconnectEv.Address) + return + case ev := <-subCh: + disconnectEv := ev.(*events.PeerDisconnectedEvent) + + // Dial the disconnected peer + item := dial.Item{ + Time: time.Now().Add(5 * time.Second), + Address: &disconnectEv.Address, + } + + sw.dialQueue.Push(item) + } } - } + }() + + wg.Wait() } // DialPeers adds the peers to the dial queue for async dialing. From 8e09e82b65a71c52705ae35cc3c1d4c0b6e3e5b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Wed, 23 Oct 2024 15:57:59 -0500 Subject: [PATCH 24/43] Simplify the persistent peer redial --- tm2/pkg/p2p/dial/dial.go | 15 ++++ tm2/pkg/p2p/discovery/mock_test.go | 11 --- tm2/pkg/p2p/events/doc.go | 3 - tm2/pkg/p2p/events/events.go | 104 --------------------------- tm2/pkg/p2p/events/events_test.go | 94 ------------------------ tm2/pkg/p2p/events/types.go | 37 ---------- tm2/pkg/p2p/switch.go | 111 +++++++++-------------------- tm2/pkg/p2p/types.go | 4 -- 8 files changed, 50 insertions(+), 329 deletions(-) delete mode 100644 tm2/pkg/p2p/events/doc.go delete mode 100644 tm2/pkg/p2p/events/events.go delete mode 100644 tm2/pkg/p2p/events/events_test.go delete mode 100644 tm2/pkg/p2p/events/types.go diff --git a/tm2/pkg/p2p/dial/dial.go b/tm2/pkg/p2p/dial/dial.go index 5dc7b8c9219..e4a7d6fd445 100644 --- a/tm2/pkg/p2p/dial/dial.go +++ b/tm2/pkg/p2p/dial/dial.go @@ -66,3 +66,18 @@ func (q *Queue) Pop() *Item { return q.items.PopFront() } + +// Has returns a flag indicating if the given +// address is in the dial queue +func (q *Queue) Has(addr *types.NetAddress) bool { + q.mux.RLock() + defer q.mux.RUnlock() + + for _, i := range q.items { + if addr.Equals(*i.Address) { + return true + } + } + + return false +} diff --git a/tm2/pkg/p2p/discovery/mock_test.go b/tm2/pkg/p2p/discovery/mock_test.go index 1a369667c73..48e0fb81e7e 100644 --- a/tm2/pkg/p2p/discovery/mock_test.go +++ b/tm2/pkg/p2p/discovery/mock_test.go @@ -4,12 +4,10 @@ import ( "net" "github.com/gnolang/gno/tm2/pkg/p2p" - "github.com/gnolang/gno/tm2/pkg/p2p/events" "github.com/gnolang/gno/tm2/pkg/p2p/types" ) type ( - subscribeDelegate func(events.EventFilter) (<-chan events.Event, func()) broadcastDelegate func(byte, []byte) peersDelegate func() p2p.PeerSet stopPeerForErrorDelegate func(p2p.Peer, error) @@ -17,21 +15,12 @@ type ( ) type mockSwitch struct { - subscribeFn subscribeDelegate broadcastFn broadcastDelegate peersFn peersDelegate stopPeerForErrorFn stopPeerForErrorDelegate dialPeersFn dialPeersDelegate } -func (m *mockSwitch) Subscribe(filterFn events.EventFilter) (<-chan events.Event, func()) { - if m.subscribeFn != nil { - return m.subscribeFn(filterFn) - } - - return nil, nil -} - func (m *mockSwitch) Broadcast(chID byte, data []byte) { if m.broadcastFn != nil { m.broadcastFn(chID, data) diff --git a/tm2/pkg/p2p/events/doc.go b/tm2/pkg/p2p/events/doc.go deleted file mode 100644 index a624102379e..00000000000 --- a/tm2/pkg/p2p/events/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -// Package events contains a simple p2p event system implementation, that simplifies asynchronous event flows in the -// p2p module. The event subscriptions allow for event filtering, which eases the load on the event notification flow. -package events diff --git a/tm2/pkg/p2p/events/events.go b/tm2/pkg/p2p/events/events.go deleted file mode 100644 index bf76e27d91e..00000000000 --- a/tm2/pkg/p2p/events/events.go +++ /dev/null @@ -1,104 +0,0 @@ -package events - -import ( - "sync" - - "github.com/rs/xid" -) - -// EventFilter is the filter function used to -// filter incoming p2p events. A false flag will -// consider the event as irrelevant -type EventFilter func(Event) bool - -// Events is the p2p event switch -type Events struct { - subs subscriptions - subscriptionsMux sync.RWMutex -} - -// New creates a new event subscription manager -func New() *Events { - return &Events{ - subs: make(subscriptions), - } -} - -// Subscribe registers a new filtered event listener -func (es *Events) Subscribe(filterFn EventFilter) (<-chan Event, func()) { - es.subscriptionsMux.Lock() - defer es.subscriptionsMux.Unlock() - - // Create a new subscription - id, ch := es.subs.add(filterFn) - - // Create the unsubscribe callback - unsubscribeFn := func() { - es.subscriptionsMux.Lock() - defer es.subscriptionsMux.Unlock() - - es.subs.remove(id) - } - - return ch, unsubscribeFn -} - -// Notify notifies all subscribers of an incoming event [BLOCKING] -func (es *Events) Notify(event Event) { - es.subscriptionsMux.RLock() - defer es.subscriptionsMux.RUnlock() - - es.subs.notify(event) -} - -type ( - // subscriptions holds the corresponding subscription information - subscriptions map[string]subscription // subscription ID -> subscription - - // subscription wraps the subscription notification channel, - // and the event filter - subscription struct { - ch chan Event - filterFn EventFilter - } -) - -// add adds a new subscription to the subscription map. -// Returns the subscription ID, and update channel -func (s *subscriptions) add(filterFn EventFilter) (string, chan Event) { - var ( - id = xid.New().String() - ch = make(chan Event, 1) - ) - - (*s)[id] = subscription{ - ch: ch, - filterFn: filterFn, - } - - return id, ch -} - -// remove removes the given subscription -func (s *subscriptions) remove(id string) { - if sub, exists := (*s)[id]; exists { - // Close the notification channel - close(sub.ch) - } - - // Delete the subscription - delete(*s, id) -} - -// notify notifies all subscription listeners, -// if their filters pass -func (s *subscriptions) notify(event Event) { - // Notify the listeners - for _, sub := range *s { - if !sub.filterFn(event) { - continue - } - - sub.ch <- event - } -} diff --git a/tm2/pkg/p2p/events/events_test.go b/tm2/pkg/p2p/events/events_test.go deleted file mode 100644 index a0feafceddb..00000000000 --- a/tm2/pkg/p2p/events/events_test.go +++ /dev/null @@ -1,94 +0,0 @@ -package events - -import ( - "fmt" - "sync" - "testing" - "time" - - "github.com/gnolang/gno/tm2/pkg/p2p/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// generateEvents generates p2p events -func generateEvents(count int) []Event { - events := make([]Event, 0, count) - - for i := range count { - var event Event - - if i%2 == 0 { - event = PeerConnectedEvent{ - PeerID: types.ID(fmt.Sprintf("peer-%d", i)), - } - } else { - event = PeerDisconnectedEvent{ - PeerID: types.ID(fmt.Sprintf("peer-%d", i)), - } - } - - events = append(events, event) - } - - return events -} - -func TestEvents_Subscribe(t *testing.T) { - t.Parallel() - - var ( - capturedEvents []Event - - events = generateEvents(10) - subFn = func(e Event) bool { - return e.Type() == PeerDisconnected - } - ) - - // Create the events manager - e := New() - - // Subscribe to events - ch, unsubFn := e.Subscribe(subFn) - defer unsubFn() - - // Listen for the events - var wg sync.WaitGroup - - wg.Add(1) - - go func() { - defer wg.Done() - - timeout := time.After(5 * time.Second) - - for { - select { - case ev := <-ch: - capturedEvents = append(capturedEvents, ev) - - if len(capturedEvents) == len(events)/2 { - return - } - case <-timeout: - return - } - } - }() - - // Send out the events - for _, ev := range events { - e.Notify(ev) - } - - wg.Wait() - - // Make sure the events were captured - // and filtered properly - require.Len(t, capturedEvents, len(events)/2) - - for _, ev := range capturedEvents { - assert.Equal(t, ev.Type(), PeerDisconnected) - } -} diff --git a/tm2/pkg/p2p/events/types.go b/tm2/pkg/p2p/events/types.go deleted file mode 100644 index 6778c3cd36d..00000000000 --- a/tm2/pkg/p2p/events/types.go +++ /dev/null @@ -1,37 +0,0 @@ -package events - -import ( - "github.com/gnolang/gno/tm2/pkg/p2p/types" -) - -type EventType string - -const ( - PeerConnected EventType = "PeerConnected" // emitted when a fresh peer connects - PeerDisconnected EventType = "PeerDisconnected" // emitted when a peer disconnects -) - -// Event is a generic p2p event -type Event interface { - // Type returns the type information for the event - Type() EventType -} - -type PeerConnectedEvent struct { - PeerID types.ID // the ID of the peer - Address types.NetAddress // the dial address of the peer -} - -func (p PeerConnectedEvent) Type() EventType { - return PeerConnected -} - -type PeerDisconnectedEvent struct { - PeerID types.ID // the ID of the peer - Address types.NetAddress // the dial address of the peer - Reason error // the disconnect reason, if any -} - -func (p PeerDisconnectedEvent) Type() EventType { - return PeerDisconnected -} diff --git a/tm2/pkg/p2p/switch.go b/tm2/pkg/p2p/switch.go index c51d1db313f..c011ffe988c 100644 --- a/tm2/pkg/p2p/switch.go +++ b/tm2/pkg/p2p/switch.go @@ -9,7 +9,6 @@ import ( "github.com/gnolang/gno/tm2/pkg/p2p/config" "github.com/gnolang/gno/tm2/pkg/p2p/conn" "github.com/gnolang/gno/tm2/pkg/p2p/dial" - "github.com/gnolang/gno/tm2/pkg/p2p/events" "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/gnolang/gno/tm2/pkg/service" "github.com/gnolang/gno/tm2/pkg/telemetry" @@ -61,7 +60,6 @@ type MultiplexSwitch struct { transport Transport dialQueue *dial.Queue - events *events.Events } // NewSwitch creates a new MultiplexSwitch with the given config. @@ -76,7 +74,6 @@ func NewSwitch( peers: newSet(), transport: transport, dialQueue: dial.NewQueue(), - events: events.New(), maxInboundPeers: defaultCfg.MaxNumInboundPeers, maxOutboundPeers: defaultCfg.MaxNumOutboundPeers, } @@ -103,12 +100,6 @@ func NewSwitch( return sw } -// Subscribe registers to live events happening on the p2p MultiplexSwitch. -// Returns the notification channel, along with an unsubscribe method -func (sw *MultiplexSwitch) Subscribe(filterFn events.EventFilter) (<-chan events.Event, func()) { - return sw.events.Subscribe(filterFn) -} - // --------------------------------------------------------------------- // Service start/stop @@ -276,6 +267,16 @@ func (sw *MultiplexSwitch) runDialLoop(ctx context.Context) { peerAddr := item.Address + // Check if the peer is already connected + if sw.Peers().Has(peerAddr.ID) || sw.Peers().HasIP(peerAddr.IP) { + sw.Logger.Warn( + "ignoring dial request for existing peer", + "id", peerAddr.ID, + ) + + continue + } + p, err := sw.transport.Dial(ctx, *peerAddr, sw.peerBehavior) if err != nil { sw.Logger.Error( @@ -318,83 +319,41 @@ func (sw *MultiplexSwitch) runDialLoop(ctx context.Context) { // runRedialLoop starts the persistent peer redial loop func (sw *MultiplexSwitch) runRedialLoop(ctx context.Context) { - var wg sync.WaitGroup - - wg.Add(2) + ticker := time.NewTicker(time.Second * 10) + defer ticker.Stop() - go func() { - defer wg.Done() - - ticker := time.NewTicker(time.Second * 5) - defer ticker.Stop() - - for { - select { - case <-ctx.Done(): - sw.Logger.Debug("redial crawl context canceled") - - return - case <-ticker.C: - peers := sw.Peers() + for { + select { + case <-ctx.Done(): + sw.Logger.Debug("redial crawl context canceled") - peersToDial := make([]*types.NetAddress, 0) + return + case <-ticker.C: + peers := sw.Peers() - sw.persistentPeers.Range(func(key, value any) bool { - var ( - id = key.(types.ID) - addr = value.(*types.NetAddress) - ) + peersToDial := make([]*types.NetAddress, 0) - if !peers.Has(id) { - peersToDial = append(peersToDial, addr) - } + sw.persistentPeers.Range(func(key, value any) bool { + var ( + id = key.(types.ID) + addr = value.(*types.NetAddress) + ) + // Check if the peer is part of the peer set + // or is scheduled for dialing + if peers.Has(id) || sw.dialQueue.Has(addr) { return true - }) - } - } - }() - - go func() { - defer wg.Done() - - // Set up the event subscription for persistent peer disconnects - subCh, unsubFn := sw.Subscribe(func(event events.Event) bool { - // Make sure the peer event relates to a peer disconnect - if event.Type() != events.PeerDisconnected { - return false - } - - disconnectEv, ok := event.(*events.PeerDisconnectedEvent) - if !ok { - return false - } - - return sw.isPersistentPeer(disconnectEv.PeerID) - }) - defer unsubFn() - - for { - select { - case <-ctx.Done(): - sw.Logger.Debug("redial event context canceled") + } - return - case ev := <-subCh: - disconnectEv := ev.(*events.PeerDisconnectedEvent) + peersToDial = append(peersToDial, addr) - // Dial the disconnected peer - item := dial.Item{ - Time: time.Now().Add(5 * time.Second), - Address: &disconnectEv.Address, - } + return true + }) - sw.dialQueue.Push(item) - } + // Add the peers to the dial queue + sw.DialPeers(peersToDial...) } - }() - - wg.Wait() + } } // DialPeers adds the peers to the dial queue for async dialing. diff --git a/tm2/pkg/p2p/types.go b/tm2/pkg/p2p/types.go index 885bd3432cd..71d11a82d9c 100644 --- a/tm2/pkg/p2p/types.go +++ b/tm2/pkg/p2p/types.go @@ -5,7 +5,6 @@ import ( "net" "github.com/gnolang/gno/tm2/pkg/p2p/conn" - "github.com/gnolang/gno/tm2/pkg/p2p/events" "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/gnolang/gno/tm2/pkg/service" ) @@ -76,9 +75,6 @@ type Transport interface { // Switch is the abstraction in the p2p module that handles // and manages peer connections thorough a Transport type Switch interface { - // Subscribe subscribes to peer events on the switch - Subscribe(filterFn events.EventFilter) (<-chan events.Event, func()) - // Broadcast publishes data on the given channel, to all peers Broadcast(chID byte, data []byte) From 2c503c9fd9df0debc5895a1dff886a65231a445b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Wed, 23 Oct 2024 16:08:02 -0500 Subject: [PATCH 25/43] Minor cleanup --- tm2/pkg/p2p/conn/connection.go | 1 - tm2/pkg/p2p/discovery/discovery.go | 3 ++- tm2/pkg/p2p/discovery/mock_test.go | 10 ---------- tm2/pkg/p2p/set.go | 8 -------- tm2/pkg/p2p/types.go | 3 +-- 5 files changed, 3 insertions(+), 22 deletions(-) diff --git a/tm2/pkg/p2p/conn/connection.go b/tm2/pkg/p2p/conn/connection.go index 678bf0b8fab..acbffdb3cd7 100644 --- a/tm2/pkg/p2p/conn/connection.go +++ b/tm2/pkg/p2p/conn/connection.go @@ -32,7 +32,6 @@ const ( // some of these defaults are written in the user config // flushThrottle, sendRate, recvRate - // TODO: remove values present in config defaultFlushThrottle = 100 * time.Millisecond defaultSendQueueCapacity = 1 diff --git a/tm2/pkg/p2p/discovery/discovery.go b/tm2/pkg/p2p/discovery/discovery.go index 724478252a7..ab36a737229 100644 --- a/tm2/pkg/p2p/discovery/discovery.go +++ b/tm2/pkg/p2p/discovery/discovery.go @@ -175,7 +175,8 @@ func (r *Reactor) Receive(chID byte, peer p2p.Peer, msgBytes []byte) { func (r *Reactor) handleDiscoveryRequest(peer p2p.Peer) error { // Check if there is anything to share, // to avoid useless traffic - if r.Switch.Peers().Size() == 0 { + switchPeers := r.Switch.Peers() + if switchPeers.NumOutbound()+switchPeers.NumInbound() == 0 { r.Logger.Warn("no peers to share in discovery request") return nil diff --git a/tm2/pkg/p2p/discovery/mock_test.go b/tm2/pkg/p2p/discovery/mock_test.go index 48e0fb81e7e..b4a21827ce7 100644 --- a/tm2/pkg/p2p/discovery/mock_test.go +++ b/tm2/pkg/p2p/discovery/mock_test.go @@ -54,7 +54,6 @@ type ( hasIPDelegate func(net.IP) bool getPeerDelegate func(types.ID) p2p.Peer listDelegate func() []p2p.Peer - sizeDelegate func() int numInboundDelegate func() uint64 numOutboundDelegate func() uint64 ) @@ -66,7 +65,6 @@ type mockPeerSet struct { hasIPFn hasIPDelegate getFn getPeerDelegate listFn listDelegate - sizeFn sizeDelegate numInboundFn numInboundDelegate numOutboundFn numOutboundDelegate } @@ -117,14 +115,6 @@ func (m *mockPeerSet) List() []p2p.Peer { return nil } -func (m *mockPeerSet) Size() int { - if m.sizeFn != nil { - return m.sizeFn() - } - - return 0 -} - func (m *mockPeerSet) NumInbound() uint64 { if m.numInboundFn != nil { return m.numInboundFn() diff --git a/tm2/pkg/p2p/set.go b/tm2/pkg/p2p/set.go index 57100015c32..a54ad949657 100644 --- a/tm2/pkg/p2p/set.go +++ b/tm2/pkg/p2p/set.go @@ -107,14 +107,6 @@ func (s *set) Remove(key types.ID) bool { return true } -// Size returns the number of unique peers in the peer table -func (s *set) Size() int { - s.mux.RLock() - defer s.mux.RUnlock() - - return len(s.peers) -} - // NumInbound returns the number of inbound peers func (s *set) NumInbound() uint64 { s.mux.RLock() diff --git a/tm2/pkg/p2p/types.go b/tm2/pkg/p2p/types.go index 71d11a82d9c..36d751458f4 100644 --- a/tm2/pkg/p2p/types.go +++ b/tm2/pkg/p2p/types.go @@ -47,8 +47,7 @@ type PeerSet interface { Has(key types.ID) bool HasIP(ip net.IP) bool Get(key types.ID) Peer - List() []Peer // TODO consider implementing an iterator - Size() int // TODO remove + List() []Peer NumInbound() uint64 // returns the number of connected inbound nodes NumOutbound() uint64 // returns the number of connected outbound nodes From a11726e0248a8af60fd5fc57acde61c58e3d0675 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Thu, 24 Oct 2024 18:01:11 -0500 Subject: [PATCH 26/43] Save switch unit test progress --- tm2/pkg/p2p/discovery/discovery_test.go | 77 +- tm2/pkg/p2p/mock/peer.go | 72 +- tm2/pkg/p2p/mock_test.go | 136 +++ tm2/pkg/p2p/peer_test.go | 24 +- tm2/pkg/p2p/set_test.go | 37 +- tm2/pkg/p2p/switch.go | 19 +- tm2/pkg/p2p/switch_test.go | 1026 ++++++++--------------- 7 files changed, 600 insertions(+), 791 deletions(-) create mode 100644 tm2/pkg/p2p/mock_test.go diff --git a/tm2/pkg/p2p/discovery/discovery_test.go b/tm2/pkg/p2p/discovery/discovery_test.go index dba2f2e719c..4ff63346000 100644 --- a/tm2/pkg/p2p/discovery/discovery_test.go +++ b/tm2/pkg/p2p/discovery/discovery_test.go @@ -1,7 +1,6 @@ package discovery import ( - "net" "slices" "testing" "time" @@ -14,37 +13,6 @@ import ( "github.com/stretchr/testify/require" ) -// generatePeers generates random peers -func generatePeers(t *testing.T, count int) []p2p.Peer { - peers := make([]p2p.Peer, count) - - for i := range count { - var ( - key = types.GenerateNodeKey() - address = "127.0.0.1:8080" - ) - - tcpAddr, err := net.ResolveTCPAddr("tcp", address) - require.NoError(t, err) - - addr, err := types.NewNetAddress(key.ID(), tcpAddr) - require.NoError(t, err) - - peers[i] = &mock.Peer{ - IDFn: func() types.ID { - return key.ID() - }, - NodeInfoFn: func() types.NodeInfo { - return types.NodeInfo{ - NetAddress: addr, - } - }, - } - } - - return peers -} - func TestReactor_DiscoveryRequest(t *testing.T) { t.Parallel() @@ -117,7 +85,7 @@ func TestReactor_DiscoveryResponse(t *testing.T) { t.Parallel() var ( - peers = generatePeers(t, 50) + peers = mock.GeneratePeers(t, 50) notifCh = make(chan struct{}, 1) capturedSend []byte @@ -136,10 +104,16 @@ func TestReactor_DiscoveryResponse(t *testing.T) { ps = &mockPeerSet{ listFn: func() []p2p.Peer { - return peers + listed := make([]p2p.Peer, 0, len(peers)) + + for _, peer := range peers { + listed = append(listed, peer) + } + + return listed }, - sizeFn: func() int { - return len(peers) + numInboundFn: func() uint64 { + return uint64(len(peers)) }, } @@ -219,9 +193,6 @@ func TestReactor_DiscoveryResponse(t *testing.T) { listFn: func() []p2p.Peer { return make([]p2p.Peer, 0) }, - sizeFn: func() int { - return 0 - }, } mockSwitch = &mockSwitch{ @@ -255,17 +226,23 @@ func TestReactor_DiscoveryResponse(t *testing.T) { t.Parallel() var ( - peers = generatePeers(t, 50) + peers = mock.GeneratePeers(t, 50) notifCh = make(chan struct{}, 1) capturedDials []*types.NetAddress ps = &mockPeerSet{ listFn: func() []p2p.Peer { - return peers + listed := make([]p2p.Peer, 0, len(peers)) + + for _, peer := range peers { + listed = append(listed, peer) + } + + return listed }, - sizeFn: func() int { - return len(peers) + numInboundFn: func() uint64 { + return uint64(len(peers)) }, } @@ -319,16 +296,22 @@ func TestReactor_DiscoveryResponse(t *testing.T) { t.Parallel() var ( - peers = generatePeers(t, 50) + peers = mock.GeneratePeers(t, 50) capturedDials []*types.NetAddress ps = &mockPeerSet{ listFn: func() []p2p.Peer { - return peers + listed := make([]p2p.Peer, 0, len(peers)) + + for _, peer := range peers { + listed = append(listed, peer) + } + + return listed }, - sizeFn: func() int { - return len(peers) + numInboundFn: func() uint64 { + return uint64(len(peers)) }, } diff --git a/tm2/pkg/p2p/mock/peer.go b/tm2/pkg/p2p/mock/peer.go index 8734f1ae2e2..d3fdbe7b34a 100644 --- a/tm2/pkg/p2p/mock/peer.go +++ b/tm2/pkg/p2p/mock/peer.go @@ -1,13 +1,17 @@ package mock import ( + "fmt" + "io" "log/slog" "net" + "testing" "time" "github.com/gnolang/gno/tm2/pkg/p2p/conn" "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/gnolang/gno/tm2/pkg/service" + "github.com/stretchr/testify/require" ) type ( @@ -25,8 +29,48 @@ type ( trySendDelegate func(byte, []byte) bool setDelegate func(string, any) getDelegate func(string) any + stopDelegate func() error ) +// GeneratePeers generates random peers +func GeneratePeers(t *testing.T, count int) []*Peer { + peers := make([]*Peer, count) + + for i := range count { + var ( + key = types.GenerateNodeKey() + address = "127.0.0.1:8080" + ) + + tcpAddr, err := net.ResolveTCPAddr("tcp", address) + require.NoError(t, err) + + addr, err := types.NewNetAddress(key.ID(), tcpAddr) + require.NoError(t, err) + + p := &Peer{ + IDFn: func() types.ID { + return key.ID() + }, + NodeInfoFn: func() types.NodeInfo { + return types.NodeInfo{ + NetAddress: addr, + } + }, + } + + p.BaseService = *service.NewBaseService( + slog.New(slog.NewTextHandler(io.Discard, nil)), + fmt.Sprintf("peer-%d", i), + p, + ) + + peers[i] = p + } + + return peers +} + type Peer struct { service.BaseService @@ -38,6 +82,7 @@ type Peer struct { IsPersistentFn isPersistentDelegate CloseConnFn closeConnDelegate NodeInfoFn nodeInfoDelegate + StopFn stopDelegate StatusFn statusDelegate SocketAddrFn socketAddrDelegate SendFn sendDelegate @@ -76,6 +121,14 @@ func (m *Peer) RemoteAddr() net.Addr { return nil } +func (m *Peer) Stop() error { + if m.StopFn != nil { + return m.StopFn() + } + + return nil +} + func (m *Peer) IsOutbound() bool { if m.IsOutboundFn != nil { return m.IsOutboundFn() @@ -239,11 +292,10 @@ func (m *MockConn) SetWriteDeadline(t time.Time) error { type ( startDelegate func() error - stopDelegate func() error stringDelegate func() string ) -type MockMConn struct { +type MConn struct { FlushFn flushStopDelegate StartFn startDelegate StopFn stopDelegate @@ -253,13 +305,13 @@ type MockMConn struct { StringFn stringDelegate } -func (m *MockMConn) FlushStop() { +func (m *MConn) FlushStop() { if m.FlushFn != nil { m.FlushFn() } } -func (m *MockMConn) Start() error { +func (m *MConn) Start() error { if m.StartFn != nil { return m.StartFn() } @@ -267,7 +319,7 @@ func (m *MockMConn) Start() error { return nil } -func (m *MockMConn) Stop() error { +func (m *MConn) Stop() error { if m.StopFn != nil { return m.StopFn() } @@ -275,7 +327,7 @@ func (m *MockMConn) Stop() error { return nil } -func (m *MockMConn) Send(ch byte, data []byte) bool { +func (m *MConn) Send(ch byte, data []byte) bool { if m.SendFn != nil { return m.SendFn(ch, data) } @@ -283,7 +335,7 @@ func (m *MockMConn) Send(ch byte, data []byte) bool { return false } -func (m *MockMConn) TrySend(ch byte, data []byte) bool { +func (m *MConn) TrySend(ch byte, data []byte) bool { if m.TrySendFn != nil { return m.TrySendFn(ch, data) } @@ -291,9 +343,9 @@ func (m *MockMConn) TrySend(ch byte, data []byte) bool { return false } -func (m *MockMConn) SetLogger(_ *slog.Logger) {} +func (m *MConn) SetLogger(_ *slog.Logger) {} -func (m *MockMConn) Status() conn.ConnectionStatus { +func (m *MConn) Status() conn.ConnectionStatus { if m.StatusFn != nil { return m.StatusFn() } @@ -301,7 +353,7 @@ func (m *MockMConn) Status() conn.ConnectionStatus { return conn.ConnectionStatus{} } -func (m *MockMConn) String() string { +func (m *MConn) String() string { if m.StringFn != nil { return m.StringFn() } diff --git a/tm2/pkg/p2p/mock_test.go b/tm2/pkg/p2p/mock_test.go new file mode 100644 index 00000000000..449662db189 --- /dev/null +++ b/tm2/pkg/p2p/mock_test.go @@ -0,0 +1,136 @@ +package p2p + +import ( + "context" + "net" + + "github.com/gnolang/gno/tm2/pkg/p2p/types" +) + +type ( + netAddressDelegate func() types.NetAddress + acceptDelegate func(context.Context, PeerBehavior) (Peer, error) + dialDelegate func(context.Context, types.NetAddress, PeerBehavior) (Peer, error) + removeDelegate func(Peer) +) + +type mockTransport struct { + netAddressFn netAddressDelegate + acceptFn acceptDelegate + dialFn dialDelegate + removeFn removeDelegate +} + +func (m *mockTransport) NetAddress() types.NetAddress { + if m.netAddressFn != nil { + return m.netAddressFn() + } + + return types.NetAddress{} +} + +func (m *mockTransport) Accept(ctx context.Context, behavior PeerBehavior) (Peer, error) { + if m.acceptFn != nil { + return m.acceptFn(ctx, behavior) + } + + return nil, nil +} + +func (m *mockTransport) Dial(ctx context.Context, address types.NetAddress, behavior PeerBehavior) (Peer, error) { + if m.dialFn != nil { + return m.dialFn(ctx, address, behavior) + } + + return nil, nil +} + +func (m *mockTransport) Remove(p Peer) { + if m.removeFn != nil { + m.removeFn(p) + } +} + +type ( + addDelegate func(Peer) + removePeerDelegate func(types.ID) bool + hasDelegate func(types.ID) bool + hasIPDelegate func(net.IP) bool + getDelegate func(types.ID) Peer + listDelegate func() []Peer + numInboundDelegate func() uint64 + numOutboundDelegate func() uint64 +) + +type mockSet struct { + addFn addDelegate + removeFn removePeerDelegate + hasFn hasDelegate + hasIPFn hasIPDelegate + listFn listDelegate + getFn getDelegate + numInboundFn numInboundDelegate + numOutboundFn numOutboundDelegate +} + +func (m *mockSet) Add(peer Peer) { + if m.addFn != nil { + m.addFn(peer) + } +} + +func (m *mockSet) Remove(key types.ID) bool { + if m.removeFn != nil { + m.removeFn(key) + } + + return false +} + +func (m *mockSet) Has(key types.ID) bool { + if m.hasFn != nil { + return m.hasFn(key) + } + + return false +} + +func (m *mockSet) HasIP(ip net.IP) bool { + if m.hasIPFn != nil { + return m.hasIPFn(ip) + } + + return false +} + +func (m *mockSet) Get(key types.ID) Peer { + if m.getFn != nil { + return m.getFn(key) + } + + return nil +} + +func (m *mockSet) List() []Peer { + if m.listFn != nil { + return m.listFn() + } + + return nil +} + +func (m *mockSet) NumInbound() uint64 { + if m.numInboundFn != nil { + return m.numInboundFn() + } + + return 0 +} + +func (m *mockSet) NumOutbound() uint64 { + if m.numOutboundFn != nil { + return m.numOutboundFn() + } + + return 0 +} diff --git a/tm2/pkg/p2p/peer_test.go b/tm2/pkg/p2p/peer_test.go index c3da539d4fa..b380ad9868f 100644 --- a/tm2/pkg/p2p/peer_test.go +++ b/tm2/pkg/p2p/peer_test.go @@ -93,7 +93,7 @@ func TestPeer_Properties(t *testing.T) { l = slog.New(slog.NewTextHandler(io.Discard, nil)) p = &peer{ - mConn: &mock.MockMConn{}, + mConn: &mock.MConn{}, } ) @@ -108,7 +108,7 @@ func TestPeer_Properties(t *testing.T) { var ( expectedErr = errors.New("some error") - mConn = &mock.MockMConn{ + mConn = &mock.MConn{ StartFn: func() error { return expectedErr }, @@ -129,7 +129,7 @@ func TestPeer_Properties(t *testing.T) { stopCalled = false expectedErr = errors.New("some error") - mConn = &mock.MockMConn{ + mConn = &mock.MConn{ StopFn: func() error { stopCalled = true @@ -155,7 +155,7 @@ func TestPeer_Properties(t *testing.T) { var ( stopCalled = false - mConn = &mock.MockMConn{ + mConn = &mock.MConn{ FlushFn: func() { stopCalled = true }, @@ -197,7 +197,7 @@ func TestPeer_Properties(t *testing.T) { Duration: 5 * time.Second, } - mConn = &mock.MockMConn{ + mConn = &mock.MConn{ StatusFn: func() conn.ConnectionStatus { return status }, @@ -237,7 +237,7 @@ func TestPeer_Properties(t *testing.T) { mConnStr = "description" p = &peer{ - mConn: &mock.MockMConn{ + mConn: &mock.MConn{ StringFn: func() string { return mConnStr }, @@ -357,7 +357,7 @@ func TestPeer_Send(t *testing.T) { capturedSendID byte capturedSendData []byte - mockConn = &mock.MockMConn{ + mockConn = &mock.MConn{ SendFn: func(c byte, d []byte) bool { capturedSendID = c capturedSendData = d @@ -395,7 +395,7 @@ func TestPeer_Send(t *testing.T) { capturedSendID byte capturedSendData []byte - mockConn = &mock.MockMConn{ + mockConn = &mock.MConn{ SendFn: func(c byte, d []byte) bool { capturedSendID = c capturedSendData = d @@ -437,7 +437,7 @@ func TestPeer_Send(t *testing.T) { capturedSendID byte capturedSendData []byte - mockConn = &mock.MockMConn{ + mockConn = &mock.MConn{ SendFn: func(c byte, d []byte) bool { capturedSendID = c capturedSendData = d @@ -485,7 +485,7 @@ func TestPeer_TrySend(t *testing.T) { capturedSendID byte capturedSendData []byte - mockConn = &mock.MockMConn{ + mockConn = &mock.MConn{ TrySendFn: func(c byte, d []byte) bool { capturedSendID = c capturedSendData = d @@ -523,7 +523,7 @@ func TestPeer_TrySend(t *testing.T) { capturedSendID byte capturedSendData []byte - mockConn = &mock.MockMConn{ + mockConn = &mock.MConn{ TrySendFn: func(c byte, d []byte) bool { capturedSendID = c capturedSendData = d @@ -565,7 +565,7 @@ func TestPeer_TrySend(t *testing.T) { capturedSendID byte capturedSendData []byte - mockConn = &mock.MockMConn{ + mockConn = &mock.MConn{ TrySendFn: func(c byte, d []byte) bool { capturedSendID = c capturedSendData = d diff --git a/tm2/pkg/p2p/set_test.go b/tm2/pkg/p2p/set_test.go index 00c18e90187..fd0428d5af0 100644 --- a/tm2/pkg/p2p/set_test.go +++ b/tm2/pkg/p2p/set_test.go @@ -6,35 +6,16 @@ import ( "testing" "github.com/gnolang/gno/tm2/pkg/p2p/mock" - "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -// generatePeers generates random node peers -func generatePeers(t *testing.T, count int) []*mock.Peer { - t.Helper() - - peers := make([]*mock.Peer, count) - - for i := 0; i < count; i++ { - id := types.GenerateNodeKey().ID() - peers[i] = &mock.Peer{ - IDFn: func() types.ID { - return id - }, - } - } - - return peers -} - func TestSet_Add(t *testing.T) { t.Parallel() var ( numPeers = 100 - peers = generatePeers(t, numPeers) + peers = mock.GeneratePeers(t, numPeers) s = newSet() ) @@ -47,7 +28,7 @@ func TestSet_Add(t *testing.T) { assert.True(t, s.Has(peer.ID())) } - assert.EqualValues(t, numPeers, s.Size()) + assert.EqualValues(t, numPeers, s.NumInbound()+s.NumOutbound()) } func TestSet_Remove(t *testing.T) { @@ -55,7 +36,7 @@ func TestSet_Remove(t *testing.T) { var ( numPeers = 100 - peers = generatePeers(t, numPeers) + peers = mock.GeneratePeers(t, numPeers) s = newSet() ) @@ -69,7 +50,7 @@ func TestSet_Remove(t *testing.T) { require.True(t, s.Has(peer.ID())) } - require.EqualValues(t, numPeers, s.Size()) + require.EqualValues(t, numPeers, s.NumInbound()+s.NumOutbound()) // Remove the peers // Add the initial peers @@ -89,7 +70,7 @@ func TestSet_HasIP(t *testing.T) { t.Parallel() var ( - peers = generatePeers(t, 100) + peers = mock.GeneratePeers(t, 100) ip = net.ParseIP("0.0.0.0") s = newSet() @@ -113,7 +94,7 @@ func TestSet_HasIP(t *testing.T) { t.Parallel() var ( - peers = generatePeers(t, 100) + peers = mock.GeneratePeers(t, 100) ip = net.ParseIP("0.0.0.0") s = newSet() @@ -136,7 +117,7 @@ func TestSet_Get(t *testing.T) { t.Parallel() var ( - peers = generatePeers(t, 100) + peers = mock.GeneratePeers(t, 100) s = newSet() ) @@ -152,7 +133,7 @@ func TestSet_Get(t *testing.T) { t.Parallel() var ( - peers = generatePeers(t, 100) + peers = mock.GeneratePeers(t, 100) s = newSet() ) @@ -182,7 +163,7 @@ func TestSet_List(t *testing.T) { t.Parallel() var ( - peers = generatePeers(t, 100) + peers = mock.GeneratePeers(t, 100) s = newSet() ) diff --git a/tm2/pkg/p2p/switch.go b/tm2/pkg/p2p/switch.go index c011ffe988c..963891844f2 100644 --- a/tm2/pkg/p2p/switch.go +++ b/tm2/pkg/p2p/switch.go @@ -65,7 +65,7 @@ type MultiplexSwitch struct { // NewSwitch creates a new MultiplexSwitch with the given config. func NewSwitch( transport Transport, - options ...SwitchOption, + opts ...SwitchOption, ) *MultiplexSwitch { defaultCfg := config.DefaultP2PConfig() @@ -93,8 +93,9 @@ func NewSwitch( // Set up the context sw.ctx, sw.cancelFn = context.WithCancel(context.Background()) - for _, option := range options { - option(sw) + // Apply the options + for _, opt := range opts { + opt(sw) } return sw @@ -206,7 +207,7 @@ func (sw *MultiplexSwitch) stopAndRemovePeer(peer Peer, err error) { sw.Logger.Error( "unable to gracefully close peer connection", "peer", peer, - "err", err, + "err", closeErr, ) } @@ -215,7 +216,7 @@ func (sw *MultiplexSwitch) stopAndRemovePeer(peer Peer, err error) { sw.Logger.Error( "unable to gracefully stop peer", "peer", peer, - "err", err, + "err", stopErr, ) } @@ -451,14 +452,6 @@ func (sw *MultiplexSwitch) runAcceptLoop(ctx context.Context) { func (sw *MultiplexSwitch) addPeer(p Peer) error { p.SetLogger(sw.Logger.With("peer", p.SocketAddr())) - // Handle the shut down case where the switch has stopped, but we're - // concurrently trying to add a peer. - if !sw.IsRunning() { - // XXX should this return an error or just log and terminate? - sw.Logger.Error("Won't start a peer - switch is not running", "peer", p) - return nil - } - // Add some data to the peer, which is required by reactors. for _, reactor := range sw.reactors { p = reactor.InitPeer(p) diff --git a/tm2/pkg/p2p/switch_test.go b/tm2/pkg/p2p/switch_test.go index 81cb0f83845..34edd251df3 100644 --- a/tm2/pkg/p2p/switch_test.go +++ b/tm2/pkg/p2p/switch_test.go @@ -1,683 +1,347 @@ package p2p -// var cfg *config.P2PConfig -// -// func init() { -// cfg = config.DefaultP2PConfig() -// cfg.PeerExchange = true -// cfg.AllowDuplicateIP = true -// } -// -// type PeerMessage struct { -// PeerID ID -// Bytes []byte -// Counter int -// } -// -// type TestReactor struct { -// BaseReactor -// -// mtx sync.Mutex -// channels []*conn.ChannelDescriptor -// logMessages bool -// msgsCounter int -// msgsReceived map[byte][]PeerMessage -// } -// -// func NewTestReactor(channels []*conn.ChannelDescriptor, logMessages bool) *TestReactor { -// tr := &TestReactor{ -// channels: channels, -// logMessages: logMessages, -// msgsReceived: make(map[byte][]PeerMessage), -// } -// tr.BaseReactor = *NewBaseReactor("TestReactor", tr) -// tr.SetLogger(log.NewNoopLogger()) -// return tr -// } -// -// func (tr *TestReactor) GetChannels() []*conn.ChannelDescriptor { -// return tr.channels -// } -// -// func (tr *TestReactor) AddPeer(peer Peer) {} -// -// func (tr *TestReactor) RemovePeer(peer Peer, reason interface{}) {} -// -// func (tr *TestReactor) Receive(chID byte, peer Peer, msgBytes []byte) { -// if tr.logMessages { -// tr.mtx.Lock() -// defer tr.mtx.Unlock() -// // fmt.Printf("Received: %X, %X\n", chID, msgBytes) -// tr.msgsReceived[chID] = append(tr.msgsReceived[chID], PeerMessage{peer.ID(), msgBytes, tr.msgsCounter}) -// tr.msgsCounter++ -// } -// } -// -// func (tr *TestReactor) getMsgs(chID byte) []PeerMessage { -// tr.mtx.Lock() -// defer tr.mtx.Unlock() -// return tr.msgsReceived[chID] -// } -// -// // ----------------------------------------------------------------------------- -// -// // convenience method for creating two switches connected to each other. -// // XXX: note this uses net.Pipe and not a proper TCP conn -// func MakeSwitchPair(_ testing.TB, initSwitch func(int, *MultiplexSwitch) *MultiplexSwitch) (*MultiplexSwitch, *MultiplexSwitch) { -// // Create two switches that will be interconnected. -// switches := MakeConnectedSwitches(cfg, 2, initSwitch, Connect2Switches) -// return switches[0], switches[1] -// } -// -// func initSwitchFunc(i int, sw *MultiplexSwitch) *MultiplexSwitch { -// // Make two reactors of two channels each -// sw.AddReactor("foo", NewTestReactor([]*conn.ChannelDescriptor{ -// {ID: byte(0x00), Priority: 10}, -// {ID: byte(0x01), Priority: 10}, -// }, true)) -// sw.AddReactor("bar", NewTestReactor([]*conn.ChannelDescriptor{ -// {ID: byte(0x02), Priority: 10}, -// {ID: byte(0x03), Priority: 10}, -// }, true)) -// -// return sw -// } -// -// func TestSwitches(t *testing.T) { -// t.Parallel() -// -// s1, s2 := MakeSwitchPair(t, initSwitchFunc) -// defer s1.Stop() -// defer s2.Stop() -// -// if s1.Peers().Size() != 1 { -// t.Errorf("Expected exactly 1 peer in s1, got %v", s1.Peers().Size()) -// } -// if s2.Peers().Size() != 1 { -// t.Errorf("Expected exactly 1 peer in s2, got %v", s2.Peers().Size()) -// } -// -// // Lets send some messages -// ch0Msg := []byte("channel zero") -// ch1Msg := []byte("channel foo") -// ch2Msg := []byte("channel bar") -// -// s1.Broadcast(byte(0x00), ch0Msg) -// s1.Broadcast(byte(0x01), ch1Msg) -// s1.Broadcast(byte(0x02), ch2Msg) -// -// assertMsgReceivedWithTimeout(t, ch0Msg, byte(0x00), s2.Reactor("foo").(*TestReactor), 10*time.Millisecond, 5*time.Second) -// assertMsgReceivedWithTimeout(t, ch1Msg, byte(0x01), s2.Reactor("foo").(*TestReactor), 10*time.Millisecond, 5*time.Second) -// assertMsgReceivedWithTimeout(t, ch2Msg, byte(0x02), s2.Reactor("bar").(*TestReactor), 10*time.Millisecond, 5*time.Second) -// } -// -// func assertMsgReceivedWithTimeout(t *testing.T, msgBytes []byte, channel byte, reactor *TestReactor, checkPeriod, timeout time.Duration) { -// t.Helper() -// -// ticker := time.NewTicker(checkPeriod) -// for { -// select { -// case <-ticker.C: -// msgs := reactor.getMsgs(channel) -// if len(msgs) > 0 { -// if !bytes.Equal(msgs[0].Bytes, msgBytes) { -// t.Fatalf("Unexpected message bytes. Wanted: %X, Got: %X", msgBytes, msgs[0].Bytes) -// } -// return -// } -// -// case <-time.After(timeout): -// t.Fatalf("Expected to have received 1 message in channel #%v, got zero", channel) -// } -// } -// } -// -// func TestSwitchFiltersOutItself(t *testing.T) { -// t.Parallel() -// -// s1 := MakeSwitch(cfg, 1, "127.0.0.1", "123.123.123", initSwitchFunc) -// -// // simulate s1 having a public IP by creating a remote peer with the same ID -// rp := &peer.remotePeer{PrivKey: s1.nodeKey.PrivKey, Config: cfg} -// rp.Start() -// -// // addr should be rejected in addPeer based on the same ID -// err := s1.dialPeerWithAddress(rp.Addr()) -// if assert.Error(t, err) { -// if err, ok := err.(RejectedError); ok { -// if !err.IsSelf() { -// t.Errorf("expected self to be rejected") -// } -// } else { -// t.Errorf("expected RejectedError") -// } -// } -// -// rp.Stop() -// -// assertNoPeersAfterTimeout(t, s1, 100*time.Millisecond) -// } -// -// func TestSwitchPeerFilter(t *testing.T) { -// t.Parallel() -// -// var ( -// filters = []PeerFilterFunc{ -// func(_ PeerSet, _ Peer) error { return nil }, -// func(_ PeerSet, _ Peer) error { return fmt.Errorf("denied!") }, -// func(_ PeerSet, _ Peer) error { return nil }, -// } -// sw = MakeSwitch( -// cfg, -// 1, -// "testing", -// "123.123.123", -// initSwitchFunc, -// WithPeerFilters(filters...), -// ) -// ) -// defer sw.Stop() -// -// // simulate remote peer -// rp := &peer.remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} -// rp.Start() -// defer rp.Stop() -// -// p, err := sw.transport.Dial(*rp.Addr(), peerConfig{ -// chDescs: sw.chDescs, -// onPeerError: sw.StopPeerForError, -// isPersistent: sw.isPeerPersistentFn(), -// reactorsByCh: sw.reactorsByCh, -// }) -// if err != nil { -// t.Fatal(err) -// } -// -// err = sw.addPeer(p) -// if err, ok := err.(RejectedError); ok { -// if !err.IsFiltered() { -// t.Errorf("expected peer to be filtered") -// } -// } else { -// t.Errorf("expected RejectedError") -// } -// } -// -// func TestSwitchPeerFilterTimeout(t *testing.T) { -// t.Parallel() -// -// var ( -// filters = []PeerFilterFunc{ -// func(_ PeerSet, _ Peer) error { -// time.Sleep(10 * time.Millisecond) -// return nil -// }, -// } -// sw = MakeSwitch( -// cfg, -// 1, -// "testing", -// "123.123.123", -// initSwitchFunc, -// SwitchFilterTimeout(5*time.Millisecond), -// WithPeerFilters(filters...), -// ) -// ) -// defer sw.Stop() -// -// // simulate remote peer -// rp := &peer.remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} -// rp.Start() -// defer rp.Stop() -// -// p, err := sw.transport.Dial(*rp.Addr(), peerConfig{ -// chDescs: sw.chDescs, -// onPeerError: sw.StopPeerForError, -// isPersistent: sw.isPeerPersistentFn(), -// reactorsByCh: sw.reactorsByCh, -// }) -// if err != nil { -// t.Fatal(err) -// } -// -// err = sw.addPeer(p) -// if _, ok := err.(FilterTimeoutError); !ok { -// t.Errorf("expected FilterTimeoutError") -// } -// } -// -// func TestSwitchPeerFilterDuplicate(t *testing.T) { -// t.Parallel() -// -// sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) -// sw.Start() -// defer sw.Stop() -// -// // simulate remote peer -// rp := &peer.remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} -// rp.Start() -// defer rp.Stop() -// -// p, err := sw.transport.Dial(*rp.Addr(), peerConfig{ -// chDescs: sw.chDescs, -// onPeerError: sw.StopPeerForError, -// isPersistent: sw.isPeerPersistentFn(), -// reactorsByCh: sw.reactorsByCh, -// }) -// if err != nil { -// t.Fatal(err) -// } -// -// if err := sw.addPeer(p); err != nil { -// t.Fatal(err) -// } -// -// err = sw.addPeer(p) -// if errRej, ok := err.(RejectedError); ok { -// if !errRej.IsDuplicate() { -// t.Errorf("expected peer to be duplicate. got %v", errRej) -// } -// } else { -// t.Errorf("expected RejectedError, got %v", err) -// } -// } -// -// func assertNoPeersAfterTimeout(t *testing.T, sw *MultiplexSwitch, timeout time.Duration) { -// t.Helper() -// -// time.Sleep(timeout) -// if sw.Peers().Size() != 0 { -// t.Fatalf("Expected %v to not connect to some peers, got %d", sw, sw.Peers().Size()) -// } -// } -// -// func TestSwitchStopsNonPersistentPeerOnError(t *testing.T) { -// t.Parallel() -// -// assert, require := assert.New(t), require.New(t) -// -// sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) -// err := sw.Start() -// if err != nil { -// t.Error(err) -// } -// defer sw.Stop() -// -// // simulate remote peer -// rp := &peer.remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} -// rp.Start() -// defer rp.Stop() -// -// p, err := sw.transport.Dial(*rp.Addr(), peerConfig{ -// chDescs: sw.chDescs, -// onPeerError: sw.StopPeerForError, -// isPersistent: sw.isPeerPersistentFn(), -// reactorsByCh: sw.reactorsByCh, -// }) -// require.Nil(err) -// -// err = sw.addPeer(p) -// require.Nil(err) -// -// require.NotNil(sw.Peers().Get(rp.ID())) -// -// // simulate failure by closing connection -// p.(*peer.peer).CloseConn() -// -// assertNoPeersAfterTimeout(t, sw, 100*time.Millisecond) -// assert.False(p.IsRunning()) -// } -// -// func TestSwitchStopPeerForError(t *testing.T) { -// t.Parallel() -// -// // make two connected switches -// sw1, sw2 := MakeSwitchPair(t, func(i int, sw *MultiplexSwitch) *MultiplexSwitch { -// return initSwitchFunc(i, sw) -// }) -// -// assert.Equal(t, len(sw1.Peers().List()), 1) -// -// // send messages to the peer from sw1 -// p := sw1.Peers().List()[0] -// p.Send(0x1, []byte("here's a message to send")) -// -// // stop sw2. this should cause the p to fail, -// // which results in calling StopPeerForError internally -// sw2.Stop() -// -// // now call StopPeerForError explicitly, eg. from a reactor -// sw1.StopPeerForError(p, fmt.Errorf("some err")) -// -// assert.Equal(t, len(sw1.Peers().List()), 0) -// } -// -// func TestSwitchReconnectsToOutboundPersistentPeer(t *testing.T) { -// t.Parallel() -// -// sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) -// err := sw.Start() -// require.NoError(t, err) -// defer sw.Stop() -// -// // 1. simulate failure by closing connection -// rp := &peer.remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} -// rp.Start() -// defer rp.Stop() -// -// err = sw.AddPersistentPeers([]string{rp.Addr().String()}) -// require.NoError(t, err) -// -// err = sw.dialPeerWithAddress(rp.Addr()) -// require.Nil(t, err) -// require.NotNil(t, sw.Peers().Get(rp.ID())) -// -// p := sw.Peers().List()[0] -// p.(*peer.peer).CloseConn() -// -// waitUntilSwitchHasAtLeastNPeers(sw, 1) -// assert.False(t, p.IsRunning()) // old peer instance -// assert.Equal(t, 1, sw.Peers().Size()) // new peer instance -// -// // 2. simulate first time dial failure -// rp = &peer.remotePeer{ -// PrivKey: ed25519.GenPrivKey(), -// Config: cfg, -// // Use different interface to prevent duplicate IP filter, this will break -// // beyond two peers. -// listenAddr: "127.0.0.1:0", -// } -// rp.Start() -// defer rp.Stop() -// -// conf := config.DefaultP2PConfig() -// conf.TestDialFail = true // will trigger a reconnect -// err = sw.addOutboundPeerWithConfig(rp.Addr(), conf) -// require.NotNil(t, err) -// // DialPeerWithAddres - sw.peerConfig resets the dialer -// waitUntilSwitchHasAtLeastNPeers(sw, 2) -// assert.Equal(t, 2, sw.Peers().Size()) -// } -// -// func TestSwitchReconnectsToInboundPersistentPeer(t *testing.T) { -// t.Parallel() -// -// sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) -// err := sw.Start() -// require.NoError(t, err) -// defer sw.Stop() -// -// // 1. simulate failure by closing the connection -// rp := &peer.remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} -// rp.Start() -// defer rp.Stop() -// -// err = sw.AddPersistentPeers([]string{rp.Addr().String()}) -// require.NoError(t, err) -// -// conn, err := rp.Dial(sw.NetAddress()) -// require.NoError(t, err) -// time.Sleep(100 * time.Millisecond) -// require.NotNil(t, sw.Peers().Get(rp.ID())) -// -// conn.Close() -// -// waitUntilSwitchHasAtLeastNPeers(sw, 1) -// assert.Equal(t, 1, sw.Peers().Size()) -// } -// -// func TestSwitchDialPeersAsync(t *testing.T) { -// t.Parallel() -// -// if testing.Short() { -// return -// } -// -// sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) -// err := sw.Start() -// require.NoError(t, err) -// defer sw.Stop() -// -// rp := &peer.remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} -// rp.Start() -// defer rp.Stop() -// -// err = sw.DialPeersAsync([]string{rp.Addr().String()}) -// require.NoError(t, err) -// time.Sleep(dialRandomizerIntervalMilliseconds * time.Millisecond) -// require.NotNil(t, sw.Peers().Get(rp.ID())) -// } -// -// func waitUntilSwitchHasAtLeastNPeers(sw *MultiplexSwitch, n int) { -// for i := 0; i < 20; i++ { -// time.Sleep(250 * time.Millisecond) -// has := sw.Peers().Size() -// if has >= n { -// break -// } -// } -// } -// -// func TestSwitchFullConnectivity(t *testing.T) { -// t.Parallel() -// -// switches := MakeConnectedSwitches(cfg, 3, initSwitchFunc, Connect2Switches) -// defer func() { -// for _, sw := range switches { -// sw.Stop() -// } -// }() -// -// for i, sw := range switches { -// if sw.Peers().Size() != 2 { -// t.Fatalf("Expected each switch to be connected to 2 other, but %d switch only connected to %d", sw.Peers().Size(), i) -// } -// } -// } -// -// func TestSwitchAcceptRoutine(t *testing.T) { -// t.Parallel() -// -// cfg.MaxNumInboundPeers = 5 -// -// // make switch -// sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) -// err := sw.Start() -// require.NoError(t, err) -// defer sw.Stop() -// -// remotePeers := make([]*peer.remotePeer, 0) -// assert.Equal(t, 0, sw.Peers().Size()) -// -// // 1. check we connect up to MaxNumInboundPeers -// for i := 0; i < cfg.MaxNumInboundPeers; i++ { -// rp := &peer.remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} -// remotePeers = append(remotePeers, rp) -// rp.Start() -// c, err := rp.Dial(sw.NetAddress()) -// require.NoError(t, err) -// // spawn a reading routine to prevent connection from closing -// go func(c net.Conn) { -// for { -// one := make([]byte, 1) -// _, err := c.Read(one) -// if err != nil { -// return -// } -// } -// }(c) -// } -// time.Sleep(100 * time.Millisecond) -// assert.Equal(t, cfg.MaxNumInboundPeers, sw.Peers().Size()) -// -// // 2. check we close new connections if we already have MaxNumInboundPeers peers -// rp := &peer.remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} -// rp.Start() -// conn, err := rp.Dial(sw.NetAddress()) -// require.NoError(t, err) -// // check conn is closed -// one := make([]byte, 1) -// conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) -// _, err = conn.Read(one) -// assert.Equal(t, io.EOF, err) -// assert.Equal(t, cfg.MaxNumInboundPeers, sw.Peers().Size()) -// rp.Stop() -// -// // stop remote peers -// for _, rp := range remotePeers { -// rp.Stop() -// } -// } -// -// type errorTransport struct { -// acceptErr error -// } -// -// func (et errorTransport) NetAddress() NetAddress { -// panic("not implemented") -// } -// -// func (et errorTransport) Accept(c peerConfig) (Peer, error) { -// return nil, et.acceptErr -// } -// -// func (errorTransport) Dial(NetAddress, peerConfig) (Peer, error) { -// panic("not implemented") -// } -// -// func (errorTransport) Cleanup(Peer) { -// panic("not implemented") -// } -// -// func TestSwitchAcceptRoutineErrorCases(t *testing.T) { -// t.Parallel() -// -// sw := NewSwitch(cfg, errorTransport{FilterTimeoutError{}}) -// assert.NotPanics(t, func() { -// err := sw.Start() -// assert.NoError(t, err) -// sw.Stop() -// }) -// -// sw = NewSwitch(cfg, errorTransport{RejectedError{conn: nil, err: errors.New("filtered"), isFiltered: true}}) -// assert.NotPanics(t, func() { -// err := sw.Start() -// assert.NoError(t, err) -// sw.Stop() -// }) -// -// sw = NewSwitch(cfg, errorTransport{TransportClosedError{}}) -// assert.NotPanics(t, func() { -// err := sw.Start() -// assert.NoError(t, err) -// sw.Stop() -// }) -// } -// -// // mockReactor checks that InitPeer never called before RemovePeer. If that's -// // not true, InitCalledBeforeRemoveFinished will return true. -// type mockReactor struct { -// *BaseReactor -// -// // atomic -// removePeerInProgress uint32 -// initCalledBeforeRemoveFinished uint32 -// } -// -// func (r *mockReactor) RemovePeer(peer Peer, reason interface{}) { -// atomic.StoreUint32(&r.removePeerInProgress, 1) -// defer atomic.StoreUint32(&r.removePeerInProgress, 0) -// time.Sleep(100 * time.Millisecond) -// } -// -// func (r *mockReactor) InitPeer(peer Peer) Peer { -// if atomic.LoadUint32(&r.removePeerInProgress) == 1 { -// atomic.StoreUint32(&r.initCalledBeforeRemoveFinished, 1) -// } -// -// return peer -// } -// -// func (r *mockReactor) InitCalledBeforeRemoveFinished() bool { -// return atomic.LoadUint32(&r.initCalledBeforeRemoveFinished) == 1 -// } -// -// // see stopAndRemovePeer -// func TestFlappySwitchInitPeerIsNotCalledBeforeRemovePeer(t *testing.T) { -// t.Parallel() -// -// testutils.FilterStability(t, testutils.Flappy) -// -// // make reactor -// reactor := &mockReactor{} -// reactor.BaseReactor = NewBaseReactor("mockReactor", reactor) -// -// // make switch -// sw := MakeSwitch(cfg, 1, "testing", "123.123.123", func(i int, sw *MultiplexSwitch) *MultiplexSwitch { -// sw.AddReactor("mock", reactor) -// return sw -// }) -// err := sw.Start() -// require.NoError(t, err) -// defer sw.Stop() -// -// // add peer -// rp := &peer.remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} -// rp.Start() -// defer rp.Stop() -// _, err = rp.Dial(sw.NetAddress()) -// require.NoError(t, err) -// // wait till the switch adds rp to the peer set -// time.Sleep(100 * time.Millisecond) -// -// // stop peer asynchronously -// go sw.StopPeerForError(sw.Peers().Get(rp.ID()), "test") -// -// // simulate peer reconnecting to us -// _, err = rp.Dial(sw.NetAddress()) -// require.NoError(t, err) -// // wait till the switch adds rp to the peer set -// time.Sleep(100 * time.Millisecond) -// -// // make sure reactor.RemovePeer is finished before InitPeer is called -// assert.False(t, reactor.InitCalledBeforeRemoveFinished()) -// } -// -// func BenchmarkSwitchBroadcast(b *testing.B) { -// s1, s2 := MakeSwitchPair(b, func(i int, sw *MultiplexSwitch) *MultiplexSwitch { -// // Make bar reactors of bar channels each -// sw.AddReactor("foo", NewTestReactor([]*conn.ChannelDescriptor{ -// {ID: byte(0x00), Priority: 10}, -// {ID: byte(0x01), Priority: 10}, -// }, false)) -// sw.AddReactor("bar", NewTestReactor([]*conn.ChannelDescriptor{ -// {ID: byte(0x02), Priority: 10}, -// {ID: byte(0x03), Priority: 10}, -// }, false)) -// return sw -// }) -// defer s1.Stop() -// defer s2.Stop() -// -// // Allow time for goroutines to boot up -// time.Sleep(1 * time.Second) -// -// b.ResetTimer() -// -// numSuccess, numFailure := 0, 0 -// -// // Send random message from foo channel to another -// for i := 0; i < b.N; i++ { -// chID := byte(i % 4) -// successChan := s1.Broadcast(chID, []byte("test data")) -// for s := range successChan { -// if s { -// numSuccess++ -// } else { -// numFailure++ -// } -// } -// } -// -// b.Logf("success: %v, failure: %v", numSuccess, numFailure) -// } +import ( + "context" + "sync" + "testing" + "time" + + "github.com/gnolang/gno/tm2/pkg/errors" + "github.com/gnolang/gno/tm2/pkg/p2p/dial" + "github.com/gnolang/gno/tm2/pkg/p2p/mock" + "github.com/gnolang/gno/tm2/pkg/p2p/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMultiplexSwitch_Broadcast(t *testing.T) { + t.Parallel() + + var ( + wg sync.WaitGroup + + expectedChID = byte(10) + expectedData = []byte("broadcast data") + + peers = mock.GeneratePeers(t, 10) + sw = NewSwitch(nil) + ) + + // Create a new peer set + sw.peers = newSet() + + for _, p := range peers { + wg.Add(1) + + p.SendFn = func(chID byte, data []byte) bool { + wg.Done() + + require.Equal(t, expectedChID, chID) + assert.Equal(t, expectedData, data) + + return false + } + + // Load it up with peers + sw.peers.Add(p) + } + + // Broadcast the data + sw.Broadcast(expectedChID, expectedData) + + wg.Wait() +} + +func TestMultiplexSwitch_Peers(t *testing.T) { + t.Parallel() + + var ( + peers = mock.GeneratePeers(t, 10) + sw = NewSwitch(nil) + ) + + // Create a new peer set + sw.peers = newSet() + + for _, p := range peers { + // Load it up with peers + sw.peers.Add(p) + } + + // Broadcast the data + ps := sw.Peers() + + require.EqualValues( + t, + len(peers), + ps.NumInbound()+ps.NumOutbound(), + ) + + for _, p := range peers { + assert.True(t, ps.Has(p.ID())) + } +} + +func TestMultiplexSwitch_StopPeer(t *testing.T) { + t.Parallel() + + t.Run("peer not persistent", func(t *testing.T) { + t.Parallel() + + var ( + p = mock.GeneratePeers(t, 1)[0] + mockTransport = &mockTransport{ + removeFn: func(removedPeer Peer) { + assert.Equal(t, p.ID(), removedPeer.ID()) + }, + } + + sw = NewSwitch(mockTransport) + ) + + // Create a new peer set + sw.peers = newSet() + + // Save the single peer + sw.peers.Add(p) + + // Stop and remove the peer + sw.StopPeerForError(p, nil) + + // Make sure the peer is removed + assert.False(t, sw.peers.Has(p.ID())) + }) + + t.Run("persistent peer", func(t *testing.T) { + t.Parallel() + + var ( + p = mock.GeneratePeers(t, 1)[0] + mockTransport = &mockTransport{ + removeFn: func(removedPeer Peer) { + assert.Equal(t, p.ID(), removedPeer.ID()) + }, + netAddressFn: func() types.NetAddress { + return types.NetAddress{} + }, + } + + sw = NewSwitch(mockTransport) + ) + + // Make sure the peer is persistent + p.IsPersistentFn = func() bool { + return true + } + + p.IsOutboundFn = func() bool { + return false + } + + // Create a new peer set + sw.peers = newSet() + + // Save the single peer + sw.peers.Add(p) + + // Stop and remove the peer + sw.StopPeerForError(p, nil) + + // Make sure the peer is removed + assert.False(t, sw.peers.Has(p.ID())) + + // Make sure the peer is in the dial queue + sw.dialQueue.Has(p.NodeInfo().NetAddress) + }) +} + +func TestMultiplexSwitch_DialLoop(t *testing.T) { + t.Parallel() + + t.Run("peer already connected", func(t *testing.T) { + t.Parallel() + + ctx, cancelFn := context.WithTimeout( + context.Background(), + 5*time.Second, + ) + defer cancelFn() + + var ( + ch = make(chan struct{}, 1) + + peerDialed bool + + p = mock.GeneratePeers(t, 1)[0] + dialTime = time.Now().Add(-5 * time.Second) // in the past + + mockSet = &mockSet{ + hasFn: func(id types.ID) bool { + require.Equal(t, p.ID(), id) + + cancelFn() + + ch <- struct{}{} + + return true + }, + } + + mockTransport = &mockTransport{ + dialFn: func( + _ context.Context, + _ types.NetAddress, + _ PeerBehavior, + ) (Peer, error) { + peerDialed = true + + return nil, nil + }, + } + + sw = NewSwitch(mockTransport) + ) + + sw.peers = mockSet + + // Prepare the dial queue + sw.dialQueue.Push(dial.Item{ + Time: dialTime, + Address: p.NodeInfo().NetAddress, + }) + + // Run the dial loop + go sw.runDialLoop(ctx) + + select { + case <-ch: + case <-time.After(5 * time.Second): + } + + assert.False(t, peerDialed) + }) + + t.Run("peer undialable", func(t *testing.T) { + t.Parallel() + + ctx, cancelFn := context.WithTimeout( + context.Background(), + 5*time.Second, + ) + defer cancelFn() + + var ( + ch = make(chan struct{}, 1) + + peerDialed bool + + p = mock.GeneratePeers(t, 1)[0] + dialTime = time.Now().Add(-5 * time.Second) // in the past + + mockSet = &mockSet{ + hasFn: func(id types.ID) bool { + require.Equal(t, p.ID(), id) + + return false + }, + } + + mockTransport = &mockTransport{ + dialFn: func( + _ context.Context, + _ types.NetAddress, + _ PeerBehavior, + ) (Peer, error) { + peerDialed = true + + cancelFn() + + ch <- struct{}{} + + return nil, errors.New("invalid dial") + }, + } + + sw = NewSwitch(mockTransport) + ) + + sw.peers = mockSet + + // Prepare the dial queue + sw.dialQueue.Push(dial.Item{ + Time: dialTime, + Address: p.NodeInfo().NetAddress, + }) + + // Run the dial loop + go sw.runDialLoop(ctx) + + select { + case <-ch: + case <-time.After(5 * time.Second): + } + + assert.True(t, peerDialed) + }) + + t.Run("peer dialed and added", func(t *testing.T) { + t.Parallel() + + ctx, cancelFn := context.WithTimeout( + context.Background(), + 5*time.Second, + ) + defer cancelFn() + + var ( + ch = make(chan struct{}, 1) + + peerDialed bool + + p = mock.GeneratePeers(t, 1)[0] + dialTime = time.Now().Add(-5 * time.Second) // in the past + + mockTransport = &mockTransport{ + dialFn: func( + _ context.Context, + _ types.NetAddress, + _ PeerBehavior, + ) (Peer, error) { + peerDialed = true + + cancelFn() + + ch <- struct{}{} + + return p, nil + }, + } + + sw = NewSwitch(mockTransport) + ) + + // Prepare the dial queue + sw.dialQueue.Push(dial.Item{ + Time: dialTime, + Address: p.NodeInfo().NetAddress, + }) + + // Run the dial loop + go sw.runDialLoop(ctx) + + select { + case <-ch: + case <-time.After(5 * time.Second): + } + + require.True(t, sw.Peers().Has(p.ID())) + + assert.True(t, peerDialed) + }) +} + +func TestMultiplexSwitch_AcceptLoop(t *testing.T) { + t.Parallel() + + // TODO implement +} From e7a987b4a34a70f8bb9ab720f3a5db176ff1b6e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Sat, 26 Oct 2024 22:08:03 -0400 Subject: [PATCH 27/43] Add remaining unit tests for the switch --- tm2/pkg/p2p/discovery/discovery.go | 2 + tm2/pkg/p2p/switch.go | 65 +++-- tm2/pkg/p2p/switch_test.go | 399 ++++++++++++++++++++++++++++- 3 files changed, 441 insertions(+), 25 deletions(-) diff --git a/tm2/pkg/p2p/discovery/discovery.go b/tm2/pkg/p2p/discovery/discovery.go index ab36a737229..132e0855cf8 100644 --- a/tm2/pkg/p2p/discovery/discovery.go +++ b/tm2/pkg/p2p/discovery/discovery.go @@ -187,6 +187,8 @@ func (r *Reactor) handleDiscoveryRequest(peer p2p.Peer) error { peers = make([]*types.NetAddress, 0, len(localPeers)) ) + // TODO exclude private peers + // Shuffle and limit the peers shared shufflePeers(localPeers) diff --git a/tm2/pkg/p2p/switch.go b/tm2/pkg/p2p/switch.go index 963891844f2..477ed5cace2 100644 --- a/tm2/pkg/p2p/switch.go +++ b/tm2/pkg/p2p/switch.go @@ -323,43 +323,60 @@ func (sw *MultiplexSwitch) runRedialLoop(ctx context.Context) { ticker := time.NewTicker(time.Second * 10) defer ticker.Stop() - for { - select { - case <-ctx.Done(): - sw.Logger.Debug("redial crawl context canceled") + // redialFn goes through the persistent peer list + // and dials missing peers + redialFn := func() { + var ( + peers = sw.Peers() + peersToDial = make([]*types.NetAddress, 0) + ) - return - case <-ticker.C: - peers := sw.Peers() + sw.persistentPeers.Range(func(key, value any) bool { + var ( + id = key.(types.ID) + addr = value.(*types.NetAddress) + ) - peersToDial := make([]*types.NetAddress, 0) + // Check if the peer is part of the peer set + // or is scheduled for dialing + if peers.Has(id) || sw.dialQueue.Has(addr) { + return true + } - sw.persistentPeers.Range(func(key, value any) bool { - var ( - id = key.(types.ID) - addr = value.(*types.NetAddress) - ) + peersToDial = append(peersToDial, addr) - // Check if the peer is part of the peer set - // or is scheduled for dialing - if peers.Has(id) || sw.dialQueue.Has(addr) { - return true - } + return true + }) - peersToDial = append(peersToDial, addr) + if len(peersToDial) == 0 { + // No persistent peers are missing + return + } - return true - }) + // Add the peers to the dial queue + sw.DialPeers(peersToDial...) + } + + // Run the initial redial loop on start, + // in case persistent peer connections are not + // active + redialFn() + + for { + select { + case <-ctx.Done(): + sw.Logger.Debug("redial crawl context canceled") - // Add the peers to the dial queue - sw.DialPeers(peersToDial...) + return + case <-ticker.C: + redialFn() } } } // DialPeers adds the peers to the dial queue for async dialing. // To monitor dial progress, subscribe to adequate p2p MultiplexSwitch events -func (sw *MultiplexSwitch) DialPeers(peerAddrs ...*types.NetAddress) { +func (sw *MultiplexSwitch) DialPeers(peerAddrs ...*types.NetAddress) { // TODO remove pointer for _, peerAddr := range peerAddrs { // Check if this is our address if peerAddr.Same(sw.transport.NetAddress()) { diff --git a/tm2/pkg/p2p/switch_test.go b/tm2/pkg/p2p/switch_test.go index 34edd251df3..fd05ded96a1 100644 --- a/tm2/pkg/p2p/switch_test.go +++ b/tm2/pkg/p2p/switch_test.go @@ -2,6 +2,7 @@ package p2p import ( "context" + "net" "sync" "testing" "time" @@ -343,5 +344,401 @@ func TestMultiplexSwitch_DialLoop(t *testing.T) { func TestMultiplexSwitch_AcceptLoop(t *testing.T) { t.Parallel() - // TODO implement + t.Run("inbound limit reached", func(t *testing.T) { + t.Parallel() + + ctx, cancelFn := context.WithTimeout( + context.Background(), + 5*time.Second, + ) + defer cancelFn() + + var ( + ch = make(chan struct{}, 1) + maxInbound = uint64(10) + + peerRemoved bool + + p = mock.GeneratePeers(t, 1)[0] + + mockTransport = &mockTransport{ + acceptFn: func(_ context.Context, _ PeerBehavior) (Peer, error) { + return p, nil + }, + removeFn: func(removedPeer Peer) { + require.Equal(t, p.ID(), removedPeer.ID()) + + peerRemoved = true + + ch <- struct{}{} + }, + } + + ps = &mockSet{ + numInboundFn: func() uint64 { + return maxInbound + }, + } + + sw = NewSwitch( + mockTransport, + WithMaxInboundPeers(maxInbound), + ) + ) + + // Set the peer set + sw.peers = ps + + // Run the accept loop + go sw.runAcceptLoop(ctx) + + select { + case <-ch: + case <-time.After(5 * time.Second): + } + + assert.True(t, peerRemoved) + }) + + t.Run("peer accepted", func(t *testing.T) { + t.Parallel() + + ctx, cancelFn := context.WithTimeout( + context.Background(), + 5*time.Second, + ) + defer cancelFn() + + var ( + ch = make(chan struct{}, 1) + maxInbound = uint64(10) + + peerAdded bool + + p = mock.GeneratePeers(t, 1)[0] + + mockTransport = &mockTransport{ + acceptFn: func(_ context.Context, _ PeerBehavior) (Peer, error) { + return p, nil + }, + } + + ps = &mockSet{ + numInboundFn: func() uint64 { + return maxInbound - 1 // available slot + }, + addFn: func(peer Peer) { + require.Equal(t, p.ID(), peer.ID()) + + peerAdded = true + + ch <- struct{}{} + }, + } + + sw = NewSwitch( + mockTransport, + WithMaxInboundPeers(maxInbound), + ) + ) + + // Set the peer set + sw.peers = ps + + // Run the accept loop + go sw.runAcceptLoop(ctx) + + select { + case <-ch: + case <-time.After(5 * time.Second): + } + + assert.True(t, peerAdded) + }) +} + +func TestMultiplexSwitch_RedialLoop(t *testing.T) { + t.Parallel() + + t.Run("no peers to dial", func(t *testing.T) { + t.Parallel() + + var ( + ch = make(chan struct{}, 1) + + peersChecked = 0 + peers = mock.GeneratePeers(t, 10) + + ps = &mockSet{ + hasFn: func(id types.ID) bool { + exists := false + for _, p := range peers { + if p.ID() == id { + exists = true + + break + } + } + + require.True(t, exists) + + peersChecked++ + + if peersChecked == len(peers) { + ch <- struct{}{} + } + + return true + }, + } + ) + + // Make sure the peers are the + // switch persistent peers + addrs := make([]*types.NetAddress, 0, len(peers)) + + for _, p := range peers { + addrs = append(addrs, p.NodeInfo().NetAddress) + } + + // Create the switch + sw := NewSwitch( + nil, + WithPersistentPeers(addrs), + ) + + // Set the peer set + sw.peers = ps + + // Run the redial loop + ctx, cancelFn := context.WithTimeout( + context.Background(), + 5*time.Second, + ) + defer cancelFn() + + go sw.runRedialLoop(ctx) + + select { + case <-ch: + case <-time.After(5 * time.Second): + } + + assert.Equal(t, len(peers), peersChecked) + }) + + t.Run("missing peers dialed", func(t *testing.T) { + t.Parallel() + + var ( + peers = mock.GeneratePeers(t, 10) + missingPeer = peers[0] + missingAddr = missingPeer.NodeInfo().NetAddress + + peersDialed []types.NetAddress + + mockTransport = &mockTransport{ + dialFn: func( + _ context.Context, + address types.NetAddress, + _ PeerBehavior, + ) (Peer, error) { + peersDialed = append(peersDialed, address) + + if address.Equals(*missingPeer.NodeInfo().NetAddress) { + return missingPeer, nil + } + + return nil, errors.New("invalid dial") + }, + } + ps = &mockSet{ + hasFn: func(id types.ID) bool { + return id != missingPeer.ID() + }, + } + ) + + // Make sure the peers are the + // switch persistent peers + addrs := make([]*types.NetAddress, 0, len(peers)) + + for _, p := range peers { + addrs = append(addrs, p.NodeInfo().NetAddress) + } + + // Create the switch + sw := NewSwitch( + mockTransport, + WithPersistentPeers(addrs), + ) + + // Set the peer set + sw.peers = ps + + // Run the redial loop + ctx, cancelFn := context.WithTimeout( + context.Background(), + 5*time.Second, + ) + defer cancelFn() + + var wg sync.WaitGroup + + wg.Add(2) + + go func() { + defer wg.Done() + + sw.runRedialLoop(ctx) + }() + + go func() { + defer wg.Done() + + deadline := time.After(5 * time.Second) + + for { + select { + case <-deadline: + return + default: + if !sw.dialQueue.Has(missingAddr) { + continue + } + + cancelFn() + + return + } + } + }() + + wg.Wait() + + require.True(t, sw.dialQueue.Has(missingAddr)) + assert.Equal(t, missingAddr, sw.dialQueue.Peek().Address) + }) +} + +func TestMultiplexSwitch_DialPeers(t *testing.T) { + t.Parallel() + + t.Run("self dial request", func(t *testing.T) { + t.Parallel() + + var ( + addr = types.NetAddress{ + ID: "id", + IP: net.IP{}, + } + + p = mock.GeneratePeers(t, 1)[0] + + mockTransport = &mockTransport{ + netAddressFn: func() types.NetAddress { + return addr + }, + } + ) + + // Make sure the "peer" has the same address + // as the transport (node) + p.NodeInfoFn = func() types.NodeInfo { + return types.NodeInfo{ + NetAddress: &addr, + } + } + + sw := NewSwitch(mockTransport) + + // Dial the peers + sw.DialPeers(p.NodeInfo().NetAddress) + + // Make sure the peer wasn't actually dialed + assert.False(t, sw.dialQueue.Has(p.NodeInfo().NetAddress)) + }) + + t.Run("outbound peer limit reached", func(t *testing.T) { + t.Parallel() + + var ( + maxOutbound = uint64(10) + peers = mock.GeneratePeers(t, 10) + + mockTransport = &mockTransport{ + netAddressFn: func() types.NetAddress { + return types.NetAddress{ + ID: "id", + IP: net.IP{}, + } + }, + } + + ps = &mockSet{ + numOutboundFn: func() uint64 { + return maxOutbound + }, + } + ) + + sw := NewSwitch( + mockTransport, + WithMaxOutboundPeers(maxOutbound), + ) + + // Set the peer set + sw.peers = ps + + // Dial the peers + addrs := make([]*types.NetAddress, 0, len(peers)) + + for _, p := range peers { + addrs = append(addrs, p.NodeInfo().NetAddress) + } + + sw.DialPeers(addrs...) + + // Make sure no peers were dialed + for _, p := range peers { + assert.False(t, sw.dialQueue.Has(p.NodeInfo().NetAddress)) + } + }) + + t.Run("peers dialed", func(t *testing.T) { + t.Parallel() + + var ( + maxOutbound = uint64(1000) + peers = mock.GeneratePeers(t, int(maxOutbound/2)) + + mockTransport = &mockTransport{ + netAddressFn: func() types.NetAddress { + return types.NetAddress{ + ID: "id", + IP: net.IP{}, + } + }, + } + ) + + sw := NewSwitch( + mockTransport, + WithMaxOutboundPeers(10), + ) + + // Dial the peers + addrs := make([]*types.NetAddress, 0, len(peers)) + + for _, p := range peers { + addrs = append(addrs, p.NodeInfo().NetAddress) + } + + sw.DialPeers(addrs...) + + // Make sure peers were dialed + for _, p := range peers { + assert.True(t, sw.dialQueue.Has(p.NodeInfo().NetAddress)) + } + }) } From 1f96b45a86a7bc004843f4d67a29b7e3b63f5e12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Thu, 31 Oct 2024 15:06:38 +0100 Subject: [PATCH 28/43] Save transport test progress --- tm2/pkg/p2p/conn/secret_connection.go | 15 +- tm2/pkg/p2p/mock_test.go | 120 ++++ tm2/pkg/p2p/switch.go | 2 +- tm2/pkg/p2p/transport.go | 57 +- tm2/pkg/p2p/transport_test.go | 873 +++++++------------------- 5 files changed, 390 insertions(+), 677 deletions(-) diff --git a/tm2/pkg/p2p/conn/secret_connection.go b/tm2/pkg/p2p/conn/secret_connection.go index a37788b947d..d45b5b3846a 100644 --- a/tm2/pkg/p2p/conn/secret_connection.go +++ b/tm2/pkg/p2p/conn/secret_connection.go @@ -7,6 +7,7 @@ import ( "crypto/sha256" "crypto/subtle" "encoding/binary" + "fmt" "io" "math" "net" @@ -128,7 +129,10 @@ func MakeSecretConnection(conn io.ReadWriteCloser, locPrivKey crypto.PrivKey) (* } // Sign the challenge bytes for authentication. - locSignature := signChallenge(challenge, locPrivKey) + locSignature, err := locPrivKey.Sign(challenge[:]) + if err != nil { + return nil, fmt.Errorf("unable to sign challenge, %w", err) + } // Share (in secret) each other's pubkey & challenge signature authSigMsg, err := shareAuthSignature(sc, locPubKey, locSignature) @@ -424,15 +428,6 @@ func sort32(foo, bar *[32]byte) (lo, hi *[32]byte) { return } -func signChallenge(challenge *[32]byte, locPrivKey crypto.PrivKey) (signature []byte) { - signature, err := locPrivKey.Sign(challenge[:]) - // TODO(ismail): let signChallenge return an error instead - if err != nil { - panic(err) - } - return -} - type authSigMessage struct { Key crypto.PubKey Sig []byte diff --git a/tm2/pkg/p2p/mock_test.go b/tm2/pkg/p2p/mock_test.go index 449662db189..8a0fdc73a78 100644 --- a/tm2/pkg/p2p/mock_test.go +++ b/tm2/pkg/p2p/mock_test.go @@ -3,6 +3,7 @@ package p2p import ( "context" "net" + "time" "github.com/gnolang/gno/tm2/pkg/p2p/types" ) @@ -134,3 +135,122 @@ func (m *mockSet) NumOutbound() uint64 { return 0 } + +type ( + listenerAcceptDelegate func() (net.Conn, error) + closeDelegate func() error + addrDelegate func() net.Addr +) + +type mockListener struct { + acceptFn listenerAcceptDelegate + closeFn closeDelegate + addrFn addrDelegate +} + +func (m *mockListener) Accept() (net.Conn, error) { + if m.acceptFn != nil { + return m.acceptFn() + } + + return nil, nil +} + +func (m *mockListener) Close() error { + if m.closeFn != nil { + return m.closeFn() + } + + return nil +} + +func (m *mockListener) Addr() net.Addr { + if m.addrFn != nil { + return m.addrFn() + } + + return nil +} + +type ( + readDelegate func([]byte) (int, error) + writeDelegate func([]byte) (int, error) + localAddrDelegate func() net.Addr + remoteAddrDelegate func() net.Addr + setDeadlineDelegate func(time.Time) error +) + +type mockConn struct { + readFn readDelegate + writeFn writeDelegate + closeFn closeDelegate + localAddrFn localAddrDelegate + remoteAddrFn remoteAddrDelegate + setDeadlineFn setDeadlineDelegate + setReadDeadlineFn setDeadlineDelegate + setWriteDeadlineFn setDeadlineDelegate +} + +func (m *mockConn) Read(buff []byte) (int, error) { + if m.readFn != nil { + return m.readFn(buff) + } + + return 0, nil +} + +func (m *mockConn) Write(buff []byte) (int, error) { + if m.writeFn != nil { + return m.writeFn(buff) + } + + return 0, nil +} + +func (m *mockConn) Close() error { + if m.closeFn != nil { + return m.closeFn() + } + + return nil +} + +func (m *mockConn) LocalAddr() net.Addr { + if m.localAddrFn != nil { + return m.localAddrFn() + } + + return nil +} + +func (m *mockConn) RemoteAddr() net.Addr { + if m.remoteAddrFn != nil { + return m.remoteAddrFn() + } + + return nil +} + +func (m *mockConn) SetDeadline(t time.Time) error { + if m.setDeadlineFn != nil { + return m.setDeadlineFn(t) + } + + return nil +} + +func (m *mockConn) SetReadDeadline(t time.Time) error { + if m.setReadDeadlineFn != nil { + return m.setReadDeadlineFn(t) + } + + return nil +} + +func (m *mockConn) SetWriteDeadline(t time.Time) error { + if m.setWriteDeadlineFn != nil { + return m.setWriteDeadlineFn(t) + } + + return nil +} diff --git a/tm2/pkg/p2p/switch.go b/tm2/pkg/p2p/switch.go index 477ed5cace2..d21ece5411e 100644 --- a/tm2/pkg/p2p/switch.go +++ b/tm2/pkg/p2p/switch.go @@ -376,7 +376,7 @@ func (sw *MultiplexSwitch) runRedialLoop(ctx context.Context) { // DialPeers adds the peers to the dial queue for async dialing. // To monitor dial progress, subscribe to adequate p2p MultiplexSwitch events -func (sw *MultiplexSwitch) DialPeers(peerAddrs ...*types.NetAddress) { // TODO remove pointer +func (sw *MultiplexSwitch) DialPeers(peerAddrs ...*types.NetAddress) { for _, peerAddr := range peerAddrs { // Check if this is our address if peerAddr.Same(sw.transport.NetAddress()) { diff --git a/tm2/pkg/p2p/transport.go b/tm2/pkg/p2p/transport.go index 3e1a735abc0..f5fb9fde445 100644 --- a/tm2/pkg/p2p/transport.go +++ b/tm2/pkg/p2p/transport.go @@ -3,6 +3,7 @@ package p2p import ( "context" "fmt" + "io" "log/slog" "net" "sync" @@ -25,6 +26,14 @@ var ( errDuplicateConnection = errors.New("duplicate peer connection") ) +type connUpgradeFn func(io.ReadWriteCloser, crypto.PrivKey) (*conn.SecretConnection, error) + +type secretConn interface { + net.Conn + + RemotePubKey() crypto.PubKey +} + // peerInfo is a wrapper for an unverified peer connection type peerInfo struct { addr *types.NetAddress // the dial address of the peer @@ -48,7 +57,7 @@ type MultiplexTransport struct { peerCh chan peerInfo // pipe for inbound peer connections activeConns sync.Map // active peer connections (remote address -> nothing) - handshakeTimeout time.Duration + connUpgradeFn connUpgradeFn // Upgrades the connection to a secret connection // TODO(xla): This config is still needed as we parameterize peerConn and // peer currently. All relevant configuration should be refactored into options @@ -64,12 +73,12 @@ func NewMultiplexTransport( logger *slog.Logger, ) *MultiplexTransport { return &MultiplexTransport{ - peerCh: make(chan peerInfo, 1), - handshakeTimeout: defaultHandshakeTimeout, - mConfig: mConfig, - nodeInfo: nodeInfo, - nodeKey: nodeKey, - logger: logger, + peerCh: make(chan peerInfo, 1), + mConfig: mConfig, + nodeInfo: nodeInfo, + nodeKey: nodeKey, + logger: logger, + connUpgradeFn: conn.MakeSecretConnection, } } @@ -125,12 +134,12 @@ func (mt *MultiplexTransport) Dial( // Close stops the multiplex transport func (mt *MultiplexTransport) Close() error { - mt.cancelFn() - if mt.listener == nil { return nil } + mt.cancelFn() + return mt.listener.Close() } @@ -153,6 +162,9 @@ func (mt *MultiplexTransport) Listen(addr types.NetAddress) error { addr.Port = uint16(tcpAddr.Port) } + // Set up the context + mt.ctx, mt.cancelFn = context.WithCancel(context.Background()) + mt.netAddr = addr mt.listener = ln @@ -164,9 +176,10 @@ func (mt *MultiplexTransport) Listen(addr types.NetAddress) error { } // runAcceptLoop runs the loop where incoming peers are: -// - 1. accepted by the transport -// - 2. filtered -// - 3. upgraded (handshaked + verified) +// +// 1. accepted by the transport +// 2. filtered +// 3. upgraded (handshaked + verified) func (mt *MultiplexTransport) runAcceptLoop() { defer close(mt.peerCh) @@ -271,14 +284,14 @@ func (mt *MultiplexTransport) Remove(p Peer) { // upgradeAndVerifyConn upgrades the connections (performs the handshaking process) // and verifies that the connecting peer is valid -func (mt *MultiplexTransport) upgradeAndVerifyConn(c net.Conn) (*conn.SecretConnection, types.NodeInfo, error) { +func (mt *MultiplexTransport) upgradeAndVerifyConn(c net.Conn) (secretConn, types.NodeInfo, error) { // Upgrade to a secret connection. // A secret connection is a connection that has passed // an initial handshaking process, as defined by the STS // protocol, and is considered to be secure and authentic - secretConn, err := upgradeToSecretConn( + sc, err := mt.upgradeToSecretConn( c, - mt.handshakeTimeout, + defaultHandshakeTimeout, mt.nodeKey.PrivKey, ) if err != nil { @@ -286,13 +299,13 @@ func (mt *MultiplexTransport) upgradeAndVerifyConn(c net.Conn) (*conn.SecretConn } // Exchange node information - nodeInfo, err := exchangeNodeInfo(secretConn, mt.handshakeTimeout, mt.nodeInfo) + nodeInfo, err := exchangeNodeInfo(sc, defaultHandshakeTimeout, mt.nodeInfo) if err != nil { return nil, types.NodeInfo{}, fmt.Errorf("unable to exchange node information, %w", err) } // Ensure the connection ID matches the node's reported ID - connID := secretConn.RemotePubKey().Address().ID() + connID := sc.RemotePubKey().Address().ID() if connID != nodeInfo.ID() { return nil, types.NodeInfo{}, fmt.Errorf( @@ -307,7 +320,7 @@ func (mt *MultiplexTransport) upgradeAndVerifyConn(c net.Conn) (*conn.SecretConn return nil, types.NodeInfo{}, fmt.Errorf("incompatible node info, %w", err) } - return secretConn, nodeInfo, nil + return sc, nodeInfo, nil } // newMultiplexPeer creates a new multiplex Peer, using @@ -357,7 +370,7 @@ func (mt *MultiplexTransport) newMultiplexPeer( // exchangeNodeInfo performs a data swap, where node // info is exchanged between the current node and a peer async func exchangeNodeInfo( - c net.Conn, + c secretConn, timeout time.Duration, nodeInfo types.NodeInfo, ) (types.NodeInfo, error) { @@ -403,17 +416,17 @@ func exchangeNodeInfo( // upgradeToSecretConn takes an active TCP connection, // and upgrades it to a verified, handshaked connection through // the STS protocol -func upgradeToSecretConn( +func (mt *MultiplexTransport) upgradeToSecretConn( c net.Conn, timeout time.Duration, privKey crypto.PrivKey, -) (*conn.SecretConnection, error) { +) (secretConn, error) { if err := c.SetDeadline(time.Now().Add(timeout)); err != nil { return nil, err } // Handshake (STS) - sc, err := conn.MakeSecretConnection(c, privKey) + sc, err := mt.connUpgradeFn(c, privKey) if err != nil { return nil, err } diff --git a/tm2/pkg/p2p/transport_test.go b/tm2/pkg/p2p/transport_test.go index f2c63475f43..d9f5af64044 100644 --- a/tm2/pkg/p2p/transport_test.go +++ b/tm2/pkg/p2p/transport_test.go @@ -1,646 +1,231 @@ package p2p -// var defaultNodeName = "host_peer" -// -// func emptyNodeInfo() NodeInfo { -// return NodeInfo{} -// } -// -// // newMultiplexTransport returns a tcp connected multiplexed peer -// // using the default MultiplexConfigFromP2P. It's a convenience function used -// // for testing. -// func newMultiplexTransport( -// nodeInfo NodeInfo, -// nodeKey NodeKey, -// ) *MultiplexTransport { -// return NewMultiplexTransport( -// nodeInfo, nodeKey, conn.DefaultMConnConfig(), -// ) -// } -// -// func TestTransportMultiplexConnFilter(t *testing.T) { -// t.Parallel() -// -// mt := newMultiplexTransport( -// emptyNodeInfo(), -// NodeKey{ -// PrivKey: ed25519.GenPrivKey(), -// }, -// ) -// id := mt.nodeKey.ID() -// -// MultiplexTransportConnFilters( -// func(_ ConnSet, _ net.Conn, _ []net.IP) error { return nil }, -// func(_ ConnSet, _ net.Conn, _ []net.IP) error { return nil }, -// func(_ ConnSet, _ net.Conn, _ []net.IP) error { -// return fmt.Errorf("rejected") -// }, -// )(mt) -// -// addr, err := NewNetAddressFromString(NetAddressString(id, "127.0.0.1:0")) -// if err != nil { -// t.Fatal(err) -// } -// -// if err := mt.Listen(*addr); err != nil { -// t.Fatal(err) -// } -// -// errc := make(chan error) -// -// go func() { -// addr, err := NewNetAddress(id, mt.listener.Addr()) -// require.NoError(t, err) -// -// _, err = addr.DialTimeout(5 * time.Second) -// if err != nil { -// errc <- err -// return -// } -// -// close(errc) -// }() -// -// if err := <-errc; err != nil { -// t.Errorf("connection failed: %v", err) -// } -// -// _, err = mt.Accept(peerConfig{}) -// if err, ok := err.(RejectedError); ok { -// if !err.IsFiltered() { -// t.Errorf("expected peer to be filtered") -// } -// } else { -// t.Errorf("expected RejectedError") -// } -// } -// -// func TestTransportMultiplexConnFilterTimeout(t *testing.T) { -// t.Parallel() -// -// mt := newMultiplexTransport( -// emptyNodeInfo(), -// NodeKey{ -// PrivKey: ed25519.GenPrivKey(), -// }, -// ) -// id := mt.nodeKey.ID() -// -// MultiplexTransportFilterTimeout(5 * time.Millisecond)(mt) -// MultiplexTransportConnFilters( -// func(_ ConnSet, _ net.Conn, _ []net.IP) error { -// time.Sleep(100 * time.Millisecond) -// return nil -// }, -// )(mt) -// -// addr, err := NewNetAddressFromString(NetAddressString(id, "127.0.0.1:0")) -// if err != nil { -// t.Fatal(err) -// } -// -// if err := mt.Listen(*addr); err != nil { -// t.Fatal(err) -// } -// -// errc := make(chan error) -// -// go func() { -// addr, err := NewNetAddress(id, mt.listener.Addr()) -// require.NoError(t, err) -// -// _, err = addr.DialTimeout(5 * time.Second) -// if err != nil { -// errc <- err -// return -// } -// -// close(errc) -// }() -// -// if err := <-errc; err != nil { -// t.Errorf("connection failed: %v", err) -// } -// -// _, err = mt.Accept(peerConfig{}) -// if _, ok := err.(FilterTimeoutError); !ok { -// t.Errorf("expected FilterTimeoutError") -// } -// } -// -// func TestTransportMultiplexAcceptMultiple(t *testing.T) { -// t.Parallel() -// -// mt := testSetupMultiplexTransport(t) -// laddr, err := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) -// require.NoError(t, err) -// -// var ( -// seed = rand.New(rand.NewSource(time.Now().UnixNano())) -// nDialers = seed.Intn(64) + 64 -// errc = make(chan error, nDialers) -// ) -// -// // Setup dialers. -// for i := 0; i < nDialers; i++ { -// go testDialer(*laddr, errc) -// } -// -// // Catch connection errors. -// for i := 0; i < nDialers; i++ { -// if err := <-errc; err != nil { -// t.Fatal(err) -// } -// } -// -// ps := []Peer{} -// -// // Accept all peers. -// for i := 0; i < cap(errc); i++ { -// p, err := mt.Accept(peerConfig{}) -// if err != nil { -// t.Fatal(err) -// } -// -// if err := p.Start(); err != nil { -// t.Fatal(err) -// } -// -// ps = append(ps, p) -// } -// -// if have, want := len(ps), cap(errc); have != want { -// t.Errorf("have %v, want %v", have, want) -// } -// -// // Stop all peers. -// for _, p := range ps { -// if err := p.Stop(); err != nil { -// t.Fatal(err) -// } -// } -// -// if err := mt.Close(); err != nil { -// t.Errorf("close errored: %v", err) -// } -// } -// -// func testDialer(dialAddr NetAddress, errc chan error) { -// var ( -// pv = ed25519.GenPrivKey() -// dialer = newMultiplexTransport( -// testNodeInfo(pv.PubKey().Address().ID(), defaultNodeName), -// NodeKey{ -// PrivKey: pv, -// }, -// ) -// ) -// -// _, err := dialer.Dial(dialAddr, peerConfig{}) -// if err != nil { -// errc <- err -// return -// } -// -// // Signal that the connection was established. -// errc <- nil -// } -// -// func TestFlappyTransportMultiplexAcceptNonBlocking(t *testing.T) { -// t.Parallel() -// -// testutils.FilterStability(t, testutils.Flappy) -// -// mt := testSetupMultiplexTransport(t) -// -// var ( -// fastNodePV = ed25519.GenPrivKey() -// fastNodeInfo = testNodeInfo(fastNodePV.PubKey().Address().ID(), "fastnode") -// errc = make(chan error) -// fastc = make(chan struct{}) -// slowc = make(chan struct{}) -// ) -// -// // Simulate slow Peer. -// go func() { -// addr, err := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) -// require.NoError(t, err) -// -// c, err := addr.DialTimeout(5 * time.Second) -// if err != nil { -// errc <- err -// return -// } -// -// close(slowc) -// -// select { -// case <-fastc: -// // Fast peer connected. -// case <-time.After(100 * time.Millisecond): -// // We error if the fast peer didn't succeed. -// errc <- fmt.Errorf("Fast peer timed out") -// } -// -// sc, err := upgradeToSecretConn(c, 100*time.Millisecond, ed25519.GenPrivKey()) -// if err != nil { -// errc <- err -// return -// } -// -// _, err = handshake(sc, 100*time.Millisecond, -// testNodeInfo( -// ed25519.GenPrivKey().PubKey().Address().ID(), -// "slow_peer", -// )) -// if err != nil { -// errc <- err -// return -// } -// }() -// -// // Simulate fast Peer. -// go func() { -// <-slowc -// -// dialer := newMultiplexTransport( -// fastNodeInfo, -// NodeKey{ -// PrivKey: fastNodePV, -// }, -// ) -// addr, err := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) -// require.NoError(t, err) -// -// _, err = dialer.Dial(*addr, peerConfig{}) -// if err != nil { -// errc <- err -// return -// } -// -// close(errc) -// close(fastc) -// }() -// -// if err := <-errc; err != nil { -// t.Errorf("connection failed: %v", err) -// } -// -// p, err := mt.Accept(peerConfig{}) -// if err != nil { -// t.Fatal(err) -// } -// -// if have, want := p.NodeInfo(), fastNodeInfo; !reflect.DeepEqual(have, want) { -// t.Errorf("have %v, want %v", have, want) -// } -// } -// -// func TestTransportMultiplexValidateNodeInfo(t *testing.T) { -// t.Parallel() -// -// mt := testSetupMultiplexTransport(t) -// -// errc := make(chan error) -// -// go func() { -// var ( -// pv = ed25519.GenPrivKey() -// dialer = newMultiplexTransport( -// testNodeInfo(pv.PubKey().Address().ID(), ""), // Should not be empty -// NodeKey{ -// PrivKey: pv, -// }, -// ) -// ) -// -// addr, err := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) -// require.NoError(t, err) -// -// _, err = dialer.Dial(*addr, peerConfig{}) -// if err != nil { -// errc <- err -// return -// } -// -// close(errc) -// }() -// -// if err := <-errc; err != nil { -// t.Errorf("connection failed: %v", err) -// } -// -// _, err := mt.Accept(peerConfig{}) -// if err, ok := err.(RejectedError); ok { -// if !err.IsNodeInfoInvalid() { -// t.Errorf("expected NodeInfo to be invalid") -// } -// } else { -// t.Errorf("expected RejectedError") -// } -// } -// -// func TestTransportMultiplexRejectMismatchID(t *testing.T) { -// t.Parallel() -// -// mt := testSetupMultiplexTransport(t) -// -// errc := make(chan error) -// -// go func() { -// dialer := newMultiplexTransport( -// testNodeInfo( -// ed25519.GenPrivKey().PubKey().Address().ID(), "dialer", -// ), -// NodeKey{ -// PrivKey: ed25519.GenPrivKey(), -// }, -// ) -// addr, err := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) -// require.NoError(t, err) -// -// _, err = dialer.Dial(*addr, peerConfig{}) -// if err != nil { -// errc <- err -// return -// } -// -// close(errc) -// }() -// -// if err := <-errc; err != nil { -// t.Errorf("connection failed: %v", err) -// } -// -// _, err := mt.Accept(peerConfig{}) -// if err, ok := err.(RejectedError); ok { -// if !err.IsAuthFailure() { -// t.Errorf("expected auth failure") -// } -// } else { -// t.Errorf("expected RejectedError") -// } -// } -// -// func TestTransportMultiplexDialRejectWrongID(t *testing.T) { -// t.Parallel() -// -// mt := testSetupMultiplexTransport(t) -// -// var ( -// pv = ed25519.GenPrivKey() -// dialer = newMultiplexTransport( -// testNodeInfo(pv.PubKey().Address().ID(), ""), // Should not be empty -// NodeKey{ -// PrivKey: pv, -// }, -// ) -// ) -// -// wrongID := ed25519.GenPrivKey().PubKey().Address().ID() -// addr, err := NewNetAddress(wrongID, mt.listener.Addr()) -// require.NoError(t, err) -// -// _, err = dialer.Dial(*addr, peerConfig{}) -// if err != nil { -// t.Logf("connection failed: %v", err) -// if err, ok := err.(RejectedError); ok { -// if !err.IsAuthFailure() { -// t.Errorf("expected auth failure") -// } -// } else { -// t.Errorf("expected RejectedError") -// } -// } -// } -// -// func TestTransportMultiplexRejectIncompatible(t *testing.T) { -// t.Parallel() -// -// mt := testSetupMultiplexTransport(t) -// -// errc := make(chan error) -// -// go func() { -// var ( -// pv = ed25519.GenPrivKey() -// dialer = newMultiplexTransport( -// testNodeInfoWithNetwork(pv.PubKey().Address().ID(), "dialer", "incompatible-network"), -// NodeKey{ -// PrivKey: pv, -// }, -// ) -// ) -// addr, err := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) -// require.NoError(t, err) -// -// _, err = dialer.Dial(*addr, peerConfig{}) -// if err != nil { -// errc <- err -// return -// } -// -// close(errc) -// }() -// -// _, err := mt.Accept(peerConfig{}) -// if err, ok := err.(RejectedError); ok { -// if !err.IsIncompatible() { -// t.Errorf("expected to reject incompatible") -// } -// } else { -// t.Errorf("expected RejectedError") -// } -// } -// -// func TestTransportMultiplexRejectSelf(t *testing.T) { -// t.Parallel() -// -// mt := testSetupMultiplexTransport(t) -// -// errc := make(chan error) -// -// go func() { -// addr, err := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) -// require.NoError(t, err) -// -// _, err = mt.Dial(*addr, peerConfig{}) -// if err != nil { -// errc <- err -// return -// } -// -// close(errc) -// }() -// -// if err := <-errc; err != nil { -// if err, ok := err.(RejectedError); ok { -// if !err.IsSelf() { -// t.Errorf("expected to reject self, got: %v", err) -// } -// } else { -// t.Errorf("expected RejectedError") -// } -// } else { -// t.Errorf("expected connection failure") -// } -// -// _, err := mt.Accept(peerConfig{}) -// if err, ok := err.(RejectedError); ok { -// if !err.IsSelf() { -// t.Errorf("expected to reject self, got: %v", err) -// } -// } else { -// t.Errorf("expected RejectedError") -// } -// } -// -// func TestTransportConnDuplicateIPFilter(t *testing.T) { -// t.Parallel() -// -// filter := ConnDuplicateIPFilter() -// -// if err := filter(nil, &testTransportConn{}, nil); err != nil { -// t.Fatal(err) -// } -// -// var ( -// c = &testTransportConn{} -// cs = NewConnSet() -// ) -// -// cs.Set(c, []net.IP{ -// {10, 0, 10, 1}, -// {10, 0, 10, 2}, -// {10, 0, 10, 3}, -// }) -// -// if err := filter(cs, c, []net.IP{ -// {10, 0, 10, 2}, -// }); err == nil { -// t.Errorf("expected Peer to be rejected as duplicate") -// } -// } -// -// func TestTransportHandshake(t *testing.T) { -// t.Parallel() -// -// ln, err := net.Listen("tcp", "127.0.0.1:0") -// if err != nil { -// t.Fatal(err) -// } -// -// var ( -// peerPV = ed25519.GenPrivKey() -// peerNodeInfo = testNodeInfo(peerPV.PubKey().Address().ID(), defaultNodeName) -// ) -// -// go func() { -// c, err := net.Dial(ln.Addr().Network(), ln.Addr().String()) -// if err != nil { -// t.Error(err) -// return -// } -// -// go func(c net.Conn) { -// _, err := amino.MarshalSizedWriter(c, peerNodeInfo) -// if err != nil { -// t.Error(err) -// } -// }(c) -// go func(c net.Conn) { -// var ni NodeInfo -// -// _, err := amino.UnmarshalSizedReader( -// c, -// &ni, -// int64(MaxNodeInfoSize), -// ) -// if err != nil { -// t.Error(err) -// } -// }(c) -// }() -// -// c, err := ln.Accept() -// if err != nil { -// t.Fatal(err) -// } -// -// ni, err := handshake(c, 100*time.Millisecond, emptyNodeInfo()) -// if err != nil { -// t.Fatal(err) -// } -// -// if have, want := ni, peerNodeInfo; !reflect.DeepEqual(have, want) { -// t.Errorf("have %v, want %v", have, want) -// } -// } -// -// // create listener -// func testSetupMultiplexTransport(t *testing.T) *MultiplexTransport { -// t.Helper() -// -// var ( -// pv = ed25519.GenPrivKey() -// id = pv.PubKey().Address().ID() -// mt = newMultiplexTransport( -// testNodeInfo( -// id, "transport", -// ), -// NodeKey{ -// PrivKey: pv, -// }, -// ) -// ) -// -// addr, err := NewNetAddressFromString(NetAddressString(id, "127.0.0.1:0")) -// if err != nil { -// t.Fatal(err) -// } -// -// if err := mt.Listen(*addr); err != nil { -// t.Fatal(err) -// } -// -// return mt -// } -// -// type testTransportAddr struct{} -// -// func (a *testTransportAddr) Network() string { return "tcp" } -// func (a *testTransportAddr) String() string { return "test.local:1234" } -// -// type testTransportConn struct{} -// -// func (c *testTransportConn) Close() error { -// return fmt.Errorf("Close() not implemented") -// } -// -// func (c *testTransportConn) LocalAddr() net.Addr { -// return &testTransportAddr{} -// } -// -// func (c *testTransportConn) RemoteAddr() net.Addr { -// return &testTransportAddr{} -// } -// -// func (c *testTransportConn) Read(_ []byte) (int, error) { -// return -1, fmt.Errorf("Read() not implemented") -// } -// -// func (c *testTransportConn) SetDeadline(_ time.Time) error { -// return fmt.Errorf("SetDeadline() not implemented") -// } -// -// func (c *testTransportConn) SetReadDeadline(_ time.Time) error { -// return fmt.Errorf("SetReadDeadline() not implemented") -// } -// -// func (c *testTransportConn) SetWriteDeadline(_ time.Time) error { -// return fmt.Errorf("SetWriteDeadline() not implemented") -// } -// -// func (c *testTransportConn) Write(_ []byte) (int, error) { -// return -1, fmt.Errorf("Write() not implemented") -// } +import ( + "context" + "net" + "testing" + + "github.com/gnolang/gno/tm2/pkg/log" + "github.com/gnolang/gno/tm2/pkg/p2p/conn" + "github.com/gnolang/gno/tm2/pkg/p2p/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// generateNetAddr generates dummy net addresses +func generateNetAddr(t *testing.T, count int) []types.NetAddress { + addrs := make([]types.NetAddress, 0, count) + + for i := 0; i < count; i++ { + var ( + key = types.GenerateNodeKey() + address = "127.0.0.1:4123" // specific port + ) + + tcpAddr, err := net.ResolveTCPAddr("tcp", address) + require.NoError(t, err) + + addr, err := types.NewNetAddress(key.ID(), tcpAddr) + require.NoError(t, err) + + addrs = append(addrs, *addr) + } + + return addrs +} + +func TestMultiplexTransport_NetAddress(t *testing.T) { + t.Parallel() + + t.Run("transport not active", func(t *testing.T) { + t.Parallel() + + var ( + ni = types.NodeInfo{} + nk = types.NodeKey{} + mCfg = conn.DefaultMConnConfig() + logger = log.NewNoopLogger() + ) + + transport := NewMultiplexTransport(ni, nk, mCfg, logger) + addr := transport.NetAddress() + + assert.Error(t, addr.Validate()) + }) + + t.Run("active transport on random port", func(t *testing.T) { + t.Parallel() + + var ( + ni = types.NodeInfo{} + nk = types.NodeKey{} + mCfg = conn.DefaultMConnConfig() + logger = log.NewNoopLogger() + addr = generateNetAddr(t, 1)[0] + ) + + transport := NewMultiplexTransport(ni, nk, mCfg, logger) + + require.NoError(t, transport.Listen(addr)) + defer func() { + require.NoError(t, transport.Close()) + }() + + netAddr := transport.NetAddress() + assert.False(t, netAddr.Equals(addr)) + assert.NoError(t, netAddr.Validate()) + }) + + t.Run("active transport on specific port", func(t *testing.T) { + t.Parallel() + + var ( + ni = types.NodeInfo{} + nk = types.NodeKey{} + mCfg = conn.DefaultMConnConfig() + logger = log.NewNoopLogger() + addr = generateNetAddr(t, 1)[0] + ) + + transport := NewMultiplexTransport(ni, nk, mCfg, logger) + + require.NoError(t, transport.Listen(addr)) + defer func() { + require.NoError(t, transport.Close()) + }() + + netAddr := transport.NetAddress() + assert.True(t, netAddr.Equals(addr)) + assert.NoError(t, netAddr.Validate()) + }) +} + +func TestMultiplexTransport_Accept(t *testing.T) { + t.Parallel() + + t.Run("inactive transport", func(t *testing.T) { + t.Parallel() + + var ( + ni = types.NodeInfo{} + nk = types.NodeKey{} + mCfg = conn.DefaultMConnConfig() + logger = log.NewNoopLogger() + ) + + transport := NewMultiplexTransport(ni, nk, mCfg, logger) + + p, err := transport.Accept(context.Background(), nil) + + assert.Nil(t, p) + assert.ErrorIs( + t, + err, + errTransportInactive, + ) + }) + + t.Run("transport closed", func(t *testing.T) { + t.Parallel() + + var ( + ni = types.NodeInfo{} + nk = types.NodeKey{} + mCfg = conn.DefaultMConnConfig() + logger = log.NewNoopLogger() + addr = generateNetAddr(t, 1)[0] + ) + + transport := NewMultiplexTransport(ni, nk, mCfg, logger) + + // Start the transport + require.NoError(t, transport.Listen(addr)) + + // Stop the transport + require.NoError(t, transport.Close()) + + p, err := transport.Accept(context.Background(), nil) + + assert.Nil(t, p) + assert.ErrorIs( + t, + err, + errTransportClosed, + ) + }) + + t.Run("context canceled", func(t *testing.T) { + t.Parallel() + + var ( + ni = types.NodeInfo{} + nk = types.NodeKey{} + mCfg = conn.DefaultMConnConfig() + logger = log.NewNoopLogger() + addr = generateNetAddr(t, 1)[0] + ) + + transport := NewMultiplexTransport(ni, nk, mCfg, logger) + + // Start the transport + require.NoError(t, transport.Listen(addr)) + + ctx, cancelFn := context.WithCancel(context.Background()) + cancelFn() + + p, err := transport.Accept(ctx, nil) + + assert.Nil(t, p) + assert.ErrorIs( + t, + err, + context.Canceled, + ) + }) + + t.Run("peer accepted", func(t *testing.T) { + t.Parallel() + + var ( + ni = types.NodeInfo{} + nk = types.NodeKey{} + mCfg = conn.DefaultMConnConfig() + logger = log.NewNoopLogger() + addr = generateNetAddr(t, 1)[0] + + mockConn = &mockConn{} + mockListener = &mockListener{ + acceptFn: func() (net.Conn, error) { + return mockConn, nil + }, + } + ) + + transport := NewMultiplexTransport(ni, nk, mCfg, logger) + + // Set the listener + transport.listener = mockListener + + p, err := transport.Accept(context.Background(), nil) + + assert.Nil(t, p) + assert.ErrorIs( + t, + err, + context.Canceled, + ) + + }) +} + +func TestMultiplexTransport_Dial(t *testing.T) { + t.Parallel() + + // TODO implement +} + +func TestMultiplexTransport_Listen(t *testing.T) { + t.Parallel() + + // TODO implement +} From 9b156bc0445dca2c2c86fedcf089509fc625e3c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Sat, 9 Nov 2024 17:10:58 +0100 Subject: [PATCH 29/43] Respect private peer IDs --- tm2/pkg/bft/node/node.go | 15 +++++++++++++-- tm2/pkg/p2p/discovery/discovery.go | 8 ++++++-- tm2/pkg/p2p/mock/peer.go | 10 ++++++++++ tm2/pkg/p2p/peer.go | 6 ++++++ tm2/pkg/p2p/switch.go | 17 +++++++++++++++++ tm2/pkg/p2p/switch_option.go | 9 +++++++++ tm2/pkg/p2p/transport.go | 1 + tm2/pkg/p2p/types.go | 6 +++++- tm2/pkg/p2p/types/key.go | 22 ++++++++++++++++++++++ 9 files changed, 89 insertions(+), 5 deletions(-) diff --git a/tm2/pkg/bft/node/node.go b/tm2/pkg/bft/node/node.go index 0a38df6a000..dd0c541e20b 100644 --- a/tm2/pkg/bft/node/node.go +++ b/tm2/pkg/bft/node/node.go @@ -443,17 +443,28 @@ func NewNode(config *cfg.Config, ) // Setup MultiplexSwitch. - peerAddrs, errs := p2pTypes.NewNetAddressFromStrings(splitAndTrimEmpty(config.P2P.PersistentPeers, ",", " ")) - for _, err := range errs { + peerAddrs, errs := p2pTypes.NewNetAddressFromStrings( + splitAndTrimEmpty(config.P2P.PersistentPeers, ",", " "), + ) + for _, err = range errs { p2pLogger.Error("invalid persistent peer address", "err", err) } + // Parse the private peer IDs + privatePeerIDs, errs := p2pTypes.NewIDFromStrings( + splitAndTrimEmpty(config.P2P.PrivatePeerIDs, ",", " "), + ) + for _, err = range errs { + p2pLogger.Error("invalid private peer ID", "err", err) + } + sw := p2p.NewSwitch( transport, p2p.WithReactor("MEMPOOL", mempoolReactor), p2p.WithReactor("BLOCKCHAIN", bcReactor), p2p.WithReactor("CONSENSUS", consensusReactor), p2p.WithPersistentPeers(peerAddrs), + p2p.WithPrivatePeers(privatePeerIDs), p2p.WithMaxInboundPeers(config.P2P.MaxNumInboundPeers), p2p.WithMaxOutboundPeers(config.P2P.MaxNumOutboundPeers), ) diff --git a/tm2/pkg/p2p/discovery/discovery.go b/tm2/pkg/p2p/discovery/discovery.go index 132e0855cf8..fc14efc3ee0 100644 --- a/tm2/pkg/p2p/discovery/discovery.go +++ b/tm2/pkg/p2p/discovery/discovery.go @@ -11,6 +11,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/p2p" "github.com/gnolang/gno/tm2/pkg/p2p/conn" "github.com/gnolang/gno/tm2/pkg/p2p/types" + "golang.org/x/exp/slices" ) const ( @@ -82,7 +83,7 @@ func (r *Reactor) StartDiscovery() { return case <-ticker.C: - // Run the discovery protocol + // Run the discovery protocol // // Grab a random peer, and engage // them for peer discovery @@ -187,7 +188,10 @@ func (r *Reactor) handleDiscoveryRequest(peer p2p.Peer) error { peers = make([]*types.NetAddress, 0, len(localPeers)) ) - // TODO exclude private peers + // Exclude the private peers from being shared + localPeers = slices.DeleteFunc(localPeers, func(p p2p.Peer) bool { + return p.IsPrivate() + }) // Shuffle and limit the peers shared shufflePeers(localPeers) diff --git a/tm2/pkg/p2p/mock/peer.go b/tm2/pkg/p2p/mock/peer.go index d3fdbe7b34a..f6809f2deee 100644 --- a/tm2/pkg/p2p/mock/peer.go +++ b/tm2/pkg/p2p/mock/peer.go @@ -21,6 +21,7 @@ type ( remoteAddrDelegate func() net.Addr isOutboundDelegate func() bool isPersistentDelegate func() bool + isPrivateDelegate func() bool closeConnDelegate func() error nodeInfoDelegate func() types.NodeInfo statusDelegate func() conn.ConnectionStatus @@ -80,6 +81,7 @@ type Peer struct { RemoteAddrFn remoteAddrDelegate IsOutboundFn isOutboundDelegate IsPersistentFn isPersistentDelegate + IsPrivateFn isPrivateDelegate CloseConnFn closeConnDelegate NodeInfoFn nodeInfoDelegate StopFn stopDelegate @@ -145,6 +147,14 @@ func (m *Peer) IsPersistent() bool { return false } +func (m *Peer) IsPrivate() bool { + if m.IsPrivateFn != nil { + return m.IsPrivateFn() + } + + return false +} + func (m *Peer) CloseConn() error { if m.CloseConnFn != nil { return m.CloseConnFn() diff --git a/tm2/pkg/p2p/peer.go b/tm2/pkg/p2p/peer.go index 62375d4fe35..4be0367af3e 100644 --- a/tm2/pkg/p2p/peer.go +++ b/tm2/pkg/p2p/peer.go @@ -22,6 +22,7 @@ type ConnConfig struct { type ConnInfo struct { Outbound bool // flag indicating if the connection is dialed Persistent bool // flag indicating if the connection is persistent + Private bool // flag indicating if the peer is private (not shared) Conn net.Conn // the source connection RemoteIP net.IP // the remote IP of the peer SocketAddr *types.NetAddress @@ -100,6 +101,11 @@ func (p *peer) IsPersistent() bool { return p.connInfo.Persistent } +// IsPrivate returns true if the peer is private, false otherwise. +func (p *peer) IsPrivate() bool { + return p.connInfo.Private +} + // SocketAddr returns the address of the socket. // For outbound peers, it's the address dialed (after DNS resolution). // For inbound peers, it's the address returned by the underlying connection diff --git a/tm2/pkg/p2p/switch.go b/tm2/pkg/p2p/switch.go index d21ece5411e..ddc4f92d4f3 100644 --- a/tm2/pkg/p2p/switch.go +++ b/tm2/pkg/p2p/switch.go @@ -21,6 +21,7 @@ type reactorPeerBehavior struct { handlePeerErrFn func(Peer, error) isPersistentPeerFn func(*types.NetAddress) bool + isPrivatePeerFn func(types.ID) bool } func (r *reactorPeerBehavior) ReactorChDescriptors() []*conn.ChannelDescriptor { @@ -39,6 +40,10 @@ func (r *reactorPeerBehavior) IsPersistentPeer(address *types.NetAddress) bool { return r.isPersistentPeerFn(address) } +func (r *reactorPeerBehavior) IsPrivatePeer(id types.ID) bool { + return r.isPrivatePeerFn(id) +} + // MultiplexSwitch handles peer connections and exposes an API to receive incoming messages // on `Reactors`. Each `Reactor` is responsible for handling incoming messages of one // or more `Channels`. So while sending outgoing messages is typically performed on the peer, @@ -57,6 +62,7 @@ type MultiplexSwitch struct { peers PeerSet // currently active peer set persistentPeers sync.Map // ID -> *NetAddress; peers whose connections are constant + privatePeers sync.Map // ID -> *NetAddress; peers who are not shared transport Transport dialQueue *dial.Queue @@ -86,6 +92,9 @@ func NewSwitch( isPersistentPeerFn: func(peer *types.NetAddress) bool { return sw.isPersistentPeer(peer.ID) }, + isPrivatePeerFn: func(id types.ID) bool { + return sw.isPrivatePeer(id) + }, } sw.BaseService = *service.NewBaseService(nil, "P2P MultiplexSwitch", sw) @@ -411,6 +420,14 @@ func (sw *MultiplexSwitch) isPersistentPeer(id types.ID) bool { return persistent } +// isPrivatePeer returns a flag indicating if a peer +// is present in the private peer set +func (sw *MultiplexSwitch) isPrivatePeer(id types.ID) bool { + _, persistent := sw.privatePeers.Load(id) + + return persistent +} + // runAcceptLoop is the main powerhouse method // for accepting incoming peer connections, filtering them, // and persisting them diff --git a/tm2/pkg/p2p/switch_option.go b/tm2/pkg/p2p/switch_option.go index 54356d83c1b..1ad334c0d85 100644 --- a/tm2/pkg/p2p/switch_option.go +++ b/tm2/pkg/p2p/switch_option.go @@ -46,6 +46,15 @@ func WithPersistentPeers(peerAddrs []*types.NetAddress) SwitchOption { } } +// WithPrivatePeers sets the p2p switch's private peer set +func WithPrivatePeers(peerIDs []types.ID) SwitchOption { + return func(sw *MultiplexSwitch) { + for _, addr := range peerIDs { + sw.privatePeers.Store(peerIDs, addr) + } + } +} + // WithMaxInboundPeers sets the p2p switch's maximum inbound peer limit func WithMaxInboundPeers(maxInbound uint64) SwitchOption { return func(sw *MultiplexSwitch) { diff --git a/tm2/pkg/p2p/transport.go b/tm2/pkg/p2p/transport.go index f5fb9fde445..6afa4fca5b5 100644 --- a/tm2/pkg/p2p/transport.go +++ b/tm2/pkg/p2p/transport.go @@ -351,6 +351,7 @@ func (mt *MultiplexTransport) newMultiplexPeer( peerConn := &ConnInfo{ Outbound: isOutbound, Persistent: persistent, + Private: behavior.IsPrivatePeer(info.nodeInfo.ID()), Conn: info.conn, RemoteIP: ips[0], // IPv4 SocketAddr: info.addr, diff --git a/tm2/pkg/p2p/types.go b/tm2/pkg/p2p/types.go index 36d751458f4..7631802c97d 100644 --- a/tm2/pkg/p2p/types.go +++ b/tm2/pkg/p2p/types.go @@ -26,6 +26,7 @@ type Peer interface { IsOutbound() bool // did we dial the peer IsPersistent() bool // do we redial this peer when we disconnect + IsPrivate() bool // do we share the peer CloseConn() error // close original connection @@ -104,5 +105,8 @@ type PeerBehavior interface { HandlePeerError(Peer, error) // IsPersistentPeer returns a flag indicating if the given peer is persistent - IsPersistentPeer(*types.NetAddress) bool + IsPersistentPeer(*types.NetAddress) bool // TODO change this to also be IDs + + // IsPrivatePeer returns a flag indicating if the given peer is private + IsPrivatePeer(types.ID) bool } diff --git a/tm2/pkg/p2p/types/key.go b/tm2/pkg/p2p/types/key.go index f6074dbc2e3..bc45de709d8 100644 --- a/tm2/pkg/p2p/types/key.go +++ b/tm2/pkg/p2p/types/key.go @@ -13,6 +13,28 @@ import ( // ID represents the cryptographically unique Peer ID type ID = crypto.ID +// NewIDFromStrings returns an array of ID's build using +// the provided strings +func NewIDFromStrings(idStrs []string) ([]ID, []error) { + var ( + ids = make([]ID, 0, len(idStrs)) + errs = make([]error, 0, len(idStrs)) + ) + + for _, idStr := range idStrs { + id := ID(idStr) + if err := id.Validate(); err != nil { + errs = append(errs, err) + + continue + } + + ids = append(ids, id) + } + + return ids, errs +} + // NodeKey is the persistent peer key. // It contains the nodes private key for authentication. // NOTE: keep in sync with gno.land/cmd/gnoland/secrets.go From ba9c83d208430f02a47225cb6f058301017430b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Sat, 9 Nov 2024 17:14:16 +0100 Subject: [PATCH 30/43] Standardize peer lookup methods for the peer behavior --- tm2/pkg/p2p/switch.go | 10 +++++----- tm2/pkg/p2p/transport.go | 4 ++-- tm2/pkg/p2p/types.go | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tm2/pkg/p2p/switch.go b/tm2/pkg/p2p/switch.go index ddc4f92d4f3..7ca5c96d956 100644 --- a/tm2/pkg/p2p/switch.go +++ b/tm2/pkg/p2p/switch.go @@ -20,7 +20,7 @@ type reactorPeerBehavior struct { reactorsByCh map[byte]Reactor handlePeerErrFn func(Peer, error) - isPersistentPeerFn func(*types.NetAddress) bool + isPersistentPeerFn func(types.ID) bool isPrivatePeerFn func(types.ID) bool } @@ -36,8 +36,8 @@ func (r *reactorPeerBehavior) HandlePeerError(p Peer, err error) { r.handlePeerErrFn(p, err) } -func (r *reactorPeerBehavior) IsPersistentPeer(address *types.NetAddress) bool { - return r.isPersistentPeerFn(address) +func (r *reactorPeerBehavior) IsPersistentPeer(id types.ID) bool { + return r.isPersistentPeerFn(id) } func (r *reactorPeerBehavior) IsPrivatePeer(id types.ID) bool { @@ -89,8 +89,8 @@ func NewSwitch( chDescs: make([]*conn.ChannelDescriptor, 0), reactorsByCh: make(map[byte]Reactor), handlePeerErrFn: sw.StopPeerForError, - isPersistentPeerFn: func(peer *types.NetAddress) bool { - return sw.isPersistentPeer(peer.ID) + isPersistentPeerFn: func(id types.ID) bool { + return sw.isPersistentPeer(id) }, isPrivatePeerFn: func(id types.ID) bool { return sw.isPrivatePeer(id) diff --git a/tm2/pkg/p2p/transport.go b/tm2/pkg/p2p/transport.go index 6afa4fca5b5..bfa18d807ac 100644 --- a/tm2/pkg/p2p/transport.go +++ b/tm2/pkg/p2p/transport.go @@ -332,8 +332,8 @@ func (mt *MultiplexTransport) newMultiplexPeer( ) (Peer, error) { // Check for peer persistence using the dial address, // as well as the self-reported address - persistent := behavior.IsPersistentPeer(info.addr) || - behavior.IsPersistentPeer(info.nodeInfo.NetAddress) + persistent := behavior.IsPersistentPeer(info.addr.ID) || + behavior.IsPersistentPeer(info.nodeInfo.NetAddress.ID) // Extract the host host, _, err := net.SplitHostPort(info.conn.RemoteAddr().String()) diff --git a/tm2/pkg/p2p/types.go b/tm2/pkg/p2p/types.go index 7631802c97d..fe6bccec072 100644 --- a/tm2/pkg/p2p/types.go +++ b/tm2/pkg/p2p/types.go @@ -105,7 +105,7 @@ type PeerBehavior interface { HandlePeerError(Peer, error) // IsPersistentPeer returns a flag indicating if the given peer is persistent - IsPersistentPeer(*types.NetAddress) bool // TODO change this to also be IDs + IsPersistentPeer(types.ID) bool // IsPrivatePeer returns a flag indicating if the given peer is private IsPrivatePeer(types.ID) bool From c744b3f4ff00f2190662cbab551ddf7da73dca00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Sat, 9 Nov 2024 17:41:54 +0100 Subject: [PATCH 31/43] Wrap up transport Accept unit tests --- tm2/pkg/p2p/transport_test.go | 66 ++++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 17 deletions(-) diff --git a/tm2/pkg/p2p/transport_test.go b/tm2/pkg/p2p/transport_test.go index d9f5af64044..6ddfc260ce7 100644 --- a/tm2/pkg/p2p/transport_test.go +++ b/tm2/pkg/p2p/transport_test.go @@ -17,15 +17,13 @@ func generateNetAddr(t *testing.T, count int) []types.NetAddress { addrs := make([]types.NetAddress, 0, count) for i := 0; i < count; i++ { - var ( - key = types.GenerateNodeKey() - address = "127.0.0.1:4123" // specific port - ) + key := types.GenerateNodeKey() - tcpAddr, err := net.ResolveTCPAddr("tcp", address) + // Grab a random port + ln, err := net.Listen("tcp", "127.0.0.1:0") require.NoError(t, err) - addr, err := types.NewNetAddress(key.ID(), tcpAddr) + addr, err := types.NewNetAddress(key.ID(), ln.Addr()) require.NoError(t, err) addrs = append(addrs, *addr) @@ -64,6 +62,8 @@ func TestMultiplexTransport_NetAddress(t *testing.T) { addr = generateNetAddr(t, 1)[0] ) + addr.Port = 0 // random port + transport := NewMultiplexTransport(ni, nk, mCfg, logger) require.NoError(t, transport.Listen(addr)) @@ -87,6 +87,8 @@ func TestMultiplexTransport_NetAddress(t *testing.T) { addr = generateNetAddr(t, 1)[0] ) + addr.Port = 4123 // specific port + transport := NewMultiplexTransport(ni, nk, mCfg, logger) require.NoError(t, transport.Listen(addr)) @@ -136,6 +138,8 @@ func TestMultiplexTransport_Accept(t *testing.T) { addr = generateNetAddr(t, 1)[0] ) + addr.Port = 0 + transport := NewMultiplexTransport(ni, nk, mCfg, logger) // Start the transport @@ -165,6 +169,8 @@ func TestMultiplexTransport_Accept(t *testing.T) { addr = generateNetAddr(t, 1)[0] ) + addr.Port = 0 + transport := NewMultiplexTransport(ni, nk, mCfg, logger) // Start the transport @@ -187,34 +193,60 @@ func TestMultiplexTransport_Accept(t *testing.T) { t.Parallel() var ( - ni = types.NodeInfo{} - nk = types.NodeKey{} mCfg = conn.DefaultMConnConfig() logger = log.NewNoopLogger() addr = generateNetAddr(t, 1)[0] - mockConn = &mockConn{} + mockConn = &mockConn{ + remoteAddrFn: func() net.Addr { + return &net.TCPAddr{ + IP: addr.IP, + Port: int(addr.Port), + } + }, + } mockListener = &mockListener{ acceptFn: func() (net.Conn, error) { return mockConn, nil }, } + + pi = peerInfo{ + addr: &addr, + conn: mockConn, + nodeInfo: types.NodeInfo{ + NetAddress: &addr, + }, + } + + peerBehavior = &reactorPeerBehavior{ + chDescs: make([]*conn.ChannelDescriptor, 0), + reactorsByCh: make(map[byte]Reactor), + handlePeerErrFn: func(_ Peer, err error) { + require.NoError(t, err) + }, + isPersistentPeerFn: func(_ types.ID) bool { + return false + }, + isPrivatePeerFn: func(_ types.ID) bool { + return false + }, + } ) - transport := NewMultiplexTransport(ni, nk, mCfg, logger) + transport := NewMultiplexTransport(types.NodeInfo{}, types.NodeKey{}, mCfg, logger) // Set the listener transport.listener = mockListener - p, err := transport.Accept(context.Background(), nil) + // Prepare the peer info for accepting + transport.peerCh <- pi - assert.Nil(t, p) - assert.ErrorIs( - t, - err, - context.Canceled, - ) + // Accept the peer + p, err := transport.Accept(context.Background(), peerBehavior) + require.NoError(t, err) + assert.Equal(t, pi.nodeInfo, p.NodeInfo()) }) } From 1f952dac6f2703bf593785069e394b3546e10314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Sat, 9 Nov 2024 17:56:20 +0100 Subject: [PATCH 32/43] Add unit test for private peers in discovery --- tm2/pkg/p2p/discovery/discovery_test.go | 102 ++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/tm2/pkg/p2p/discovery/discovery_test.go b/tm2/pkg/p2p/discovery/discovery_test.go index 4ff63346000..bd6f9d4bf3f 100644 --- a/tm2/pkg/p2p/discovery/discovery_test.go +++ b/tm2/pkg/p2p/discovery/discovery_test.go @@ -222,6 +222,108 @@ func TestReactor_DiscoveryResponse(t *testing.T) { assert.Nil(t, capturedSend) }) + t.Run("private peers not shared", func(t *testing.T) { + t.Parallel() + + var ( + publicPeers = 1 + privatePeers = 50 + + peers = mock.GeneratePeers(t, publicPeers+privatePeers) + notifCh = make(chan struct{}, 1) + + capturedSend []byte + + mockPeer = &mock.Peer{ + SendFn: func(chID byte, data []byte) bool { + require.Equal(t, Channel, chID) + + capturedSend = data + + notifCh <- struct{}{} + + return true + }, + } + + ps = &mockPeerSet{ + listFn: func() []p2p.Peer { + listed := make([]p2p.Peer, 0, len(peers)) + + for _, peer := range peers { + listed = append(listed, peer) + } + + return listed + }, + numInboundFn: func() uint64 { + return uint64(len(peers)) + }, + } + + mockSwitch = &mockSwitch{ + peersFn: func() p2p.PeerSet { + return ps + }, + } + ) + + // Mark all except the last X peers as private + for _, peer := range peers[:privatePeers] { + peer.IsPrivateFn = func() bool { + return true + } + } + + r := NewReactor( + WithDiscoveryInterval(10 * time.Millisecond), + ) + + // Set the mock switch + r.SetSwitch(mockSwitch) + + // Prepare the message + req := &Request{} + + preparedReq, err := amino.MarshalAny(req) + require.NoError(t, err) + + // Receive the message + r.Receive(Channel, mockPeer, preparedReq) + + select { + case <-notifCh: + case <-time.After(5 * time.Second): + } + + // Make sure the adequate message was captured + require.NotNil(t, capturedSend) + + // Parse the message + var msg Message + + require.NoError(t, amino.Unmarshal(capturedSend, &msg)) + + // Make sure the base message is valid + require.NoError(t, msg.ValidateBasic()) + + resp, ok := msg.(*Response) + require.True(t, ok) + + // Make sure the peers are valid + require.Len(t, resp.Peers, publicPeers) + + slices.ContainsFunc(resp.Peers, func(addr *types.NetAddress) bool { + for _, localP := range peers { + if localP.NodeInfo().NetAddress.Equals(*addr) { + return true + } + } + + return false + }) + }) + t.Run("peer response received", func(t *testing.T) { t.Parallel() From d32b926be264cb378e9c36e3e07a8e5d7b494c3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Sat, 9 Nov 2024 18:28:51 +0100 Subject: [PATCH 33/43] Drop useless test --- tm2/pkg/p2p/transport_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tm2/pkg/p2p/transport_test.go b/tm2/pkg/p2p/transport_test.go index 6ddfc260ce7..a2374014d92 100644 --- a/tm2/pkg/p2p/transport_test.go +++ b/tm2/pkg/p2p/transport_test.go @@ -189,7 +189,7 @@ func TestMultiplexTransport_Accept(t *testing.T) { ) }) - t.Run("peer accepted", func(t *testing.T) { + t.Run("peer accepted, direct", func(t *testing.T) { t.Parallel() var ( @@ -248,15 +248,15 @@ func TestMultiplexTransport_Accept(t *testing.T) { assert.Equal(t, pi.nodeInfo, p.NodeInfo()) }) -} -func TestMultiplexTransport_Dial(t *testing.T) { - t.Parallel() + t.Run("peer accepted, accept loop", func(t *testing.T) { + t.Parallel() - // TODO implement + // TODO implement + }) } -func TestMultiplexTransport_Listen(t *testing.T) { +func TestMultiplexTransport_Dial(t *testing.T) { t.Parallel() // TODO implement From a82475e550dd59391e0a58acbe6ae584faf8ee8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Fri, 15 Nov 2024 17:03:38 +0900 Subject: [PATCH 34/43] Add additional unit tests --- tm2/pkg/p2p/mock_test.go | 156 +++++++++++++++ tm2/pkg/p2p/switch.go | 2 +- tm2/pkg/p2p/switch_option.go | 15 +- tm2/pkg/p2p/switch_test.go | 83 +++++++- tm2/pkg/p2p/transport.go | 28 +-- tm2/pkg/p2p/transport_test.go | 346 +++++++++++++++++++++++++++++----- 6 files changed, 559 insertions(+), 71 deletions(-) diff --git a/tm2/pkg/p2p/mock_test.go b/tm2/pkg/p2p/mock_test.go index 8a0fdc73a78..25fbb800e69 100644 --- a/tm2/pkg/p2p/mock_test.go +++ b/tm2/pkg/p2p/mock_test.go @@ -2,9 +2,11 @@ package p2p import ( "context" + "log/slog" "net" "time" + "github.com/gnolang/gno/tm2/pkg/p2p/conn" "github.com/gnolang/gno/tm2/pkg/p2p/types" ) @@ -254,3 +256,157 @@ func (m *mockConn) SetWriteDeadline(t time.Time) error { return nil } + +type ( + startDelegate func() error + onStartDelegate func() error + stopDelegate func() error + onStopDelegate func() + resetDelegate func() error + onResetDelegate func() error + isRunningDelegate func() bool + quitDelegate func() <-chan struct{} + stringDelegate func() string + setLoggerDelegate func(*slog.Logger) + setSwitchDelegate func(Switch) + getChannelsDelegate func() []*conn.ChannelDescriptor + initPeerDelegate func(Peer) + addPeerDelegate func(Peer) + removeSwitchPeerDelegate func(Peer, any) + receiveDelegate func(byte, Peer, []byte) +) + +type mockReactor struct { + startFn startDelegate + onStartFn onStartDelegate + stopFn stopDelegate + onStopFn onStopDelegate + resetFn resetDelegate + onResetFn onResetDelegate + isRunningFn isRunningDelegate + quitFn quitDelegate + stringFn stringDelegate + setLoggerFn setLoggerDelegate + setSwitchFn setSwitchDelegate + getChannelsFn getChannelsDelegate + initPeerFn initPeerDelegate + addPeerFn addPeerDelegate + removePeerFn removeSwitchPeerDelegate + receiveFn receiveDelegate +} + +func (m *mockReactor) Start() error { + if m.startFn != nil { + return m.startFn() + } + + return nil +} + +func (m *mockReactor) OnStart() error { + if m.onStartFn != nil { + return m.onStartFn() + } + + return nil +} + +func (m *mockReactor) Stop() error { + if m.stopFn != nil { + return m.stopFn() + } + + return nil +} + +func (m *mockReactor) OnStop() { + if m.onStopFn != nil { + m.onStopFn() + } +} + +func (m *mockReactor) Reset() error { + if m.resetFn != nil { + return m.resetFn() + } + + return nil +} + +func (m *mockReactor) OnReset() error { + if m.onResetFn != nil { + return m.onResetFn() + } + + return nil +} + +func (m *mockReactor) IsRunning() bool { + if m.isRunningFn != nil { + return m.isRunningFn() + } + + return false +} + +func (m *mockReactor) Quit() <-chan struct{} { + if m.quitFn != nil { + return m.quitFn() + } + + return nil +} + +func (m *mockReactor) String() string { + if m.stringFn != nil { + return m.stringFn() + } + + return "" +} + +func (m *mockReactor) SetLogger(logger *slog.Logger) { + if m.setLoggerFn != nil { + m.setLoggerFn(logger) + } +} + +func (m *mockReactor) SetSwitch(s Switch) { + if m.setSwitchFn != nil { + m.setSwitchFn(s) + } +} + +func (m *mockReactor) GetChannels() []*conn.ChannelDescriptor { + if m.getChannelsFn != nil { + return m.getChannelsFn() + } + + return nil +} + +func (m *mockReactor) InitPeer(peer Peer) Peer { + if m.initPeerFn != nil { + m.initPeerFn(peer) + } + + return nil +} + +func (m *mockReactor) AddPeer(peer Peer) { + if m.addPeerFn != nil { + m.addPeerFn(peer) + } +} + +func (m *mockReactor) RemovePeer(peer Peer, reason any) { + if m.removePeerFn != nil { + m.removePeerFn(peer, reason) + } +} + +func (m *mockReactor) Receive(chID byte, peer Peer, msgBytes []byte) { + if m.receiveFn != nil { + m.receiveFn(chID, peer, msgBytes) + } +} diff --git a/tm2/pkg/p2p/switch.go b/tm2/pkg/p2p/switch.go index 7ca5c96d956..1ec18437b4a 100644 --- a/tm2/pkg/p2p/switch.go +++ b/tm2/pkg/p2p/switch.go @@ -62,7 +62,7 @@ type MultiplexSwitch struct { peers PeerSet // currently active peer set persistentPeers sync.Map // ID -> *NetAddress; peers whose connections are constant - privatePeers sync.Map // ID -> *NetAddress; peers who are not shared + privatePeers sync.Map // ID -> nothing; peers who are not shared transport Transport dialQueue *dial.Queue diff --git a/tm2/pkg/p2p/switch_option.go b/tm2/pkg/p2p/switch_option.go index 1ad334c0d85..83a6920f2cd 100644 --- a/tm2/pkg/p2p/switch_option.go +++ b/tm2/pkg/p2p/switch_option.go @@ -1,8 +1,6 @@ package p2p import ( - "fmt" - "github.com/gnolang/gno/tm2/pkg/p2p/types" ) @@ -17,14 +15,7 @@ func WithReactor(name string, reactor Reactor) SwitchOption { // No two reactors can share the same channel if sw.peerBehavior.reactorsByCh[chID] != nil { - panic( - fmt.Sprintf( - "Channel %X has multiple reactors %v & %v", - chID, - sw.peerBehavior.reactorsByCh[chID], - reactor, - ), - ) + continue } sw.peerBehavior.chDescs = append(sw.peerBehavior.chDescs, chDesc) @@ -49,8 +40,8 @@ func WithPersistentPeers(peerAddrs []*types.NetAddress) SwitchOption { // WithPrivatePeers sets the p2p switch's private peer set func WithPrivatePeers(peerIDs []types.ID) SwitchOption { return func(sw *MultiplexSwitch) { - for _, addr := range peerIDs { - sw.privatePeers.Store(peerIDs, addr) + for _, id := range peerIDs { + sw.privatePeers.Store(id, struct{}{}) } } } diff --git a/tm2/pkg/p2p/switch_test.go b/tm2/pkg/p2p/switch_test.go index fd05ded96a1..992c547eab4 100644 --- a/tm2/pkg/p2p/switch_test.go +++ b/tm2/pkg/p2p/switch_test.go @@ -15,6 +15,78 @@ import ( "github.com/stretchr/testify/require" ) +func TestMultiplexSwitch_Options(t *testing.T) { + t.Parallel() + + t.Run("custom reactors", func(t *testing.T) { + t.Parallel() + + var ( + name = "custom reactor" + mockReactor = &mockReactor{ + setSwitchFn: func(s Switch) { + require.NotNil(t, s) + }, + } + ) + + sw := NewSwitch(nil, WithReactor(name, mockReactor)) + + assert.Equal(t, mockReactor, sw.reactors[name]) + }) + + t.Run("persistent peers", func(t *testing.T) { + t.Parallel() + + peers := generateNetAddr(t, 10) + + sw := NewSwitch(nil, WithPersistentPeers(peers)) + + for _, p := range peers { + assert.True(t, sw.isPersistentPeer(p.ID)) + } + }) + + t.Run("private peers", func(t *testing.T) { + t.Parallel() + + var ( + peers = generateNetAddr(t, 10) + ids = make([]types.ID, 0, len(peers)) + ) + + for _, p := range peers { + ids = append(ids, p.ID) + } + + sw := NewSwitch(nil, WithPrivatePeers(ids)) + + for _, p := range peers { + assert.True(t, sw.isPrivatePeer(p.ID)) + } + }) + + t.Run("max inbound peers", func(t *testing.T) { + t.Parallel() + + maxInbound := uint64(500) + + sw := NewSwitch(nil, WithMaxInboundPeers(maxInbound)) + + assert.Equal(t, maxInbound, sw.maxInboundPeers) + }) + + t.Run("max outbound peers", func(t *testing.T) { + t.Parallel() + + maxOutbound := uint64(500) + + sw := NewSwitch(nil, WithMaxOutboundPeers(maxOutbound)) + + assert.Equal(t, maxOutbound, sw.maxOutboundPeers) + }) +} + func TestMultiplexSwitch_Broadcast(t *testing.T) { t.Parallel() @@ -24,10 +96,19 @@ func TestMultiplexSwitch_Broadcast(t *testing.T) { expectedChID = byte(10) expectedData = []byte("broadcast data") + mockTransport = &mockTransport{ + acceptFn: func(_ context.Context, _ PeerBehavior) (Peer, error) { + return nil, errors.New("constant error") + }, + } + peers = mock.GeneratePeers(t, 10) - sw = NewSwitch(nil) + sw = NewSwitch(mockTransport) ) + require.NoError(t, sw.OnStart()) + t.Cleanup(sw.OnStop) + // Create a new peer set sw.peers = newSet() diff --git a/tm2/pkg/p2p/transport.go b/tm2/pkg/p2p/transport.go index bfa18d807ac..fad0701d15e 100644 --- a/tm2/pkg/p2p/transport.go +++ b/tm2/pkg/p2p/transport.go @@ -21,9 +21,12 @@ import ( const defaultHandshakeTimeout = 3 * time.Second var ( - errTransportClosed = errors.New("transport is closed") - errTransportInactive = errors.New("transport is inactive") - errDuplicateConnection = errors.New("duplicate peer connection") + errTransportClosed = errors.New("transport is closed") + errTransportInactive = errors.New("transport is inactive") + errDuplicateConnection = errors.New("duplicate peer connection") + errPeerIDNodeInfoMismatch = errors.New("connection ID does not match node info ID") + errPeerIDDialMismatch = errors.New("connection ID does not match dialed ID") + errIncompatibleNodeInfo = errors.New("incompatible node info") ) type connUpgradeFn func(io.ReadWriteCloser, crypto.PrivKey) (*conn.SecretConnection, error) @@ -244,15 +247,12 @@ func (mt *MultiplexTransport) processConn(c net.Conn, expectedID types.ID) (peer return peerInfo{}, fmt.Errorf("unable to upgrade connection, %w", err) } - // Verify the connection ID + // Grab the connection ID. + // At this point, the connection and information shared + // with the peer is considered valid, since full handshaking + // and verification took place id := secretConn.RemotePubKey().Address().ID() - if err = id.Validate(); err != nil { - mt.activeConns.Delete(dialAddr) - - return peerInfo{}, fmt.Errorf("unable to validate connection ID, %w", err) - } - // The reason the dial ID needs to be verified is because // for outbound peers (peers the node dials), there is an expected peer ID // when initializing the outbound connection, that can differ from the exchanged one. @@ -262,7 +262,8 @@ func (mt *MultiplexTransport) processConn(c net.Conn, expectedID types.ID) (peer mt.activeConns.Delete(dialAddr) return peerInfo{}, fmt.Errorf( - "connection ID does not match dialed ID (expected %q got %q)", + "%w (expected %q got %q)", + errPeerIDDialMismatch, expectedID, id, ) @@ -309,7 +310,8 @@ func (mt *MultiplexTransport) upgradeAndVerifyConn(c net.Conn) (secretConn, type if connID != nodeInfo.ID() { return nil, types.NodeInfo{}, fmt.Errorf( - "connection ID does not match node info ID (expected %q got %q)", + "%w (expected %q got %q)", + errPeerIDNodeInfoMismatch, connID.String(), nodeInfo.ID().String(), ) @@ -317,7 +319,7 @@ func (mt *MultiplexTransport) upgradeAndVerifyConn(c net.Conn) (secretConn, type // Check compatibility with the node if err = mt.nodeInfo.CompatibleWith(nodeInfo); err != nil { - return nil, types.NodeInfo{}, fmt.Errorf("incompatible node info, %w", err) + return nil, types.NodeInfo{}, fmt.Errorf("%w, %w", errIncompatibleNodeInfo, err) } return sc, nodeInfo, nil diff --git a/tm2/pkg/p2p/transport_test.go b/tm2/pkg/p2p/transport_test.go index a2374014d92..29818e5bafe 100644 --- a/tm2/pkg/p2p/transport_test.go +++ b/tm2/pkg/p2p/transport_test.go @@ -2,19 +2,22 @@ package p2p import ( "context" + "fmt" "net" "testing" + "time" "github.com/gnolang/gno/tm2/pkg/log" "github.com/gnolang/gno/tm2/pkg/p2p/conn" "github.com/gnolang/gno/tm2/pkg/p2p/types" + "github.com/gnolang/gno/tm2/pkg/versionset" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // generateNetAddr generates dummy net addresses -func generateNetAddr(t *testing.T, count int) []types.NetAddress { - addrs := make([]types.NetAddress, 0, count) +func generateNetAddr(t *testing.T, count int) []*types.NetAddress { + addrs := make([]*types.NetAddress, 0, count) for i := 0; i < count; i++ { key := types.GenerateNodeKey() @@ -26,7 +29,7 @@ func generateNetAddr(t *testing.T, count int) []types.NetAddress { addr, err := types.NewNetAddress(key.ID(), ln.Addr()) require.NoError(t, err) - addrs = append(addrs, *addr) + addrs = append(addrs, addr) } return addrs @@ -66,13 +69,13 @@ func TestMultiplexTransport_NetAddress(t *testing.T) { transport := NewMultiplexTransport(ni, nk, mCfg, logger) - require.NoError(t, transport.Listen(addr)) + require.NoError(t, transport.Listen(*addr)) defer func() { require.NoError(t, transport.Close()) }() netAddr := transport.NetAddress() - assert.False(t, netAddr.Equals(addr)) + assert.False(t, netAddr.Equals(*addr)) assert.NoError(t, netAddr.Validate()) }) @@ -91,13 +94,13 @@ func TestMultiplexTransport_NetAddress(t *testing.T) { transport := NewMultiplexTransport(ni, nk, mCfg, logger) - require.NoError(t, transport.Listen(addr)) + require.NoError(t, transport.Listen(*addr)) defer func() { require.NoError(t, transport.Close()) }() netAddr := transport.NetAddress() - assert.True(t, netAddr.Equals(addr)) + assert.True(t, netAddr.Equals(*addr)) assert.NoError(t, netAddr.Validate()) }) } @@ -143,7 +146,7 @@ func TestMultiplexTransport_Accept(t *testing.T) { transport := NewMultiplexTransport(ni, nk, mCfg, logger) // Start the transport - require.NoError(t, transport.Listen(addr)) + require.NoError(t, transport.Listen(*addr)) // Stop the transport require.NoError(t, transport.Close()) @@ -174,7 +177,7 @@ func TestMultiplexTransport_Accept(t *testing.T) { transport := NewMultiplexTransport(ni, nk, mCfg, logger) // Start the transport - require.NoError(t, transport.Listen(addr)) + require.NoError(t, transport.Listen(*addr)) ctx, cancelFn := context.WithCancel(context.Background()) cancelFn() @@ -189,34 +192,99 @@ func TestMultiplexTransport_Accept(t *testing.T) { ) }) - t.Run("peer accepted, direct", func(t *testing.T) { + t.Run("duplicate peer connection", func(t *testing.T) { + t.Parallel() + }) + + t.Run("peer ID mismatch", func(t *testing.T) { t.Parallel() var ( - mCfg = conn.DefaultMConnConfig() - logger = log.NewNoopLogger() - addr = generateNetAddr(t, 1)[0] + network = "dev" + mCfg = conn.DefaultMConnConfig() + logger = log.NewNoopLogger() + keys = []*types.NodeKey{ + types.GenerateNodeKey(), + types.GenerateNodeKey(), + } - mockConn = &mockConn{ - remoteAddrFn: func() net.Addr { - return &net.TCPAddr{ - IP: addr.IP, - Port: int(addr.Port), - } + peerBehavior = &reactorPeerBehavior{ + chDescs: make([]*conn.ChannelDescriptor, 0), + reactorsByCh: make(map[byte]Reactor), + handlePeerErrFn: func(_ Peer, err error) { + require.NoError(t, err) }, - } - mockListener = &mockListener{ - acceptFn: func() (net.Conn, error) { - return mockConn, nil + isPersistentPeerFn: func(_ types.ID) bool { + return false + }, + isPrivatePeerFn: func(_ types.ID) bool { + return false }, } + ) - pi = peerInfo{ - addr: &addr, - conn: mockConn, - nodeInfo: types.NodeInfo{ - NetAddress: &addr, - }, + peers := make([]*MultiplexTransport, 0, len(keys)) + + for index, key := range keys { + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + require.NoError(t, err) + + id := key.ID() + + if index%1 == 0 { + // Hijack the key value + id = types.GenerateNodeKey().ID() + } + + na, err := types.NewNetAddress(id, addr) + require.NoError(t, err) + + ni := types.NodeInfo{ + Network: network, // common network + NetAddress: na, + Version: "v1.0.0-rc.0", + Moniker: fmt.Sprintf("node-%d", index), + VersionSet: make(versionset.VersionSet, 0), // compatible version set + Channels: []byte{42}, // common channel + } + + // Create a fresh transport + tr := NewMultiplexTransport(ni, *key, mCfg, logger) + + // Start the transport + require.NoError(t, tr.Listen(*na)) + + t.Cleanup(func() { + assert.NoError(t, tr.Close()) + }) + + peers = append( + peers, + tr, + ) + } + + // Make peer 1 --dial--> peer 2, and handshake. + // This "upgrade" should fail because the peer shared a different + // peer ID than what they actually used for the secret connection + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + p, err := peers[0].Dial(ctx, peers[1].netAddr, peerBehavior) + assert.ErrorIs(t, err, errPeerIDNodeInfoMismatch) + require.Nil(t, p) + }) + + t.Run("incompatible peers", func(t *testing.T) { + t.Parallel() + + var ( + network = "dev" + mCfg = conn.DefaultMConnConfig() + logger = log.NewNoopLogger() + keys = []*types.NodeKey{ + types.GenerateNodeKey(), + types.GenerateNodeKey(), } peerBehavior = &reactorPeerBehavior{ @@ -234,30 +302,220 @@ func TestMultiplexTransport_Accept(t *testing.T) { } ) - transport := NewMultiplexTransport(types.NodeInfo{}, types.NodeKey{}, mCfg, logger) + peers := make([]*MultiplexTransport, 0, len(keys)) - // Set the listener - transport.listener = mockListener + for index, key := range keys { + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + require.NoError(t, err) - // Prepare the peer info for accepting - transport.peerCh <- pi + id := key.ID() - // Accept the peer - p, err := transport.Accept(context.Background(), peerBehavior) - require.NoError(t, err) + na, err := types.NewNetAddress(id, addr) + require.NoError(t, err) + + chainID := network + + if index%2 == 0 { + chainID = "totally-random-network" + } - assert.Equal(t, pi.nodeInfo, p.NodeInfo()) + ni := types.NodeInfo{ + Network: chainID, + NetAddress: na, + Version: "v1.0.0-rc.0", + Moniker: fmt.Sprintf("node-%d", index), + VersionSet: make(versionset.VersionSet, 0), // compatible version set + Channels: []byte{42}, // common channel + } + + // Create a fresh transport + tr := NewMultiplexTransport(ni, *key, mCfg, logger) + + // Start the transport + require.NoError(t, tr.Listen(*na)) + + t.Cleanup(func() { + assert.NoError(t, tr.Close()) + }) + + peers = append( + peers, + tr, + ) + } + + // Make peer 1 --dial--> peer 2, and handshake. + // This "upgrade" should fail because the peer shared a different + // peer ID than what they actually used for the secret connection + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + p, err := peers[0].Dial(ctx, peers[1].netAddr, peerBehavior) + assert.ErrorIs(t, err, errIncompatibleNodeInfo) + require.Nil(t, p) }) - t.Run("peer accepted, accept loop", func(t *testing.T) { + t.Run("dialed peer ID mismatch", func(t *testing.T) { t.Parallel() - // TODO implement + var ( + network = "dev" + mCfg = conn.DefaultMConnConfig() + logger = log.NewNoopLogger() + keys = []*types.NodeKey{ + types.GenerateNodeKey(), + types.GenerateNodeKey(), + } + + peerBehavior = &reactorPeerBehavior{ + chDescs: make([]*conn.ChannelDescriptor, 0), + reactorsByCh: make(map[byte]Reactor), + handlePeerErrFn: func(_ Peer, err error) { + require.NoError(t, err) + }, + isPersistentPeerFn: func(_ types.ID) bool { + return false + }, + isPrivatePeerFn: func(_ types.ID) bool { + return false + }, + } + ) + + peers := make([]*MultiplexTransport, 0, len(keys)) + + for index, key := range keys { + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + require.NoError(t, err) + + na, err := types.NewNetAddress(key.ID(), addr) + require.NoError(t, err) + + ni := types.NodeInfo{ + Network: network, // common network + NetAddress: na, + Version: "v1.0.0-rc.0", + Moniker: fmt.Sprintf("node-%d", index), + VersionSet: make(versionset.VersionSet, 0), // compatible version set + Channels: []byte{42}, // common channel + } + + // Create a fresh transport + tr := NewMultiplexTransport(ni, *key, mCfg, logger) + + // Start the transport + require.NoError(t, tr.Listen(*na)) + + t.Cleanup(func() { + assert.NoError(t, tr.Close()) + }) + + peers = append( + peers, + tr, + ) + } + + // Make peer 1 --dial--> peer 2, and handshake + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + p, err := peers[0].Dial( + ctx, + types.NetAddress{ + ID: types.GenerateNodeKey().ID(), // mismatched ID + IP: peers[1].netAddr.IP, + Port: peers[1].netAddr.Port, + }, + peerBehavior, + ) + assert.ErrorIs(t, err, errPeerIDDialMismatch) + assert.Nil(t, p) }) -} -func TestMultiplexTransport_Dial(t *testing.T) { - t.Parallel() + t.Run("valid peer accepted", func(t *testing.T) { + t.Parallel() - // TODO implement + var ( + network = "dev" + mCfg = conn.DefaultMConnConfig() + logger = log.NewNoopLogger() + keys = []*types.NodeKey{ + types.GenerateNodeKey(), + types.GenerateNodeKey(), + } + + peerBehavior = &reactorPeerBehavior{ + chDescs: make([]*conn.ChannelDescriptor, 0), + reactorsByCh: make(map[byte]Reactor), + handlePeerErrFn: func(_ Peer, err error) { + require.NoError(t, err) + }, + isPersistentPeerFn: func(_ types.ID) bool { + return false + }, + isPrivatePeerFn: func(_ types.ID) bool { + return false + }, + } + ) + + peers := make([]*MultiplexTransport, 0, len(keys)) + + for index, key := range keys { + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + require.NoError(t, err) + + na, err := types.NewNetAddress(key.ID(), addr) + require.NoError(t, err) + + ni := types.NodeInfo{ + Network: network, // common network + NetAddress: na, + Version: "v1.0.0-rc.0", + Moniker: fmt.Sprintf("node-%d", index), + VersionSet: make(versionset.VersionSet, 0), // compatible version set + Channels: []byte{42}, // common channel + } + + // Create a fresh transport + tr := NewMultiplexTransport(ni, *key, mCfg, logger) + + // Start the transport + require.NoError(t, tr.Listen(*na)) + + t.Cleanup(func() { + assert.NoError(t, tr.Close()) + }) + + peers = append( + peers, + tr, + ) + } + + // Make peer 1 --dial--> peer 2, and handshake + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + p, err := peers[0].Dial(ctx, peers[1].netAddr, peerBehavior) + require.NoError(t, err) + require.NotNil(t, p) + + // Make sure the new peer info is valid + assert.Equal(t, peers[1].netAddr.ID, p.ID()) + + assert.Equal(t, peers[1].nodeInfo.Channels, p.NodeInfo().Channels) + assert.Equal(t, peers[1].nodeInfo.Moniker, p.NodeInfo().Moniker) + assert.Equal(t, peers[1].nodeInfo.Network, p.NodeInfo().Network) + + // Attempt to dial again, expect the dial to fail + // because the connection is already active + dialedPeer, err := peers[0].Dial(ctx, peers[1].netAddr, peerBehavior) + require.ErrorIs(t, err, errDuplicateConnection) + assert.Nil(t, dialedPeer) + + // Remove the peer + peers[0].Remove(p) + }) } From 7c59c1356a93e386c85bee004e21fcb4483a4b91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Sat, 16 Nov 2024 12:51:53 +0900 Subject: [PATCH 35/43] Save unit test progress --- gno.land/cmd/gnoland/secrets_get.go | 3 +- tm2/pkg/bft/mempool/reactor.go | 22 +-- tm2/pkg/bft/mempool/reactor_test.go | 220 ++++++++++++++++++++++++---- tm2/pkg/bft/node/node.go | 8 +- tm2/pkg/bft/rpc/client/local.go | 8 - tm2/pkg/bft/rpc/core/net.go | 42 +----- tm2/pkg/bft/rpc/core/net_test.go | 77 ---------- tm2/pkg/bft/rpc/core/pipe.go | 3 - tm2/pkg/bft/rpc/core/routes.go | 2 - tm2/pkg/p2p/discovery/mock_test.go | 19 ++- tm2/pkg/p2p/events/doc.go | 3 + tm2/pkg/p2p/events/events.go | 104 +++++++++++++ tm2/pkg/p2p/events/events_test.go | 94 ++++++++++++ tm2/pkg/p2p/events/types.go | 39 +++++ tm2/pkg/p2p/mock_test.go | 8 - tm2/pkg/p2p/set.go | 18 +-- tm2/pkg/p2p/set_test.go | 48 ------ tm2/pkg/p2p/switch.go | 29 +++- tm2/pkg/p2p/switch_test.go | 38 ++--- tm2/pkg/p2p/types.go | 5 +- 20 files changed, 517 insertions(+), 273 deletions(-) delete mode 100644 tm2/pkg/bft/rpc/core/net_test.go create mode 100644 tm2/pkg/p2p/events/doc.go create mode 100644 tm2/pkg/p2p/events/events.go create mode 100644 tm2/pkg/p2p/events/events_test.go create mode 100644 tm2/pkg/p2p/events/types.go diff --git a/gno.land/cmd/gnoland/secrets_get.go b/gno.land/cmd/gnoland/secrets_get.go index 64eb48f7d27..0a0a714f6ee 100644 --- a/gno.land/cmd/gnoland/secrets_get.go +++ b/gno.land/cmd/gnoland/secrets_get.go @@ -12,7 +12,6 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/privval" "github.com/gnolang/gno/tm2/pkg/commands" osm "github.com/gnolang/gno/tm2/pkg/os" - "github.com/gnolang/gno/tm2/pkg/p2p" "github.com/gnolang/gno/tm2/pkg/p2p/types" ) @@ -200,7 +199,7 @@ func readNodeID(path string) (*nodeIDInfo, error) { // constructP2PAddress constructs the P2P address other nodes can use // to connect directly -func constructP2PAddress(nodeID p2p.ID, listenAddress string) string { +func constructP2PAddress(nodeID types.ID, listenAddress string) string { var ( address string parts = strings.SplitN(listenAddress, "://", 2) diff --git a/tm2/pkg/bft/mempool/reactor.go b/tm2/pkg/bft/mempool/reactor.go index 634d92277c2..acb4e351f3f 100644 --- a/tm2/pkg/bft/mempool/reactor.go +++ b/tm2/pkg/bft/mempool/reactor.go @@ -47,12 +47,12 @@ type mempoolIDs struct { // Reserve searches for the next unused ID and assigns it to the // peer. -func (ids *mempoolIDs) ReserveForPeer(peer p2p.Peer) { +func (ids *mempoolIDs) ReserveForPeer(id p2pTypes.ID) { ids.mtx.Lock() defer ids.mtx.Unlock() curID := ids.nextPeerID() - ids.peerMap[peer.ID()] = curID + ids.peerMap[id] = curID ids.activeIDs[curID] = struct{}{} } @@ -74,23 +74,23 @@ func (ids *mempoolIDs) nextPeerID() uint16 { } // Reclaim returns the ID reserved for the peer back to unused pool. -func (ids *mempoolIDs) Reclaim(peer p2p.Peer) { +func (ids *mempoolIDs) Reclaim(id p2pTypes.ID) { ids.mtx.Lock() defer ids.mtx.Unlock() - removedID, ok := ids.peerMap[peer.ID()] + removedID, ok := ids.peerMap[id] if ok { delete(ids.activeIDs, removedID) - delete(ids.peerMap, peer.ID()) + delete(ids.peerMap, id) } } // GetForPeer returns an ID reserved for the peer. -func (ids *mempoolIDs) GetForPeer(peer p2p.Peer) uint16 { +func (ids *mempoolIDs) GetForPeer(id p2pTypes.ID) uint16 { ids.mtx.RLock() defer ids.mtx.RUnlock() - return ids.peerMap[peer.ID()] + return ids.peerMap[id] } func newMempoolIDs() *mempoolIDs { @@ -140,13 +140,13 @@ func (memR *Reactor) GetChannels() []*p2p.ChannelDescriptor { // AddPeer implements Reactor. // It starts a broadcast routine ensuring all txs are forwarded to the given peer. func (memR *Reactor) AddPeer(peer p2p.Peer) { - memR.ids.ReserveForPeer(peer) + memR.ids.ReserveForPeer(peer.ID()) go memR.broadcastTxRoutine(peer) } // RemovePeer implements Reactor. func (memR *Reactor) RemovePeer(peer p2p.Peer, reason interface{}) { - memR.ids.Reclaim(peer) + memR.ids.Reclaim(peer.ID()) // broadcast routine checks if peer is gone and returns } @@ -163,7 +163,7 @@ func (memR *Reactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) { switch msg := msg.(type) { case *TxMessage: - peerID := memR.ids.GetForPeer(src) + peerID := memR.ids.GetForPeer(src.ID()) err := memR.mempool.CheckTxWithInfo(msg.Tx, nil, TxInfo{SenderID: peerID}) if err != nil { memR.Logger.Info("Could not check tx", "tx", txID(msg.Tx), "err", err) @@ -185,7 +185,7 @@ func (memR *Reactor) broadcastTxRoutine(peer p2p.Peer) { return } - peerID := memR.ids.GetForPeer(peer) + peerID := memR.ids.GetForPeer(peer.ID()) var next *clist.CElement for { // In case of both next.NextWaitChan() and peer.Quit() are variable at the same time diff --git a/tm2/pkg/bft/mempool/reactor_test.go b/tm2/pkg/bft/mempool/reactor_test.go index 19403d09b76..d877af3e9cc 100644 --- a/tm2/pkg/bft/mempool/reactor_test.go +++ b/tm2/pkg/bft/mempool/reactor_test.go @@ -1,13 +1,23 @@ package mempool import ( + "context" + "fmt" + "log/slog" "net" + "os" "sync" "testing" "time" "github.com/fortytw2/leaktest" + "github.com/gnolang/gno/tm2/pkg/p2p/conn" + "github.com/gnolang/gno/tm2/pkg/p2p/events" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" + "github.com/gnolang/gno/tm2/pkg/versionset" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/sync/errgroup" "github.com/gnolang/gno/tm2/pkg/bft/abci/example/kvstore" memcfg "github.com/gnolang/gno/tm2/pkg/bft/mempool/config" @@ -17,7 +27,6 @@ import ( "github.com/gnolang/gno/tm2/pkg/log" "github.com/gnolang/gno/tm2/pkg/p2p" p2pcfg "github.com/gnolang/gno/tm2/pkg/p2p/config" - "github.com/gnolang/gno/tm2/pkg/p2p/mock" "github.com/gnolang/gno/tm2/pkg/testutils" ) @@ -39,26 +48,178 @@ func (ps peerState) GetHeight() int64 { } // connect N mempool reactors through N switches -func makeAndConnectReactors(mconfig *memcfg.MempoolConfig, pconfig *p2pcfg.P2PConfig, n int) []*Reactor { - reactors := make([]*Reactor, n) - logger := log.NewNoopLogger() +func makeAndConnectReactors(t *testing.T, mconfig *memcfg.MempoolConfig, pconfig *p2pcfg.P2PConfig, n int) []*Reactor { + t.Helper() + + var ( + reactors = make([]*Reactor, n) + logger = log.NewNoopLogger() + options = make(map[int][]p2p.SwitchOption) + ) + for i := 0; i < n; i++ { app := kvstore.NewKVStoreApplication() cc := proxy.NewLocalClientCreator(app) mempool, cleanup := newMempoolWithApp(cc) defer cleanup() - reactors[i] = NewReactor(mconfig, mempool) // so we dont start the consensus states - reactors[i].SetLogger(logger.With("validator", i)) + reactor := NewReactor(mconfig, mempool) // so we dont start the consensus states + reactor.SetLogger(logger.With("validator", i)) + + options[i] = []p2p.SwitchOption{ + p2p.WithReactor("MEMPOOL", reactor), + } + + reactors[i] = reactor } - p2p.MakeConnectedSwitches(pconfig, n, func(i int, s *p2p.MultiplexSwitch) *p2p.MultiplexSwitch { - s.AddReactor("MEMPOOL", reactors[i]) - return s - }, p2p.Connect2Switches) + // "Simulate" the networking layer + MakeConnectedSwitches(t, pconfig, n, options) + return reactors } +func MakeConnectedSwitches( + t *testing.T, + cfg *p2pcfg.P2PConfig, + n int, + opts map[int][]p2p.SwitchOption, +) []*p2p.MultiplexSwitch { + t.Helper() + + var ( + sws = make([]*p2p.MultiplexSwitch, 0, n) + ts = make([]*p2p.MultiplexTransport, 0, n) + addrs = make([]*p2pTypes.NetAddress, 0, n) + + // TODO remove + lgs = make([]*slog.Logger, 0, n) + ) + + // Generate the switches + for i := range n { + var ( + key = p2pTypes.GenerateNodeKey() + tcpAddr = &net.TCPAddr{ + IP: net.ParseIP("127.0.0.1"), + Port: 0, // random port + } + ) + + addr, err := p2pTypes.NewNetAddress(key.ID(), tcpAddr) + require.NoError(t, err) + + info := p2pTypes.NodeInfo{ + VersionSet: versionset.VersionSet{ + versionset.VersionInfo{ + Name: "p2p", + Version: "v0.0.0", + }, + }, + NetAddress: addr, + Network: "testing", + Software: "p2ptest", + Version: "v1.2.3-rc.0-deadbeef", + Channels: []byte{0x01}, + Moniker: fmt.Sprintf("node-%d", i), + Other: p2pTypes.NodeInfoOther{ + TxIndex: "off", + RPCAddress: fmt.Sprintf("127.0.0.1:%d", 0), + }, + } + + // TODO remove + lg := slog.New(slog.NewTextHandler(os.Stdout, nil)) + + // Create the multiplex transport + multiplexTransport := p2p.NewMultiplexTransport( + info, + *key, + conn.MConfigFromP2P(cfg), + lg, + ) + + // Start the transport + require.NoError(t, multiplexTransport.Listen(*info.NetAddress)) + + t.Cleanup(func() { + assert.NoError(t, multiplexTransport.Close()) + }) + + dialAddr := multiplexTransport.NetAddress() + addrs = append(addrs, &dialAddr) + + ts = append(ts, multiplexTransport) + + // TODO remove + lgs = append(lgs, lg) + } + + ctx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second) + defer cancelFn() + + g, _ := errgroup.WithContext(ctx) + + for i := range n { + // Make sure the switches connect to each other. + // Set up event listeners to make sure + // the setup method blocks until switches are connected + // Create the multiplex switch + newOpts := []p2p.SwitchOption{ + p2p.WithPersistentPeers(addrs), + } + + newOpts = append(newOpts, opts[i]...) + + multiplexSwitch := p2p.NewMultiplexSwitch(ts[i], newOpts...) + + multiplexSwitch.SetLogger(lgs[i].With("sw", i)) // TODO remove + + ch, unsubFn := multiplexSwitch.Subscribe(func(event events.Event) bool { + return event.Type() == events.PeerConnected + }) + + // Start the switch + require.NoError(t, multiplexSwitch.Start()) + + sws = append(sws, multiplexSwitch) + + g.Go(func() error { + defer func() { + unsubFn() + }() + + timer := time.NewTimer(5 * time.Second) + defer timer.Stop() + + connectedPeers := make(map[p2pTypes.ID]struct{}) + + for { + select { + case evRaw := <-ch: + ev := evRaw.(events.PeerConnectedEvent) + + connectedPeers[ev.PeerID] = struct{}{} + + if len(connectedPeers) == n-1 { + return nil + } + case <-timer.C: + return errors.New("timed out waiting for peers to connect") + } + } + }) + + sws[i].DialPeers(addrs...) + } + + require.NoError(t, g.Wait()) + + fmt.Printf("\n\nDONE\n\n") + + return sws +} + func waitForTxsOnReactors(t *testing.T, txs types.Txs, reactors []*Reactor) { t.Helper() @@ -120,14 +281,16 @@ func TestReactorBroadcastTxMessage(t *testing.T) { mconfig := memcfg.TestMempoolConfig() pconfig := testP2PConfig() const N = 4 - reactors := makeAndConnectReactors(mconfig, pconfig, N) - defer func() { + reactors := makeAndConnectReactors(t, mconfig, pconfig, N) + t.Cleanup(func() { for _, r := range reactors { - r.Stop() + assert.NoError(t, r.Stop()) } - }() + }) + for _, r := range reactors { for _, peer := range r.Switch.Peers().List() { + fmt.Printf("Setting peer %s\n", peer.ID()) peer.Set(types.PeerStateKey, peerState{1}) } } @@ -144,7 +307,7 @@ func TestReactorNoBroadcastToSender(t *testing.T) { mconfig := memcfg.TestMempoolConfig() pconfig := testP2PConfig() const N = 2 - reactors := makeAndConnectReactors(mconfig, pconfig, N) + reactors := makeAndConnectReactors(t, mconfig, pconfig, N) defer func() { for _, r := range reactors { r.Stop() @@ -169,7 +332,7 @@ func TestFlappyBroadcastTxForPeerStopsWhenPeerStops(t *testing.T) { mconfig := memcfg.TestMempoolConfig() pconfig := testP2PConfig() const N = 2 - reactors := makeAndConnectReactors(mconfig, pconfig, N) + reactors := makeAndConnectReactors(t, mconfig, pconfig, N) defer func() { for _, r := range reactors { r.Stop() @@ -197,7 +360,7 @@ func TestFlappyBroadcastTxForPeerStopsWhenReactorStops(t *testing.T) { mconfig := memcfg.TestMempoolConfig() pconfig := testP2PConfig() const N = 2 - reactors := makeAndConnectReactors(mconfig, pconfig, N) + reactors := makeAndConnectReactors(t, mconfig, pconfig, N) // stop reactors for _, r := range reactors { @@ -214,15 +377,15 @@ func TestMempoolIDsBasic(t *testing.T) { ids := newMempoolIDs() - peer := mock.NewPeer(net.IP{127, 0, 0, 1}) + id := p2pTypes.GenerateNodeKey().ID() - ids.ReserveForPeer(peer) - assert.EqualValues(t, 1, ids.GetForPeer(peer)) - ids.Reclaim(peer) + ids.ReserveForPeer(id) + assert.EqualValues(t, 1, ids.GetForPeer(id)) + ids.Reclaim(id) - ids.ReserveForPeer(peer) - assert.EqualValues(t, 2, ids.GetForPeer(peer)) - ids.Reclaim(peer) + ids.ReserveForPeer(id) + assert.EqualValues(t, 2, ids.GetForPeer(id)) + ids.Reclaim(id) } func TestMempoolIDsPanicsIfNodeRequestsOvermaxActiveIDs(t *testing.T) { @@ -236,12 +399,13 @@ func TestMempoolIDsPanicsIfNodeRequestsOvermaxActiveIDs(t *testing.T) { ids := newMempoolIDs() for i := 0; i < maxActiveIDs-1; i++ { - peer := mock.NewPeer(net.IP{127, 0, 0, 1}) - ids.ReserveForPeer(peer) + id := p2pTypes.GenerateNodeKey().ID() + ids.ReserveForPeer(id) } assert.Panics(t, func() { - peer := mock.NewPeer(net.IP{127, 0, 0, 1}) - ids.ReserveForPeer(peer) + id := p2pTypes.GenerateNodeKey().ID() + + ids.ReserveForPeer(id) }) } diff --git a/tm2/pkg/bft/node/node.go b/tm2/pkg/bft/node/node.go index dd0c541e20b..8ca3d60fc04 100644 --- a/tm2/pkg/bft/node/node.go +++ b/tm2/pkg/bft/node/node.go @@ -132,7 +132,7 @@ type Node struct { privValidator types.PrivValidator // local node's validator key // network - transport *p2p.Transport + transport *p2p.MultiplexTransport sw *p2p.MultiplexSwitch // p2p connections nodeInfo p2pTypes.NodeInfo nodeKey *p2pTypes.NodeKey // our node privkey @@ -458,7 +458,7 @@ func NewNode(config *cfg.Config, p2pLogger.Error("invalid private peer ID", "err", err) } - sw := p2p.NewSwitch( + sw := p2p.NewMultiplexSwitch( transport, p2p.WithReactor("MEMPOOL", mempoolReactor), p2p.WithReactor("BLOCKCHAIN", bcReactor), @@ -601,7 +601,9 @@ func (n *Node) OnStop() { } // now stop the reactors - n.sw.Stop() + if err := n.sw.Stop(); err != nil { + n.Logger.Error("unable to gracefully close switch", "err", err) + } // stop mempool WAL if n.config.Mempool.WalEnabled() { diff --git a/tm2/pkg/bft/rpc/client/local.go b/tm2/pkg/bft/rpc/client/local.go index 59c4216a468..4bc724e7d70 100644 --- a/tm2/pkg/bft/rpc/client/local.go +++ b/tm2/pkg/bft/rpc/client/local.go @@ -106,14 +106,6 @@ func (c *Local) Health() (*ctypes.ResultHealth, error) { return core.Health(c.ctx) } -func (c *Local) DialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { - return core.UnsafeDialSeeds(c.ctx, seeds) -} - -func (c *Local) DialPeers(peers []string, persistent bool) (*ctypes.ResultDialPeers, error) { - return core.UnsafeDialPeers(c.ctx, peers, persistent) -} - func (c *Local) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { return core.BlockchainInfo(c.ctx, minHeight, maxHeight) } diff --git a/tm2/pkg/bft/rpc/core/net.go b/tm2/pkg/bft/rpc/core/net.go index 975d5ed822f..f8839b7d91f 100644 --- a/tm2/pkg/bft/rpc/core/net.go +++ b/tm2/pkg/bft/rpc/core/net.go @@ -3,7 +3,6 @@ package core import ( ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" rpctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/types" - "github.com/gnolang/gno/tm2/pkg/errors" ) // Get network info. @@ -154,10 +153,14 @@ import ( // } // // ``` -func NetInfo(ctx *rpctypes.Context) (*ctypes.ResultNetInfo, error) { - out, in, _ := p2pPeers.NumPeers() +func NetInfo(_ *rpctypes.Context) (*ctypes.ResultNetInfo, error) { + var ( + set = p2pPeers.Peers() + out, in = set.NumOutbound(), set.NumInbound() + ) + peers := make([]ctypes.Peer, 0, out+in) - for _, peer := range p2pPeers.Peers().List() { + for _, peer := range set.List() { nodeInfo := peer.NodeInfo() peers = append(peers, ctypes.Peer{ NodeInfo: nodeInfo, @@ -166,9 +169,7 @@ func NetInfo(ctx *rpctypes.Context) (*ctypes.ResultNetInfo, error) { RemoteIP: peer.RemoteIP().String(), }) } - // TODO: Should we include PersistentPeers and Seeds in here? - // PRO: useful info - // CON: privacy + return &ctypes.ResultNetInfo{ Listening: p2pTransport.IsListening(), Listeners: p2pTransport.Listeners(), @@ -177,33 +178,6 @@ func NetInfo(ctx *rpctypes.Context) (*ctypes.ResultNetInfo, error) { }, nil } -func UnsafeDialSeeds(ctx *rpctypes.Context, seeds []string) (*ctypes.ResultDialSeeds, error) { - if len(seeds) == 0 { - return &ctypes.ResultDialSeeds{}, errors.New("No seeds provided") - } - logger.Info("DialSeeds", "seeds", seeds) - if err := p2pPeers.DialPeersAsync(seeds); err != nil { - return &ctypes.ResultDialSeeds{}, err - } - return &ctypes.ResultDialSeeds{Log: "Dialing seeds in progress. See /net_info for details"}, nil -} - -func UnsafeDialPeers(ctx *rpctypes.Context, peers []string, persistent bool) (*ctypes.ResultDialPeers, error) { - if len(peers) == 0 { - return &ctypes.ResultDialPeers{}, errors.New("No peers provided") - } - logger.Info("DialPeers", "peers", peers, "persistent", persistent) - if persistent { - if err := p2pPeers.AddPersistentPeers(peers); err != nil { - return &ctypes.ResultDialPeers{}, err - } - } - if err := p2pPeers.DialPeersAsync(peers); err != nil { - return &ctypes.ResultDialPeers{}, err - } - return &ctypes.ResultDialPeers{Log: "Dialing peers in progress. See /net_info for details"}, nil -} - // Get genesis file. // // ```shell diff --git a/tm2/pkg/bft/rpc/core/net_test.go b/tm2/pkg/bft/rpc/core/net_test.go deleted file mode 100644 index 163312e2ed9..00000000000 --- a/tm2/pkg/bft/rpc/core/net_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package core - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - rpctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/types" - "github.com/gnolang/gno/tm2/pkg/log" - "github.com/gnolang/gno/tm2/pkg/p2p" - p2pcfg "github.com/gnolang/gno/tm2/pkg/p2p/config" -) - -func TestUnsafeDialSeeds(t *testing.T) { - t.Parallel() - - sw := p2p.MakeSwitch(p2pcfg.DefaultP2PConfig(), 1, "testing", "123.123.123", - func(n int, sw *p2p.MultiplexSwitch) *p2p.MultiplexSwitch { return sw }) - err := sw.Start() - require.NoError(t, err) - defer sw.Stop() - - logger = log.NewNoopLogger() - p2pPeers = sw - - testCases := []struct { - seeds []string - isErr bool - }{ - {[]string{}, true}, - {[]string{"g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:41198"}, false}, - {[]string{"127.0.0.1:41198"}, true}, - } - - for _, tc := range testCases { - res, err := UnsafeDialSeeds(&rpctypes.Context{}, tc.seeds) - if tc.isErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - assert.NotNil(t, res) - } - } -} - -func TestUnsafeDialPeers(t *testing.T) { - t.Parallel() - - sw := p2p.MakeSwitch(p2pcfg.DefaultP2PConfig(), 1, "testing", "123.123.123", - func(n int, sw *p2p.MultiplexSwitch) *p2p.MultiplexSwitch { return sw }) - err := sw.Start() - require.NoError(t, err) - defer sw.Stop() - - logger = log.NewNoopLogger() - p2pPeers = sw - - testCases := []struct { - peers []string - isErr bool - }{ - {[]string{}, true}, - {[]string{"g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:41198"}, false}, - {[]string{"127.0.0.1:41198"}, true}, - } - - for _, tc := range testCases { - res, err := UnsafeDialPeers(&rpctypes.Context{}, tc.peers, false) - if tc.isErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - assert.NotNil(t, res) - } - } -} diff --git a/tm2/pkg/bft/rpc/core/pipe.go b/tm2/pkg/bft/rpc/core/pipe.go index d5cdf2c268c..085fc35da55 100644 --- a/tm2/pkg/bft/rpc/core/pipe.go +++ b/tm2/pkg/bft/rpc/core/pipe.go @@ -43,9 +43,6 @@ type transport interface { } type peers interface { - AddPersistentPeers([]string) error - DialPeersAsync([]string) error - NumPeers() (outbound, inbound, dialig int) Peers() p2p.PeerSet } diff --git a/tm2/pkg/bft/rpc/core/routes.go b/tm2/pkg/bft/rpc/core/routes.go index 8d210f67985..76217a7cbd9 100644 --- a/tm2/pkg/bft/rpc/core/routes.go +++ b/tm2/pkg/bft/rpc/core/routes.go @@ -36,8 +36,6 @@ var Routes = map[string]*rpc.RPCFunc{ func AddUnsafeRoutes() { // control API - Routes["dial_seeds"] = rpc.NewRPCFunc(UnsafeDialSeeds, "seeds") - Routes["dial_peers"] = rpc.NewRPCFunc(UnsafeDialPeers, "peers,persistent") Routes["unsafe_flush_mempool"] = rpc.NewRPCFunc(UnsafeFlushMempool, "") // profiler API diff --git a/tm2/pkg/p2p/discovery/mock_test.go b/tm2/pkg/p2p/discovery/mock_test.go index b4a21827ce7..03919a673d3 100644 --- a/tm2/pkg/p2p/discovery/mock_test.go +++ b/tm2/pkg/p2p/discovery/mock_test.go @@ -4,6 +4,7 @@ import ( "net" "github.com/gnolang/gno/tm2/pkg/p2p" + "github.com/gnolang/gno/tm2/pkg/p2p/events" "github.com/gnolang/gno/tm2/pkg/p2p/types" ) @@ -12,6 +13,7 @@ type ( peersDelegate func() p2p.PeerSet stopPeerForErrorDelegate func(p2p.Peer, error) dialPeersDelegate func(...*types.NetAddress) + subscribeDelegate func(events.EventFilter) (<-chan events.Event, func()) ) type mockSwitch struct { @@ -19,6 +21,7 @@ type mockSwitch struct { peersFn peersDelegate stopPeerForErrorFn stopPeerForErrorDelegate dialPeersFn dialPeersDelegate + subscribeFn subscribeDelegate } func (m *mockSwitch) Broadcast(chID byte, data []byte) { @@ -47,6 +50,14 @@ func (m *mockSwitch) DialPeers(peerAddrs ...*types.NetAddress) { } } +func (m *mockSwitch) Subscribe(filter events.EventFilter) (<-chan events.Event, func()) { + if m.subscribeFn != nil { + m.subscribeFn(filter) + } + + return nil, func() {} +} + type ( addDelegate func(p2p.Peer) removeDelegate func(types.ID) bool @@ -91,14 +102,6 @@ func (m *mockPeerSet) Has(key types.ID) bool { return false } -func (m *mockPeerSet) HasIP(ip net.IP) bool { - if m.hasIPFn != nil { - return m.hasIPFn(ip) - } - - return false -} - func (m *mockPeerSet) Get(key types.ID) p2p.Peer { if m.getFn != nil { return m.getFn(key) diff --git a/tm2/pkg/p2p/events/doc.go b/tm2/pkg/p2p/events/doc.go new file mode 100644 index 00000000000..a624102379e --- /dev/null +++ b/tm2/pkg/p2p/events/doc.go @@ -0,0 +1,3 @@ +// Package events contains a simple p2p event system implementation, that simplifies asynchronous event flows in the +// p2p module. The event subscriptions allow for event filtering, which eases the load on the event notification flow. +package events diff --git a/tm2/pkg/p2p/events/events.go b/tm2/pkg/p2p/events/events.go new file mode 100644 index 00000000000..bf76e27d91e --- /dev/null +++ b/tm2/pkg/p2p/events/events.go @@ -0,0 +1,104 @@ +package events + +import ( + "sync" + + "github.com/rs/xid" +) + +// EventFilter is the filter function used to +// filter incoming p2p events. A false flag will +// consider the event as irrelevant +type EventFilter func(Event) bool + +// Events is the p2p event switch +type Events struct { + subs subscriptions + subscriptionsMux sync.RWMutex +} + +// New creates a new event subscription manager +func New() *Events { + return &Events{ + subs: make(subscriptions), + } +} + +// Subscribe registers a new filtered event listener +func (es *Events) Subscribe(filterFn EventFilter) (<-chan Event, func()) { + es.subscriptionsMux.Lock() + defer es.subscriptionsMux.Unlock() + + // Create a new subscription + id, ch := es.subs.add(filterFn) + + // Create the unsubscribe callback + unsubscribeFn := func() { + es.subscriptionsMux.Lock() + defer es.subscriptionsMux.Unlock() + + es.subs.remove(id) + } + + return ch, unsubscribeFn +} + +// Notify notifies all subscribers of an incoming event [BLOCKING] +func (es *Events) Notify(event Event) { + es.subscriptionsMux.RLock() + defer es.subscriptionsMux.RUnlock() + + es.subs.notify(event) +} + +type ( + // subscriptions holds the corresponding subscription information + subscriptions map[string]subscription // subscription ID -> subscription + + // subscription wraps the subscription notification channel, + // and the event filter + subscription struct { + ch chan Event + filterFn EventFilter + } +) + +// add adds a new subscription to the subscription map. +// Returns the subscription ID, and update channel +func (s *subscriptions) add(filterFn EventFilter) (string, chan Event) { + var ( + id = xid.New().String() + ch = make(chan Event, 1) + ) + + (*s)[id] = subscription{ + ch: ch, + filterFn: filterFn, + } + + return id, ch +} + +// remove removes the given subscription +func (s *subscriptions) remove(id string) { + if sub, exists := (*s)[id]; exists { + // Close the notification channel + close(sub.ch) + } + + // Delete the subscription + delete(*s, id) +} + +// notify notifies all subscription listeners, +// if their filters pass +func (s *subscriptions) notify(event Event) { + // Notify the listeners + for _, sub := range *s { + if !sub.filterFn(event) { + continue + } + + sub.ch <- event + } +} diff --git a/tm2/pkg/p2p/events/events_test.go b/tm2/pkg/p2p/events/events_test.go new file mode 100644 index 00000000000..a0feafceddb --- /dev/null +++ b/tm2/pkg/p2p/events/events_test.go @@ -0,0 +1,94 @@ +package events + +import ( + "fmt" + "sync" + "testing" + "time" + + "github.com/gnolang/gno/tm2/pkg/p2p/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// generateEvents generates p2p events +func generateEvents(count int) []Event { + events := make([]Event, 0, count) + + for i := range count { + var event Event + + if i%2 == 0 { + event = PeerConnectedEvent{ + PeerID: types.ID(fmt.Sprintf("peer-%d", i)), + } + } else { + event = PeerDisconnectedEvent{ + PeerID: types.ID(fmt.Sprintf("peer-%d", i)), + } + } + + events = append(events, event) + } + + return events +} + +func TestEvents_Subscribe(t *testing.T) { + t.Parallel() + + var ( + capturedEvents []Event + + events = generateEvents(10) + subFn = func(e Event) bool { + return e.Type() == PeerDisconnected + } + ) + + // Create the events manager + e := New() + + // Subscribe to events + ch, unsubFn := e.Subscribe(subFn) + defer unsubFn() + + // Listen for the events + var wg sync.WaitGroup + + wg.Add(1) + + go func() { + defer wg.Done() + + timeout := time.After(5 * time.Second) + + for { + select { + case ev := <-ch: + capturedEvents = append(capturedEvents, ev) + + if len(capturedEvents) == len(events)/2 { + return + } + case <-timeout: + return + } + } + }() + + // Send out the events + for _, ev := range events { + e.Notify(ev) + } + + wg.Wait() + + // Make sure the events were captured + // and filtered properly + require.Len(t, capturedEvents, len(events)/2) + + for _, ev := range capturedEvents { + assert.Equal(t, ev.Type(), PeerDisconnected) + } +} diff --git a/tm2/pkg/p2p/events/types.go b/tm2/pkg/p2p/events/types.go new file mode 100644 index 00000000000..cbaac1816ff --- /dev/null +++ b/tm2/pkg/p2p/events/types.go @@ -0,0 +1,39 @@ +package events + +import ( + "net" + + "github.com/gnolang/gno/tm2/pkg/p2p/types" +) + +type EventType string + +const ( + PeerConnected EventType = "PeerConnected" // emitted when a fresh peer connects + PeerDisconnected EventType = "PeerDisconnected" // emitted when a peer disconnects +) + +// Event is a generic p2p event +type Event interface { + // Type returns the type information for the event + Type() EventType +} + +type PeerConnectedEvent struct { + PeerID types.ID // the ID of the peer + Address net.Addr // the remote address of the peer +} + +func (p PeerConnectedEvent) Type() EventType { + return PeerConnected +} + +type PeerDisconnectedEvent struct { + PeerID types.ID // the ID of the peer + Address net.Addr // the remote address of the peer + Reason error // the disconnect reason, if any +} + +func (p PeerDisconnectedEvent) Type() EventType { + return PeerDisconnected +} diff --git a/tm2/pkg/p2p/mock_test.go b/tm2/pkg/p2p/mock_test.go index 25fbb800e69..d870e3f8133 100644 --- a/tm2/pkg/p2p/mock_test.go +++ b/tm2/pkg/p2p/mock_test.go @@ -98,14 +98,6 @@ func (m *mockSet) Has(key types.ID) bool { return false } -func (m *mockSet) HasIP(ip net.IP) bool { - if m.hasIPFn != nil { - return m.hasIPFn(ip) - } - - return false -} - func (m *mockSet) Get(key types.ID) Peer { if m.getFn != nil { return m.getFn(key) diff --git a/tm2/pkg/p2p/set.go b/tm2/pkg/p2p/set.go index a54ad949657..9905fe0ac66 100644 --- a/tm2/pkg/p2p/set.go +++ b/tm2/pkg/p2p/set.go @@ -1,7 +1,6 @@ package p2p import ( - "net" "sync" "github.com/gnolang/gno/tm2/pkg/p2p/types" @@ -51,22 +50,7 @@ func (s *set) Has(peerKey types.ID) bool { return exists } -// HasIP returns true if the set contains the peer referred to by this IP -// address, otherwise false. -func (s *set) HasIP(peerIP net.IP) bool { - s.mux.RLock() - defer s.mux.RUnlock() - - for _, p := range s.peers { - if p.(Peer).RemoteIP().Equal(peerIP) { - return true - } - } - - return false -} - -// Get looks up a peer by the provtypes.IDed peerKey. Returns nil if peer is not +// Get looks up a peer by the peer ID. Returns nil if peer is not // found. func (s *set) Get(key types.ID) Peer { s.mux.RLock() diff --git a/tm2/pkg/p2p/set_test.go b/tm2/pkg/p2p/set_test.go index fd0428d5af0..ced35538a9b 100644 --- a/tm2/pkg/p2p/set_test.go +++ b/tm2/pkg/p2p/set_test.go @@ -1,7 +1,6 @@ package p2p import ( - "net" "sort" "testing" @@ -63,53 +62,6 @@ func TestSet_Remove(t *testing.T) { } } -func TestSet_HasIP(t *testing.T) { - t.Parallel() - - t.Run("present peer with IP", func(t *testing.T) { - t.Parallel() - - var ( - peers = mock.GeneratePeers(t, 100) - ip = net.ParseIP("0.0.0.0") - - s = newSet() - ) - - // Make sure at least one peer has the set IP - peers[len(peers)/2].RemoteIPFn = func() net.IP { - return ip - } - - // Add the peers - for _, peer := range peers { - s.Add(peer) - } - - // Make sure the peer is present - assert.True(t, s.HasIP(ip)) - }) - - t.Run("missing peer with IP", func(t *testing.T) { - t.Parallel() - - var ( - peers = mock.GeneratePeers(t, 100) - ip = net.ParseIP("0.0.0.0") - - s = newSet() - ) - - // Add the peers - for _, peer := range peers { - s.Add(peer) - } - - // Make sure the peer is not present - assert.False(t, s.HasIP(ip)) - }) -} - func TestSet_Get(t *testing.T) { t.Parallel() diff --git a/tm2/pkg/p2p/switch.go b/tm2/pkg/p2p/switch.go index 1ec18437b4a..2cc16b0122e 100644 --- a/tm2/pkg/p2p/switch.go +++ b/tm2/pkg/p2p/switch.go @@ -9,6 +9,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/p2p/config" "github.com/gnolang/gno/tm2/pkg/p2p/conn" "github.com/gnolang/gno/tm2/pkg/p2p/dial" + "github.com/gnolang/gno/tm2/pkg/p2p/events" "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/gnolang/gno/tm2/pkg/service" "github.com/gnolang/gno/tm2/pkg/telemetry" @@ -66,10 +67,11 @@ type MultiplexSwitch struct { transport Transport dialQueue *dial.Queue + events *events.Events } -// NewSwitch creates a new MultiplexSwitch with the given config. -func NewSwitch( +// NewMultiplexSwitch creates a new MultiplexSwitch with the given config. +func NewMultiplexSwitch( transport Transport, opts ...SwitchOption, ) *MultiplexSwitch { @@ -80,6 +82,7 @@ func NewSwitch( peers: newSet(), transport: transport, dialQueue: dial.NewQueue(), + events: events.New(), maxInboundPeers: defaultCfg.MaxNumInboundPeers, maxOutboundPeers: defaultCfg.MaxNumOutboundPeers, } @@ -110,6 +113,12 @@ func NewSwitch( return sw } +// Subscribe registers to live events happening on the p2p Switch. +// Returns the notification channel, along with an unsubscribe method +func (sw *MultiplexSwitch) Subscribe(filterFn events.EventFilter) (<-chan events.Event, func()) { + return sw.events.Subscribe(filterFn) +} + // --------------------------------------------------------------------- // Service start/stop @@ -239,6 +248,12 @@ func (sw *MultiplexSwitch) stopAndRemovePeer(peer Peer, err error) { // RemovePeer is finished. // https://github.com/tendermint/classic/issues/3338 sw.peers.Remove(peer.ID()) + + sw.events.Notify(events.PeerDisconnectedEvent{ + Address: peer.RemoteAddr(), + PeerID: peer.ID(), + Reason: err, + }) } // --------------------------------------------------------------------- @@ -278,7 +293,8 @@ func (sw *MultiplexSwitch) runDialLoop(ctx context.Context) { peerAddr := item.Address // Check if the peer is already connected - if sw.Peers().Has(peerAddr.ID) || sw.Peers().HasIP(peerAddr.IP) { + ps := sw.Peers() + if ps.Has(peerAddr.ID) { sw.Logger.Warn( "ignoring dial request for existing peer", "id", peerAddr.ID, @@ -329,7 +345,7 @@ func (sw *MultiplexSwitch) runDialLoop(ctx context.Context) { // runRedialLoop starts the persistent peer redial loop func (sw *MultiplexSwitch) runRedialLoop(ctx context.Context) { - ticker := time.NewTicker(time.Second * 10) + ticker := time.NewTicker(time.Second * 1) // TODO make option defer ticker.Stop() // redialFn goes through the persistent peer list @@ -511,6 +527,11 @@ func (sw *MultiplexSwitch) addPeer(p Peer) error { sw.Logger.Info("Added peer", "peer", p) + sw.events.Notify(events.PeerConnectedEvent{ + Address: p.RemoteAddr(), + PeerID: p.ID(), + }) + return nil } diff --git a/tm2/pkg/p2p/switch_test.go b/tm2/pkg/p2p/switch_test.go index 992c547eab4..d2d8cf06b30 100644 --- a/tm2/pkg/p2p/switch_test.go +++ b/tm2/pkg/p2p/switch_test.go @@ -30,7 +30,7 @@ func TestMultiplexSwitch_Options(t *testing.T) { } ) - sw := NewSwitch(nil, WithReactor(name, mockReactor)) + sw := NewMultiplexSwitch(nil, WithReactor(name, mockReactor)) assert.Equal(t, mockReactor, sw.reactors[name]) }) @@ -40,7 +40,7 @@ func TestMultiplexSwitch_Options(t *testing.T) { peers := generateNetAddr(t, 10) - sw := NewSwitch(nil, WithPersistentPeers(peers)) + sw := NewMultiplexSwitch(nil, WithPersistentPeers(peers)) for _, p := range peers { assert.True(t, sw.isPersistentPeer(p.ID)) @@ -59,7 +59,7 @@ func TestMultiplexSwitch_Options(t *testing.T) { ids = append(ids, p.ID) } - sw := NewSwitch(nil, WithPrivatePeers(ids)) + sw := NewMultiplexSwitch(nil, WithPrivatePeers(ids)) for _, p := range peers { assert.True(t, sw.isPrivatePeer(p.ID)) @@ -71,7 +71,7 @@ func TestMultiplexSwitch_Options(t *testing.T) { maxInbound := uint64(500) - sw := NewSwitch(nil, WithMaxInboundPeers(maxInbound)) + sw := NewMultiplexSwitch(nil, WithMaxInboundPeers(maxInbound)) assert.Equal(t, maxInbound, sw.maxInboundPeers) }) @@ -81,7 +81,7 @@ func TestMultiplexSwitch_Options(t *testing.T) { maxOutbound := uint64(500) - sw := NewSwitch(nil, WithMaxOutboundPeers(maxOutbound)) + sw := NewMultiplexSwitch(nil, WithMaxOutboundPeers(maxOutbound)) assert.Equal(t, maxOutbound, sw.maxOutboundPeers) }) @@ -103,7 +103,7 @@ func TestMultiplexSwitch_Broadcast(t *testing.T) { } peers = mock.GeneratePeers(t, 10) - sw = NewSwitch(mockTransport) + sw = NewMultiplexSwitch(mockTransport) ) require.NoError(t, sw.OnStart()) @@ -139,7 +139,7 @@ func TestMultiplexSwitch_Peers(t *testing.T) { var ( peers = mock.GeneratePeers(t, 10) - sw = NewSwitch(nil) + sw = NewMultiplexSwitch(nil) ) // Create a new peer set @@ -178,7 +178,7 @@ func TestMultiplexSwitch_StopPeer(t *testing.T) { }, } - sw = NewSwitch(mockTransport) + sw = NewMultiplexSwitch(mockTransport) ) // Create a new peer set @@ -208,7 +208,7 @@ func TestMultiplexSwitch_StopPeer(t *testing.T) { }, } - sw = NewSwitch(mockTransport) + sw = NewMultiplexSwitch(mockTransport) ) // Make sure the peer is persistent @@ -281,7 +281,7 @@ func TestMultiplexSwitch_DialLoop(t *testing.T) { }, } - sw = NewSwitch(mockTransport) + sw = NewMultiplexSwitch(mockTransport) ) sw.peers = mockSet @@ -344,7 +344,7 @@ func TestMultiplexSwitch_DialLoop(t *testing.T) { }, } - sw = NewSwitch(mockTransport) + sw = NewMultiplexSwitch(mockTransport) ) sw.peers = mockSet @@ -399,7 +399,7 @@ func TestMultiplexSwitch_DialLoop(t *testing.T) { }, } - sw = NewSwitch(mockTransport) + sw = NewMultiplexSwitch(mockTransport) ) // Prepare the dial queue @@ -461,7 +461,7 @@ func TestMultiplexSwitch_AcceptLoop(t *testing.T) { }, } - sw = NewSwitch( + sw = NewMultiplexSwitch( mockTransport, WithMaxInboundPeers(maxInbound), ) @@ -517,7 +517,7 @@ func TestMultiplexSwitch_AcceptLoop(t *testing.T) { }, } - sw = NewSwitch( + sw = NewMultiplexSwitch( mockTransport, WithMaxInboundPeers(maxInbound), ) @@ -583,7 +583,7 @@ func TestMultiplexSwitch_RedialLoop(t *testing.T) { } // Create the switch - sw := NewSwitch( + sw := NewMultiplexSwitch( nil, WithPersistentPeers(addrs), ) @@ -649,7 +649,7 @@ func TestMultiplexSwitch_RedialLoop(t *testing.T) { } // Create the switch - sw := NewSwitch( + sw := NewMultiplexSwitch( mockTransport, WithPersistentPeers(addrs), ) @@ -731,7 +731,7 @@ func TestMultiplexSwitch_DialPeers(t *testing.T) { } } - sw := NewSwitch(mockTransport) + sw := NewMultiplexSwitch(mockTransport) // Dial the peers sw.DialPeers(p.NodeInfo().NetAddress) @@ -763,7 +763,7 @@ func TestMultiplexSwitch_DialPeers(t *testing.T) { } ) - sw := NewSwitch( + sw := NewMultiplexSwitch( mockTransport, WithMaxOutboundPeers(maxOutbound), ) @@ -803,7 +803,7 @@ func TestMultiplexSwitch_DialPeers(t *testing.T) { } ) - sw := NewSwitch( + sw := NewMultiplexSwitch( mockTransport, WithMaxOutboundPeers(10), ) diff --git a/tm2/pkg/p2p/types.go b/tm2/pkg/p2p/types.go index fe6bccec072..418c5095347 100644 --- a/tm2/pkg/p2p/types.go +++ b/tm2/pkg/p2p/types.go @@ -5,6 +5,7 @@ import ( "net" "github.com/gnolang/gno/tm2/pkg/p2p/conn" + "github.com/gnolang/gno/tm2/pkg/p2p/events" "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/gnolang/gno/tm2/pkg/service" ) @@ -46,7 +47,6 @@ type PeerSet interface { Add(peer Peer) Remove(key types.ID) bool Has(key types.ID) bool - HasIP(ip net.IP) bool Get(key types.ID) Peer List() []Peer @@ -81,6 +81,9 @@ type Switch interface { // Peers returns the latest peer set Peers() PeerSet + // Subscribe subscribes to active switch events + Subscribe(filterFn events.EventFilter) (<-chan events.Event, func()) + // StopPeerForError stops the peer with the given reason StopPeerForError(peer Peer, err error) From a17902638eb76a7a0430497a3a70bc7d56fa0330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Sun, 17 Nov 2024 08:43:57 +0900 Subject: [PATCH 36/43] Fix failing mempool tests --- tm2/pkg/bft/mempool/reactor_test.go | 119 ++++++++++++++-------------- 1 file changed, 59 insertions(+), 60 deletions(-) diff --git a/tm2/pkg/bft/mempool/reactor_test.go b/tm2/pkg/bft/mempool/reactor_test.go index d877af3e9cc..6f84c7d7951 100644 --- a/tm2/pkg/bft/mempool/reactor_test.go +++ b/tm2/pkg/bft/mempool/reactor_test.go @@ -3,14 +3,13 @@ package mempool import ( "context" "fmt" - "log/slog" "net" - "os" "sync" "testing" "time" "github.com/fortytw2/leaktest" + "github.com/gnolang/gno/tm2/pkg/log" "github.com/gnolang/gno/tm2/pkg/p2p/conn" "github.com/gnolang/gno/tm2/pkg/p2p/events" p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" @@ -24,7 +23,6 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/proxy" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/errors" - "github.com/gnolang/gno/tm2/pkg/log" "github.com/gnolang/gno/tm2/pkg/p2p" p2pcfg "github.com/gnolang/gno/tm2/pkg/p2p/config" "github.com/gnolang/gno/tm2/pkg/testutils" @@ -74,12 +72,14 @@ func makeAndConnectReactors(t *testing.T, mconfig *memcfg.MempoolConfig, pconfig } // "Simulate" the networking layer - MakeConnectedSwitches(t, pconfig, n, options) + makeConnectedSwitches(t, pconfig, n, options) return reactors } -func MakeConnectedSwitches( +// makeConnectedSwitches creates a cluster of peers, with the given options. +// Used to simulate the networking layer for the specific module +func makeConnectedSwitches( t *testing.T, cfg *p2pcfg.P2PConfig, n int, @@ -91,9 +91,6 @@ func MakeConnectedSwitches( sws = make([]*p2p.MultiplexSwitch, 0, n) ts = make([]*p2p.MultiplexTransport, 0, n) addrs = make([]*p2pTypes.NetAddress, 0, n) - - // TODO remove - lgs = make([]*slog.Logger, 0, n) ) // Generate the switches @@ -120,7 +117,7 @@ func MakeConnectedSwitches( Network: "testing", Software: "p2ptest", Version: "v1.2.3-rc.0-deadbeef", - Channels: []byte{0x01}, + Channels: []byte{MempoolChannel}, Moniker: fmt.Sprintf("node-%d", i), Other: p2pTypes.NodeInfoOther{ TxIndex: "off", @@ -128,15 +125,12 @@ func MakeConnectedSwitches( }, } - // TODO remove - lg := slog.New(slog.NewTextHandler(os.Stdout, nil)) - // Create the multiplex transport multiplexTransport := p2p.NewMultiplexTransport( info, *key, conn.MConfigFromP2P(cfg), - lg, + log.NewNoopLogger(), ) // Start the transport @@ -147,12 +141,9 @@ func MakeConnectedSwitches( }) dialAddr := multiplexTransport.NetAddress() - addrs = append(addrs, &dialAddr) + addrs = append(addrs, &dialAddr) ts = append(ts, multiplexTransport) - - // TODO remove - lgs = append(lgs, lg) } ctx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second) @@ -164,16 +155,9 @@ func MakeConnectedSwitches( // Make sure the switches connect to each other. // Set up event listeners to make sure // the setup method blocks until switches are connected - // Create the multiplex switch - newOpts := []p2p.SwitchOption{ - p2p.WithPersistentPeers(addrs), - } + opts[i] = append(opts[i], p2p.WithPersistentPeers(addrs)) - newOpts = append(newOpts, opts[i]...) - - multiplexSwitch := p2p.NewMultiplexSwitch(ts[i], newOpts...) - - multiplexSwitch.SetLogger(lgs[i].With("sw", i)) // TODO remove + multiplexSwitch := p2p.NewMultiplexSwitch(ts[i], opts[i]...) ch, unsubFn := multiplexSwitch.Subscribe(func(event events.Event) bool { return event.Type() == events.PeerConnected @@ -215,50 +199,70 @@ func MakeConnectedSwitches( require.NoError(t, g.Wait()) - fmt.Printf("\n\nDONE\n\n") - return sws } -func waitForTxsOnReactors(t *testing.T, txs types.Txs, reactors []*Reactor) { +func waitForTxsOnReactors( + t *testing.T, + txs types.Txs, + reactors []*Reactor, +) { t.Helper() - // wait for the txs in all mempools - wg := new(sync.WaitGroup) + ctx, cancelFn := context.WithTimeout(context.Background(), 30*time.Second) + defer cancelFn() + + // Wait for the txs to propagate in all mempools + var wg sync.WaitGroup + for i, reactor := range reactors { wg.Add(1) + go func(r *Reactor, reactorIndex int) { defer wg.Done() - waitForTxsOnReactor(t, txs, r, reactorIndex) + + reapedTxs := waitForTxsOnReactor(t, ctx, len(txs), r) + + for i, tx := range txs { + assert.Equalf(t, tx, reapedTxs[i], + fmt.Sprintf( + "txs at index %d on reactor %d don't match: %v vs %v", + i, reactorIndex, + tx, + reapedTxs[i], + ), + ) + } }(reactor, i) } - done := make(chan struct{}) - go func() { - wg.Wait() - close(done) - }() - - timer := time.After(timeout) - select { - case <-timer: - t.Fatal("Timed out waiting for txs") - case <-done: - } + wg.Wait() } -func waitForTxsOnReactor(t *testing.T, txs types.Txs, reactor *Reactor, reactorIndex int) { +func waitForTxsOnReactor( + t *testing.T, + ctx context.Context, + expectedLength int, + reactor *Reactor, +) types.Txs { t.Helper() - mempool := reactor.mempool - for mempool.Size() < len(txs) { - time.Sleep(time.Millisecond * 100) - } + var ( + mempool = reactor.mempool + ticker = time.NewTicker(100 * time.Millisecond) + ) + + for { + select { + case <-ctx.Done(): + t.Fatal("timed out waiting for txs") + case <-ticker.C: + if mempool.Size() < expectedLength { + continue + } - reapedTxs := mempool.ReapMaxTxs(len(txs)) - for i, tx := range txs { - assert.Equalf(t, tx, reapedTxs[i], - "txs at index %d on reactor %d don't match: %v vs %v", i, reactorIndex, tx, reapedTxs[i]) + return mempool.ReapMaxTxs(expectedLength) + } } } @@ -270,11 +274,6 @@ func ensureNoTxs(t *testing.T, reactor *Reactor, timeout time.Duration) { assert.Zero(t, reactor.mempool.Size()) } -const ( - numTxs = 1000 - timeout = 120 * time.Second // ridiculously high because CircleCI is slow -) - func TestReactorBroadcastTxMessage(t *testing.T) { t.Parallel() @@ -297,7 +296,7 @@ func TestReactorBroadcastTxMessage(t *testing.T) { // send a bunch of txs to the first reactor's mempool // and wait for them all to be received in the others - txs := checkTxs(t, reactors[0].mempool, numTxs, UnknownPeerID, true) + txs := checkTxs(t, reactors[0].mempool, 1000, UnknownPeerID, true) waitForTxsOnReactors(t, txs, reactors) } @@ -316,7 +315,7 @@ func TestReactorNoBroadcastToSender(t *testing.T) { // send a bunch of txs to the first reactor's mempool, claiming it came from peer // ensure peer gets no txs - checkTxs(t, reactors[0].mempool, numTxs, 1, true) + checkTxs(t, reactors[0].mempool, 1000, 1, true) ensureNoTxs(t, reactors[1], 100*time.Millisecond) } From d4ff4578bbdcc5283c8a6535d4300906fd712c49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Sun, 17 Nov 2024 11:16:26 +0900 Subject: [PATCH 37/43] Fixup module tests --- tm2/pkg/bft/blockchain/reactor_test.go | 111 ++++++++--- tm2/pkg/bft/consensus/reactor_test.go | 70 ++++--- tm2/pkg/bft/consensus/state_test.go | 4 +- tm2/pkg/bft/mempool/reactor_test.go | 143 ++------------ tm2/pkg/bft/node/node.go | 9 +- tm2/pkg/internal/p2p/p2p.go | 249 +++++++++++++++++++++++++ tm2/pkg/p2p/types/netaddress.go | 31 +-- tm2/pkg/p2p/types/node_info.go | 32 ++-- tm2/pkg/p2p/types/node_info_test.go | 18 +- 9 files changed, 445 insertions(+), 222 deletions(-) create mode 100644 tm2/pkg/internal/p2p/p2p.go diff --git a/tm2/pkg/bft/blockchain/reactor_test.go b/tm2/pkg/bft/blockchain/reactor_test.go index c2fd66712db..1bc2df59055 100644 --- a/tm2/pkg/bft/blockchain/reactor_test.go +++ b/tm2/pkg/bft/blockchain/reactor_test.go @@ -1,14 +1,13 @@ package blockchain import ( + "context" "log/slog" "os" "sort" "testing" "time" - "github.com/stretchr/testify/assert" - abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" "github.com/gnolang/gno/tm2/pkg/bft/appconn" cfg "github.com/gnolang/gno/tm2/pkg/bft/config" @@ -20,9 +19,12 @@ import ( tmtime "github.com/gnolang/gno/tm2/pkg/bft/types/time" "github.com/gnolang/gno/tm2/pkg/db/memdb" "github.com/gnolang/gno/tm2/pkg/errors" + p2pTesting "github.com/gnolang/gno/tm2/pkg/internal/p2p" "github.com/gnolang/gno/tm2/pkg/log" "github.com/gnolang/gno/tm2/pkg/p2p" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/gnolang/gno/tm2/pkg/testutils" + "github.com/stretchr/testify/assert" ) var config *cfg.Config @@ -125,15 +127,35 @@ func TestNoBlockResponse(t *testing.T) { maxBlockHeight := int64(65) - reactorPairs := make([]BlockchainReactorPair, 2) + var ( + reactorPairs = make([]BlockchainReactorPair, 2) + options = make(map[int][]p2p.SwitchOption) + ) - reactorPairs[0] = newBlockchainReactor(log.NewTestingLogger(t), genDoc, privVals, maxBlockHeight) - reactorPairs[1] = newBlockchainReactor(log.NewTestingLogger(t), genDoc, privVals, 0) + for i := range reactorPairs { + height := int64(0) + if i == 0 { + height = maxBlockHeight + } - p2p.MakeConnectedSwitches(config.P2P, 2, func(i int, s *p2p.MultiplexSwitch) *p2p.MultiplexSwitch { - s.AddReactor("BLOCKCHAIN", reactorPairs[i].reactor) - return s - }, p2p.Connect2Switches) + reactorPairs[i] = newBlockchainReactor(log.NewTestingLogger(t), genDoc, privVals, height) + + options[i] = []p2p.SwitchOption{ + p2p.WithReactor("BLOCKCHAIN", reactorPairs[i].reactor), + } + } + + ctx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second) + defer cancelFn() + + testingCfg := p2pTesting.TestingConfig{ + Count: 2, + P2PCfg: config.P2P, + SwitchOptions: options, + Channels: []byte{BlockchainChannel}, + } + + p2pTesting.MakeConnectedPeers(t, ctx, testingCfg) defer func() { for _, r := range reactorPairs { @@ -194,17 +216,35 @@ func TestFlappyBadBlockStopsPeer(t *testing.T) { otherChain.app.Stop() }() - reactorPairs := make([]BlockchainReactorPair, 4) + var ( + reactorPairs = make([]BlockchainReactorPair, 4) + options = make(map[int][]p2p.SwitchOption) + ) + + for i := range reactorPairs { + height := int64(0) + if i == 0 { + height = maxBlockHeight + } + + reactorPairs[i] = newBlockchainReactor(log.NewNoopLogger(), genDoc, privVals, height) - reactorPairs[0] = newBlockchainReactor(log.NewNoopLogger(), genDoc, privVals, maxBlockHeight) - reactorPairs[1] = newBlockchainReactor(log.NewNoopLogger(), genDoc, privVals, 0) - reactorPairs[2] = newBlockchainReactor(log.NewNoopLogger(), genDoc, privVals, 0) - reactorPairs[3] = newBlockchainReactor(log.NewNoopLogger(), genDoc, privVals, 0) + options[i] = []p2p.SwitchOption{ + p2p.WithReactor("BLOCKCHAIN", reactorPairs[i].reactor), + } + } - switches := p2p.MakeConnectedSwitches(config.P2P, 4, func(i int, s *p2p.MultiplexSwitch) *p2p.MultiplexSwitch { - s.AddReactor("BLOCKCHAIN", reactorPairs[i].reactor) - return s - }, p2p.Connect2Switches) + ctx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second) + defer cancelFn() + + testingCfg := p2pTesting.TestingConfig{ + Count: 4, + P2PCfg: config.P2P, + SwitchOptions: options, + Channels: []byte{BlockchainChannel}, + } + + switches, transports := p2pTesting.MakeConnectedPeers(t, ctx, testingCfg) defer func() { for _, r := range reactorPairs { @@ -222,7 +262,7 @@ func TestFlappyBadBlockStopsPeer(t *testing.T) { } // at this time, reactors[0-3] is the newest - assert.Equal(t, 3, reactorPairs[1].reactor.Switch.Peers().Size()) + assert.Equal(t, 3, len(reactorPairs[1].reactor.Switch.Peers().List())) // mark reactorPairs[3] is an invalid peer reactorPairs[3].reactor.store = otherChain.reactor.store @@ -230,24 +270,41 @@ func TestFlappyBadBlockStopsPeer(t *testing.T) { lastReactorPair := newBlockchainReactor(log.NewNoopLogger(), genDoc, privVals, 0) reactorPairs = append(reactorPairs, lastReactorPair) - switches = append(switches, p2p.MakeConnectedSwitches(config.P2P, 1, func(i int, s *p2p.MultiplexSwitch) *p2p.MultiplexSwitch { - s.AddReactor("BLOCKCHAIN", reactorPairs[len(reactorPairs)-1].reactor) - return s - }, p2p.Connect2Switches)...) + persistentPeers := make([]*p2pTypes.NetAddress, 0, len(transports)) - for i := 0; i < len(reactorPairs)-1; i++ { - p2p.Connect2Switches(switches, i, len(reactorPairs)-1) + for _, tr := range transports { + addr := tr.NetAddress() + persistentPeers = append(persistentPeers, &addr) } + for i, opt := range options { + opt = append(opt, p2p.WithPersistentPeers(persistentPeers)) + + options[i] = opt + } + + ctx, cancelFn = context.WithTimeout(context.Background(), 10*time.Second) + defer cancelFn() + + testingCfg = p2pTesting.TestingConfig{ + Count: 1, + P2PCfg: config.P2P, + SwitchOptions: options, + Channels: []byte{BlockchainChannel}, + } + + sw, _ := p2pTesting.MakeConnectedPeers(t, ctx, testingCfg) + switches = append(switches, sw...) + for { - if lastReactorPair.reactor.pool.IsCaughtUp() || lastReactorPair.reactor.Switch.Peers().Size() == 0 { + if lastReactorPair.reactor.pool.IsCaughtUp() || len(lastReactorPair.reactor.Switch.Peers().List()) == 0 { break } time.Sleep(1 * time.Second) } - assert.True(t, lastReactorPair.reactor.Switch.Peers().Size() < len(reactorPairs)-1) + assert.True(t, len(lastReactorPair.reactor.Switch.Peers().List()) < len(reactorPairs)-1) } func TestBcBlockRequestMessageValidateBasic(t *testing.T) { diff --git a/tm2/pkg/bft/consensus/reactor_test.go b/tm2/pkg/bft/consensus/reactor_test.go index a9fb1968212..c864eb36360 100644 --- a/tm2/pkg/bft/consensus/reactor_test.go +++ b/tm2/pkg/bft/consensus/reactor_test.go @@ -1,14 +1,13 @@ package consensus import ( + "context" "fmt" "log/slog" "sync" "testing" "time" - "github.com/stretchr/testify/assert" - "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/bft/abci/example/kvstore" cfg "github.com/gnolang/gno/tm2/pkg/bft/config" @@ -18,27 +17,39 @@ import ( "github.com/gnolang/gno/tm2/pkg/bitarray" "github.com/gnolang/gno/tm2/pkg/crypto/tmhash" "github.com/gnolang/gno/tm2/pkg/events" + p2pTesting "github.com/gnolang/gno/tm2/pkg/internal/p2p" "github.com/gnolang/gno/tm2/pkg/log" osm "github.com/gnolang/gno/tm2/pkg/os" "github.com/gnolang/gno/tm2/pkg/p2p" - "github.com/gnolang/gno/tm2/pkg/p2p/mock" "github.com/gnolang/gno/tm2/pkg/testutils" + "github.com/stretchr/testify/assert" ) // ---------------------------------------------- // in-process testnets -func startConsensusNet(css []*ConsensusState, n int) ([]*ConsensusReactor, []<-chan events.Event, []events.EventSwitch, []*p2p.MultiplexSwitch) { +func startConsensusNet( + t *testing.T, + css []*ConsensusState, + n int, +) ([]*ConsensusReactor, []<-chan events.Event, []events.EventSwitch, []*p2p.MultiplexSwitch) { + t.Helper() + reactors := make([]*ConsensusReactor, n) blocksSubs := make([]<-chan events.Event, 0) eventSwitches := make([]events.EventSwitch, n) p2pSwitches := ([]*p2p.MultiplexSwitch)(nil) + options := make(map[int][]p2p.SwitchOption) for i := 0; i < n; i++ { /*logger, err := tmflags.ParseLogLevel("consensus:info,*:error", logger, "info") if err != nil { t.Fatal(err)}*/ reactors[i] = NewConsensusReactor(css[i], true) // so we dont start the consensus states reactors[i].SetLogger(css[i].Logger) + options[i] = []p2p.SwitchOption{ + p2p.WithReactor("CONSENSUS", reactors[i]), + } + // evsw is already started with the cs eventSwitches[i] = css[i].evsw reactors[i].SetEventSwitch(eventSwitches[i]) @@ -51,11 +62,22 @@ func startConsensusNet(css []*ConsensusState, n int) ([]*ConsensusReactor, []<-c } } // make connected switches and start all reactors - p2pSwitches = p2p.MakeConnectedSwitches(config.P2P, n, func(i int, s *p2p.MultiplexSwitch) *p2p.MultiplexSwitch { - s.AddReactor("CONSENSUS", reactors[i]) - s.SetLogger(reactors[i].conS.Logger.With("module", "p2p")) - return s - }, p2p.Connect2Switches) + ctx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second) + defer cancelFn() + + testingCfg := p2pTesting.TestingConfig{ + P2PCfg: config.P2P, + Count: n, + SwitchOptions: options, + Channels: []byte{ + StateChannel, + DataChannel, + VoteChannel, + VoteSetBitsChannel, + }, + } + + p2pSwitches, _ = p2pTesting.MakeConnectedPeers(t, ctx, testingCfg) // now that everyone is connected, start the state machines // If we started the state machines before everyone was connected, @@ -68,11 +90,15 @@ func startConsensusNet(css []*ConsensusState, n int) ([]*ConsensusReactor, []<-c return reactors, blocksSubs, eventSwitches, p2pSwitches } -func stopConsensusNet(logger *slog.Logger, reactors []*ConsensusReactor, eventSwitches []events.EventSwitch, p2pSwitches []*p2p.MultiplexSwitch) { +func stopConsensusNet( + logger *slog.Logger, + reactors []*ConsensusReactor, + eventSwitches []events.EventSwitch, + p2pSwitches []*p2p.MultiplexSwitch, +) { logger.Info("stopConsensusNet", "n", len(reactors)) - for i, r := range reactors { + for i, _ := range reactors { logger.Info("stopConsensusNet: Stopping ConsensusReactor", "i", i) - r.Switch.Stop() } for i, b := range eventSwitches { logger.Info("stopConsensusNet: Stopping evsw", "i", i) @@ -92,7 +118,7 @@ func TestReactorBasic(t *testing.T) { N := 4 css, cleanup := randConsensusNet(N, "consensus_reactor_test", newMockTickerFunc(true), newCounter) defer cleanup() - reactors, blocksSubs, eventSwitches, p2pSwitches := startConsensusNet(css, N) + reactors, blocksSubs, eventSwitches, p2pSwitches := startConsensusNet(t, css, N) defer stopConsensusNet(log.NewTestingLogger(t), reactors, eventSwitches, p2pSwitches) // wait till everyone makes the first new block timeoutWaitGroup(t, N, func(j int) { @@ -112,7 +138,7 @@ func TestReactorCreatesBlockWhenEmptyBlocksFalse(t *testing.T) { c.Consensus.CreateEmptyBlocks = false }) defer cleanup() - reactors, blocksSubs, eventSwitches, p2pSwitches := startConsensusNet(css, N) + reactors, blocksSubs, eventSwitches, p2pSwitches := startConsensusNet(t, css, N) defer stopConsensusNet(log.NewTestingLogger(t), reactors, eventSwitches, p2pSwitches) // send a tx @@ -132,12 +158,12 @@ func TestReactorReceiveDoesNotPanicIfAddPeerHasntBeenCalledYet(t *testing.T) { N := 1 css, cleanup := randConsensusNet(N, "consensus_reactor_test", newMockTickerFunc(true), newCounter) defer cleanup() - reactors, _, eventSwitches, p2pSwitches := startConsensusNet(css, N) + reactors, _, eventSwitches, p2pSwitches := startConsensusNet(t, css, N) defer stopConsensusNet(log.NewTestingLogger(t), reactors, eventSwitches, p2pSwitches) var ( reactor = reactors[0] - peer = mock.NewPeer(nil) + peer = p2pTesting.NewPeer(t) msg = amino.MustMarshalAny(&HasVoteMessage{Height: 1, Round: 1, Index: 1, Type: types.PrevoteType}) ) @@ -156,12 +182,12 @@ func TestReactorReceivePanicsIfInitPeerHasntBeenCalledYet(t *testing.T) { N := 1 css, cleanup := randConsensusNet(N, "consensus_reactor_test", newMockTickerFunc(true), newCounter) defer cleanup() - reactors, _, eventSwitches, p2pSwitches := startConsensusNet(css, N) + reactors, _, eventSwitches, p2pSwitches := startConsensusNet(t, css, N) defer stopConsensusNet(log.NewTestingLogger(t), reactors, eventSwitches, p2pSwitches) var ( reactor = reactors[0] - peer = mock.NewPeer(nil) + peer = p2pTesting.NewPeer(t) msg = amino.MustMarshalAny(&HasVoteMessage{Height: 1, Round: 1, Index: 1, Type: types.PrevoteType}) ) @@ -182,7 +208,7 @@ func TestFlappyReactorRecordsVotesAndBlockParts(t *testing.T) { N := 4 css, cleanup := randConsensusNet(N, "consensus_reactor_test", newMockTickerFunc(true), newCounter) defer cleanup() - reactors, blocksSubs, eventSwitches, p2pSwitches := startConsensusNet(css, N) + reactors, blocksSubs, eventSwitches, p2pSwitches := startConsensusNet(t, css, N) defer stopConsensusNet(log.NewTestingLogger(t), reactors, eventSwitches, p2pSwitches) // wait till everyone makes the first new block @@ -210,7 +236,7 @@ func TestReactorVotingPowerChange(t *testing.T) { css, cleanup := randConsensusNet(nVals, "consensus_voting_power_changes_test", newMockTickerFunc(true), newPersistentKVStore) defer cleanup() - reactors, blocksSubs, eventSwitches, p2pSwitches := startConsensusNet(css, nVals) + reactors, blocksSubs, eventSwitches, p2pSwitches := startConsensusNet(t, css, nVals) defer stopConsensusNet(logger, reactors, eventSwitches, p2pSwitches) // map of active validators @@ -276,7 +302,7 @@ func TestReactorValidatorSetChanges(t *testing.T) { logger := log.NewTestingLogger(t) - reactors, blocksSubs, eventSwitches, p2pSwitches := startConsensusNet(css, nPeers) + reactors, blocksSubs, eventSwitches, p2pSwitches := startConsensusNet(t, css, nPeers) defer stopConsensusNet(logger, reactors, eventSwitches, p2pSwitches) // map of active validators @@ -375,7 +401,7 @@ func TestReactorWithTimeoutCommit(t *testing.T) { css[i].config.SkipTimeoutCommit = false } - reactors, blocksSubs, eventSwitches, p2pSwitches := startConsensusNet(css, N-1) + reactors, blocksSubs, eventSwitches, p2pSwitches := startConsensusNet(t, css, N-1) defer stopConsensusNet(log.NewTestingLogger(t), reactors, eventSwitches, p2pSwitches) // wait till everyone makes the first new block diff --git a/tm2/pkg/bft/consensus/state_test.go b/tm2/pkg/bft/consensus/state_test.go index 201cf8906b3..8c340d12eae 100644 --- a/tm2/pkg/bft/consensus/state_test.go +++ b/tm2/pkg/bft/consensus/state_test.go @@ -1733,7 +1733,7 @@ func TestStateOutputsBlockPartsStats(t *testing.T) { // create dummy peer cs, _ := randConsensusState(1) - peer := p2pmock.NewPeer(nil) + peer := p2pmock.Peer{} // 1) new block part parts := types.NewPartSetFromData(random.RandBytes(100), 10) @@ -1777,7 +1777,7 @@ func TestStateOutputVoteStats(t *testing.T) { cs, vss := randConsensusState(2) // create dummy peer - peer := p2pmock.NewPeer(nil) + peer := &p2pmock.Peer{} vote := signVote(vss[1], types.PrecommitType, []byte("test"), types.PartSetHeader{}) diff --git a/tm2/pkg/bft/mempool/reactor_test.go b/tm2/pkg/bft/mempool/reactor_test.go index 6f84c7d7951..2d20fb252e2 100644 --- a/tm2/pkg/bft/mempool/reactor_test.go +++ b/tm2/pkg/bft/mempool/reactor_test.go @@ -3,29 +3,23 @@ package mempool import ( "context" "fmt" - "net" "sync" "testing" "time" "github.com/fortytw2/leaktest" - "github.com/gnolang/gno/tm2/pkg/log" - "github.com/gnolang/gno/tm2/pkg/p2p/conn" - "github.com/gnolang/gno/tm2/pkg/p2p/events" - p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" - "github.com/gnolang/gno/tm2/pkg/versionset" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "golang.org/x/sync/errgroup" - "github.com/gnolang/gno/tm2/pkg/bft/abci/example/kvstore" memcfg "github.com/gnolang/gno/tm2/pkg/bft/mempool/config" "github.com/gnolang/gno/tm2/pkg/bft/proxy" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/errors" + p2pTesting "github.com/gnolang/gno/tm2/pkg/internal/p2p" + "github.com/gnolang/gno/tm2/pkg/log" "github.com/gnolang/gno/tm2/pkg/p2p" p2pcfg "github.com/gnolang/gno/tm2/pkg/p2p/config" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/gnolang/gno/tm2/pkg/testutils" + "github.com/stretchr/testify/assert" ) // testP2PConfig returns a configuration for testing the peer-to-peer layer @@ -72,134 +66,19 @@ func makeAndConnectReactors(t *testing.T, mconfig *memcfg.MempoolConfig, pconfig } // "Simulate" the networking layer - makeConnectedSwitches(t, pconfig, n, options) - - return reactors -} - -// makeConnectedSwitches creates a cluster of peers, with the given options. -// Used to simulate the networking layer for the specific module -func makeConnectedSwitches( - t *testing.T, - cfg *p2pcfg.P2PConfig, - n int, - opts map[int][]p2p.SwitchOption, -) []*p2p.MultiplexSwitch { - t.Helper() - - var ( - sws = make([]*p2p.MultiplexSwitch, 0, n) - ts = make([]*p2p.MultiplexTransport, 0, n) - addrs = make([]*p2pTypes.NetAddress, 0, n) - ) - - // Generate the switches - for i := range n { - var ( - key = p2pTypes.GenerateNodeKey() - tcpAddr = &net.TCPAddr{ - IP: net.ParseIP("127.0.0.1"), - Port: 0, // random port - } - ) - - addr, err := p2pTypes.NewNetAddress(key.ID(), tcpAddr) - require.NoError(t, err) - - info := p2pTypes.NodeInfo{ - VersionSet: versionset.VersionSet{ - versionset.VersionInfo{ - Name: "p2p", - Version: "v0.0.0", - }, - }, - NetAddress: addr, - Network: "testing", - Software: "p2ptest", - Version: "v1.2.3-rc.0-deadbeef", - Channels: []byte{MempoolChannel}, - Moniker: fmt.Sprintf("node-%d", i), - Other: p2pTypes.NodeInfoOther{ - TxIndex: "off", - RPCAddress: fmt.Sprintf("127.0.0.1:%d", 0), - }, - } - - // Create the multiplex transport - multiplexTransport := p2p.NewMultiplexTransport( - info, - *key, - conn.MConfigFromP2P(cfg), - log.NewNoopLogger(), - ) - - // Start the transport - require.NoError(t, multiplexTransport.Listen(*info.NetAddress)) - - t.Cleanup(func() { - assert.NoError(t, multiplexTransport.Close()) - }) - - dialAddr := multiplexTransport.NetAddress() - - addrs = append(addrs, &dialAddr) - ts = append(ts, multiplexTransport) - } - ctx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second) defer cancelFn() - g, _ := errgroup.WithContext(ctx) - - for i := range n { - // Make sure the switches connect to each other. - // Set up event listeners to make sure - // the setup method blocks until switches are connected - opts[i] = append(opts[i], p2p.WithPersistentPeers(addrs)) - - multiplexSwitch := p2p.NewMultiplexSwitch(ts[i], opts[i]...) - - ch, unsubFn := multiplexSwitch.Subscribe(func(event events.Event) bool { - return event.Type() == events.PeerConnected - }) - - // Start the switch - require.NoError(t, multiplexSwitch.Start()) - - sws = append(sws, multiplexSwitch) - - g.Go(func() error { - defer func() { - unsubFn() - }() - - timer := time.NewTimer(5 * time.Second) - defer timer.Stop() - - connectedPeers := make(map[p2pTypes.ID]struct{}) - - for { - select { - case evRaw := <-ch: - ev := evRaw.(events.PeerConnectedEvent) - - connectedPeers[ev.PeerID] = struct{}{} - - if len(connectedPeers) == n-1 { - return nil - } - case <-timer.C: - return errors.New("timed out waiting for peers to connect") - } - } - }) - - sws[i].DialPeers(addrs...) + cfg := p2pTesting.TestingConfig{ + Count: n, + P2PCfg: pconfig, + SwitchOptions: options, + Channels: []byte{MempoolChannel}, } - require.NoError(t, g.Wait()) + p2pTesting.MakeConnectedPeers(t, ctx, cfg) - return sws + return reactors } func waitForTxsOnReactors( diff --git a/tm2/pkg/bft/node/node.go b/tm2/pkg/bft/node/node.go index 8ca3d60fc04..aa22250e522 100644 --- a/tm2/pkg/bft/node/node.go +++ b/tm2/pkg/bft/node/node.go @@ -12,6 +12,8 @@ import ( "sync" "time" + goErrors "errors" + "github.com/gnolang/gno/tm2/pkg/bft/appconn" "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/file" "github.com/gnolang/gno/tm2/pkg/p2p/conn" @@ -853,8 +855,13 @@ func makeNodeInfo( } nodeInfo.NetAddress = addr + // Validate the node info err = nodeInfo.Validate() - return nodeInfo, err + if !goErrors.Is(err, p2pTypes.ErrUnspecifiedIP) { + return p2pTypes.NodeInfo{}, fmt.Errorf("unable to validate node info, %w", err) + } + + return nodeInfo, nil } // ------------------------------------------------------------------------------ diff --git a/tm2/pkg/internal/p2p/p2p.go b/tm2/pkg/internal/p2p/p2p.go new file mode 100644 index 00000000000..ed3f2f0f70e --- /dev/null +++ b/tm2/pkg/internal/p2p/p2p.go @@ -0,0 +1,249 @@ +// Package p2p contains testing code that is moved over, and adapted from p2p/test_utils.go. +// This isn't a good way to simulate the networking layer in TM2 modules. +// It actually isn't a good way to simulate the networking layer, in anything. +// +// Code is carried over to keep the testing code of p2p-dependent modules happy +// and "working". We should delete this entire package the second TM2 module unit tests don't +// need to rely on a live p2p cluster to pass. +package p2p + +import ( + "context" + "crypto/rand" + "errors" + "fmt" + "net" + "testing" + "time" + + "github.com/gnolang/gno/tm2/pkg/log" + "github.com/gnolang/gno/tm2/pkg/p2p" + p2pcfg "github.com/gnolang/gno/tm2/pkg/p2p/config" + "github.com/gnolang/gno/tm2/pkg/p2p/conn" + "github.com/gnolang/gno/tm2/pkg/p2p/events" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" + "github.com/gnolang/gno/tm2/pkg/service" + "github.com/gnolang/gno/tm2/pkg/versionset" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/sync/errgroup" +) + +// TestingConfig is the P2P cluster testing config +type TestingConfig struct { + P2PCfg *p2pcfg.P2PConfig // the common p2p configuration + Count int // the size of the cluster + SwitchOptions map[int][]p2p.SwitchOption // multiplex switch options + Channels []byte // the common p2p peer multiplex channels +} + +// MakeConnectedPeers creates a cluster of peers, with the given options. +// Used to simulate the networking layer for a TM2 module +func MakeConnectedPeers( + t *testing.T, + ctx context.Context, + cfg TestingConfig, +) ([]*p2p.MultiplexSwitch, []*p2p.MultiplexTransport) { + t.Helper() + + // Initialize collections for switches, transports, and addresses. + var ( + sws = make([]*p2p.MultiplexSwitch, 0, cfg.Count) + ts = make([]*p2p.MultiplexTransport, 0, cfg.Count) + addrs = make([]*p2pTypes.NetAddress, 0, cfg.Count) + ) + + createTransport := func(index int) *p2p.MultiplexTransport { + // Generate a fresh key + key := p2pTypes.GenerateNodeKey() + + addr, err := p2pTypes.NewNetAddress( + key.ID(), + &net.TCPAddr{ + IP: net.ParseIP("127.0.0.1"), + Port: 0, // random free port + }, + ) + require.NoError(t, err) + + info := p2pTypes.NodeInfo{ + VersionSet: versionset.VersionSet{ + versionset.VersionInfo{Name: "p2p", Version: "v0.0.0"}, + }, + NetAddress: addr, + Network: "testing", + Software: "p2ptest", + Version: "v1.2.3-rc.0-deadbeef", + Channels: cfg.Channels, + Moniker: fmt.Sprintf("node-%d", index), + Other: p2pTypes.NodeInfoOther{ + TxIndex: "off", + RPCAddress: fmt.Sprintf("127.0.0.1:%d", 0), + }, + } + + transport := p2p.NewMultiplexTransport( + info, + *key, + conn.MConfigFromP2P(cfg.P2PCfg), + log.NewNoopLogger(), + ) + + require.NoError(t, transport.Listen(*info.NetAddress)) + t.Cleanup(func() { assert.NoError(t, transport.Close()) }) + + return transport + } + + // Create transports and gather addresses + for i := 0; i < cfg.Count; i++ { + transport := createTransport(i) + addr := transport.NetAddress() + + addrs = append(addrs, &addr) + ts = append(ts, transport) + } + + // Connect switches and ensure all peers are connected + connectPeers := func(switchIndex int) error { + multiplexSwitch := p2p.NewMultiplexSwitch( + ts[switchIndex], + cfg.SwitchOptions[switchIndex]..., + ) + + ch, unsubFn := multiplexSwitch.Subscribe(func(event events.Event) bool { + return event.Type() == events.PeerConnected + }) + defer unsubFn() + + // Start the switch + require.NoError(t, multiplexSwitch.Start()) + + // Save it + sws = append(sws, multiplexSwitch) + + if cfg.Count == 1 { + // No peers to dial, switch is alone + return nil + } + + // Async dial the other peers + multiplexSwitch.DialPeers(addrs...) + + // Set up an exit timer + timer := time.NewTimer(5 * time.Second) + defer timer.Stop() + + connectedPeers := make(map[p2pTypes.ID]struct{}) + + for { + select { + case evRaw := <-ch: + ev := evRaw.(events.PeerConnectedEvent) + + connectedPeers[ev.PeerID] = struct{}{} + + if len(connectedPeers) == cfg.Count-1 { + return nil + } + case <-timer.C: + return errors.New("timed out waiting for peer switches to connect") + } + } + } + + g, _ := errgroup.WithContext(ctx) + for i := 0; i < cfg.Count; i++ { + g.Go(func() error { return connectPeers(i) }) + } + + require.NoError(t, g.Wait()) + + return sws, ts +} + +// createRoutableAddr generates a valid, routable NetAddress for the given node ID using a secure random IP +func createRoutableAddr(t *testing.T, id p2pTypes.ID) *p2pTypes.NetAddress { + generateIP := func() string { + ip := make([]byte, 4) + + _, err := rand.Read(ip) + require.NoError(t, err) + + return fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]) + } + + for { + addrStr := fmt.Sprintf("%s@%s:26656", id, generateIP()) + + netAddr, err := p2pTypes.NewNetAddressFromString(addrStr) + require.NoError(t, err) + + if netAddr.Routable() { + return netAddr + } + } +} + +// Peer is a live peer, utilized for testing purposes +type Peer struct { + *service.BaseService + ip net.IP + id p2pTypes.ID + addr *p2pTypes.NetAddress + kv map[string]any + + Outbound, Persistent, Private bool +} + +// NewPeer creates and starts a new mock peer. +// It generates a new routable address for the peer +func NewPeer(t *testing.T) *Peer { + t.Helper() + + var ( + nodeKey = p2pTypes.GenerateNodeKey() + netAddr = createRoutableAddr(t, nodeKey.ID()) + ) + + mp := &Peer{ + ip: netAddr.IP, + id: nodeKey.ID(), + addr: netAddr, + kv: make(map[string]interface{}), + } + + mp.BaseService = service.NewBaseService(nil, "MockPeer", mp) + + require.NoError(t, mp.Start()) + + return mp +} + +func (mp *Peer) FlushStop() { mp.Stop() } +func (mp *Peer) TrySend(_ byte, _ []byte) bool { return true } +func (mp *Peer) Send(_ byte, _ []byte) bool { return true } +func (mp *Peer) NodeInfo() p2pTypes.NodeInfo { + return p2pTypes.NodeInfo{ + NetAddress: mp.addr, + } +} +func (mp *Peer) Status() conn.ConnectionStatus { return conn.ConnectionStatus{} } +func (mp *Peer) ID() p2pTypes.ID { return mp.id } +func (mp *Peer) IsOutbound() bool { return mp.Outbound } +func (mp *Peer) IsPersistent() bool { return mp.Persistent } +func (mp *Peer) IsPrivate() bool { return mp.Private } +func (mp *Peer) Get(key string) interface{} { + if value, ok := mp.kv[key]; ok { + return value + } + return nil +} + +func (mp *Peer) Set(key string, value interface{}) { + mp.kv[key] = value +} +func (mp *Peer) RemoteIP() net.IP { return mp.ip } +func (mp *Peer) SocketAddr() *p2pTypes.NetAddress { return mp.addr } +func (mp *Peer) RemoteAddr() net.Addr { return &net.TCPAddr{IP: mp.ip, Port: 8800} } +func (mp *Peer) CloseConn() error { return nil } diff --git a/tm2/pkg/p2p/types/netaddress.go b/tm2/pkg/p2p/types/netaddress.go index 746a287067b..25dbc8def0b 100644 --- a/tm2/pkg/p2p/types/netaddress.go +++ b/tm2/pkg/p2p/types/netaddress.go @@ -21,12 +21,12 @@ const ( ) var ( - errInvalidTCPAddress = errors.New("invalid TCP address") - errUnsetIPAddress = errors.New("unset IP address") - errInvalidIP = errors.New("invalid IP address") - errUnspecifiedIP = errors.New("unspecified IP address") - errInvalidNetAddress = errors.New("invalid net address") - errEmptyHost = errors.New("empty host address") + ErrInvalidTCPAddress = errors.New("invalid TCP address") + ErrUnsetIPAddress = errors.New("unset IP address") + ErrInvalidIP = errors.New("invalid IP address") + ErrUnspecifiedIP = errors.New("unspecified IP address") + ErrInvalidNetAddress = errors.New("invalid net address") + ErrEmptyHost = errors.New("empty host address") ) // NetAddress defines information about a peer on the network @@ -53,7 +53,7 @@ func NewNetAddress(id ID, addr net.Addr) (*NetAddress, error) { // Make sure the address is valid tcpAddr, ok := addr.(*net.TCPAddr) if !ok { - return nil, errInvalidTCPAddress + return nil, ErrInvalidTCPAddress } // Validate the ID @@ -82,7 +82,7 @@ func NewNetAddressFromString(idaddr string) (*NetAddress, error) { ) if len(spl) != 2 { - return nil, errInvalidNetAddress + return nil, ErrInvalidNetAddress } var ( @@ -102,7 +102,7 @@ func NewNetAddressFromString(idaddr string) (*NetAddress, error) { } if host == "" { - return nil, errEmptyHost + return nil, ErrEmptyHost } ip := net.ParseIP(host) @@ -258,17 +258,22 @@ func (na *NetAddress) Validate() error { // Make sure the IP is set if na.IP == nil { - return errUnsetIPAddress + return ErrUnsetIPAddress } // Make sure the IP is valid if len(na.IP) != 4 && len(na.IP) != 16 { - return errInvalidIP + return ErrInvalidIP } // Check if the IP is unspecified - if na.IP.IsUnspecified() || na.RFC3849() || na.IP.Equal(net.IPv4bcast) { - return errUnspecifiedIP + if na.IP.IsUnspecified() { + return ErrUnspecifiedIP + } + + // Check if the IP conforms to standards, or is a broadcast + if na.RFC3849() || na.IP.Equal(net.IPv4bcast) { + return ErrInvalidIP } return nil diff --git a/tm2/pkg/p2p/types/node_info.go b/tm2/pkg/p2p/types/node_info.go index c511eab6545..b3881e90fe3 100644 --- a/tm2/pkg/p2p/types/node_info.go +++ b/tm2/pkg/p2p/types/node_info.go @@ -14,14 +14,14 @@ const ( ) var ( - errInvalidNetworkAddress = errors.New("invalid node network address") - errInvalidVersion = errors.New("invalid node version") - errInvalidMoniker = errors.New("invalid node moniker") - errInvalidRPCAddress = errors.New("invalid node RPC address") - errExcessiveChannels = errors.New("excessive node channels") - errDuplicateChannels = errors.New("duplicate node channels") - errIncompatibleNetworks = errors.New("incompatible networks") - errNoCommonChannels = errors.New("no common channels") + ErrInvalidNetworkAddress = errors.New("invalid node network address") + ErrInvalidVersion = errors.New("invalid node version") + ErrInvalidMoniker = errors.New("invalid node moniker") + ErrInvalidRPCAddress = errors.New("invalid node RPC address") + ErrExcessiveChannels = errors.New("excessive node channels") + ErrDuplicateChannels = errors.New("duplicate node channels") + ErrIncompatibleNetworks = errors.New("incompatible networks") + ErrNoCommonChannels = errors.New("no common channels") ) // NodeInfo is the basic node information exchanged @@ -59,7 +59,7 @@ type NodeInfoOther struct { func (info NodeInfo) Validate() error { // Validate the network address if info.NetAddress == nil { - return errInvalidNetworkAddress + return ErrInvalidNetworkAddress } if err := info.NetAddress.Validate(); err != nil { @@ -70,18 +70,18 @@ func (info NodeInfo) Validate() error { if len(info.Version) > 0 && (!strings.IsASCIIText(info.Version) || strings.ASCIITrim(info.Version) == "") { - return errInvalidVersion + return ErrInvalidVersion } // Validate Channels - ensure max and check for duplicates. if len(info.Channels) > maxNumChannels { - return errExcessiveChannels + return ErrExcessiveChannels } channelMap := make(map[byte]struct{}, len(info.Channels)) for _, ch := range info.Channels { if _, ok := channelMap[ch]; ok { - return errDuplicateChannels + return ErrDuplicateChannels } // Mark the channel as present @@ -90,13 +90,13 @@ func (info NodeInfo) Validate() error { // Validate Moniker. if !strings.IsASCIIText(info.Moniker) || strings.ASCIITrim(info.Moniker) == "" { - return errInvalidMoniker + return ErrInvalidMoniker } // XXX: Should we be more strict about address formats? rpcAddr := info.Other.RPCAddress if len(rpcAddr) > 0 && (!strings.IsASCIIText(rpcAddr) || strings.ASCIITrim(rpcAddr) == "") { - return errInvalidRPCAddress + return ErrInvalidRPCAddress } return nil @@ -118,7 +118,7 @@ func (info NodeInfo) CompatibleWith(other NodeInfo) error { // Make sure nodes are on the same network if info.Network != other.Network { - return errIncompatibleNetworks + return ErrIncompatibleNetworks } // Make sure there is at least 1 channel in common @@ -138,7 +138,7 @@ func (info NodeInfo) CompatibleWith(other NodeInfo) error { } if !commonFound { - return errNoCommonChannels + return ErrNoCommonChannels } return nil diff --git a/tm2/pkg/p2p/types/node_info_test.go b/tm2/pkg/p2p/types/node_info_test.go index d44c72bb20b..aa1e93a4aa6 100644 --- a/tm2/pkg/p2p/types/node_info_test.go +++ b/tm2/pkg/p2p/types/node_info_test.go @@ -35,7 +35,7 @@ func TestNodeInfo_Validate(t *testing.T) { { "unset net address", nil, - errInvalidNetworkAddress, + ErrInvalidNetworkAddress, }, { "zero net address ID", @@ -50,7 +50,7 @@ func TestNodeInfo_Validate(t *testing.T) { ID: GenerateNodeKey().ID(), IP: net.IP([]byte{0x00}), }, - errInvalidIP, + ErrInvalidIP, }, } @@ -97,7 +97,7 @@ func TestNodeInfo_Validate(t *testing.T) { Version: testCase.version, } - assert.ErrorIs(t, info.Validate(), errInvalidVersion) + assert.ErrorIs(t, info.Validate(), ErrInvalidVersion) }) } }) @@ -136,7 +136,7 @@ func TestNodeInfo_Validate(t *testing.T) { Moniker: testCase.moniker, } - assert.ErrorIs(t, info.Validate(), errInvalidMoniker) + assert.ErrorIs(t, info.Validate(), ErrInvalidMoniker) }) } }) @@ -174,7 +174,7 @@ func TestNodeInfo_Validate(t *testing.T) { }, } - assert.ErrorIs(t, info.Validate(), errInvalidRPCAddress) + assert.ErrorIs(t, info.Validate(), ErrInvalidRPCAddress) }) } }) @@ -190,7 +190,7 @@ func TestNodeInfo_Validate(t *testing.T) { { "too many channels", make([]byte, maxNumChannels+1), - errExcessiveChannels, + ErrExcessiveChannels, }, { "duplicate channels", @@ -199,7 +199,7 @@ func TestNodeInfo_Validate(t *testing.T) { byte(20), byte(10), }, - errDuplicateChannels, + ErrDuplicateChannels, }, } @@ -293,7 +293,7 @@ func TestNodeInfo_CompatibleWith(t *testing.T) { } ) - assert.ErrorIs(t, infoTwo.CompatibleWith(*infoOne), errIncompatibleNetworks) + assert.ErrorIs(t, infoTwo.CompatibleWith(*infoOne), ErrIncompatibleNetworks) }) t.Run("no common channels", func(t *testing.T) { @@ -327,7 +327,7 @@ func TestNodeInfo_CompatibleWith(t *testing.T) { } ) - assert.ErrorIs(t, infoTwo.CompatibleWith(*infoOne), errNoCommonChannels) + assert.ErrorIs(t, infoTwo.CompatibleWith(*infoOne), ErrNoCommonChannels) }) t.Run("fully compatible node infos", func(t *testing.T) { From 8c0e6a6dd19fc8ffc2ea9f9a3d2c562cd46cf8d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Sun, 17 Nov 2024 11:23:56 +0900 Subject: [PATCH 38/43] Tidy mods --- contribs/gnodev/go.mod | 1 + contribs/gnodev/go.sum | 2 ++ contribs/gnofaucet/go.mod | 1 + contribs/gnofaucet/go.sum | 2 ++ contribs/gnogenesis/go.mod | 2 ++ contribs/gnogenesis/go.sum | 2 ++ contribs/gnokeykc/go.mod | 2 ++ contribs/gnokeykc/go.sum | 4 ++++ contribs/gnomigrate/go.mod | 2 ++ contribs/gnomigrate/go.sum | 2 ++ misc/autocounterd/go.mod | 2 ++ misc/autocounterd/go.sum | 4 ++++ misc/loop/go.mod | 2 ++ misc/loop/go.sum | 2 ++ 14 files changed, 30 insertions(+) diff --git a/contribs/gnodev/go.mod b/contribs/gnodev/go.mod index a315d88591c..310da4e0df6 100644 --- a/contribs/gnodev/go.mod +++ b/contribs/gnodev/go.mod @@ -80,6 +80,7 @@ require ( github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/rs/cors v1.11.1 // indirect github.com/rs/xid v1.6.0 // indirect + github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/yuin/goldmark v1.5.4 // indirect diff --git a/contribs/gnodev/go.sum b/contribs/gnodev/go.sum index e38c3621483..96b11146f85 100644 --- a/contribs/gnodev/go.sum +++ b/contribs/gnodev/go.sum @@ -221,6 +221,8 @@ github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b/go.mod h1:JprPCeMgYyLKJoAy9nxpVScm7NwFSwpibdrUKm4kcw0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/contribs/gnofaucet/go.mod b/contribs/gnofaucet/go.mod index c5bb1ad0d81..a1f80a1c08e 100644 --- a/contribs/gnofaucet/go.mod +++ b/contribs/gnofaucet/go.mod @@ -32,6 +32,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/cors v1.11.1 // indirect github.com/rs/xid v1.6.0 // indirect + github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect go.opentelemetry.io/otel v1.29.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 // indirect diff --git a/contribs/gnofaucet/go.sum b/contribs/gnofaucet/go.sum index f4bdc65d7ec..76cbdc05ed0 100644 --- a/contribs/gnofaucet/go.sum +++ b/contribs/gnofaucet/go.sum @@ -113,6 +113,8 @@ github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b/go.mod h1:JprPCeMgYyLKJoAy9nxpVScm7NwFSwpibdrUKm4kcw0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/contribs/gnogenesis/go.mod b/contribs/gnogenesis/go.mod index 393fed0725d..61f210c80fd 100644 --- a/contribs/gnogenesis/go.mod +++ b/contribs/gnogenesis/go.mod @@ -33,6 +33,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/cors v1.11.1 // indirect github.com/rs/xid v1.6.0 // indirect + github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect @@ -50,6 +51,7 @@ require ( golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect golang.org/x/mod v0.20.0 // indirect golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.24.0 // indirect golang.org/x/term v0.23.0 // indirect golang.org/x/text v0.17.0 // indirect diff --git a/contribs/gnogenesis/go.sum b/contribs/gnogenesis/go.sum index f3161e47bad..82fe1918f59 100644 --- a/contribs/gnogenesis/go.sum +++ b/contribs/gnogenesis/go.sum @@ -122,6 +122,8 @@ github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b/go.mod h1:JprPCeMgYyLKJoAy9nxpVScm7NwFSwpibdrUKm4kcw0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/contribs/gnokeykc/go.mod b/contribs/gnokeykc/go.mod index 0c794afd54c..f5ea9d107b7 100644 --- a/contribs/gnokeykc/go.mod +++ b/contribs/gnokeykc/go.mod @@ -36,6 +36,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/xid v1.6.0 // indirect + github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect github.com/stretchr/testify v1.9.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/zondax/hid v0.9.2 // indirect @@ -53,6 +54,7 @@ require ( golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect golang.org/x/mod v0.20.0 // indirect golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.24.0 // indirect golang.org/x/term v0.23.0 // indirect golang.org/x/text v0.17.0 // indirect diff --git a/contribs/gnokeykc/go.sum b/contribs/gnokeykc/go.sum index 50eb5add218..16fe8a08090 100644 --- a/contribs/gnokeykc/go.sum +++ b/contribs/gnokeykc/go.sum @@ -126,6 +126,8 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b/go.mod h1:JprPCeMgYyLKJoAy9nxpVScm7NwFSwpibdrUKm4kcw0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -182,6 +184,8 @@ golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/contribs/gnomigrate/go.mod b/contribs/gnomigrate/go.mod index c492ae7c818..44d2e92c764 100644 --- a/contribs/gnomigrate/go.mod +++ b/contribs/gnomigrate/go.mod @@ -30,6 +30,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/cors v1.11.1 // indirect github.com/rs/xid v1.6.0 // indirect + github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect go.etcd.io/bbolt v1.3.11 // indirect go.opentelemetry.io/otel v1.29.0 // indirect @@ -45,6 +46,7 @@ require ( golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect golang.org/x/mod v0.20.0 // indirect golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.24.0 // indirect golang.org/x/term v0.23.0 // indirect golang.org/x/text v0.17.0 // indirect diff --git a/contribs/gnomigrate/go.sum b/contribs/gnomigrate/go.sum index f3161e47bad..82fe1918f59 100644 --- a/contribs/gnomigrate/go.sum +++ b/contribs/gnomigrate/go.sum @@ -122,6 +122,8 @@ github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b/go.mod h1:JprPCeMgYyLKJoAy9nxpVScm7NwFSwpibdrUKm4kcw0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/misc/autocounterd/go.mod b/misc/autocounterd/go.mod index 5de1d3c2974..13c461da65c 100644 --- a/misc/autocounterd/go.mod +++ b/misc/autocounterd/go.mod @@ -27,6 +27,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/xid v1.6.0 // indirect + github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect github.com/stretchr/testify v1.9.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/zondax/hid v0.9.2 // indirect @@ -44,6 +45,7 @@ require ( golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect golang.org/x/mod v0.20.0 // indirect golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.24.0 // indirect golang.org/x/term v0.23.0 // indirect golang.org/x/text v0.17.0 // indirect diff --git a/misc/autocounterd/go.sum b/misc/autocounterd/go.sum index b34cbde0c00..284e58e3ae6 100644 --- a/misc/autocounterd/go.sum +++ b/misc/autocounterd/go.sum @@ -122,6 +122,8 @@ github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b/go.mod h1:JprPCeMgYyLKJoAy9nxpVScm7NwFSwpibdrUKm4kcw0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -178,6 +180,8 @@ golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/misc/loop/go.mod b/misc/loop/go.mod index 9fc5bfb2d57..b1f978173b1 100644 --- a/misc/loop/go.mod +++ b/misc/loop/go.mod @@ -53,6 +53,7 @@ require ( github.com/prometheus/procfs v0.11.1 // indirect github.com/rs/cors v1.11.1 // indirect github.com/rs/xid v1.6.0 // indirect + github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect github.com/stretchr/testify v1.9.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect go.etcd.io/bbolt v1.3.11 // indirect @@ -71,6 +72,7 @@ require ( golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect golang.org/x/mod v0.20.0 // indirect golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.24.0 // indirect golang.org/x/term v0.23.0 // indirect golang.org/x/text v0.17.0 // indirect diff --git a/misc/loop/go.sum b/misc/loop/go.sum index 27ed94fecae..5a3b4c1c7b4 100644 --- a/misc/loop/go.sum +++ b/misc/loop/go.sum @@ -164,6 +164,8 @@ github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b/go.mod h1:JprPCeMgYyLKJoAy9nxpVScm7NwFSwpibdrUKm4kcw0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= From b02dd87a796998b6eb0e4a77b556aee9c5a69c33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Sun, 17 Nov 2024 11:26:00 +0900 Subject: [PATCH 39/43] Make fmt --- tm2/pkg/bft/consensus/reactor_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm2/pkg/bft/consensus/reactor_test.go b/tm2/pkg/bft/consensus/reactor_test.go index c864eb36360..0e1d6249783 100644 --- a/tm2/pkg/bft/consensus/reactor_test.go +++ b/tm2/pkg/bft/consensus/reactor_test.go @@ -97,7 +97,7 @@ func stopConsensusNet( p2pSwitches []*p2p.MultiplexSwitch, ) { logger.Info("stopConsensusNet", "n", len(reactors)) - for i, _ := range reactors { + for i := range reactors { logger.Info("stopConsensusNet: Stopping ConsensusReactor", "i", i) } for i, b := range eventSwitches { From 198484849134292cd6acdd625a84d510bc70950a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Sun, 17 Nov 2024 11:28:27 +0900 Subject: [PATCH 40/43] Fix lint errors --- .github/golangci.yml | 1 + tm2/pkg/internal/p2p/p2p.go | 4 +++- tm2/pkg/p2p/mock/peer.go | 2 ++ tm2/pkg/p2p/transport_test.go | 2 ++ 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/golangci.yml b/.github/golangci.yml index 43cea27a791..5be4fb5fdf4 100644 --- a/.github/golangci.yml +++ b/.github/golangci.yml @@ -79,6 +79,7 @@ issues: - gosec # Disabled linting of weak number generators - makezero # Disabled linting of intentional slice appends - goconst # Disabled linting of common mnemonics and test case strings + - unused # Disabled linting of unused mock methods - path: _\.gno linters: - errorlint # Disabled linting of error comparisons, because of lacking std lib support diff --git a/tm2/pkg/internal/p2p/p2p.go b/tm2/pkg/internal/p2p/p2p.go index ed3f2f0f70e..ea452d49934 100644 --- a/tm2/pkg/internal/p2p/p2p.go +++ b/tm2/pkg/internal/p2p/p2p.go @@ -164,6 +164,8 @@ func MakeConnectedPeers( // createRoutableAddr generates a valid, routable NetAddress for the given node ID using a secure random IP func createRoutableAddr(t *testing.T, id p2pTypes.ID) *p2pTypes.NetAddress { + t.Helper() + generateIP := func() string { ip := make([]byte, 4) @@ -210,7 +212,7 @@ func NewPeer(t *testing.T) *Peer { ip: netAddr.IP, id: nodeKey.ID(), addr: netAddr, - kv: make(map[string]interface{}), + kv: make(map[string]any), } mp.BaseService = service.NewBaseService(nil, "MockPeer", mp) diff --git a/tm2/pkg/p2p/mock/peer.go b/tm2/pkg/p2p/mock/peer.go index f6809f2deee..a3adf92a963 100644 --- a/tm2/pkg/p2p/mock/peer.go +++ b/tm2/pkg/p2p/mock/peer.go @@ -35,6 +35,8 @@ type ( // GeneratePeers generates random peers func GeneratePeers(t *testing.T, count int) []*Peer { + t.Helper() + peers := make([]*Peer, count) for i := range count { diff --git a/tm2/pkg/p2p/transport_test.go b/tm2/pkg/p2p/transport_test.go index 29818e5bafe..025b02219d6 100644 --- a/tm2/pkg/p2p/transport_test.go +++ b/tm2/pkg/p2p/transport_test.go @@ -17,6 +17,8 @@ import ( // generateNetAddr generates dummy net addresses func generateNetAddr(t *testing.T, count int) []*types.NetAddress { + t.Helper() + addrs := make([]*types.NetAddress, 0, count) for i := 0; i < count; i++ { From 372e37d359f575a697b9562b811739e24c7dd038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Sun, 17 Nov 2024 11:30:50 +0900 Subject: [PATCH 41/43] Fixup condition --- tm2/pkg/bft/node/node.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm2/pkg/bft/node/node.go b/tm2/pkg/bft/node/node.go index aa22250e522..f2e4c2848a1 100644 --- a/tm2/pkg/bft/node/node.go +++ b/tm2/pkg/bft/node/node.go @@ -857,7 +857,7 @@ func makeNodeInfo( // Validate the node info err = nodeInfo.Validate() - if !goErrors.Is(err, p2pTypes.ErrUnspecifiedIP) { + if err != nil && !goErrors.Is(err, p2pTypes.ErrUnspecifiedIP) { return p2pTypes.NodeInfo{}, fmt.Errorf("unable to validate node info, %w", err) } From a28c81426fa12aad069a4e7b751a765354d2b0a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Sun, 17 Nov 2024 12:05:31 +0900 Subject: [PATCH 42/43] Update config get unit tests --- gno.land/cmd/gnoland/config_get_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gno.land/cmd/gnoland/config_get_test.go b/gno.land/cmd/gnoland/config_get_test.go index b546d7d4cea..84cf0ba3d37 100644 --- a/gno.land/cmd/gnoland/config_get_test.go +++ b/gno.land/cmd/gnoland/config_get_test.go @@ -612,7 +612,7 @@ func TestConfig_Get_P2P(t *testing.T) { "max inbound peers", "p2p.max_num_inbound_peers", func(loadedCfg *config.Config, value []byte) { - assert.Equal(t, loadedCfg.P2P.MaxNumInboundPeers, unmarshalJSONCommon[int](t, value)) + assert.Equal(t, loadedCfg.P2P.MaxNumInboundPeers, unmarshalJSONCommon[uint64](t, value)) }, false, }, @@ -620,7 +620,7 @@ func TestConfig_Get_P2P(t *testing.T) { "max outbound peers", "p2p.max_num_outbound_peers", func(loadedCfg *config.Config, value []byte) { - assert.Equal(t, loadedCfg.P2P.MaxNumOutboundPeers, unmarshalJSONCommon[int](t, value)) + assert.Equal(t, loadedCfg.P2P.MaxNumOutboundPeers, unmarshalJSONCommon[uint64](t, value)) }, false, }, From 7d64ff97a9f6b527abd89f46705a43dd3b58a22a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Sun, 17 Nov 2024 19:52:11 +0900 Subject: [PATCH 43/43] Update README --- tm2/pkg/p2p/README.md | 604 +++++++++++++++++++++++++++++++- tm2/pkg/p2p/types/netaddress.go | 6 +- 2 files changed, 605 insertions(+), 5 deletions(-) diff --git a/tm2/pkg/p2p/README.md b/tm2/pkg/p2p/README.md index 81888403e1c..f64fafb53e0 100644 --- a/tm2/pkg/p2p/README.md +++ b/tm2/pkg/p2p/README.md @@ -1,4 +1,604 @@ -# p2p +## Overview -The p2p package provides an abstraction around peer-to-peer communication. +The `p2p` package, and its “sub-packages” contain the required building blocks for Tendermint2’s networking layer. +This document aims to explain the `p2p` terminology, and better document the way the `p2p` module works within the TM2 +ecosystem, especially in relation to other modules like `consensus`, `blockchain` and `mempool`. + +## Common Types + +To fully understand the `Concepts` section of the `p2p` documentation, there must be at least a basic understanding of +the terminology of the `p2p` module, because there are types that keep popping up constantly, and it’s worth +understanding what they’re about. + +### `NetAddress` + +```go +package types + +// NetAddress defines information about a peer on the network +// including its ID, IP address, and port +type NetAddress struct { + ID ID `json:"id"` // unique peer identifier (public key address) + IP net.IP `json:"ip"` // the IP part of the dial address + Port uint16 `json:"port"` // the port part of the dial address +} +``` + +A `NetAddress` is simply a wrapper for a unique peer in the network. + +This address consists of several parts: + +- the peer’s ID, derived from the peer’s public key (it’s the address). +- the peer’s dial address, used for executing TCP dials. + +### `ID` + +```go +// ID represents the cryptographically unique Peer ID +type ID = crypto.ID +``` + +The peer ID is the unique peer identifier. It is used for unambiguously resolving who a peer is, during communication. + +The reason the peer ID is utilized is because it is derived from the peer’s public key, used to encrypt communication, +and it needs to match the public key used in p2p communication. It can, and should be, considered unique. + +### `Reactor` + +Without going too much into detail in the terminology section, as a much more detailed explanation is discussed below: + +A `Reactor` is an abstraction of a Tendermint2 module, that needs to utilize the `p2p` layer. + +Currently active reactors in TM2, that utilize the p2p layer: + +- the consensus reactor, that handles consensus message passing +- the blockchain reactor, that handles block syncing +- the mempool reactor, that handles transaction gossiping + +All of these functionalities require a live p2p network to work, and `Reactor`s are the answer for how they can be aware +of things happening in the network (like new peers joining, for example). + +## Concepts + +### Peer + +`Peer` is an abstraction over a p2p connection that is: + +- **verified**, meaning it went through the handshaking process and the information the other peer shared checked out ( + this process is discussed in detail later). +- **multiplexed over TCP** (the only kind of p2p connections TM2 supports). + +```go +package p2p + +// Peer is a wrapper for a connected peer +type Peer interface { + service.Service + + FlushStop() + + ID() types.ID // peer's cryptographic ID + RemoteIP() net.IP // remote IP of the connection + RemoteAddr() net.Addr // remote address of the connection + + IsOutbound() bool // did we dial the peer + IsPersistent() bool // do we redial this peer when we disconnect + IsPrivate() bool // do we share the peer + + CloseConn() error // close original connection + + NodeInfo() types.NodeInfo // peer's info + Status() ConnectionStatus + SocketAddr() *types.NetAddress // actual address of the socket + + Send(byte, []byte) bool + TrySend(byte, []byte) bool + + Set(string, any) + Get(string) any +} +``` + +There are more than a few things to break down here, so let’s tackle them individually. + +The `Peer` abstraction holds callbacks relating to information about the actual live peer connection, such as what kind +of direction it is, what is the connection status, and others. + +```go +package p2p + +type Peer interface { + // ... + + ID() types.ID // peer's cryptographic ID + RemoteIP() net.IP // remote IP of the connection + RemoteAddr() net.Addr // remote address of the connection + + NodeInfo() types.NodeInfo // peer's info + Status() ConnectionStatus + SocketAddr() *types.NetAddress // actual address of the socket + + IsOutbound() bool // did we dial the peer + IsPersistent() bool // do we redial this peer when we disconnect + IsPrivate() bool // do we share the peer + + // ... +} + +``` + +However, there is part of the `Peer` abstraction that outlines the flipped design of the entire `p2p` module, and a +severe limitation of this implementation. + +```go +package p2p + +type Peer interface { + // ... + + Send(byte, []byte) bool + TrySend(byte, []byte) bool + + // ... +} +``` + +The `Peer` abstraction is used internally in `p2p`, but also by other modules that need to interact with the networking +layer — this is in itself the biggest crux of the current `p2p` implementation: modules *need to understand* how to use +and communicate with peers, regardless of the protocol logic. Networking is not an abstraction for the modules, but a +spec requirement. What this essentially means is there is heavy implementation leaking to parts of the TM2 codebase that +shouldn’t need to know how to handle individual peer broadcasts, or how to trigger custom protocol communication (like +syncing for example). +If `module A` wants to broadcast something to the peer network of the node, it needs to do something like this: + +```go +package main + +func main() { + // ... + + peers := sw.Peers().List() // fetch the peer list + + for _, p := range peers { + p.Send(...) // directly message the peer (imitate broadcast) + } + + // ... +} +``` + +An additional odd choice in the `Peer` API is the ability to use the peer as a KV store: + +```go +package p2p + +type Peer interface { + // ... + + Set(string, any) + Get(string) any + + // ... +} +``` + +For example, these methods are used within the `consensus` and `mempool` modules to keep track of active peer states ( +like current HRS data, or current peer mempool metadata). Instead of the module handling individual peer state, this +responsibility is shifted to the peer implementation, causing an odd code dependency situation. + +The root of this “flipped” design (modules needing to understand how to interact with peers) stems from the fact that +peers are instantiated with a multiplex TCP connection under the hood, and basically just wrap that connection. The +`Peer` API is an abstraction for the multiplexed TCP connection, under the hood. + +Changing this dependency stew would require a much larger rewrite of not just the `p2p` module, but other modules ( +`consensus`, `blockchain`, `mempool`) as well, and is as such left as-is. + +### Switch + +In short, a `Switch` is just the middleware layer that handles module <> `Transport` requests, and manages peers on a +high application level (that the `Transport` doesn’t concern itself with). + +The `Switch` is the entity that manages active peer connections. + +```go +package p2p + +// Switch is the abstraction in the p2p module that handles +// and manages peer connections thorough a Transport +type Switch interface { + // Broadcast publishes data on the given channel, to all peers + Broadcast(chID byte, data []byte) + + // Peers returns the latest peer set + Peers() PeerSet + + // Subscribe subscribes to active switch events + Subscribe(filterFn events.EventFilter) (<-chan events.Event, func()) + + // StopPeerForError stops the peer with the given reason + StopPeerForError(peer Peer, err error) + + // DialPeers marks the given peers as ready for async dialing + DialPeers(peerAddrs ...*types.NetAddress) +} + +``` + +The API of the `Switch` is relatively straightforward. Users of the `Switch` instantiate it with a `Transport`, and +utilize it as-is. + +The API of the `Switch` is geared towards asynchronicity, and as such users of the `Switch` need to adapt to some +limitations, such as not having synchronous dials, or synchronous broadcasts. + +#### Services + +There are 3 services that run on top of the `MultiplexSwitch`, upon startup: + +- **the accept service** +- **the dial service** +- **the redial service** + +```go +package p2p + +// OnStart implements BaseService. It starts all the reactors and peers. +func (sw *MultiplexSwitch) OnStart() error { + // Start reactors + for _, reactor := range sw.reactors { + if err := reactor.Start(); err != nil { + return fmt.Errorf("unable to start reactor %w", err) + } + } + + // Run the peer accept routine. + // The accept routine asynchronously accepts + // and processes incoming peer connections + go sw.runAcceptLoop(sw.ctx) + + // Run the dial routine. + // The dial routine parses items in the dial queue + // and initiates outbound peer connections + go sw.runDialLoop(sw.ctx) + + // Run the redial routine. + // The redial routine monitors for important + // peer disconnects, and attempts to reconnect + // to them + go sw.runRedialLoop(sw.ctx) + + return nil +} +``` + +##### Accept Service + +The `MultiplexSwitch` needs to actively listen for incoming connections, and handle them accordingly. These situations +occur when a peer *Dials* (more on this later) another peer, and wants to establish a connection. This connection is +outbound for one peer, and inbound for the other. + +Depending on what kind of security policies or configuration the peer has in place, the connection can be accepted, or +rejected for a number of reasons: + +- the maximum number of inbound peers is reached +- the multiplex connection fails upon startup (rare) + +The `Switch` relies on the `Transport` to return a **verified and valid** peer connection. After the `Transport` +delivers, the `Switch` makes sure having the peer makes sense, given the p2p configuration of the node. + +```go +package p2p + +func (sw *MultiplexSwitch) runAcceptLoop(ctx context.Context) { + // ... + + p, err := sw.transport.Accept(ctx, sw.peerBehavior) + if err != nil { + sw.Logger.Error( + "error encountered during peer connection accept", + "err", err, + ) + + continue + } + + // Ignore connection if we already have enough peers. + if in := sw.Peers().NumInbound(); in >= sw.maxInboundPeers { + sw.Logger.Info( + "Ignoring inbound connection: already have enough inbound peers", + "address", p.SocketAddr(), + "have", in, + "max", sw.maxInboundPeers, + ) + + sw.transport.Remove(p) + + continue + } + + // ... +} + +``` + +In fact, this is the central point in the relationship between the `Switch` and `Transport`. +The `Transport` is responsible for establishing the connection, and the `Switch` is responsible for handling it after +it’s been established. + +When TM2 modules communicate with the `p2p` module, they communicate *with the `Switch`, not the `Transport`* to execute +peer-related actions. + +##### Dial Service + +Peers are dialed asynchronously in the `Switch`, as is suggested by the `Switch` API: + +```go +DialPeers(peerAddrs ...*types.NetAddress) +``` + +The `MultiplexSwitch` implementation utilizes a concept called a *dial queue*. + +A dial queue is a priority-based queue (sorted by dial time, ascending) from which dial requests are taken out of and +executed in the form of peer dialing (through the `Transport`, of course). + +The queue needs to be sorted by the dial time, since there are asynchronous dial requests that need to be executed as +soon as possible, while others can wait to be executed up until a certain point in time. + +```go +package p2p + +func (sw *MultiplexSwitch) runDialLoop(ctx context.Context) { + // ... + + // Grab a dial item + item := sw.dialQueue.Peek() + if item == nil { + // Nothing to dial + continue + } + + // Check if the dial time is right + // for the item + if time.Now().Before(item.Time) { + // Nothing to dial + continue + } + + // Pop the item from the dial queue + item = sw.dialQueue.Pop() + + // Dial the peer + sw.Logger.Info( + "dialing peer", + "address", item.Address.String(), + ) + + // ... +} +``` + +To follow the outcomes of dial requests, users of the `Switch` can subscribe to peer events (more on this later). + +##### Redial Service + +The TM2 `p2p` module has a concept of something called *persistent peers*. + +Persistent peers are specific peers whose connections must be preserved, at all costs. They are specified in the +top-level node P2P configuration, under `p2p.persistent_peers`. + +These peer connections are special, as they don’t adhere to high-level configuration limits like the maximum peer cap, +instead, they are monitored and handled actively. + +A good candidate for a persistent peer is a bootnode, that bootstraps and facilitates peer discovery for the network. + +If a persistent peer connection is lost for whatever reason (for ex, the peer disconnects), the redial service of the +`MultiplexSwitch` will create a dial request for the dial service, and attempt to re-establish the lost connection. + +```go +package p2p + +func (sw *MultiplexSwitch) runRedialLoop(ctx context.Context) { + // ... + + var ( + peers = sw.Peers() + peersToDial = make([]*types.NetAddress, 0) + ) + + sw.persistentPeers.Range(func(key, value any) bool { + var ( + id = key.(types.ID) + addr = value.(*types.NetAddress) + ) + + // Check if the peer is part of the peer set + // or is scheduled for dialing + if peers.Has(id) || sw.dialQueue.Has(addr) { + return true + } + + peersToDial = append(peersToDial, addr) + + return true + }) + + if len(peersToDial) == 0 { + // No persistent peers are missing + return + } + + // Add the peers to the dial queue + sw.DialPeers(peersToDial...) + + // ... +} +``` + +#### Events + +The `Switch` is meant to be asynchronous. + +This means that processes like dialing peers, removing peers, doing broadcasts and more, is not a synchronous blocking +process for the `Switch` user. + +To be able to tap into the outcome of these asynchronous events, the `Switch` utilizes a simple event system, based on +event filters. + +```go +package main + +func main() { + // ... + + // Subscribe to live switch events + ch, unsubFn := multiplexSwitch.Subscribe(func(event events.Event) bool { + // This subscription will only return "PeerConnected" events + return event.Type() == events.PeerConnected + }) + + defer unsubFn() // removes the subscription + + select { + // Events are sent to the channel as soon as + // they appear and pass the subscription filter + case ev <- ch: + e := ev.(*events.PeerConnectedEvent) + // use event data... + case <-ctx.Done(): + // ... + } + + // ... +} +``` + +An event setup like this is useful for example when the user of the `Switch` wants to capture successful peer dial +events, in realtime. + +#### What is “peer behavior”? + +```go +package p2p + +// PeerBehavior wraps the Reactor and MultiplexSwitch information a Transport would need when +// dialing or accepting new Peer connections. +// It is worth noting that the only reason why this information is required in the first place, +// is because Peers expose an API through which different TM modules can interact with them. +// In the future™, modules should not directly "Send" anything to Peers, but instead communicate through +// other mediums, such as the P2P module +type PeerBehavior interface { + // ReactorChDescriptors returns the Reactor channel descriptors + ReactorChDescriptors() []*conn.ChannelDescriptor + + // Reactors returns the node's active p2p Reactors (modules) + Reactors() map[byte]Reactor + + // HandlePeerError propagates a peer connection error for further processing + HandlePeerError(Peer, error) + + // IsPersistentPeer returns a flag indicating if the given peer is persistent + IsPersistentPeer(types.ID) bool + + // IsPrivatePeer returns a flag indicating if the given peer is private + IsPrivatePeer(types.ID) bool +} + +``` + +In short, the previously-mentioned crux of the `p2p` implementation (having `Peer`s be directly managed by different TM2 +modules) requires information on how to behave when interacting with other peers. + +TM2 modules on `peer A` communicate through something called *channels* to the same modules on `peer B`. For example, if +the `mempool` module on `peer A` wants to share a transaction to the mempool module on `peer B`, it will utilize a +dedicated (and unique!) channel for it (ex. `0x30`). This is a protocol that lives on top of the already-established +multiplexed connection, and metadata relating to it is passed down through *peer behavior*. + +### Transport + +As previously mentioned, the `Transport` is the infrastructure layer of the `p2p` module. + +In contrast to the `Switch`, which is concerned with higher-level application logic (like the number of peers, peer +limits, etc), the `Transport` is concerned with actually establishing and maintaining peer connections on a much lower +level. + +```go +package p2p + +// Transport handles peer dialing and connection acceptance. Additionally, +// it is also responsible for any custom connection mechanisms (like handshaking). +// Peers returned by the transport are considered to be verified and sound +type Transport interface { + // NetAddress returns the Transport's dial address + NetAddress() types.NetAddress + + // Accept returns a newly connected inbound peer + Accept(context.Context, PeerBehavior) (Peer, error) + + // Dial dials a peer, and returns it + Dial(context.Context, types.NetAddress, PeerBehavior) (Peer, error) + + // Remove drops any resources associated + // with the Peer in the transport + Remove(Peer) +} +``` + +When peers dial other peers in TM2, they are in fact dialing their `Transport`s, and the connection is being handled +here. + +- `Accept` waits for an **incoming** connection, parses it and returns it. +- `Dial` attempts to establish an **outgoing** connection, parses it and returns it. + +There are a few important steps that happen when establishing a p2p connection in TM2, between 2 different peers: + +1. The peers go through a handshaking process, and establish something called a *secret connection*. The handshaking + process is based on the [STS protocol](https://github.com/tendermint/tendermint/blob/0.1/docs/sts-final.pdf), and + after it is completed successfully, all communication between the 2 peers is **encrypted**. +2. After establishing a secret connection, the peers exchange their respective node information. The purpose of this + step is to verify that the peers are indeed compatible with each other, and should be establishing a connection in + the first place (same network, common protocols , etc). +3. Once the secret connection is established, and the node information is exchanged, the connection to the peer is + considered valid and verified — it can now be used by the `Switch` (accepted, or rejected, based on `Switch` + high-level constraints). Note the distinction here that the `Transport` establishes and maintains the connection, but + it can ultimately be scraped by the `Switch` at any point in time. + +### Peer Discovery + +There is a final service that runs alongside the previously-mentioned `Switch` services — peer discovery. + +Every blockchain node needs an adequate amount of peers to communicate with, in order to ensure smooth functioning. For +validator nodes, they need to be *loosely connected* to at least 2/3+ of the validator set in order to participate and +not cause block misses or mis-votes (loosely connected means that there always exists a path between different peers in +the network topology, that allows them to be reachable to each other). + +The peer discovery service ensures that the given node is always learning more about the overall network topology, and +filling out any empty connection slots (outbound peers). + +This background service works in the following (albeit primitive) way: + +1. At specific intervals, `node A` checks its peer table, and picks a random peer `P`, from the active peer list. +2. When `P` is picked, `node A` initiates a discovery protocol process, in which: + - `node A` sends a request to peer `P` for his peer list (max 30 peers) + - peer `P` responds to the request + +3. Once `node A` has the peer list from `P`, it adds the entire peer list into the dial queue, to establish outbound + peer connections. + +This process repeats at specific intervals. It is worth nothing that if the limit of outbound peers is reached, the peer +dials have no effect. + +#### Bootnodes (Seeds) + +Bootnodes are specialized network nodes that play a critical role in the initial peer discovery process for new nodes +joining the network. + +When a blockchain client starts, it needs to find and connect to other nodes to synchronize data and participate in the +network. Bootnodes provide a predefined list of accessible and reliable entry points that act as a gateway for +discovering other active nodes (through peer discovery). + +These nodes are provided as part of the node’s p2p configuration. Once connected to a bootnode, the client uses peer +discovery to discover and connect to additional peers, enabling full participation and unlocking other client +protocols (consensus, mempool…). + +Bootnodes usually do not store the full blockchain or participate in consensus; their primary role is to facilitate +connectivity in the network (act as a peer relay). \ No newline at end of file diff --git a/tm2/pkg/p2p/types/netaddress.go b/tm2/pkg/p2p/types/netaddress.go index 25dbc8def0b..7d1920e29f3 100644 --- a/tm2/pkg/p2p/types/netaddress.go +++ b/tm2/pkg/p2p/types/netaddress.go @@ -32,9 +32,9 @@ var ( // NetAddress defines information about a peer on the network // including its ID, IP address, and port type NetAddress struct { - ID ID `json:"id"` // authenticated identifier - IP net.IP `json:"ip"` // part of "addr" - Port uint16 `json:"port"` // part of "addr" + ID ID `json:"id"` // unique peer identifier (public key address) + IP net.IP `json:"ip"` // the IP part of the dial address + Port uint16 `json:"port"` // the port part of the dial address } // NetAddressString returns id@addr. It strips the leading