# 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()