Browse Source

Merge branch 'develop'

master 0.99.1
Neil Booth 8 years ago
parent
commit
a2c5ecf0a0
  1. 15
      README.rst
  2. 4
      lib/peer.py
  3. 9
      server/controller.py
  4. 56
      server/peers.py
  5. 19
      server/session.py
  6. 2
      server/version.py

15
README.rst

@ -132,6 +132,17 @@ version for the release of 1.0.
ChangeLog
=========
Version 0.99.1
--------------
* Add more verbose logging in attempt to understand issue `#135`_
* REPORT_TCP_PORT_TOR and REPORT_SSL_PORT_TOR were ignored when constructing
IRC real names. Fixes `#136`_
* Only serve chunk requests in forward direction; disconnect clients iterating
backwards. Minimizes bandwidth consumption caused by misbehaving Electrum
clients. Closes `#132`_
* Tor coin peers would always be scheduled for check, fixes `#138`_ (fr3aker)
Version 0.99
------------
@ -367,6 +378,10 @@ stability please stick with the 0.9 series.
.. _#124: https://github.com/kyuupichan/electrumx/issues/124
.. _#126: https://github.com/kyuupichan/electrumx/issues/126
.. _#129: https://github.com/kyuupichan/electrumx/issues/129
.. _#132: https://github.com/kyuupichan/electrumx/issues/132
.. _#135: https://github.com/kyuupichan/electrumx/issues/135
.. _#136: https://github.com/kyuupichan/electrumx/issues/136
.. _#138: https://github.com/kyuupichan/electrumx/issues/138
.. _docs/HOWTO.rst: https://github.com/kyuupichan/electrumx/blob/master/docs/HOWTO.rst
.. _docs/ENVIRONMENT.rst: https://github.com/kyuupichan/electrumx/blob/master/docs/ENVIRONMENT.rst
.. _docs/PEER_DISCOVERY.rst: https://github.com/kyuupichan/electrumx/blob/master/docs/PEER_DISCOVERY.rst

4
lib/peer.py

@ -243,7 +243,7 @@ class Peer(object):
details = self.real_name().split()[1:]
return (self.ip_addr or self.host, self.host, details)
def real_name(self, host_override=None):
def real_name(self):
'''Real name of this peer as used on IRC.'''
def port_text(letter, port):
if port == self.DEFAULT_PORTS.get(letter):
@ -251,7 +251,7 @@ class Peer(object):
else:
return letter + str(port)
parts = [host_override or self.host, 'v' + self.protocol_max]
parts = [self.host, 'v' + self.protocol_max]
if self.pruning:
parts.append('p{:d}'.format(self.pruning))
for letter, port in (('s', self.ssl_port), ('t', self.tcp_port)):

9
server/controller.py

@ -81,7 +81,7 @@ class Controller(util.LoggedClass):
('blockchain',
'address.get_balance address.get_history address.get_mempool '
'address.get_proof address.listunspent '
'block.get_header block.get_chunk estimatefee relayfee '
'block.get_header estimatefee relayfee '
'transaction.get transaction.get_merkle utxo.get_address'),
('server', 'donation_address'),
]
@ -794,13 +794,6 @@ class Controller(util.LoggedClass):
'height': utxo.height, 'value': utxo.value}
for utxo in sorted(await self.get_utxos(hashX))]
def block_get_chunk(self, index):
'''Return a chunk of block headers.
index: the chunk index'''
index = self.non_negative_integer(index)
return self.get_chunk(index)
def block_get_header(self, height):
'''The deserialized header at a given height.

56
server/peers.py

@ -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):

19
server/session.py

@ -112,8 +112,10 @@ class ElectrumX(SessionBase):
self.max_subs = self.env.max_session_subs
self.hashX_subs = {}
self.mempool_statuses = {}
self.chunk_indices = []
self.electrumx_handlers = {
'blockchain.address.subscribe': self.address_subscribe,
'blockchain.block.get_chunk': self.block_get_chunk,
'blockchain.headers.subscribe': self.headers_subscribe,
'blockchain.numblocks.subscribe': self.numblocks_subscribe,
'blockchain.script_hash.subscribe': self.script_hash_subscribe,
@ -259,7 +261,22 @@ class ElectrumX(SessionBase):
def server_features(self):
'''Returns a dictionary of server features.'''
return self.controller.peer_mgr.myself.features
return self.controller.peer_mgr.my_clearnet_peer().features
def block_get_chunk(self, index):
'''Return a chunk of block headers as a hexadecimal string.
index: the chunk index'''
index = self.controller.non_negative_integer(index)
self.chunk_indices.append(index)
self.chunk_indices = self.chunk_indices[-5:]
# -2 allows backing up a single chunk but no more.
if index <= max(self.chunk_indices[:-2], default=-1):
msg = ('chunk indices not advancing (wrong network?): {}'
.format(self.chunk_indices))
# INVALID_REQUEST triggers a disconnect
raise RPCError(mrg, JSONRPC.INVALID_REQUEST)
return self.controller.get_chunk(index)
def is_tor(self):
'''Try to detect if the connection is to a tor hidden service we are

2
server/version.py

@ -1,5 +1,5 @@
# Server name and protocol versions
VERSION = 'ElectrumX 0.99'
VERSION = 'ElectrumX 0.99.1'
PROTOCOL_MIN = '1.0'
PROTOCOL_MAX = '1.0'

Loading…
Cancel
Save