return zero if unconfirmed
[electrum-nvc.git] / lib / verifier.py
1 #!/usr/bin/env python
2 #
3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 thomasv@ecdsa.org
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19
20 import threading, time, Queue, os, sys
21 from util import user_dir
22 from bitcoin import *
23
24
25
26
27 class WalletVerifier(threading.Thread):
28
29     def __init__(self, interface, config, get_transactions):
30         threading.Thread.__init__(self)
31         self.daemon = True
32         self.config = config
33         self.interface = interface
34         self.get_transactions = get_transactions
35         self.interface.register_channel('verifier')
36         self.verified_tx     = config.get('verified_tx',{})
37         self.merkle_roots    = config.get('merkle_roots',{})      # hashed by me
38         self.targets         = config.get('targets',{})           # compute targets
39         self.lock = threading.Lock()
40         self.pending_headers = [] # headers that have not been verified
41         self.height = 0
42         self.local_height = 0
43         self.set_local_height()
44
45     def get_confirmations(self, tx):
46         return (self.local_height - self.verified_tx[tx] + 1) if tx in self.verified_tx else 0
47
48     def run(self):
49         requested_merkle = []
50         requested_chunks = []
51         requested_headers = []
52         pending_headers_changed = False
53         
54         # subscribe to block headers
55         self.interface.send([ ('blockchain.headers.subscribe',[])], 'verifier')
56
57         while True:
58             # request missing chunks
59             max_index = (self.height+1)/2016
60             if not requested_chunks:
61                 for i in range(0, max_index + 1):
62                     # test if we can read the first header of the chunk
63                     if self.read_header(i*2016): continue
64                     print "requesting chunk", i
65                     self.interface.send([ ('blockchain.block.get_chunk',[i])], 'verifier')
66                     requested_chunks.append(i)
67                     break
68
69             # request missing headers
70             if not requested_chunks and self.local_height:
71                 for i in range(self.local_height + 1, self.height + 1):
72                     if i not in requested_headers:
73                         print "requesting header", i
74                         self.interface.send([ ('blockchain.block.get_header',[i])], 'verifier')
75                         requested_headers.append(i)
76             
77             # request missing tx merkle
78             txlist = self.get_transactions()
79             for tx in txlist:
80                 if tx not in self.verified_tx:
81                     if tx not in requested_merkle:
82                         requested_merkle.append(tx)
83                         self.request_merkle(tx)
84                         #break
85
86             try:
87                 r = self.interface.get_response('verifier',timeout=1)
88             except Queue.Empty:
89                 time.sleep(1)
90                 continue
91
92             # 3. handle response
93             method = r['method']
94             params = r['params']
95             result = r['result']
96
97             if method == 'blockchain.transaction.get_merkle':
98                 tx_hash = params[0]
99                 self.verify_merkle(tx_hash, result)
100                 requested_merkle.remove(tx_hash)
101
102             elif method == 'blockchain.block.get_chunk':
103                 index = params[0]
104                 self.verify_chunk(index, result)
105                 requested_chunks.remove(index)
106
107             elif method == 'blockchain.headers.subscribe':
108                 self.height = result.get('block_height')
109                 self.pending_headers.append(result)
110                 pending_headers_changed = True
111
112             elif method == 'blockchain.block.get_header':
113                 height = result.get('block_height')
114                 requested_headers.remove(height)
115                 self.pending_headers.append(result)
116                 pending_headers_changed = True
117
118             # process pending headers
119             if pending_headers_changed:
120                 self.pending_headers.sort(key=lambda x: x.get('block_height'))
121                 print "pending headers", map(lambda x: x.get('block_height'), self.pending_headers)
122                 for header in self.pending_headers:
123                     if self.verify_header(header):
124                         self.pending_headers.remove(header)
125                     else:
126                         break
127                 pending_headers_changed = False
128
129
130
131     def request_merkle(self, tx_hash):
132         self.interface.send([ ('blockchain.transaction.get_merkle',[tx_hash]) ], 'verifier')
133
134
135     def verify_merkle(self, tx_hash, result):
136         tx_height = result.get('block_height')
137         self.merkle_roots[tx_hash] = self.hash_merkle_root(result['merkle'], tx_hash)
138         header = self.read_header(tx_height)
139         if header:
140             assert header.get('merkle_root') == self.merkle_roots[tx_hash]
141             self.verified_tx[tx_hash] = tx_height
142             print "verified", tx_hash
143             self.config.set_key('verified_tx', self.verified_tx, True)
144
145
146     def verify_chunk(self, index, hexdata):
147         data = hexdata.decode('hex')
148         height = index*2016
149         num = len(data)/80
150         print "validate_chunk", index, num
151
152         if index == 0:  
153             previous_hash = ("0"*64)
154         else:
155             prev_header = self.read_header(index*2016-1)
156             if prev_header is None: raise
157             previous_hash = self.hash_header(prev_header)
158
159         bits, target = self.get_target(index)
160
161         for i in range(num):
162             height = index*2016 + i
163             raw_header = data[i*80:(i+1)*80]
164             header = self.header_from_string(raw_header)
165             _hash = self.hash_header(header)
166             assert previous_hash == header.get('prev_block_hash')
167             assert bits == header.get('bits')
168             assert eval('0x'+_hash) < target
169
170             previous_header = header
171             previous_hash = _hash 
172
173         self.save_chunk(index, data)
174
175
176     def verify_header(self, header):
177         # add header to the blockchain file
178         # if there is a reorg, push it in a stack
179
180         height = header.get('block_height')
181
182         prev_header = self.read_header(height -1)
183         if not prev_header:
184             print "no previous header", height
185             return
186
187         #prev_hash = prev_header.get('block_height')
188         prev_hash = self.hash_header(prev_header)
189         bits, target = self.get_target(height/2016)
190         _hash = self.hash_header(header)
191         try:
192             assert prev_hash == header.get('prev_block_hash')
193             assert bits == header.get('bits')
194             assert eval('0x'+_hash) < target
195             ok = True
196         except:
197             print "verify header failed", header
198             raise
199             # this could be caused by a reorg. request the previous header
200             ok = False
201             #request previous one
202
203         if ok:
204             self.save_header(header)
205             print "verify header: ok", height
206             return True
207         
208
209             
210
211     def header_to_string(self, res):
212         s = int_to_hex(res.get('version'),4) \
213             + rev_hex(res.get('prev_block_hash')) \
214             + rev_hex(res.get('merkle_root')) \
215             + int_to_hex(int(res.get('timestamp')),4) \
216             + int_to_hex(int(res.get('bits')),4) \
217             + int_to_hex(int(res.get('nonce')),4)
218         return s
219
220
221     def header_from_string(self, s):
222         hex_to_int = lambda s: eval('0x' + s[::-1].encode('hex'))
223         h = {}
224         h['version'] = hex_to_int(s[0:4])
225         h['prev_block_hash'] = hash_encode(s[4:36])
226         h['merkle_root'] = hash_encode(s[36:68])
227         h['timestamp'] = hex_to_int(s[68:72])
228         h['bits'] = hex_to_int(s[72:76])
229         h['nonce'] = hex_to_int(s[76:80])
230         return h
231
232
233     def hash_header(self, header):
234         return rev_hex(Hash(self.header_to_string(header).decode('hex')).encode('hex'))
235
236
237     def hash_merkle_root(self, merkle_s, target_hash):
238         h = hash_decode(target_hash)
239         for item in merkle_s:
240             is_left = item[0] == 'L'
241             h = Hash( h + hash_decode(item[1:]) ) if is_left else Hash( hash_decode(item[1:]) + h )
242         return hash_encode(h)
243
244     def path(self):
245         wdir = user_dir()
246         if not os.path.exists( wdir ):
247             wdir = os.path.dirname(self.config.path)
248         return os.path.join( wdir, 'blockchain_headers')
249
250     def save_chunk(self, index, chunk):
251         filename = self.path()
252         if os.path.exists(filename):
253             f = open(filename,'rw+')
254         else:
255             print "creating file", filename
256             f = open(filename,'w+')
257         f.seek(index*2016*80)
258         h = f.write(chunk)
259         f.close()
260         self.set_local_height()
261
262     def save_header(self, header):
263         data = self.header_to_string(header).decode('hex')
264         assert len(data) == 80
265         height = header.get('block_height')
266         filename = self.path()
267         f = open(filename,'rw+')
268         f.seek(height*80)
269         h = f.write(data)
270         f.close()
271         self.set_local_height()
272
273
274     def set_local_height(self):
275         name = self.path()
276         if os.path.exists(name):
277             h = os.path.getsize(name)/80 - 1
278             if self.local_height != h:
279                 self.local_height = h
280                 self.interface.trigger_callback('updated')
281
282
283     def read_header(self, block_height):
284         name = self.path()
285         if os.path.exists(name):
286             f = open(name,'rb')
287             f.seek(block_height*80)
288             h = f.read(80)
289             f.close()
290             if len(h) == 80:
291                 h = self.header_from_string(h)
292                 return h 
293
294
295     def get_target(self, index):
296
297         max_target = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
298         if index == 0: return 0x1d00ffff, max_target
299
300         first = self.read_header((index-1)*2016)
301         last = self.read_header(index*2016-1)
302         
303         nActualTimespan = last.get('timestamp') - first.get('timestamp')
304         nTargetTimespan = 14*24*60*60
305         nActualTimespan = max(nActualTimespan, nTargetTimespan/4)
306         nActualTimespan = min(nActualTimespan, nTargetTimespan*4)
307
308         bits = last.get('bits') 
309         # convert to bignum
310         MM = 256*256*256
311         a = bits%MM
312         if a < 0x8000:
313             a *= 256
314         target = (a) * pow(2, 8 * (bits/MM - 3))
315
316         # new target
317         new_target = min( max_target, (target * nActualTimespan)/nTargetTimespan )
318         
319         # convert it to bits
320         c = ("%064X"%new_target)[2:]
321         i = 31
322         while c[0:2]=="00":
323             c = c[2:]
324             i -= 1
325
326         c = eval('0x'+c[0:6])
327         if c > 0x800000: 
328             c /= 256
329             i += 1
330
331         new_bits = c + MM * i
332         # print "%3d"%index, "%8x"%bits, "%64X"%new_target, hex(c)[2:].upper(), hex(new_bits)
333         return new_bits, new_target
334