p2p: use sub dirs

This commit is contained in:
Ethan Buchman
2018-01-20 21:35:37 -05:00
parent 03550c7076
commit 5b5cbaa66a
28 changed files with 366 additions and 407 deletions
+20
View File
@@ -0,0 +1,20 @@
package types
import (
"errors"
"fmt"
)
var (
ErrSwitchDuplicatePeer = errors.New("Duplicate peer")
ErrSwitchConnectToSelf = errors.New("Connect to self")
)
type ErrSwitchAuthenticationFailure struct {
Dialed *NetAddress
Got ID
}
func (e ErrSwitchAuthenticationFailure) Error() string {
return fmt.Sprintf("Failed to authenticate peer. Dialed %v, but got peer with ID %s", e.Dialed, e.Got)
}
+113
View File
@@ -0,0 +1,113 @@
package types
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
crypto "github.com/tendermint/go-crypto"
cmn "github.com/tendermint/tmlibs/common"
)
// ID is a hex-encoded crypto.Address
type ID string
// IDByteLength is the length of a crypto.Address. Currently only 20.
// TODO: support other length addresses ?
const IDByteLength = 20
//------------------------------------------------------------------------------
// Persistent peer ID
// TODO: encrypt on disk
// NodeKey is the persistent peer key.
// It contains the nodes private key for authentication.
type NodeKey struct {
PrivKey crypto.PrivKey `json:"priv_key"` // our priv key
}
// ID returns the peer's canonical ID - the hash of its public key.
func (nodeKey *NodeKey) ID() ID {
return PubKeyToID(nodeKey.PubKey())
}
// PubKey returns the peer's PubKey
func (nodeKey *NodeKey) PubKey() crypto.PubKey {
return nodeKey.PrivKey.PubKey()
}
// PubKeyToID returns the ID corresponding to the given PubKey.
// It's the hex-encoding of the pubKey.Address().
func PubKeyToID(pubKey crypto.PubKey) ID {
return ID(hex.EncodeToString(pubKey.Address()))
}
// 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 cmn.FileExists(filePath) {
nodeKey, err := loadNodeKey(filePath)
if err != nil {
return nil, err
}
return nodeKey, nil
} else {
return genNodeKey(filePath)
}
}
func loadNodeKey(filePath string) (*NodeKey, error) {
jsonBytes, err := ioutil.ReadFile(filePath)
if err != nil {
return nil, err
}
nodeKey := new(NodeKey)
err = json.Unmarshal(jsonBytes, nodeKey)
if err != nil {
return nil, fmt.Errorf("Error reading NodeKey from %v: %v\n", filePath, err)
}
return nodeKey, nil
}
func genNodeKey(filePath string) (*NodeKey, error) {
privKey := crypto.GenPrivKeyEd25519().Wrap()
nodeKey := &NodeKey{
PrivKey: privKey,
}
jsonBytes, err := json.Marshal(nodeKey)
if err != nil {
return nil, err
}
err = ioutil.WriteFile(filePath, jsonBytes, 0600)
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))
}
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)...)
}
+50
View File
@@ -0,0 +1,50 @@
package types
import (
"bytes"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
cmn "github.com/tendermint/tmlibs/common"
)
func TestLoadOrGenNodeKey(t *testing.T) {
filePath := filepath.Join(os.TempDir(), cmn.RandStr(12)+"_peer_id.json")
nodeKey, err := LoadOrGenNodeKey(filePath)
assert.Nil(t, err)
nodeKey2, err := LoadOrGenNodeKey(filePath)
assert.Nil(t, err)
assert.Equal(t, nodeKey, nodeKey2)
}
//----------------------------------------------------------
func padBytes(bz []byte, targetBytes int) []byte {
return append(bz, bytes.Repeat([]byte{0xFF}, targetBytes-len(bz))...)
}
func TestPoWTarget(t *testing.T) {
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)},
}
for _, c := range cases {
assert.Equal(t, MakePoWTarget(c.difficulty, 20*8), c.target)
}
}
+300
View File
@@ -0,0 +1,300 @@
// Modified for Tendermint
// Originally Copyright (c) 2013-2014 Conformal Systems LLC.
// https://github.com/conformal/btcd/blob/master/LICENSE
package types
import (
"encoding/hex"
"flag"
"fmt"
"net"
"strconv"
"strings"
"time"
"github.com/pkg/errors"
cmn "github.com/tendermint/tmlibs/common"
)
// NetAddress defines information about a peer on the network
// including its ID, IP address, and port.
type NetAddress struct {
ID ID
IP net.IP
Port uint16
str string
}
// IDAddressString returns id@hostPort.
func IDAddressString(id ID, hostPort string) string {
return fmt.Sprintf("%s@%s", id, hostPort)
}
// 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.
// TODO: socks proxies?
func NewNetAddress(id ID, addr net.Addr) *NetAddress {
tcpAddr, ok := addr.(*net.TCPAddr)
if !ok {
if flag.Lookup("test.v") == nil { // normal run
cmn.PanicSanity(cmn.Fmt("Only TCPAddrs are supported. Got: %v", addr))
} else { // in testing
netAddr := NewNetAddressIPPort(net.IP("0.0.0.0"), 0)
netAddr.ID = id
return netAddr
}
}
ip := tcpAddr.IP
port := uint16(tcpAddr.Port)
netAddr := NewNetAddressIPPort(ip, port)
netAddr.ID = id
return netAddr
}
// NewNetAddressString returns a new NetAddress using the provided
// address in the form of "ID@IP:Port", where the ID is optional.
// Also resolves the host if host is not an IP.
func NewNetAddressString(addr string) (*NetAddress, error) {
addr = removeProtocolIfDefined(addr)
var id ID
spl := strings.Split(addr, "@")
if len(spl) == 2 {
idStr := spl[0]
idBytes, err := hex.DecodeString(idStr)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("Address (%s) contains invalid ID", addr))
}
if len(idBytes) != IDByteLength {
return nil, fmt.Errorf("Address (%s) contains ID of invalid length (%d). Should be %d hex-encoded bytes",
addr, len(idBytes), IDByteLength)
}
id, addr = ID(idStr), spl[1]
}
host, portStr, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
ip := net.ParseIP(host)
if ip == nil {
if len(host) > 0 {
ips, err := net.LookupIP(host)
if err != nil {
return nil, err
}
ip = ips[0]
}
}
port, err := strconv.ParseUint(portStr, 10, 16)
if err != nil {
return nil, err
}
na := NewNetAddressIPPort(ip, uint16(port))
na.ID = id
return na, nil
}
// NewNetAddressStrings returns an array of NetAddress'es build using
// the provided strings.
func NewNetAddressStrings(addrs []string) ([]*NetAddress, []error) {
netAddrs := make([]*NetAddress, 0)
errs := make([]error, 0)
for _, addr := range addrs {
netAddr, err := NewNetAddressString(addr)
if err != nil {
errs = append(errs, fmt.Errorf("Error in address %s: %v", addr, err))
} else {
netAddrs = append(netAddrs, netAddr)
}
}
return netAddrs, errs
}
// NewNetAddressIPPort returns a new NetAddress using the provided IP
// and port number.
func NewNetAddressIPPort(ip net.IP, port uint16) *NetAddress {
na := &NetAddress{
IP: ip,
Port: port,
}
return na
}
// 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
}
// 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
}
// String representation: <ID>@<IP>:<PORT>
func (na *NetAddress) String() string {
if na.str == "" {
addrStr := na.DialString()
if na.ID != "" {
addrStr = IDAddressString(na.ID, addrStr)
}
na.str = addrStr
}
return na.str
}
func (na *NetAddress) DialString() string {
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 conn, nil
}
// Routable returns true if the address is routable.
func (na *NetAddress) Routable() bool {
// TODO(oga) bitcoind doesn't include RFC3849 here, but should we?
return na.Valid() && !(na.RFC1918() || na.RFC3927() || na.RFC4862() ||
na.RFC4193() || na.RFC4843() || na.Local())
}
// For IPv4 these are either a 0 or all bits set address. For IPv6 a zero
// address or one that matches the RFC3849 documentation address format.
func (na *NetAddress) Valid() bool {
return na.IP != nil && !(na.IP.IsUnspecified() || na.RFC3849() ||
na.IP.Equal(net.IPv4bcast))
}
// Local returns true if it is a local address.
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
)
if !na.Routable() {
return Unreachable
} else if na.RFC4380() {
if !o.Routable() {
return Default
} else if o.RFC4380() {
return Teredo
} else if o.IP.To4() != nil {
return Ipv4
} else { // ipv6
return Ipv6_weak
}
} else if na.IP.To4() != nil {
if o.Routable() && o.IP.To4() != nil {
return Ipv4
}
return Default
} else /* ipv6 */ {
var tunnelled bool
// Is our v6 is tunnelled?
if o.RFC3964() || o.RFC6052() || o.RFC6145() {
tunnelled = true
}
if !o.Routable() {
return Default
} else if o.RFC4380() {
return Teredo
} else if o.IP.To4() != nil {
return Ipv4
} else if 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)
// RFC3964: IPv6 6to4 (2002::/16)
// RFC4193: IPv6 unique local (FC00::/7)
// RFC4380: IPv6 Teredo tunneling (2001::/32)
// RFC4843: IPv6 ORCHID: (2001:10::/28)
// RFC4862: IPv6 Autoconfig (FE80::/64)
// RFC6052: IPv6 well known prefix (64:FF9B::/96)
// RFC6145: IPv6 IPv4 translated address ::FFFF:0:0:0/96
var rfc1918_10 = net.IPNet{IP: net.ParseIP("10.0.0.0"), Mask: net.CIDRMask(8, 32)}
var rfc1918_192 = net.IPNet{IP: net.ParseIP("192.168.0.0"), Mask: net.CIDRMask(16, 32)}
var rfc1918_172 = net.IPNet{IP: net.ParseIP("172.16.0.0"), Mask: net.CIDRMask(12, 32)}
var rfc3849 = net.IPNet{IP: net.ParseIP("2001:0DB8::"), Mask: net.CIDRMask(32, 128)}
var rfc3927 = net.IPNet{IP: net.ParseIP("169.254.0.0"), Mask: net.CIDRMask(16, 32)}
var rfc3964 = net.IPNet{IP: net.ParseIP("2002::"), Mask: net.CIDRMask(16, 128)}
var rfc4193 = net.IPNet{IP: net.ParseIP("FC00::"), Mask: net.CIDRMask(7, 128)}
var rfc4380 = net.IPNet{IP: net.ParseIP("2001::"), Mask: net.CIDRMask(32, 128)}
var rfc4843 = net.IPNet{IP: net.ParseIP("2001:10::"), Mask: net.CIDRMask(28, 128)}
var rfc4862 = net.IPNet{IP: net.ParseIP("FE80::"), Mask: net.CIDRMask(64, 128)}
var rfc6052 = net.IPNet{IP: net.ParseIP("64:FF9B::"), Mask: net.CIDRMask(96, 128)}
var rfc6145 = net.IPNet{IP: net.ParseIP("::FFFF:0:0:0"), Mask: net.CIDRMask(96, 128)}
var zero4 = net.IPNet{IP: net.ParseIP("0.0.0.0"), Mask: net.CIDRMask(8, 32)}
func (na *NetAddress) RFC1918() bool {
return rfc1918_10.Contains(na.IP) ||
rfc1918_192.Contains(na.IP) ||
rfc1918_172.Contains(na.IP)
}
func (na *NetAddress) RFC3849() bool { return rfc3849.Contains(na.IP) }
func (na *NetAddress) RFC3927() bool { return rfc3927.Contains(na.IP) }
func (na *NetAddress) RFC3964() bool { return rfc3964.Contains(na.IP) }
func (na *NetAddress) RFC4193() bool { return rfc4193.Contains(na.IP) }
func (na *NetAddress) RFC4380() bool { return rfc4380.Contains(na.IP) }
func (na *NetAddress) RFC4843() bool { return rfc4843.Contains(na.IP) }
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) }
func removeProtocolIfDefined(addr string) string {
if strings.Contains(addr, "://") {
return strings.Split(addr, "://")[1]
} else {
return addr
}
}
+131
View File
@@ -0,0 +1,131 @@
package types
import (
"net"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewNetAddress(t *testing.T) {
assert, require := assert.New(t), require.New(t)
tcpAddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8080")
require.Nil(err)
addr := NewNetAddress("", tcpAddr)
assert.Equal("127.0.0.1:8080", addr.String())
assert.NotPanics(func() {
NewNetAddress("", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8000})
}, "Calling NewNetAddress with UDPAddr should not panic in testing")
}
func TestNewNetAddressString(t *testing.T) {
testCases := []struct {
addr string
expected string
correct bool
}{
{"127.0.0.1:8080", "127.0.0.1:8080", true},
{"tcp://127.0.0.1:8080", "127.0.0.1:8080", true},
{"udp://127.0.0.1:8080", "127.0.0.1:8080", true},
{"udp//127.0.0.1:8080", "", false},
// {"127.0.0:8080", false},
{"notahost", "", false},
{"127.0.0.1:notapath", "", false},
{"notahost:8080", "", false},
{"8082", "", false},
{"127.0.0:8080000", "", false},
{"deadbeef@127.0.0.1:8080", "", false},
{"this-isnot-hex@127.0.0.1:8080", "", false},
{"xxxxbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", "", false},
{"deadbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", true},
{"tcp://deadbeef@127.0.0.1:8080", "", false},
{"tcp://this-isnot-hex@127.0.0.1:8080", "", false},
{"tcp://xxxxbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", "", false},
{"tcp://deadbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", true},
{"tcp://@127.0.0.1:8080", "", false},
{"tcp://@", "", false},
{"", "", false},
{"@", "", false},
{" @", "", false},
{" @ ", "", false},
}
for _, tc := range testCases {
addr, err := NewNetAddressString(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)
}
}
}
func TestNewNetAddressStrings(t *testing.T) {
addrs, errs := NewNetAddressStrings([]string{"127.0.0.1:8080", "127.0.0.2:8080"})
assert.Len(t, errs, 0)
assert.Equal(t, 2, len(addrs))
}
func TestNewNetAddressIPPort(t *testing.T) {
assert := assert.New(t)
addr := NewNetAddressIPPort(net.ParseIP("127.0.0.1"), 8080)
assert.Equal("127.0.0.1:8080", addr.String())
}
func TestNetAddressProperties(t *testing.T) {
assert, require := assert.New(t), require.New(t)
// TODO add more test cases
tests := []struct {
addr string
valid bool
local bool
routable bool
}{
{"127.0.0.1:8080", true, true, false},
{"ya.ru:80", true, false, true},
}
for _, t := range tests {
addr, err := NewNetAddressString(t.addr)
require.Nil(err)
assert.Equal(t.valid, addr.Valid())
assert.Equal(t.local, addr.Local())
assert.Equal(t.routable, addr.Routable())
}
}
func TestNetAddressReachabilityTo(t *testing.T) {
assert, require := assert.New(t), require.New(t)
// TODO add more test cases
tests := []struct {
addr string
other string
reachability int
}{
{"127.0.0.1:8080", "127.0.0.1:8081", 0},
{"ya.ru:80", "127.0.0.1:8080", 1},
}
for _, t := range tests {
addr, err := NewNetAddressString(t.addr)
require.Nil(err)
other, err := NewNetAddressString(t.other)
require.Nil(err)
assert.Equal(t.reachability, addr.ReachabilityTo(other))
}
}
+116
View File
@@ -0,0 +1,116 @@
package types
import (
"fmt"
"net"
"strconv"
"strings"
crypto "github.com/tendermint/go-crypto"
)
const maxNodeInfoSize = 10240 // 10Kb
func MaxNodeInfoSize() int {
return maxNodeInfoSize
}
// NodeInfo is the basic node information exchanged
// between two peers during the Tendermint P2P handshake.
type NodeInfo struct {
// Authenticate
PubKey crypto.PubKey `json:"pub_key"` // authenticated pubkey
ListenAddr string `json:"listen_addr"` // accepting incoming
// Check compatibility
Network string `json:"network"` // network/chain ID
Version string `json:"version"` // major.minor.revision
// Sanitize
Moniker string `json:"moniker"` // arbitrary moniker
Other []string `json:"other"` // other application specific data
}
// Validate checks the self-reported NodeInfo is safe.
// It returns an error if the info.PubKey doesn't match the given pubKey.
// TODO: constraints for Moniker/Other? Or is that for the UI ?
func (info NodeInfo) Validate(pubKey crypto.PubKey) error {
if !info.PubKey.Equals(pubKey) {
return fmt.Errorf("info.PubKey (%v) doesn't match peer.PubKey (%v)",
info.PubKey, pubKey)
}
return nil
}
// CONTRACT: two nodes are compatible if the major/minor versions match and network match
func (info NodeInfo) CompatibleWith(other NodeInfo) error {
iMajor, iMinor, _, iErr := splitVersion(info.Version)
oMajor, oMinor, _, oErr := splitVersion(other.Version)
// if our own version number is not formatted right, we messed up
if iErr != nil {
return iErr
}
// version number must be formatted correctly ("x.x.x")
if oErr != nil {
return oErr
}
// major version must match
if iMajor != oMajor {
return fmt.Errorf("Peer is on a different major version. Got %v, expected %v", oMajor, iMajor)
}
// minor version must match
if iMinor != oMinor {
return fmt.Errorf("Peer is on a different minor version. Got %v, expected %v", oMinor, iMinor)
}
// nodes must be 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 nil
}
func (info NodeInfo) ID() ID {
return PubKeyToID(info.PubKey)
}
func (info NodeInfo) NetAddress() *NetAddress {
id := PubKeyToID(info.PubKey)
addr := info.ListenAddr
netAddr, err := NewNetAddressString(IDAddressString(id, addr))
if err != nil {
panic(err) // everything should be well formed by now
}
return netAddr
}
func (info NodeInfo) ListenHost() string {
host, _, _ := net.SplitHostPort(info.ListenAddr) // nolint: errcheck, gas
return host
}
func (info NodeInfo) ListenPort() int {
_, port, _ := net.SplitHostPort(info.ListenAddr) // nolint: errcheck, gas
port_i, err := strconv.Atoi(port)
if err != nil {
return -1
}
return port_i
}
func (info NodeInfo) String() string {
return fmt.Sprintf("NodeInfo{pk: %v, moniker: %v, network: %v [listen %v], version: %v (%v)}", info.PubKey, info.Moniker, info.Network, info.ListenAddr, info.Version, info.Other)
}
func splitVersion(version string) (string, string, string, error) {
spl := strings.Split(version, ".")
if len(spl) != 3 {
return "", "", "", fmt.Errorf("Invalid version format %v", version)
}
return spl[0], spl[1], spl[2], nil
}