p2p: bust up into sub dirspull/1154/head
@ -0,0 +1,20 @@ | |||
package p2p | |||
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) | |||
} |
@ -0,0 +1,105 @@ | |||
package p2p | |||
import ( | |||
"fmt" | |||
"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 | |||
} | |||
// CompatibleWith checks if two NodeInfo are compatible with eachother. | |||
// 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) | |||
} | |||
// NetAddress returns a NetAddress derived from the NodeInfo - | |||
// it includes the authenticated peer ID and the self-reported | |||
// ListenAddr. Note that the ListenAddr is not authenticated and | |||
// may not match that address actually dialed if its an outbound peer. | |||
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) 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 | |||
} |
@ -0,0 +1,83 @@ | |||
package pex | |||
import ( | |||
"encoding/json" | |||
"os" | |||
cmn "github.com/tendermint/tmlibs/common" | |||
) | |||
/* Loading & Saving */ | |||
type addrBookJSON struct { | |||
Key string `json:"key"` | |||
Addrs []*knownAddress `json:"addrs"` | |||
} | |||
func (a *addrBook) saveToFile(filePath string) { | |||
a.Logger.Info("Saving AddrBook to file", "size", a.Size()) | |||
a.mtx.Lock() | |||
defer a.mtx.Unlock() | |||
// Compile Addrs | |||
addrs := []*knownAddress{} | |||
for _, ka := range a.addrLookup { | |||
addrs = append(addrs, ka) | |||
} | |||
aJSON := &addrBookJSON{ | |||
Key: a.key, | |||
Addrs: addrs, | |||
} | |||
jsonBytes, err := json.MarshalIndent(aJSON, "", "\t") | |||
if err != nil { | |||
a.Logger.Error("Failed to save AddrBook to file", "err", err) | |||
return | |||
} | |||
err = cmn.WriteFileAtomic(filePath, jsonBytes, 0644) | |||
if err != nil { | |||
a.Logger.Error("Failed to save AddrBook to file", "file", filePath, "err", err) | |||
} | |||
} | |||
// Returns false if file does not exist. | |||
// cmn.Panics if file is corrupt. | |||
func (a *addrBook) loadFromFile(filePath string) bool { | |||
// If doesn't exist, do nothing. | |||
_, err := os.Stat(filePath) | |||
if os.IsNotExist(err) { | |||
return false | |||
} | |||
// Load addrBookJSON{} | |||
r, err := os.Open(filePath) | |||
if err != nil { | |||
cmn.PanicCrisis(cmn.Fmt("Error opening file %s: %v", filePath, err)) | |||
} | |||
defer r.Close() // nolint: errcheck | |||
aJSON := &addrBookJSON{} | |||
dec := json.NewDecoder(r) | |||
err = dec.Decode(aJSON) | |||
if err != nil { | |||
cmn.PanicCrisis(cmn.Fmt("Error reading file %s: %v", filePath, err)) | |||
} | |||
// Restore all the fields... | |||
// Restore the key | |||
a.key = aJSON.Key | |||
// Restore .bucketsNew & .bucketsOld | |||
for _, ka := range aJSON.Addrs { | |||
for _, bucketIndex := range ka.Buckets { | |||
bucket := a.getBucket(ka.BucketType, bucketIndex) | |||
bucket[ka.Addr.String()] = ka | |||
} | |||
a.addrLookup[ka.ID()] = ka | |||
if ka.BucketType == bucketTypeNew { | |||
a.nNew++ | |||
} else { | |||
a.nOld++ | |||
} | |||
} | |||
return true | |||
} |
@ -0,0 +1,142 @@ | |||
package pex | |||
import ( | |||
"time" | |||
"github.com/tendermint/tendermint/p2p" | |||
) | |||
// knownAddress tracks information about a known network address | |||
// that is used to determine how viable an address is. | |||
type knownAddress struct { | |||
Addr *p2p.NetAddress `json:"addr"` | |||
Src *p2p.NetAddress `json:"src"` | |||
Attempts int32 `json:"attempts"` | |||
LastAttempt time.Time `json:"last_attempt"` | |||
LastSuccess time.Time `json:"last_success"` | |||
BucketType byte `json:"bucket_type"` | |||
Buckets []int `json:"buckets"` | |||
} | |||
func newKnownAddress(addr *p2p.NetAddress, src *p2p.NetAddress) *knownAddress { | |||
return &knownAddress{ | |||
Addr: addr, | |||
Src: src, | |||
Attempts: 0, | |||
LastAttempt: time.Now(), | |||
BucketType: bucketTypeNew, | |||
Buckets: nil, | |||
} | |||
} | |||
func (ka *knownAddress) ID() p2p.ID { | |||
return ka.Addr.ID | |||
} | |||
func (ka *knownAddress) copy() *knownAddress { | |||
return &knownAddress{ | |||
Addr: ka.Addr, | |||
Src: ka.Src, | |||
Attempts: ka.Attempts, | |||
LastAttempt: ka.LastAttempt, | |||
LastSuccess: ka.LastSuccess, | |||
BucketType: ka.BucketType, | |||
Buckets: ka.Buckets, | |||
} | |||
} | |||
func (ka *knownAddress) isOld() bool { | |||
return ka.BucketType == bucketTypeOld | |||
} | |||
func (ka *knownAddress) isNew() bool { | |||
return ka.BucketType == bucketTypeNew | |||
} | |||
func (ka *knownAddress) markAttempt() { | |||
now := time.Now() | |||
ka.LastAttempt = now | |||
ka.Attempts += 1 | |||
} | |||
func (ka *knownAddress) markGood() { | |||
now := time.Now() | |||
ka.LastAttempt = now | |||
ka.Attempts = 0 | |||
ka.LastSuccess = now | |||
} | |||
func (ka *knownAddress) addBucketRef(bucketIdx int) int { | |||
for _, bucket := range ka.Buckets { | |||
if bucket == bucketIdx { | |||
// TODO refactor to return error? | |||
// log.Warn(Fmt("Bucket already exists in ka.Buckets: %v", ka)) | |||
return -1 | |||
} | |||
} | |||
ka.Buckets = append(ka.Buckets, bucketIdx) | |||
return len(ka.Buckets) | |||
} | |||
func (ka *knownAddress) removeBucketRef(bucketIdx int) int { | |||
buckets := []int{} | |||
for _, bucket := range ka.Buckets { | |||
if bucket != bucketIdx { | |||
buckets = append(buckets, bucket) | |||
} | |||
} | |||
if len(buckets) != len(ka.Buckets)-1 { | |||
// TODO refactor to return error? | |||
// log.Warn(Fmt("bucketIdx not found in ka.Buckets: %v", ka)) | |||
return -1 | |||
} | |||
ka.Buckets = buckets | |||
return len(ka.Buckets) | |||
} | |||
/* | |||
An address is bad if the address in question is a New address, has not been tried in the last | |||
minute, and meets one of the following criteria: | |||
1) It claims to be from the future | |||
2) It hasn't been seen in over a week | |||
3) It has failed at least three times and never succeeded | |||
4) It has failed ten times in the last week | |||
All addresses that meet these criteria are assumed to be worthless and not | |||
worth keeping hold of. | |||
XXX: so a good peer needs us to call MarkGood before the conditions above are reached! | |||
*/ | |||
func (ka *knownAddress) isBad() bool { | |||
// Is Old --> good | |||
if ka.BucketType == bucketTypeOld { | |||
return false | |||
} | |||
// Has been attempted in the last minute --> good | |||
if ka.LastAttempt.Before(time.Now().Add(-1 * time.Minute)) { | |||
return false | |||
} | |||
// Too old? | |||
// XXX: does this mean if we've kept a connection up for this long we'll disconnect?! | |||
// and shouldn't it be .Before ? | |||
if ka.LastAttempt.After(time.Now().Add(-1 * numMissingDays * time.Hour * 24)) { | |||
return true | |||
} | |||
// Never succeeded? | |||
if ka.LastSuccess.IsZero() && ka.Attempts >= numRetries { | |||
return true | |||
} | |||
// Hasn't succeeded in too long? | |||
// XXX: does this mean if we've kept a connection up for this long we'll disconnect?! | |||
if ka.LastSuccess.Before(time.Now().Add(-1*minBadDays*time.Hour*24)) && | |||
ka.Attempts >= maxFailures { | |||
return true | |||
} | |||
return false | |||
} |
@ -0,0 +1,55 @@ | |||
package pex | |||
import "time" | |||
const ( | |||
// addresses under which the address manager will claim to need more addresses. | |||
needAddressThreshold = 1000 | |||
// interval used to dump the address cache to disk for future use. | |||
dumpAddressInterval = time.Minute * 2 | |||
// max addresses in each old address bucket. | |||
oldBucketSize = 64 | |||
// buckets we split old addresses over. | |||
oldBucketCount = 64 | |||
// max addresses in each new address bucket. | |||
newBucketSize = 64 | |||
// buckets that we spread new addresses over. | |||
newBucketCount = 256 | |||
// old buckets over which an address group will be spread. | |||
oldBucketsPerGroup = 4 | |||
// new buckets over which a source address group will be spread. | |||
newBucketsPerGroup = 32 | |||
// buckets a frequently seen new address may end up in. | |||
maxNewBucketsPerAddress = 4 | |||
// days before which we assume an address has vanished | |||
// if we have not seen it announced in that long. | |||
numMissingDays = 7 | |||
// tries without a single success before we assume an address is bad. | |||
numRetries = 3 | |||
// max failures we will accept without a success before considering an address bad. | |||
maxFailures = 10 // ? | |||
// days since the last success before we will consider evicting an address. | |||
minBadDays = 7 | |||
// % of total addresses known returned by GetSelection. | |||
getSelectionPercent = 23 | |||
// min addresses that must be returned by GetSelection. Useful for bootstrapping. | |||
minGetSelection = 32 | |||
// max addresses returned by GetSelection | |||
// NOTE: this must match "maxPexMessageSize" | |||
maxGetSelection = 250 | |||
) |
@ -1,112 +1,8 @@ | |||
package p2p | |||
import ( | |||
"fmt" | |||
"net" | |||
"strconv" | |||
"strings" | |||
crypto "github.com/tendermint/go-crypto" | |||
"github.com/tendermint/tendermint/p2p/conn" | |||
) | |||
const maxNodeInfoSize = 10240 // 10Kb | |||
// 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 | |||
} | |||
type ChannelDescriptor = conn.ChannelDescriptor | |||
type ConnectionStatus = conn.ConnectionStatus |