package p2p_test import ( "net" "strings" "testing" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/internal/p2p" "github.com/tendermint/tendermint/types" ) func TestNewNodeID(t *testing.T) { // Most tests are in TestNodeID_Validate, this just checks that it's validated. testcases := []struct { input string expect types.NodeID ok bool }{ {"", "", false}, {"foo", "", false}, {"00112233445566778899aabbccddeeff00112233", "00112233445566778899aabbccddeeff00112233", true}, {"00112233445566778899AABBCCDDEEFF00112233", "00112233445566778899aabbccddeeff00112233", true}, {"00112233445566778899aabbccddeeff0011223", "", false}, {"00112233445566778899aabbccddeeff0011223g", "", false}, } for _, tc := range testcases { tc := tc t.Run(tc.input, func(t *testing.T) { id, err := types.NewNodeID(tc.input) if !tc.ok { require.Error(t, err) } else { require.NoError(t, err) require.Equal(t, id, tc.expect) } }) } } func TestNewNodeIDFromPubKey(t *testing.T) { privKey := ed25519.GenPrivKeyFromSecret([]byte("foo")) nodeID := types.NodeIDFromPubKey(privKey.PubKey()) require.Equal(t, types.NodeID("045f5600654182cfeaccfe6cb19f0642e8a59898"), nodeID) require.NoError(t, nodeID.Validate()) } func TestNodeID_Bytes(t *testing.T) { testcases := []struct { nodeID types.NodeID expect []byte ok bool }{ {"", []byte{}, true}, {"01f0", []byte{0x01, 0xf0}, true}, {"01F0", []byte{0x01, 0xf0}, true}, {"01F", nil, false}, {"01g0", nil, false}, } for _, tc := range testcases { tc := tc t.Run(string(tc.nodeID), func(t *testing.T) { bz, err := tc.nodeID.Bytes() if tc.ok { require.NoError(t, err) require.Equal(t, tc.expect, bz) } else { require.Error(t, err) } }) } } func TestNodeID_Validate(t *testing.T) { testcases := []struct { nodeID types.NodeID ok bool }{ {"", false}, {"00", false}, {"00112233445566778899aabbccddeeff00112233", true}, {"00112233445566778899aabbccddeeff001122334", false}, {"00112233445566778899aabbccddeeffgg001122", false}, {"00112233445566778899AABBCCDDEEFF00112233", false}, } for _, tc := range testcases { tc := tc t.Run(string(tc.nodeID), func(t *testing.T) { err := tc.nodeID.Validate() if tc.ok { require.NoError(t, err) } else { require.Error(t, err) } }) } } func TestParseNodeAddress(t *testing.T) { user := "00112233445566778899aabbccddeeff00112233" id := types.NodeID(user) testcases := []struct { url string expect p2p.NodeAddress ok bool }{ // Valid addresses. { "mconn://" + user + "@127.0.0.1:26657/some/path?foo=bar", p2p.NodeAddress{Protocol: "mconn", NodeID: id, Hostname: "127.0.0.1", Port: 26657, Path: "/some/path?foo=bar"}, true, }, { "TCP://" + strings.ToUpper(user) + "@hostname.DOMAIN:8080/Path/%f0%9f%91%8B#Anchor", p2p.NodeAddress{Protocol: "tcp", NodeID: id, Hostname: "hostname.domain", Port: 8080, Path: "/Path/👋#Anchor"}, true, }, { user + "@127.0.0.1", p2p.NodeAddress{Protocol: "mconn", NodeID: id, Hostname: "127.0.0.1"}, true, }, { user + "@hostname.domain", p2p.NodeAddress{Protocol: "mconn", NodeID: id, Hostname: "hostname.domain"}, true, }, { user + "@hostname.domain:80", p2p.NodeAddress{Protocol: "mconn", NodeID: id, Hostname: "hostname.domain", Port: 80}, true, }, { user + "@%F0%9F%91%8B", p2p.NodeAddress{Protocol: "mconn", NodeID: id, Hostname: "👋"}, true, }, { user + "@%F0%9F%91%8B:80/path", p2p.NodeAddress{Protocol: "mconn", NodeID: id, Hostname: "👋", Port: 80, Path: "/path"}, true, }, { user + "@127.0.0.1:26657", p2p.NodeAddress{Protocol: "mconn", NodeID: id, Hostname: "127.0.0.1", Port: 26657}, true, }, { user + "@127.0.0.1:26657/path", p2p.NodeAddress{Protocol: "mconn", NodeID: id, Hostname: "127.0.0.1", Port: 26657, Path: "/path"}, true, }, { user + "@0.0.0.0:0", p2p.NodeAddress{Protocol: "mconn", NodeID: id, Hostname: "0.0.0.0", Port: 0}, true, }, { "memory:" + user, p2p.NodeAddress{Protocol: "memory", NodeID: id}, true, }, { user + "@[1::]", p2p.NodeAddress{Protocol: "mconn", NodeID: id, Hostname: "1::"}, true, }, { "mconn://" + user + "@[fd80:b10c::2]:26657", p2p.NodeAddress{Protocol: "mconn", NodeID: id, Hostname: "fd80:b10c::2", Port: 26657}, true, }, // Invalid addresses. {"", p2p.NodeAddress{}, false}, {"127.0.0.1", p2p.NodeAddress{}, false}, {"hostname", p2p.NodeAddress{}, false}, {"scheme:", p2p.NodeAddress{}, false}, {"memory:foo", p2p.NodeAddress{}, false}, {user + "@%F%F0", p2p.NodeAddress{}, false}, {"//" + user + "@127.0.0.1", p2p.NodeAddress{}, false}, {"://" + user + "@127.0.0.1", p2p.NodeAddress{}, false}, {"mconn://foo@127.0.0.1", p2p.NodeAddress{}, false}, {"mconn://" + user + "@127.0.0.1:65536", p2p.NodeAddress{}, false}, {"mconn://" + user + "@:80", p2p.NodeAddress{}, false}, } for _, tc := range testcases { tc := tc t.Run(tc.url, func(t *testing.T) { address, err := p2p.ParseNodeAddress(tc.url) if !tc.ok { require.Error(t, err) } else { require.NoError(t, err) require.Equal(t, tc.expect, address) } }) } } func TestNodeAddress_Resolve(t *testing.T) { id := types.NodeID("00112233445566778899aabbccddeeff00112233") testcases := []struct { address p2p.NodeAddress expect p2p.Endpoint ok bool }{ // Valid networked addresses (with hostname). { p2p.NodeAddress{Protocol: "tcp", Hostname: "127.0.0.1", Port: 80, Path: "/path"}, p2p.Endpoint{Protocol: "tcp", IP: net.IPv4(127, 0, 0, 1), Port: 80, Path: "/path"}, true, }, { p2p.NodeAddress{Protocol: "tcp", Hostname: "localhost", Port: 80, Path: "/path"}, p2p.Endpoint{Protocol: "tcp", IP: net.IPv4(127, 0, 0, 1), Port: 80, Path: "/path"}, true, }, { p2p.NodeAddress{Protocol: "tcp", Hostname: "localhost", Port: 80, Path: "/path"}, p2p.Endpoint{Protocol: "tcp", IP: net.IPv6loopback, Port: 80, Path: "/path"}, true, }, { p2p.NodeAddress{Protocol: "tcp", Hostname: "127.0.0.1"}, p2p.Endpoint{Protocol: "tcp", IP: net.IPv4(127, 0, 0, 1)}, true, }, { p2p.NodeAddress{Protocol: "tcp", Hostname: "::1"}, p2p.Endpoint{Protocol: "tcp", IP: net.IPv6loopback}, true, }, { p2p.NodeAddress{Protocol: "tcp", Hostname: "8.8.8.8"}, p2p.Endpoint{Protocol: "tcp", IP: net.IPv4(8, 8, 8, 8)}, true, }, { p2p.NodeAddress{Protocol: "tcp", Hostname: "2001:0db8::ff00:0042:8329"}, p2p.Endpoint{Protocol: "tcp", IP: []byte{ 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x42, 0x83, 0x29}}, true, }, { p2p.NodeAddress{Protocol: "tcp", Hostname: "some.missing.host.tendermint.com"}, p2p.Endpoint{}, false, }, // Valid non-networked addresses. { p2p.NodeAddress{Protocol: "memory", NodeID: id}, p2p.Endpoint{Protocol: "memory", Path: string(id)}, true, }, { p2p.NodeAddress{Protocol: "memory", NodeID: id, Path: string(id)}, p2p.Endpoint{Protocol: "memory", Path: string(id)}, true, }, // Invalid addresses. {p2p.NodeAddress{}, p2p.Endpoint{}, false}, {p2p.NodeAddress{Hostname: "127.0.0.1"}, p2p.Endpoint{}, false}, {p2p.NodeAddress{Protocol: "tcp", Hostname: "127.0.0.1:80"}, p2p.Endpoint{}, false}, {p2p.NodeAddress{Protocol: "memory"}, p2p.Endpoint{}, false}, {p2p.NodeAddress{Protocol: "memory", Path: string(id)}, p2p.Endpoint{}, false}, {p2p.NodeAddress{Protocol: "tcp", Hostname: "💥"}, p2p.Endpoint{}, false}, } for _, tc := range testcases { tc := tc t.Run(tc.address.String(), func(t *testing.T) { endpoints, err := tc.address.Resolve(ctx) if !tc.ok { require.Error(t, err) return } require.Contains(t, endpoints, tc.expect) }) } } func TestNodeAddress_String(t *testing.T) { id := types.NodeID("00112233445566778899aabbccddeeff00112233") user := string(id) testcases := []struct { address p2p.NodeAddress expect string }{ // Valid networked addresses (with hostname). { p2p.NodeAddress{Protocol: "tcp", NodeID: id, Hostname: "host", Port: 80, Path: "/path/sub?foo=bar&x=y#anchor"}, "tcp://" + user + "@host:80/path/sub%3Ffoo=bar&x=y%23anchor", }, { p2p.NodeAddress{Protocol: "tcp", NodeID: id, Hostname: "host.domain"}, "tcp://" + user + "@host.domain", }, { p2p.NodeAddress{NodeID: id, Hostname: "host", Port: 80, Path: "foo/bar"}, user + "@host:80/foo/bar", }, // Valid non-networked addresses (without hostname). { p2p.NodeAddress{Protocol: "memory", NodeID: id, Path: string(id)}, "memory:" + user, }, { p2p.NodeAddress{Protocol: "memory", NodeID: id}, "memory:" + user, }, // Addresses with weird contents, which are technically fine (not harmful). { p2p.NodeAddress{Protocol: "💬", NodeID: "👨", Hostname: "💻", Port: 80, Path: "🛣"}, "💬://%F0%9F%91%A8@%F0%9F%92%BB:80/%F0%9F%9B%A3", }, // Partial (invalid) addresses. {p2p.NodeAddress{}, ""}, {p2p.NodeAddress{NodeID: id}, user + "@"}, {p2p.NodeAddress{Protocol: "tcp"}, "tcp:"}, {p2p.NodeAddress{Hostname: "host"}, "host"}, {p2p.NodeAddress{Port: 80}, ""}, {p2p.NodeAddress{Path: "path"}, "/path"}, {p2p.NodeAddress{NodeID: id, Port: 80}, user + "@"}, {p2p.NodeAddress{Protocol: "tcp", Hostname: "host"}, "tcp://host"}, { p2p.NodeAddress{Protocol: "memory", NodeID: id, Path: "path"}, "memory://00112233445566778899aabbccddeeff00112233@/path", }, { p2p.NodeAddress{Protocol: "memory", NodeID: id, Port: 80}, "memory:00112233445566778899aabbccddeeff00112233", }, } for _, tc := range testcases { tc := tc t.Run(tc.address.String(), func(t *testing.T) { require.Equal(t, tc.expect, tc.address.String()) }) } } func TestNodeAddress_Validate(t *testing.T) { id := types.NodeID("00112233445566778899aabbccddeeff00112233") testcases := []struct { address p2p.NodeAddress ok bool }{ // Valid addresses. {p2p.NodeAddress{Protocol: "mconn", NodeID: id, Hostname: "host", Port: 80, Path: "/path"}, true}, {p2p.NodeAddress{Protocol: "mconn", NodeID: id, Hostname: "host"}, true}, {p2p.NodeAddress{Protocol: "mconn", NodeID: id, Path: "path"}, true}, {p2p.NodeAddress{Protocol: "mconn", NodeID: id, Hostname: "👋", Path: "👋"}, true}, // Invalid addresses. {p2p.NodeAddress{}, false}, {p2p.NodeAddress{NodeID: "foo", Hostname: "host"}, false}, {p2p.NodeAddress{Protocol: "mconn", NodeID: id}, true}, {p2p.NodeAddress{Protocol: "mconn", NodeID: "foo", Hostname: "host"}, false}, {p2p.NodeAddress{Protocol: "mconn", NodeID: id, Port: 80, Path: "path"}, false}, } for _, tc := range testcases { tc := tc t.Run(tc.address.String(), func(t *testing.T) { err := tc.address.Validate() if tc.ok { require.NoError(t, err) } else { require.Error(t, err) } }) } }