|
|
@ -29,9 +29,8 @@ STALE_SECS = 86400 |
|
|
|
WAKEUP_SECS = 300 |
|
|
|
|
|
|
|
|
|
|
|
def peer_from_env(env): |
|
|
|
'''Return ourself as a peer from the environment settings.''' |
|
|
|
main_identity = env.identities[0] |
|
|
|
def peers_from_env(env): |
|
|
|
'''Return a list of peers from the environment settings.''' |
|
|
|
hosts = {identity.host: {'tcp_port': identity.tcp_port, |
|
|
|
'ssl_port': identity.ssl_port} |
|
|
|
for identity in env.identities} |
|
|
@ -44,7 +43,7 @@ def peer_from_env(env): |
|
|
|
'genesis_hash': env.coin.GENESIS_HASH, |
|
|
|
} |
|
|
|
|
|
|
|
return Peer(main_identity.host, features, 'env') |
|
|
|
return [Peer(ident.host, features, 'env') for ident in env.identities] |
|
|
|
|
|
|
|
|
|
|
|
class PeerSession(JSONSession): |
|
|
@ -117,7 +116,7 @@ class PeerSession(JSONSession): |
|
|
|
return |
|
|
|
|
|
|
|
# Announce ourself if not present |
|
|
|
my = self.peer_mgr.myself |
|
|
|
my = self.peer_mgr.my_clearnet_peer() |
|
|
|
for peer in my.matches(peers): |
|
|
|
if peer.tcp_port == my.tcp_port and peer.ssl_port == my.ssl_port: |
|
|
|
return |
|
|
@ -189,15 +188,15 @@ class PeerSession(JSONSession): |
|
|
|
def close_if_done(self): |
|
|
|
if not self.has_pending_requests(): |
|
|
|
is_good = not self.failed |
|
|
|
self.peer.last_connect = time.time() |
|
|
|
self.peer_mgr.set_connection_status(self.peer, is_good) |
|
|
|
if is_good: |
|
|
|
if self.peer.is_tor: |
|
|
|
self.log_info('verified via {} over Tor'.format(self.kind)) |
|
|
|
else: |
|
|
|
self.log_info('verified via {} at {}' |
|
|
|
.format(self.kind, |
|
|
|
self.peer_addr(anon=False))) |
|
|
|
if self.peer.is_tor: |
|
|
|
how = 'via {} over Tor'.format(self.kind) |
|
|
|
else: |
|
|
|
how = 'via {} at {}'.format(self.kind, |
|
|
|
self.peer_addr(anon=False)) |
|
|
|
status = 'verified' if is_good else 'failed to verify' |
|
|
|
elapsed = time.time() - self.peer.last_try |
|
|
|
self.log_info('{} {} in {:.1f}s'.format(status, how, elapsed)) |
|
|
|
self.close_connection() |
|
|
|
|
|
|
|
|
|
|
@ -215,7 +214,7 @@ class PeerManager(util.LoggedClass): |
|
|
|
self.controller = controller |
|
|
|
self.loop = controller.loop |
|
|
|
self.irc = IRC(env, self) |
|
|
|
self.myself = peer_from_env(env) |
|
|
|
self.myselves = peers_from_env(env) |
|
|
|
# value is max outgoing connections at a time |
|
|
|
self.semaphore = asyncio.BoundedSemaphore(value=8) |
|
|
|
self.retry_event = asyncio.Event() |
|
|
@ -230,6 +229,10 @@ class PeerManager(util.LoggedClass): |
|
|
|
loop=self.loop) |
|
|
|
self.import_peers() |
|
|
|
|
|
|
|
def my_clearnet_peer(self): |
|
|
|
'''Returns the clearnet peer representing this server.''' |
|
|
|
return [peer for peer in self.myselves if not peer.is_tor][0] |
|
|
|
|
|
|
|
def info(self): |
|
|
|
'''The number of peers.''' |
|
|
|
self.set_peer_statuses() |
|
|
@ -323,9 +326,8 @@ class PeerManager(util.LoggedClass): |
|
|
|
onion_peers = [] |
|
|
|
|
|
|
|
# Always report ourselves if valid (even if not public) |
|
|
|
peers = set() |
|
|
|
if self.myself.last_connect > cutoff: |
|
|
|
peers.add(self.myself) |
|
|
|
peers = set(myself for myself in self.myselves |
|
|
|
if myself.last_connect > cutoff) |
|
|
|
|
|
|
|
# Bucket the clearnet peers and select one from each |
|
|
|
buckets = defaultdict(list) |
|
|
@ -371,7 +373,7 @@ class PeerManager(util.LoggedClass): |
|
|
|
|
|
|
|
def import_peers(self): |
|
|
|
'''Import hard-coded peers from a file or the coin defaults.''' |
|
|
|
self.add_peers([self.myself]) |
|
|
|
self.add_peers(self.myselves) |
|
|
|
coin_peers = self.env.coin.PEERS |
|
|
|
self.onion_peers = [Peer.from_real_name(rn, 'coins.py') |
|
|
|
for rn in coin_peers if '.onion ' in rn] |
|
|
@ -387,8 +389,8 @@ class PeerManager(util.LoggedClass): |
|
|
|
def connect_to_irc(self): |
|
|
|
'''Connect to IRC if not disabled.''' |
|
|
|
if self.env.irc and self.env.coin.IRC_PREFIX: |
|
|
|
pairs = [(self.myself.real_name(ident.host), ident.nick_suffix) |
|
|
|
for ident in self.env.identities] |
|
|
|
pairs = [(peer.real_name(), ident.nick_suffix) for peer, ident |
|
|
|
in zip(self.myselves, self.env.identities)] |
|
|
|
self.ensure_future(self.irc.start(pairs)) |
|
|
|
else: |
|
|
|
self.logger.info('IRC is disabled') |
|
|
@ -462,16 +464,21 @@ class PeerManager(util.LoggedClass): |
|
|
|
self.last_tor_retry_time = now |
|
|
|
|
|
|
|
for peer in peers: |
|
|
|
peer.last_try = time.time() |
|
|
|
peer.try_count += 1 |
|
|
|
pairs = peer.connection_port_pairs() |
|
|
|
if peer.bad or not pairs: |
|
|
|
self.maybe_forget_peer(peer) |
|
|
|
else: |
|
|
|
start = time.time() |
|
|
|
await self.semaphore.acquire() |
|
|
|
elapsed = time.time() - start |
|
|
|
if elapsed > 5: |
|
|
|
self.log_warning('waited {:.1f}s for connection semaphore' |
|
|
|
.format(elapsed)) |
|
|
|
self.retry_peer(peer, pairs) |
|
|
|
|
|
|
|
def retry_peer(self, peer, port_pairs): |
|
|
|
peer.last_try = time.time() |
|
|
|
kind, port = port_pairs[0] |
|
|
|
# Python 3.5.3: use PROTOCOL_TLS |
|
|
|
sslc = ssl.SSLContext(ssl.PROTOCOL_SSLv23) if kind == 'SSL' else None |
|
|
@ -495,8 +502,10 @@ class PeerManager(util.LoggedClass): |
|
|
|
exception = future.exception() |
|
|
|
if exception: |
|
|
|
kind, port = port_pairs[0] |
|
|
|
self.logger.info('failed connecting to {} at {} port {:d}: {}' |
|
|
|
.format(peer, kind, port, exception)) |
|
|
|
self.logger.info('failed connecting to {} at {} port {:d} ' |
|
|
|
'in {:.1f}s: {}' |
|
|
|
.format(peer, kind, port, |
|
|
|
time.time() - peer.last_try, exception)) |
|
|
|
port_pairs = port_pairs[1:] |
|
|
|
if port_pairs: |
|
|
|
self.retry_peer(peer, port_pairs) |
|
|
@ -512,6 +521,7 @@ class PeerManager(util.LoggedClass): |
|
|
|
'''Called when a connection succeeded or failed.''' |
|
|
|
if good: |
|
|
|
peer.try_count = 0 |
|
|
|
peer.last_connect = time.time() |
|
|
|
peer.source = 'peer' |
|
|
|
# Remove matching IP addresses |
|
|
|
for match in peer.matches(self.peers): |
|
|
|