# Python 2.5
# terms of use: public domain. you are free to use or abuse this code anyway you want :)
import socket, base64, hashlib
op={
chr(0): 'OP_FALSE', # this value is replaced later
chr(81): 'OP_TRUE', # this value is replaced later
chr(0): 'OP_0',
chr(76): 'OP_PUSHDATA1',
chr(77): 'OP_PUSHDATA2',
chr(78): 'OP_PUSHDATA4',
chr(79): 'OP_1NEGATE',
chr(81): 'OP_1',
chr(97): 'OP_NOP',
chr(99): 'OP_IF',
chr(100): 'OP_NOTIF',
chr(103): 'OP_ELSE',
chr(104): 'OP_ENDIF',
chr(105): 'OP_VERIFY',
chr(106): 'OP_RETURN',
chr(107): 'OP_TOALTSTACK',
chr(108): 'OP_FROMALTSTACK',
chr(115): 'OP_IFDUP',
chr(116): 'OP_DEPTH',
chr(117): 'OP_DROP',
chr(118): 'OP_DUP',
chr(119): 'OP_NIP',
chr(120): 'OP_OVER',
chr(121): 'OP_PICK',
chr(122): 'OP_ROLL',
chr(123): 'OP_ROT',
chr(124): 'OP_SWAP',
chr(125): 'OP_TUCK',
chr(109): 'OP_2DROP',
chr(110): 'OP_2DUP',
chr(111): 'OP_3DUP',
chr(112): 'OP_2OVER',
chr(113): 'OP_2ROT',
chr(114): 'OP_2SWAP',
chr(126): 'OP_CAT',
chr(127): 'OP_SUBSTR',
chr(128): 'OP_LEFT',
chr(129): 'OP_RIGHT',
chr(130): 'OP_SIZE',
chr(131): 'OP_INVERT',
chr(132): 'OP_AND',
chr(133): 'OP_OR',
chr(134): 'OP_XOR',
chr(135): 'OP_EQUAL',
chr(136): 'OP_EQUALVERIFY',
chr(139): 'OP_1ADD',
chr(140): 'OP_1SUB',
chr(141): 'OP_2MUL',
chr(142): 'OP_2DIV',
chr(143): 'OP_NEGATE',
chr(144): 'OP_ABS',
chr(145): 'OP_NOT',
chr(146): 'OP_0NOTEQUAL',
chr(147): 'OP_ADD',
chr(148): 'OP_SUB',
chr(149): 'OP_MUL',
chr(150): 'OP_DIV',
chr(151): 'OP_MOD',
chr(152): 'OP_LSHIFT',
chr(153): 'OP_RSHIFT',
chr(154): 'OP_BOOLAND',
chr(155): 'OP_BOOLOR',
chr(156): 'OP_NUMEQUAL',
chr(157): 'OP_NUMEQUALVERIFY',
chr(158): 'OP_NUMNOTEQUAL',
chr(159): 'OP_LESSTHAN',
chr(160): 'OP_GREATERTHAN',
chr(161): 'OP_LESSTHANOREQUAL',
chr(162): 'OP_GREATERTHANOREQUAL',
chr(163): 'OP_MIN',
chr(164): 'OP_MAX',
chr(165): 'OP_WITHIN',
chr(166): 'OP_RIPEMD160',
chr(167): 'OP_SHA1',
chr(168): 'OP_SHA256',
chr(169): 'OP_HASH160',
chr(170): 'OP_HASH256',
chr(171): 'OP_CODESEPARATOR',
chr(172): 'OP_CHECKSIG',
chr(173): 'OP_CHECKSIGVERIFY',
chr(174): 'OP_CHECKMULTISIG',
chr(175): 'OP_CHECKMULTISIGVERIFY',
chr(253): 'OP_PUBKEYHASH',
chr(254): 'OP_PUBKEY',
chr(255): 'OP_INVALIDOPCODE',
chr(80): 'OP_RESERVED',
chr(98): 'OP_VER',
chr(101): 'OP_VERIF',
chr(102): 'OP_VERNOTIF',
chr(137): 'OP_RESERVED1',
chr(138): 'OP_RESERVED2'
}
for i in range(1,76): op[chr(i)]='OP_PUSH'+str(i) # name isn't shown in the script
for i in range(82,97): op[chr(i)]='OP_'+str(i-80) # name isn't shown in the script
for i in range(176,186): op[chr(i)]='OP_NOP'+str(i-175)
for i in range(256): op[chr(i)]= op[chr(i)] if (chr(i) in op) else 'OP_UNKNOWN'
class jsonrpcserver():
def __init__(self, host, port, username, password):
self.jsonsock=socket.socket()
self.jsonsock.connect((host,port))
self.authkey=base64.b64encode(username+':'+password)
class jsonerror(Exception):
def __init__(self, errormessage, response):
self.errormessage = errormessage
self.response = response
def __str__(self):
return str(self.errormessage)
def request(self, method, params, errormessage):
k='POST / HTTP/1.1\r\nContent-Type: application/json-rpc\r\nAuthorization: Basic '+self.authkey+'\r\nContent-Length: '
k2='{"method": "'+method+'", "params": '+str(params).replace("'",'"')+', "id":0}'
self.jsonsock.send(k+str(len(k2))+'\r\n\r\n'+k2); response=self.jsonsock.recv(2**20)
while response.count('{')!=response.count('}'): response+=self.jsonsock.recv(2**20)
c=eval(response.split('\r\n\r\n')[-1].replace('null','None'))
if c['error']:
raise self.jsonerror(errormessage+' - "'+str(c['error']['message'])+'"', c)
return c['result']
def getrawtx(self, hash):
return self.request('getrawtransaction', [hash], 'getrawtx failed to retrieve tx '+hash)
def getblock(self, hash):
return self.request('getblock', [hash], 'getblock failed to retrieve block '+hash)
def getblockcount(self):
return self.request('getblockcount', [], 'getblockcount failed to retrieve block count')
def getblockhash(self, blocknum):
return self.request('getblockhash', [blocknum], 'getblockhash failed to retrieve hash for block #'+str(blocknum))
#bitcoinqt=jsonrpcserver('localhost',8332,'bitcoinrpc','password')
#print bitcoinqt.getrawtx('384287be164a79c707f93a687a57ec5d9c1230af521a546186cd5c3af2011dae')
#print bitcoinqt.getblock('00000000000000eac823d5c3f6c17f9ec08414c4e23fff6060e842fae0581fd6')
#print bitcoinqt.getblockcount()
#print bitcoinqt.getblockhash(0)
def hash256(x): return hashlib.sha256(hashlib.sha256(x).digest()).digest()
def makemerkletree(txhashes):
levels=[txhashes]
while len(levels[-1])!=1:
newlevel=[]
for l in range((len(levels[-1])+1)/2):
newlevel.append(hash256(levels[-1][l*2].decode('hex')[::-1]+levels[-1][min(l*2+1,len(levels[-1])-1)].decode('hex')[::-1])[::-1].encode('hex'))
levels.append(newlevel)
tree=[]
for level in levels: tree+=level
return tree
def humanreadablescript(script): # same as blockexplorer/getblock's, I personally prefer OP_FALSE and OP_TRUE
out='' # maybe this function should be called asm ? what does asm stand for ?
while script!='':
if 'OP_PUSH' in op[script[0]]:
if 'OP_PUSHDATA' in op[script[0]]: # 199593
numbytes=int(op[script[0]][-1]); script=script[1:]
pushlen=sum([ord(script[x])*(256**x) for x in range(numbytes)]); script=script[numbytes:]
else: pushlen=ord(script[0]); script=script[1:]
out+=script[:pushlen].encode('hex')+' '; script=script[pushlen:]
elif op[script[0]][3:].isdigit(): out+=op[script[0]][3:]+' '; script=script[1:] # 199388 # 201121
else:
out+=op[script[0]]+' '; script=script[1:]
return out.strip()
#print humanreadablescript('76a914de7a247c0d617aafa5823994ecb648411e008f1288ac'.decode('hex'))
def humanreadabletx(rawtx): # similar to decoderawtransaction, but faster than using the rpc. also, fun to write
tx={} # needs better name # code could be prettier
tx['size']=len(rawtx)
tx['hash']=hash256(rawtx)[::-1].encode('hex')
tx['ver']=sum([ord(rawtx[x])*(256**x) for x in range(4)]); rawtx=rawtx[4:]
numinputs=ord(rawtx[0]); rawtx=rawtx[1:]
if numinputs==253: numinputs=sum([ord(rawtx[x])*(256**x) for x in range(2)]); rawtx=rawtx[2:] # 199090
tx['vin_sz']=numinputs
tx['in']=[]
for inputnum in range(numinputs):
input={'prev_out':{}}
input['prev_out']['hash']=rawtx[:32][::-1].encode('hex'); rawtx=rawtx[32:]
if input['prev_out']['hash']=='0'*64:
input['prev_out']['n']=sum([ord(rawtx[x])*(256**x) for x in range(4)]); rawtx=rawtx[4:]
coinbasesize=ord(rawtx[0]); rawtx=rawtx[1:]
if coinbasesize==253: coinbasesize=sum([ord(rawtx[x])*(256**x) for x in range(2)]); rawtx=rawtx[2:] # 199593
input['coinbase']=rawtx[:coinbasesize].encode('hex'); rawtx=rawtx[coinbasesize:]
input['sequence']=sum([ord(rawtx[x])*(256**x) for x in range(4)]); rawtx=rawtx[4:] # 199002
if input['sequence']==256**4-1: del input['sequence']
else:
input['prev_out']['n']=sum([ord(rawtx[x])*(256**x) for x in range(4)]); rawtx=rawtx[4:]
sigsize=ord(rawtx[0]); rawtx=rawtx[1:]
if sigsize==253: sigsize=sum([ord(rawtx[x])*(256**x) for x in range(2)]); rawtx=rawtx[2:] # 199593
input['scriptSig']=humanreadablescript(rawtx[:sigsize]); rawtx=rawtx[sigsize:]
input['sequence']=sum([ord(rawtx[x])*(256**x) for x in range(4)]); rawtx=rawtx[4:]
if input['sequence']==256**4-1: del input['sequence']
tx['in'].append(input)
numoutputs=ord(rawtx[0]); rawtx=rawtx[1:]
if numoutputs==253: numoutputs=sum([ord(rawtx[x])*(256**x) for x in range(2)]); rawtx=rawtx[2:] # 199025
tx['vout_sz']=numoutputs
tx['out']=[]
for outputnum in range(numoutputs):
output={}
numericvalue=sum([ord(rawtx[x])*(256**x) for x in range(8)]); rawtx=rawtx[8:]
output['value']=str(numericvalue/100000000)+'.'+str(numericvalue%100000000).zfill(8)
scriptsize=ord(rawtx[0]); rawtx=rawtx[1:]
if scriptsize==253: scriptsize=sum([ord(rawtx[x])*(256**x) for x in range(2)]); rawtx=rawtx[2:] # 71036
output['scriptPubKey']=humanreadablescript(rawtx[:scriptsize]); rawtx=rawtx[scriptsize:]
tx['out'].append(output)
tx['lock_time']=sum([ord(rawtx[x])*(256**x) for x in range(4)]); rawtx=rawtx[4:]
return tx
#print humanreadabletx('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000'.decode('hex')) # genesis transaction, good luck trying to get that from bitcoinqt though. I wonder if Satoshi can spend that tx if he wanted to...
# grunt work
def bxformattx(hrtx): # block explorer format
out ='{\n'
out+=' "hash":"'+hrtx['hash']+'",\n'
out+=' "ver":'+str(hrtx['ver'])+',\n'
out+=' "vin_sz":'+str(hrtx['vin_sz'])+',\n'
out+=' "vout_sz":'+str(hrtx['vout_sz'])+',\n'
out+=' "lock_time":'+str(hrtx['lock_time'])+',\n'
out+=' "size":'+str(hrtx['size'])+',\n'
out+=' "in":[\n'
for input in hrtx['in']:
out+=' {\n'
out+=' "prev_out":{\n'
out+=' "hash":"'+input['prev_out']['hash']+'",\n'
out+=' "n":'+str(input['prev_out']['n'])+'\n'
out+=' },\n'
field=['scriptSig','coinbase'][input['prev_out']['hash']=='0'*64]
out+=' "'+field+'":"'+input[field]+'"\n'
if 'sequence' in input:
out=out[:-1]+',\n'
out+=' "sequence":'+str(input['sequence'])+'\n'
out+=' },\n'
out=out[:-2]+'\n' # removing the last comma
out+=' ],\n'
out+=' "out":[\n'
for output in hrtx['out']:
out+=' {\n'
out+=' "value":"'+output['value']+'",\n'
out+=' "scriptPubKey":"'+output['scriptPubKey']+'"\n'
out+=' },\n'
out=out[:-2]+'\n' # removing the last comma
out+=' ]\n'
out+='}'
return out
def bxformatblock(block, transactiondata): # block explorer format
out ='{\n'
out+=' "hash":"'+block['hash']+'",\n'
out+=' "ver":'+str(block['version'])+',\n'
out+=' "prev_block":"'+block['previousblockhash']+'",\n'
out+=' "mrkl_root":"'+block['merkleroot']+'",\n'
out+=' "time":'+str(block['time'])+',\n'
out+=' "bits":'+str(int(block['bits'],16))+',\n'
out+=' "nonce":'+str(block['nonce'])+',\n'
out+=' "n_tx":'+str(len(block['tx']))+',\n'
out+=' "size":'+str(block['size'])+',\n'
out+=' "tx":[\n'
for transactionhash in block['tx']:
out+=' '+bxformattx(transactiondata[transactionhash]).replace('\n','\n ')+',\n' # add indentation
out=out[:-2]+'\n' # removing the last comma
out+=' ],\n'
out+=' "mrkl_tree":[\n'
for i in makemerkletree(block['tx']):
out+=' "'+i+'",\n'
out=out[:-2]+'\n' # removing the last comma
out+=' ]\n'
out+='}'
return out
# end of grunt work
def bxgettx(txid, server):
return bxformattx(humanreadabletx(server.getrawtx(txid).decode('hex')))
def bxgetblock(blockhash, server):
blockdata=server.getblock(blockhash)
txdata={}
for tx in blockdata['tx']: txdata[tx]=humanreadabletx(server.getrawtx(tx).decode('hex'))
return bxformatblock(blockdata,txdata)
#bitcoinqt=jsonrpcserver('localhost',8332,'bitcoinrpc','password')
#print bxgetblock('00000000000000eac823d5c3f6c17f9ec08414c4e23fff6060e842fae0581fd6', bitcoinqt)
# http server for offline block explorer
import threading
sock=socket.socket()
sock.bind(('',8334))
sock.listen(100)
def handleconnection(newconnection):
bitcoinqt=jsonrpcserver('localhost',8332,'bitcoinrpc','password')
req=newconnection.recv(2**20)
reqpage=req.split('\r\n')[0].split(' ')[1]
foundheader='HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: text/plain\r\nContent-Length: '
notfoundheader='HTTP/1.1 404 Not Found\r\nConnection: close\r\nContent-Type: text/plain\r\nContent-Length: '
def response(data, found=True): return [notfoundheader,foundheader][found]+str(len(data))+'\r\n\r\n'+data
try:
if reqpage[:10]=='/rawblock/': resptext=response(bxgetblock(reqpage[10:], bitcoinqt))
elif reqpage[:7]=='/rawtx/': resptext=response(bxgettx(reqpage[7:], bitcoinqt))
elif reqpage=='/q/getblockcount': resptext=response(str(bitcoinqt.getblockcount()))
elif reqpage[:16]=='/q/getblockhash/': resptext=response(bitcoinqt.getblockhash(int(reqpage[16:])).upper())
else: resptext=response('Nothing here.', found=False)
except jsonrpcserver.jsonerror, e: resptext=response(str(e.response))
except: resptext=response('Nothing here.', found=False); print 'ERROR - disable line 324 and restart to see what went wrong'
newconnection.send(resptext); newconnection.close()
while True: threading.Thread(target=handleconnection, args=(sock.accept()[0],)).start()