Skip to content

Instantly share code, notes, and snippets.

@chris-belcher chris-belcher/alectryon.py
Last active Dec 18, 2017

Embed
What would you like to do?
bitcoin-blockchain-feed-bot
#jsonrpc.py from https://github.com/JoinMarket-Org/joinmarket/blob/master/joinmarket/jsonrpc.py
#copyright # Copyright (C) 2013,2015 by Daniel Kraft <d@domob.eu> and phelix / blockchained.com
import base64
import httplib
import json
class JsonRpcError(Exception):
def __init__(self, obj):
self.code = obj["code"]
self.message = obj["message"]
class JsonRpcConnectionError(JsonRpcError): pass
class JsonRpc(object):
def __init__(self, host, port, user, password):
self.host = host
self.port = port
self.authstr = "%s:%s" % (user, password)
self.queryId = 1
def queryHTTP(self, obj):
headers = {"User-Agent": "joinmarket",
"Content-Type": "application/json",
"Accept": "application/json"}
headers["Authorization"] = "Basic %s" % base64.b64encode(self.authstr)
body = json.dumps(obj)
try:
conn = httplib.HTTPConnection(self.host, self.port)
conn.request("POST", "", body, headers)
response = conn.getresponse()
if response.status == 401:
conn.close()
raise JsonRpcConnectionError(
"authentication for JSON-RPC failed")
# All of the codes below are 'fine' from a JSON-RPC point of view.
if response.status not in [200, 404, 500]:
conn.close()
raise JsonRpcConnectionError("unknown error in JSON-RPC")
data = response.read()
conn.close()
return json.loads(data)
except JsonRpcConnectionError as exc:
raise exc
except Exception as exc:
raise JsonRpcConnectionError("JSON-RPC connection failed. Err:" +
repr(exc))
def call(self, method, params):
currentId = self.queryId
self.queryId += 1
request = {"method": method, "params": params, "id": currentId}
response = self.queryHTTP(request)
if response["id"] != currentId:
raise JsonRpcConnectionError("invalid id returned by query")
if response["error"] is not None:
raise JsonRpcError(response["error"])
return response["result"]
#irc bot code starts here
##single file, no dependencies
#by belcher
#Ares punished Alectryon by turning him into a rooster which never forgets
#to announce the arrival of the sun in the morning by its crowing.
import socket, time
from datetime import datetime
from getpass import getpass
#configuration stuff
nick = 'Alectryon'
hostport = ('irc.freenode.net', 6667)
nickserv_password = getpass('enter nickserv password for ' + nick + ': ')
channel = '#bitcoin-blocks'
#TODO :orwell.freenode.net 437 * Alectryon :Nick/channel is temporarily unavailable, then send /ns release <nick> <password>
rpc = JsonRpc(host = 'localhost',
port = 8332,
user = 'bitcoinrpc',
password = 'password')
#TODO :Alectryon!~Alectryon@host NICK :Guest11162, for when you fuck up and dont identify in time
#TODO :barjavel.freenode.net 404 Guest878 #bitcoin-blocks :Cannot send to channel
check_for_new_block_interval = 10
fee_estimate_output_interval = 60*15
ping_interval_seconds = 120
ping_timeout_seconds = 60*5
old_bci = [rpc.call('getblockchaininfo', [])]
print 'bestblock = ' + old_bci[0]['bestblockhash']
old_head = [rpc.call('getblockheader', [old_bci[0]['bestblockhash']])]
block_times = []
head = old_head[0]
for c in range(2016):
block_times.append(head['time'])
head = rpc.call('getblockheader', [head['previousblockhash']])
block_times.reverse()
last_fee_estimate = [datetime.now()]
def strtimediff(s):
return "%02d:%02d" % (s/60, s%60)
def strfeerate(con, econ):
if con == econ:
return str(con*1e5)
else:
return str(con*1e5) + ', ' + str(econ*1e5)
def create_fee_rate_stats():
st = time.time()
blocks = [(2, 'asap'), (6, 'hour'), (12, '2h'), (24, '4h'), (36, '6h'), (72, '12h'), (144, 'day'), (432, '3day'), (1008, 'week')]
data = []
for b, t in blocks:
con = rpc.call('estimatesmartfee', [b, 'CONSERVATIVE'])
econ = rpc.call('estimatesmartfee', [b, 'ECONOMICAL'])
data.append((b, t, con['feerate'], econ['feerate']))
message = 'FEE ESTIMATION'
message += ' fee rates: target-->(conservative, economical)sat/vbyte ' + ' '.join(
[t + '-->(' + strfeerate(con, econ) + ')' for b, t, con, econ in data])
blocks_index = [0, 6, 8] #asap, day, week
#typical tx 74a0b9a414f59dfe846473474b24c5d1d9d9c5110b0f54c8a22811f9eaa7a137
TYPICAL_VBYTES_KB = 168
message += ' typical tx(' + str(TYPICAL_VBYTES_KB) + ' vbytes): ' + ' '.join(
[t + '-->' + '%.5f'%(con*TYPICAL_VBYTES_KB) + 'mbtc' for b, t, con, econ in [data[i] for i in blocks_index]])
#TODO maybe also cost per input/cost per output
et = time.time()
print 'fees = ' + str(et - st) + 'sec'
return message
def check_fee_estimate(sock):
try:
if (datetime.now() - last_fee_estimate[0]).total_seconds() < fee_estimate_output_interval:
return
last_fee_estimate[0] = datetime.now()
message = create_fee_rate_stats()
print message
sock.sendall('PRIVMSG ' + channel + ' :' + message + '\r\n')
except JsonRpcError as e:
print repr(e)
def create_block_stats(blockhash):
st = time.time()
#TODO average fee rate, median fee rate, minimum fee rate
bci = rpc.call('getblockchaininfo', [])
head = rpc.call('getblockheader', [blockhash])
block_times.pop(0) #remove oldest block
block_times.append(head['time']) #add new block
MAX_WEIGHT = 4000000 #TODO lower this
SUBSIDY = 12.5 #TODO implement inflation schedule
block = rpc.call('getblock', [blockhash, 2])
coinbase_tx = block['tx'][0]
reward = sum([tx_out['value'] for tx_out in coinbase_tx['vout']])
fees = reward - SUBSIDY
coinbase_str = coinbase_tx['vin'][0]['coinbase']
readable_coinbase = ''.join([i for i in coinbase_str.decode('hex')
if i < '\x7f' and i > '\x19'])
windows = [6, 36, 144, 432, 1008, 2016]
intervals = [(block_times[-1] - block_times[-w])/w for w in windows]
until_retarget = -(bci['blocks'] % -2016)
utxos_produced = 0
utxos_consumed = 0
for tx in block['tx']:
utxos_produced += len(tx['vout'])
utxos_consumed += len(tx['vin'])
message = 'NEW BLOCK!'
message += ' hash=' + blockhash
#message += ' prevhash=' + head['previousblockhash']
message += ' height=' + str(bci['blocks'])
message += (' mediantime=' +
datetime.fromtimestamp(bci['mediantime']).strftime("%Y-%m-%d %H:%M:%S")
+ ' timestamp=' +
datetime.fromtimestamp(head['time']).strftime("%Y-%m-%d %H:%M:%S"))
message += ' #tx=' + str(len(block['tx']))
message += ' outputs=' + str(utxos_produced) + ' inputs=' + str(utxos_consumed) + ' utxoset-change=' + '%+d'%(utxos_produced - utxos_consumed)
message += ' fees=' + str(fees) + ' btc(' + ('%.2f%%)' % (100.0*fees / reward))
message += ' weight=' + str(block['weight']) + '(%.0f%% full)' % (100.0 * block['weight'] / MAX_WEIGHT)
message += ' interval=' + str(strtimediff(head['time'] - old_head[0]['time']))
message += ' avg-intervals(' + ', '.join([str(w) for w in windows]) + ')blocks = (' + ', '.join([str(strtimediff(d)) for d in intervals]) + ')'
message += ' retarget=' + str(until_retarget) + 'blocks(' + str(until_retarget/144) + ' days)'
message += ' minermsg=' + readable_coinbase
print 'diff=' + str(bci['difficulty']) + ' olddiff=' + str(old_bci[0]['difficulty'])
if bci['difficulty'] != old_bci[0]['difficulty']:
message += (' RETARGET! new difficulty=' + str(bci['difficulty']) + '(' + ('%+.1f'
% ((bci['difficulty'] - old_bci[0]['difficulty']) /
old_bci[0]['difficulty'] * 100)) + ')')
old_bci[0] = bci
old_head[0] = head
et = time.time()
print 'block stats = ' + str(et - st) + 'sec'
return message
def check_new_block(sock):
try:
blockhash = rpc.call('getbestblockhash', [])
if blockhash == old_bci[0]['bestblockhash']:
return
last_fee_estimate[0] = datetime.fromtimestamp(0)
message = create_block_stats(blockhash)
print message
sock.sendall('PRIVMSG ' + channel + ' :' + message + '\r\n')
check_fee_estimate(sock)
except JsonRpcError as e:
print repr(e)
def handle_irc_line(sock, line, chunks):
if len(chunks) < 2:
return
# sock.sendall('JOIN ' + channel + '\r\n')
#nuh_chunks = chunks[0].split('!')
#if nuh_chunks[0] == ':NickServ' and 'registered' in line and 'identify' in line:
if chunks[1] == '376': ##end of modt
print 'sending nickserv password'
sock.sendall('PRIVMSG NickServ :identify ' + nickserv_password + '\r\n')
if chunks[1] == '396':
#:sinisalo.freenode.net 396 beIcher unaffiliated/belcher :is now your hidden host (set by services.)
print 'joining channel'
sock.sendall('JOIN ' + channel + '\r\n')
print create_fee_rate_stats()
print create_block_stats(old_bci[0]['bestblockhash'])
'''
import sys
sys.exit(0)
'''
while True:
try:
print 'connecting'
sock = socket.socket()
sock.connect(hostport)
print 'connected'
sock.settimeout(check_for_new_block_interval)
sock.sendall('USER ' + nick + ' b c :' + nick + '\r\n')
sock.sendall('NICK ' + nick + '\r\n')
recv_buffer = ""
last_ping = datetime.now()
waiting_for_pong = False
while True:
try:
#print 'reading'
recv_data = sock.recv(4096)
if not recv_data or len(recv_data) == 0:
raise EOFError()
recv_buffer += recv_data
lb = recv_data.find('\n')
if lb == -1:
continue
while lb != -1:
line = recv_buffer[:lb].rstrip()
recv_buffer = recv_buffer[lb + 1:]
lb = recv_buffer.find('\n')
#print str(datetime.now()) + ' ' + line
print line
chunks = line.split(' ')
if chunks[0] == 'PING':
sock.sendall(line.replace('PING', 'PONG') + '\r\n')
elif len(chunks) > 1 and chunks[1] == 'PONG':
#print 'server replied to ping'
last_ping = datetime.now()
waiting_for_pong = False
else:
handle_irc_line(sock, line, chunks)
except socket.timeout:
#print 'timed out'
check_new_block(sock)
check_fee_estimate(sock)
if waiting_for_pong:
if (datetime.now() - last_ping).total_seconds() < ping_timeout_seconds:
continue
print 'server ping timed out'
sock.close()
else:
if (datetime.now() - last_ping).total_seconds() < ping_interval_seconds:
continue
last_ping = datetime.now()
#print 'sending ping to server'
waiting_for_pong = True
sock.sendall('PING :hello world\r\n')
except (IOError, EOFError) as e:
print repr(e)
time.sleep(5)
finally:
try:
sock.close()
except IOError:
pass
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.