Browse Source

Merge branch 'release-0.7.13'

master 0.7.13
Neil Booth 9 years ago
parent
commit
096b8483c6
  1. 7
      RELEASE-NOTES
  2. 3
      docs/ENV-NOTES
  3. 26
      electrumx_rpc.py
  4. 13
      lib/coins.py
  5. 1
      server/env.py
  6. 66
      server/protocol.py
  7. 2
      server/version.py

7
RELEASE-NOTES

@ -1,3 +1,10 @@
version 0.7.13
--------------
- the output of the RPC sessions and getinfo calls are now written to logs
periodically by default. See LOG_SESSIONS in docs/ENV-NOTES
- Litecoin update (santzi)
version 0.7.12 version 0.7.12
-------------- --------------

3
docs/ENV-NOTES

@ -36,6 +36,9 @@ BANNER_FILE - a path to a banner file to serve to clients. The banner file
is re-read for each new client. The string $VERSION in your is re-read for each new client. The string $VERSION in your
banner file will be replaced with the ElectrumX version you banner file will be replaced with the ElectrumX version you
are runnning, such as 'ElectrumX 0.7.11'. are runnning, such as 'ElectrumX 0.7.11'.
LOG_SESSIONS - the number of seconds between printing session statistics to
the log. Defaults to 3600. Set to zero to suppress this
logging.
ANON_LOGS - set to anything non-empty to remove IP addresses from ANON_LOGS - set to anything non-empty to remove IP addresses from
logs. By default IP addresses will be logged. logs. By default IP addresses will be logged.
DONATION_ADDRESS - server donation address. Defaults to none. DONATION_ADDRESS - server donation address. Defaults to none.

26
electrumx_rpc.py

@ -17,6 +17,7 @@ from functools import partial
from os import environ from os import environ
from lib.jsonrpc import JSONRPC from lib.jsonrpc import JSONRPC
from server.protocol import ServerManager
class RPCClient(JSONRPC): class RPCClient(JSONRPC):
@ -36,33 +37,12 @@ class RPCClient(JSONRPC):
async def handle_response(self, result, error, method): async def handle_response(self, result, error, method):
if result and method == 'sessions': if result and method == 'sessions':
self.print_sessions(result) for line in ServerManager.sessions_text_lines(result):
print(line)
else: else:
value = {'error': error} if error else result value = {'error': error} if error else result
print(json.dumps(value, indent=4, sort_keys=True)) print(json.dumps(value, indent=4, sort_keys=True))
def print_sessions(self, result):
def data_fmt(count, size):
return '{:,d}/{:,d}KB'.format(count, size // 1024)
def time_fmt(t):
t = int(t)
return ('{:3d}:{:02d}:{:02d}'
.format(t // 3600, (t % 3600) // 60, t % 60))
fmt = ('{:<4} {:>23} {:>15} {:>7} '
'{:>7} {:>7} {:>7} {:>7} {:>5} {:>9}')
print(fmt.format('Type', 'Peer', 'Client', 'Subs',
'Recv #', 'Recv KB', 'Sent #', 'Sent KB',
'Errs', 'Time'))
for (kind, peer, subs, client, recv_count, recv_size,
send_count, send_size, error_count, time) in result:
print(fmt.format(kind, peer, client, '{:,d}'.format(subs),
'{:,d}'.format(recv_count),
'{:,d}'.format(recv_size // 1024),
'{:,d}'.format(send_count),
'{:,d}'.format(send_size // 1024),
'{:,d}'.format(error_count),
time_fmt(time)))
def main(): def main():
'''Send the RPC command to the server and print the result.''' '''Send the RPC command to the server and print the result.'''

13
lib/coins.py

@ -281,7 +281,6 @@ class BitcoinTestnet(Bitcoin):
REORG_LIMIT = 2000 REORG_LIMIT = 2000
# Source: pycoin and others
class Litecoin(Coin): class Litecoin(Coin):
NAME = "Litecoin" NAME = "Litecoin"
SHORTNAME = "LTC" SHORTNAME = "LTC"
@ -291,10 +290,17 @@ class Litecoin(Coin):
P2PKH_VERBYTE = 0x30 P2PKH_VERBYTE = 0x30
P2SH_VERBYTE = 0x05 P2SH_VERBYTE = 0x05
WIF_BYTE = 0xb0 WIF_BYTE = 0xb0
GENESIS_HASH=(b'000000000019d6689c085ae165831e93'
b'4ff763ae46a2a6c172b3f1b60a8ce26f')
TX_COUNT = 8908766
TX_COUNT_HEIGHT = 1105256
TX_PER_BLOCK = 10
IRC_PREFIX = "EL_"
IRC_CHANNEL = "#electrum-ltc"
RPC_PORT = 9332
class LitecoinTestnet(Coin): class LitecoinTestnet(Litecoin):
NAME = "Litecoin"
SHORTNAME = "XLT" SHORTNAME = "XLT"
NET = "testnet" NET = "testnet"
XPUB_VERBYTES = bytes.fromhex("0436f6e1") XPUB_VERBYTES = bytes.fromhex("0436f6e1")
@ -302,6 +308,7 @@ class LitecoinTestnet(Coin):
P2PKH_VERBYTE = 0x6f P2PKH_VERBYTE = 0x6f
P2SH_VERBYTE = 0xc4 P2SH_VERBYTE = 0xc4
WIF_BYTE = 0xef WIF_BYTE = 0xef
# Some details missing...
# Source: namecoin.org # Source: namecoin.org

1
server/env.py

@ -41,6 +41,7 @@ class Env(LoggedClass):
self.max_subscriptions = self.integer('MAX_SUBSCRIPTIONS', 10000) self.max_subscriptions = self.integer('MAX_SUBSCRIPTIONS', 10000)
self.banner_file = self.default('BANNER_FILE', None) self.banner_file = self.default('BANNER_FILE', None)
self.anon_logs = self.default('ANON_LOGS', False) self.anon_logs = self.default('ANON_LOGS', False)
self.log_sessions = self.default('LOG_SESSIONS', 3600)
# The electrum client takes the empty string as unspecified # The electrum client takes the empty string as unspecified
self.donation_address = self.default('DONATION_ADDRESS', '') self.donation_address = self.default('DONATION_ADDRESS', '')
self.db_engine = self.default('DB_ENGINE', 'leveldb') self.db_engine = self.default('DB_ENGINE', 'leveldb')

66
server/protocol.py

@ -224,6 +224,8 @@ class ServerManager(util.LoggedClass):
self.env = env self.env = env
self.servers = [] self.servers = []
self.sessions = {} self.sessions = {}
self.txs_sent = 0
self.next_log_sessions = 0
self.max_subs = env.max_subs self.max_subs = env.max_subs
self.subscription_count = 0 self.subscription_count = 0
self.futures = [] self.futures = []
@ -314,6 +316,13 @@ class ServerManager(util.LoggedClass):
# Use a tuple to distinguish from JSON # Use a tuple to distinguish from JSON
triple = (self.bp.db_height, touched, cache) triple = (self.bp.db_height, touched, cache)
session.messages.put_nowait(triple) session.messages.put_nowait(triple)
# Periodically log sessions
if self.env.log_sessions and time.time() > self.next_log_sessions:
data = self.session_data(for_log=True)
for line in ServerManager.sessions_text_lines(data):
self.logger.info(line)
self.logger.info(json.dumps(self.server_summary()))
self.next_log_sessions = time.time() + self.env.log_sessions
async def shutdown(self): async def shutdown(self):
'''Call to shutdown the servers. Returns when done.''' '''Call to shutdown the servers. Returns when done.'''
@ -372,32 +381,66 @@ class ServerManager(util.LoggedClass):
return self.irc.peers return self.irc.peers
def session_count(self): def session_count(self):
'''Returns a dictionary.''' '''The number of connections that we've sent something to.'''
active = len([s for s in self.sessions if s.send_count]) return len([s for s in self.sessions if s.send_count])
total = len(self.sessions)
return {'active': active, 'inert': total - active, 'total': total}
async def rpc_getinfo(self, params): def server_summary(self):
'''The RPC 'getinfo' call.''' '''A one-line summary of server state.'''
return { return {
'blocks': self.bp.db_height, 'blocks': self.bp.db_height,
'errors': sum(s.error_count for s in self.sessions),
'peers': len(self.irc.peers), 'peers': len(self.irc.peers),
'sessions': self.session_count(), 'sessions': self.session_count(),
'txs_sent': self.txs_sent,
'watched': self.subscription_count, 'watched': self.subscription_count,
} }
async def rpc_sessions(self, params): @staticmethod
def sessions_text_lines(data):
'''A generator returning lines for a list of sessions.
data is the return value of rpc_sessions().'''
def time_fmt(t):
t = int(t)
return ('{:3d}:{:02d}:{:02d}'
.format(t // 3600, (t % 3600) // 60, t % 60))
fmt = ('{:<4} {:>23} {:>15} {:>7} '
'{:>7} {:>7} {:>7} {:>7} {:>5} {:>9}')
yield fmt.format('Type', 'Peer', 'Client', 'Subs',
'Recv', 'Recv KB', 'Sent', 'Sent KB',
'Txs', 'Time')
for (kind, peer, subs, client, recv_count, recv_size,
send_count, send_size, txs_sent, time) in data:
yield fmt.format(kind, peer, client,
'{:,d}'.format(subs),
'{:,d}'.format(recv_count),
'{:,d}'.format(recv_size // 1024),
'{:,d}'.format(send_count),
'{:,d}'.format(send_size // 1024),
'{:,d}'.format(txs_sent),
time_fmt(time))
def session_data(self, for_log):
'''Returned to the RPC 'sessions' call.''' '''Returned to the RPC 'sessions' call.'''
now = time.time() now = time.time()
sessions = sorted(self.sessions.keys(), key=lambda s: s.start)
return [(session.kind, return [(session.kind,
session.peername(for_log=False), session.peername(for_log=for_log),
session.sub_count(), session.sub_count(),
session.client, session.client,
session.recv_count, session.recv_size, session.recv_count, session.recv_size,
session.send_count, session.send_size, session.send_count, session.send_size,
session.error_count, session.txs_sent,
now - session.start) now - session.start)
for session in self.sessions] for session in sessions]
async def rpc_getinfo(self, params):
return self.server_summary()
async def rpc_sessions(self, params):
return self.session_data(for_log=False)
async def rpc_numsessions(self, params): async def rpc_numsessions(self, params):
return self.session_count() return self.session_count()
@ -428,6 +471,7 @@ class Session(JSONRPC):
self.client = 'unknown' self.client = 'unknown'
self.anon_logs = env.anon_logs self.anon_logs = env.anon_logs
self.max_send = env.max_send self.max_send = env.max_send
self.txs_sent = 0
def connection_made(self, transport): def connection_made(self, transport):
'''Handle an incoming client connection.''' '''Handle an incoming client connection.'''
@ -776,6 +820,8 @@ class ElectrumX(Session):
''' '''
try: try:
tx_hash = await self.daemon.sendrawtransaction(params) tx_hash = await self.daemon.sendrawtransaction(params)
self.txs_sent += 1
self.manager.txs_sent += 1
self.logger.info('sent tx: {}'.format(tx_hash)) self.logger.info('sent tx: {}'.format(tx_hash))
return tx_hash return tx_hash
except DaemonError as e: except DaemonError as e:

2
server/version.py

@ -1 +1 @@
VERSION = "ElectrumX 0.7.12" VERSION = "ElectrumX 0.7.13"

Loading…
Cancel
Save