What are the exact rules applied by Geth, Parity, and/or other clients to determine a peer's reputation?
The exact answer to this is the source code for each client. To answer this question I've only looked at Geth's source, I can't speak to what Parity does.
In particular, is being honest sufficient to get a good or even optimal reputation or is usefulness required for this as well?
After looking at Geth it appears that usefulness is the only thing which is considered. Remote nodes which Geth has been able to quickly receive data from are preferentially used for future requests.
Under what condition do peers disconnect?
If you search eth/downloader/downloader.go for "errBadPeer" you'll get some examples. In general it disconnects when the remote peer gives Geth responses which are invalid such as returning one block header when Geth asked for a different block header.
Is reputation remembered beyond the lifetime of a connection and used for peer selection (for instance in order to prevent immediate reconnects by bad peers)?
As far as I can tell (after like 10 minutes of reading code) Geth has no memory of bad peers. It simply disconnects and forgets all about the peer.
A search in geth's source for "reputation" only turns up matches in eth/downloader/peer.go.
It has some blocks which look like this:
// HeaderIdlePeers retrieves a flat list of all the currently header-idle peers
// within the active peer set, ordered by their reputation.
func (ps *peerSet) HeaderIdlePeers() ([]*peerConnection, int) {
idle := func(p *peerConnection) bool {
return atomic.LoadInt32(&p.headerIdle) == 0
}
throughput := func(p *peerConnection) float64 {
p.lock.RLock()
defer p.lock.RUnlock()
return p.headerThroughput
}
return ps.idlePeers(62, 64, idle, throughput)
}
The blocks all call idlePeers
:
// idlePeers retrieves a flat list of all currently idle peers satisfying the
// protocol version constraints, using the provided function to check idleness.
// The resulting set of peers are sorted by their measure throughput.
func (ps *peerSet) idlePeers(minProtocol, maxProtocol int, idleCheck func(*peerConnection) bool, throughput func(*peerConnection) float64) ([]*peerConnection, int) {
ps.lock.RLock()
defer ps.lock.RUnlock()
idle, total := make([]*peerConnection, 0, len(ps.peers)), 0
for _, p := range ps.peers {
if p.version >= minProtocol && p.version <= maxProtocol {
if idleCheck(p) {
idle = append(idle, p)
}
total++
}
}
for i := 0; i < len(idle); i++ {
for j := i + 1; j < len(idle); j++ {
if throughput(idle[i]) < throughput(idle[j]) {
idle[i], idle[j] = idle[j], idle[i]
}
}
}
return idle, total
}
idlePeers
, does not seem to track reputation. Instead it orders peers based on their measured throughput. Amusingly enough, it does so with a bubblesort :)