bff0744c6ee535899201a162e0572f81223c8968
[novacoin.git] / bitcointools / block.py
1 #
2 # Code for dumping a single block, given its ID (hash)
3 #
4
5 from bsddb.db import *
6 import logging
7 import os.path
8 import re
9 import sys
10 import time
11
12 from BCDataStream import *
13 from base58 import public_key_to_bc_address
14 from util import short_hex, long_hex
15 from deserialize import *
16
17 def _open_blkindex(db_env):
18   db = DB(db_env)
19   try:
20     r = db.open("blkindex.dat", "main", DB_BTREE, DB_THREAD|DB_RDONLY)
21   except DBError:
22     r = True
23   if r is not None:
24     logging.error("Couldn't open blkindex.dat/main.  Try quitting any running Bitcoin apps.")
25     sys.exit(1)
26   return db
27
28 def _read_CDiskTxPos(stream):
29   n_file = stream.read_uint32()
30   n_block_pos = stream.read_uint32()
31   n_tx_pos = stream.read_uint32()
32   return (n_file, n_block_pos, n_tx_pos)
33
34 def _dump_block(datadir, nFile, nBlockPos, hash256, hashNext, do_print=True):
35   blockfile = open(os.path.join(datadir, "blk%04d.dat"%(nFile,)), "rb")
36   ds = BCDataStream()
37   ds.map_file(blockfile, nBlockPos)
38   d = parse_Block(ds)
39   block_string = deserialize_Block(d)
40   ds.close_file()
41   blockfile.close()
42   if do_print:
43     print "BLOCK "+long_hex(hash256[::-1])
44     print "Next block: "+long_hex(hashNext[::-1])
45     print block_string
46   return block_string
47
48 def _parse_block_index(vds):
49   d = {}
50   d['version'] = vds.read_int32()
51   d['hashNext'] = vds.read_bytes(32)
52   d['nFile'] = vds.read_uint32()
53   d['nBlockPos'] = vds.read_uint32()
54   d['nHeight'] = vds.read_int32()
55
56   header_start = vds.read_cursor
57   d['b_version'] = vds.read_int32()
58   d['hashPrev'] = vds.read_bytes(32)
59   d['hashMerkle'] = vds.read_bytes(32)
60   d['nTime'] = vds.read_int32()
61   d['nBits'] = vds.read_int32()
62   d['nNonce'] = vds.read_int32()
63   header_end = vds.read_cursor
64   d['__header__'] = vds.input[header_start:header_end]
65   return d
66
67 def dump_block(datadir, db_env, block_hash):
68   """ Dump a block, given hexadecimal hash-- either the full hash
69       OR a short_hex version of the it.
70   """
71   db = _open_blkindex(db_env)
72
73   kds = BCDataStream()
74   vds = BCDataStream()
75
76   n_blockindex = 0
77
78   key_prefix = "\x0ablockindex"
79   cursor = db.cursor()
80   (key, value) = cursor.set_range(key_prefix)
81
82   while key.startswith(key_prefix):
83     kds.clear(); kds.write(key)
84     vds.clear(); vds.write(value)
85
86     type = kds.read_string()
87     hash256 = kds.read_bytes(32)
88     hash_hex = long_hex(hash256[::-1])
89     block_data = _parse_block_index(vds)
90
91     if (hash_hex.startswith(block_hash) or short_hex(hash256[::-1]).startswith(block_hash)):
92       print "Block height: "+str(block_data['nHeight'])
93       _dump_block(datadir, block_data['nFile'], block_data['nBlockPos'], hash256, block_data['hashNext'])
94
95     (key, value) = cursor.next()
96
97   db.close()
98
99 def read_block(db_cursor, hash):
100   (key,value) = db_cursor.set_range("\x0ablockindex"+hash)
101   vds = BCDataStream()
102   vds.clear(); vds.write(value)
103   block_data = _parse_block_index(vds)
104   block_data['hash256'] = hash
105   return block_data
106
107 def scan_blocks(datadir, db_env, callback_fn):
108   """ Scan through blocks, from last through genesis block,
109       calling callback_fn(block_data) for each.
110       callback_fn should return False if scanning should
111       stop, True if it should continue.
112       Returns last block_data scanned.
113   """
114   db = _open_blkindex(db_env)
115
116   kds = BCDataStream()
117   vds = BCDataStream()
118   
119   # Read the hashBestChain record:
120   cursor = db.cursor()
121   (key, value) = cursor.set_range("\x0dhashBestChain")
122   vds.write(value)
123   hashBestChain = vds.read_bytes(32)
124
125   block_data = read_block(cursor, hashBestChain)
126
127   while callback_fn(block_data):
128     if block_data['nHeight'] == 0:
129       break;
130     block_data = read_block(cursor, block_data['hashPrev'])
131   return block_data
132
133
134 def dump_block_n(datadir, db_env, block_number):
135   """ Dump a block given block number (== height, genesis block is 0)
136   """
137   def scan_callback(block_data):
138     return not block_data['nHeight'] == block_number
139
140   block_data = scan_blocks(datadir, db_env, scan_callback)
141
142   print "Block height: "+str(block_data['nHeight'])
143   _dump_block(datadir, block_data['nFile'], block_data['nBlockPos'], block_data['hash256'], block_data['hashNext'])
144
145 def search_blocks(datadir, db_env, pattern):
146   """ Dump a block given block number (== height, genesis block is 0)
147   """
148   db = _open_blkindex(db_env)
149   kds = BCDataStream()
150   vds = BCDataStream()
151   
152   # Read the hashBestChain record:
153   cursor = db.cursor()
154   (key, value) = cursor.set_range("\x0dhashBestChain")
155   vds.write(value)
156   hashBestChain = vds.read_bytes(32)
157   block_data = read_block(cursor, hashBestChain)
158
159   if pattern == "NONSTANDARD_CSCRIPTS": # Hack to look for non-standard transactions
160     search_odd_scripts(datadir, cursor, block_data)
161     return
162
163   while True:
164     block_string = _dump_block(datadir, block_data['nFile'], block_data['nBlockPos'],
165                                block_data['hash256'], block_data['hashNext'], False)
166     
167     if re.search(pattern, block_string) is not None:
168       print "MATCH: Block height: "+str(block_data['nHeight'])
169       print block_string
170
171     if block_data['nHeight'] == 0:
172       break
173     block_data = read_block(cursor, block_data['hashPrev'])
174     
175 def search_odd_scripts(datadir, cursor, block_data):
176   """ Look for non-standard transactions """
177   while True:
178     block_string = _dump_block(datadir, block_data['nFile'], block_data['nBlockPos'],
179                                block_data['hash256'], block_data['hashNext'], False)
180     
181     found_nonstandard = False
182     for m in re.finditer(r'TxIn:(.*?)$', block_string, re.MULTILINE):
183       s = m.group(1)
184       if re.match(r'\s*COIN GENERATED coinbase:\w+$', s): continue
185       if re.match(r'.*sig: \d+:\w+...\w+ \d+:\w+...\w+$', s): continue
186       if re.match(r'.*sig: \d+:\w+...\w+$', s): continue
187       print "Nonstandard TxIn: "+s
188       found_nonstandard = True
189       break
190
191     for m in re.finditer(r'TxOut:(.*?)$', block_string, re.MULTILINE):
192       s = m.group(1)
193       if re.match(r'.*Script: DUP HASH160 \d+:\w+...\w+ EQUALVERIFY CHECKSIG$', s): continue
194       if re.match(r'.*Script: \d+:\w+...\w+ CHECKSIG$', s): continue
195       print "Nonstandard TxOut: "+s
196       found_nonstandard = True
197       break
198
199     if found_nonstandard:
200       print "NONSTANDARD TXN: Block height: "+str(block_data['nHeight'])
201       print block_string
202
203     if block_data['nHeight'] == 0:
204       break
205     block_data = read_block(cursor, block_data['hashPrev'])
206   
207 def check_block_chain(db_env):
208   """ Make sure hashPrev/hashNext pointers are consistent through block chain """
209   db = _open_blkindex(db_env)
210
211   kds = BCDataStream()
212   vds = BCDataStream()
213   
214   # Read the hashBestChain record:
215   cursor = db.cursor()
216   (key, value) = cursor.set_range("\x0dhashBestChain")
217   vds.write(value)
218   hashBestChain = vds.read_bytes(32)
219
220   back_blocks = []
221
222   block_data = read_block(cursor, hashBestChain)
223
224   while block_data['nHeight'] > 0:
225     back_blocks.append( (block_data['nHeight'], block_data['hashMerkle'], block_data['hashPrev'], block_data['hashNext']) )
226     block_data = read_block(cursor, block_data['hashPrev'])
227
228   back_blocks.append( (block_data['nHeight'], block_data['hashMerkle'], block_data['hashPrev'], block_data['hashNext']) )
229   genesis_block = block_data
230   
231   print("check block chain: genesis block merkle hash is: %s"%(block_data['hashMerkle'][::-1].encode('hex_codec')))
232
233   while block_data['hashNext'] != ('\0'*32):
234     forward = (block_data['nHeight'], block_data['hashMerkle'], block_data['hashPrev'], block_data['hashNext'])
235     back = back_blocks.pop()
236     if forward != back:
237       print("Forward/back block mismatch at height %d!"%(block_data['nHeight'],))
238       print(" Forward: "+str(forward))
239       print(" Back: "+str(back))
240     block_data = read_block(cursor, block_data['hashNext'])