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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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