Merge branch 'develop'
This commit is contained in:
commit
a2c5ecf0a0
15
README.rst
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
|
||||
|
||||
@ -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)):
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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…
Reference in New Issue
Block a user