package types import ( "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto/ed25519" tmnet "github.com/tendermint/tendermint/libs/net" "github.com/tendermint/tendermint/version" ) const testCh = 0x01 func TestNodeInfoValidate(t *testing.T) { // empty fails ni := NodeInfo{} assert.Error(t, ni.Validate()) 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 := "\t" emptySpace := " " testCases := []struct { testName string malleateNodeInfo func(*NodeInfo) expectErr bool }{ { "Too Many Channels", func(ni *NodeInfo) { ni.Channels = append(channels, byte(maxNumChannels)) }, // nolint: gocritic true, }, {"Duplicate Channel", func(ni *NodeInfo) { ni.Channels = dupChannels }, true}, {"Good Channels", func(ni *NodeInfo) { ni.Channels = ni.Channels[:5] }, false}, {"Invalid NetAddress", func(ni *NodeInfo) { ni.ListenAddr = "not-an-address" }, true}, {"Good NetAddress", func(ni *NodeInfo) { ni.ListenAddr = "0.0.0.0:26656" }, false}, {"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 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}, {"Empty RPCAddress", func(ni *NodeInfo) { ni.Other.RPCAddress = "" }, false}, {"Good RPCAddress", func(ni *NodeInfo) { ni.Other.RPCAddress = "0.0.0.0:26657" }, false}, } nodeKeyID := testNodeID() name := "testing" // test case passes ni = testNodeInfo(t, nodeKeyID, name) ni.Channels = channels assert.NoError(t, ni.Validate()) for _, tc := range testCases { ni := testNodeInfo(t, nodeKeyID, 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) } } } func testNodeID() NodeID { return NodeIDFromPubKey(ed25519.GenPrivKey().PubKey()) } func testNodeInfo(t *testing.T, id NodeID, name string) NodeInfo { return testNodeInfoWithNetwork(t, id, name, "testing") } func testNodeInfoWithNetwork(t *testing.T, id NodeID, name, network string) NodeInfo { t.Helper() return NodeInfo{ ProtocolVersion: ProtocolVersion{ P2P: version.P2PProtocol, Block: version.BlockProtocol, App: 0, }, NodeID: id, ListenAddr: fmt.Sprintf("127.0.0.1:%d", getFreePort(t)), Network: network, Version: "1.2.3-rc0-deadbeef", Channels: []byte{testCh}, Moniker: name, Other: NodeInfoOther{ TxIndex: "on", RPCAddress: fmt.Sprintf("127.0.0.1:%d", getFreePort(t)), }, } } func getFreePort(t *testing.T) int { t.Helper() port, err := tmnet.GetFreePort() require.NoError(t, err) return port } func TestNodeInfoCompatible(t *testing.T) { nodeKey1ID := testNodeID() nodeKey2ID := testNodeID() name := "testing" var newTestChannel byte = 0x2 // test NodeInfo is compatible ni1 := testNodeInfo(t, nodeKey1ID, name) ni2 := testNodeInfo(t, nodeKey2ID, name) assert.NoError(t, ni1.CompatibleWith(ni2)) // add another channel; still compatible ni2.Channels = []byte{newTestChannel, testCh} assert.NoError(t, ni1.CompatibleWith(ni2)) testCases := []struct { testName string malleateNodeInfo func(*NodeInfo) }{ {"Wrong block version", func(ni *NodeInfo) { ni.ProtocolVersion.Block++ }}, {"Wrong network", func(ni *NodeInfo) { ni.Network += "-wrong" }}, {"No common channels", func(ni *NodeInfo) { ni.Channels = []byte{newTestChannel} }}, } for _, tc := range testCases { ni := testNodeInfo(t, nodeKey2ID, name) tc.malleateNodeInfo(&ni) assert.Error(t, ni1.CompatibleWith(ni)) } } func TestNodeInfoAddChannel(t *testing.T) { nodeInfo := testNodeInfo(t, testNodeID(), "testing") nodeInfo.Channels = []byte{} require.Empty(t, nodeInfo.Channels) nodeInfo.AddChannel(2) require.Contains(t, nodeInfo.Channels, byte(0x02)) // adding the same channel again shouldn't be a problem nodeInfo.AddChannel(2) require.Contains(t, nodeInfo.Channels, byte(0x02)) } func TestParseAddressString(t *testing.T) { 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", "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", true, }, { "tcp input", "tcp://deadbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", true, }, { "udp input", "udp://deadbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", true, }, {"malformed tcp input", "tcp//deadbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", "", false}, {"malformed udp input", "udp//deadbeefdeadbeefdeadbeefdeadbeefdeadbeef@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 hex nodeId", "xxxxbeefdeadbeefdeadbeefdeadbeefdeadbeef@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}, {"notHex nodeId w/tcp", "tcp://xxxxbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", "", false}, { "correct nodeId w/tcp", "tcp://deadbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef@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}, } for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { addr, port, err := ParseAddressString(tc.addr) if tc.correct { require.NoError(t, err, tc.addr) assert.Contains(t, tc.expected, addr.String()) assert.Contains(t, tc.expected, fmt.Sprint(port)) } else { assert.Error(t, err, "%v", tc.addr) } }) } }