simple payment verification: check targets, use block headers file.
authorThomasV <thomasv@gitorious>
Wed, 24 Oct 2012 19:45:45 +0000 (21:45 +0200)
committerThomasV <thomasv@gitorious>
Wed, 24 Oct 2012 19:45:45 +0000 (21:45 +0200)
lib/__init__.py
lib/bitcoin.py
lib/interface.py
lib/verifier.py [new file with mode: 0644]
lib/wallet.py
setup.py

index 258a661..9981167 100644 (file)
@@ -1,5 +1,6 @@
 from util import format_satoshis
-from wallet import Wallet, WalletSynchronizer, WalletVerifier
+from wallet import Wallet, WalletSynchronizer
+from verifier import WalletVerifier
 from interface import Interface, pick_random_server, DEFAULT_SERVERS
 from simple_config import SimpleConfig
 import bitcoin
index 9cdb25b..d9bb2d7 100644 (file)
@@ -28,10 +28,9 @@ def int_to_hex(i, length=1):
     s = "0"*(2*length - len(s)) + s
     return rev_hex(s)
 
-
-def Hash(data):
-    return hashlib.sha256(hashlib.sha256(data).digest()).digest()
-
+Hash = lambda x: hashlib.sha256(hashlib.sha256(x).digest()).digest()
+hash_encode = lambda x: x[::-1].encode('hex')
+hash_decode = lambda x: x.decode('hex')[::-1]
 
 ############ functions from pywallet ##################### 
 
index af3ada1..1a6885b 100644 (file)
@@ -89,7 +89,7 @@ class Interface(threading.Thread):
                 method, params, channel = self.unanswered_requests.pop(msg_id)
             result = c.get('result')
         else:
-            # notification. we should find the channel(s)..
+            # notification: find the channel(s)
             method = c.get('method')
             params = c.get('params')
 
diff --git a/lib/verifier.py b/lib/verifier.py
new file mode 100644 (file)
index 0000000..2a71958
--- /dev/null
@@ -0,0 +1,268 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2012 thomasv@ecdsa.org
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+import threading, time, Queue, os, sys
+from util import user_dir
+from bitcoin import *
+
+
+
+
+class WalletVerifier(threading.Thread):
+
+    def __init__(self, wallet, config):
+        threading.Thread.__init__(self)
+        self.daemon = True
+        self.config = config
+        self.wallet = wallet
+        self.interface = self.wallet.interface
+        self.interface.register_channel('verifier')
+        self.verified_tx     = config.get('verified_tx',[])
+        self.merkle_roots    = config.get('merkle_roots',{})      # hashed by me
+        self.targets         = config.get('targets',{})           # compute targets
+        self.lock = threading.Lock()
+
+        #self.config.set_key('verified_tx', [], True)
+        #for i in range(70): self.get_target(i)
+        #sys.exit()
+
+        
+
+    def run(self):
+        requested_merkle = []
+        requested_chunks = []
+
+        while True:
+            # request missing chunks
+            max_index = self.wallet.blocks/2016
+            if not requested_chunks:
+                for i in range(0, max_index + 1):
+                    # test if we can read the first header of the chunk
+                    if self.read_header(i*2016): continue
+                    print "requesting chunk", i
+                    self.interface.send([ ('blockchain.block.get_chunk',[i])], 'verifier')
+                    requested_chunks.append(i)
+                    break
+
+            # todo: request missing blocks too
+
+            # request missing tx merkle
+            txlist = self.wallet.get_tx_hashes()
+            for tx in txlist:
+                if tx not in self.verified_tx:
+                    if tx not in requested_merkle:
+                        requested_merkle.append(tx)
+                        self.request_merkle(tx)
+                        break
+
+            try:
+                r = self.interface.get_response('verifier',timeout=1)
+            except Queue.Empty:
+                time.sleep(1)
+                continue
+
+            # 3. handle response
+            method = r['method']
+            params = r['params']
+            result = r['result']
+
+            if method == 'blockchain.transaction.get_merkle':
+                tx_hash = params[0]
+                self.verify_merkle(tx_hash, result)
+                requested_merkle.remove(tx_hash)
+
+            elif method == 'blockchain.block.get_chunk':
+                index = params[0]
+                self.verify_chunk(index, result)
+                requested_chunks.remove(index)
+
+            elif method == 'blockchain.block.get_header':
+                self.verify_header(result)
+
+
+    def request_merkle(self, tx_hash):
+        self.interface.send([ ('blockchain.transaction.get_merkle',[tx_hash]) ], 'verifier')
+
+
+    def verify_merkle(self, tx_hash, result):
+        tx_height = result.get('block_height')
+        self.merkle_roots[tx_hash] = self.hash_merkle_root(result['merkle'], tx_hash)
+        header = self.read_header(tx_height)
+        if header:
+            assert header.get('merkle_root') == self.merkle_roots[tx_hash]
+            self.verified_tx.append(tx_hash)
+            print "verified", tx_hash
+            self.config.set_key('verified_tx', self.verified_tx, True)
+
+
+    def verify_chunk(self, index, hexdata):
+        data = hexdata.decode('hex')
+        height = index*2016
+        numblocks = len(data)/80
+        print "validate_chunk", index, numblocks
+
+        if index == 0:  
+            previous_hash = ("0"*64)
+        else:
+            prev_header = self.read_header(index*2016-1)
+            if prev_header is None: raise
+            previous_hash = self.hash_header(prev_header)
+
+        bits, target = self.get_target(index)
+
+        for i in range(numblocks):
+            height = index*2016 + i
+            raw_header = data[i*80:(i+1)*80]
+            header = self.header_from_string(raw_header)
+            _hash = self.hash_header(header)
+            assert previous_hash == header.get('prev_block_hash')
+            try:
+                assert bits == header.get('bits')
+            except:
+                print index, hex(bits), hex(header.get('bits'))
+
+            try:
+                assert eval('0x'+_hash) < target
+            except:
+                print _hash, hex(target)
+
+            previous_header = header
+            previous_hash = _hash 
+
+        self.save_chunk(index, data)
+
+
+    def validate_header(self, header):
+        """ if there is a previous or a next block in the list, check the hash"""
+        height = header.get('block_height')
+        with self.lock:
+            self.headers[height] = header # detect conflicts
+            prev_header = next_header = None
+            if height-1 in self.headers:
+                prev_header = self.headers[height-1]
+            if height+1 in self.headers:
+                next_header = self.headers[height+1]
+
+        if prev_header:
+            prev_hash = self.hash_header(prev_header)
+            assert prev_hash == header.get('prev_block_hash')
+            self.save_header(header)
+        if next_header:
+            _hash = self.hash_header(header)
+            assert _hash == next_header.get('prev_block_hash')
+            
+
+    def header_to_string(self, res):
+        s = int_to_hex(res.get('version'),4) \
+            + rev_hex(res.get('prev_block_hash')) \
+            + rev_hex(res.get('merkle_root')) \
+            + int_to_hex(int(res.get('timestamp')),4) \
+            + int_to_hex(int(res.get('bits')),4) \
+            + int_to_hex(int(res.get('nonce')),4)
+        return s
+
+
+    def header_from_string(self, s):
+        hex_to_int = lambda s: eval('0x' + s[::-1].encode('hex'))
+        h = {}
+        h['version'] = hex_to_int(s[0:4])
+        h['prev_block_hash'] = hash_encode(s[4:36])
+        h['merkle_root'] = hash_encode(s[36:68])
+        h['timestamp'] = hex_to_int(s[68:72])
+        h['bits'] = hex_to_int(s[72:76])
+        h['nonce'] = hex_to_int(s[76:80])
+        return h
+
+
+    def hash_header(self, header):
+        return rev_hex(Hash(self.header_to_string(header).decode('hex')).encode('hex'))
+
+
+    def hash_merkle_root(self, merkle_s, target_hash):
+        h = hash_decode(target_hash)
+        for item in merkle_s:
+            is_left = item[0] == 'L'
+            h = Hash( h + hash_decode(item[1:]) ) if is_left else Hash( hash_decode(item[1:]) + h )
+        return hash_encode(h)
+
+
+    def save_chunk(self, index, chunk):
+        name = os.path.join( user_dir(), 'blockchain_headers')
+        if os.path.exists(name):
+            f = open(name,'rw+')
+        else:
+            f = open(name,'w+')
+
+        f.seek(index*2016*80)
+        h = f.write(chunk)
+        f.close()
+
+
+    def read_header(self, block_height):
+        name = os.path.join( user_dir(), 'blockchain_headers')
+        if os.path.exists(name):
+            f = open(name,'rb')
+            f.seek(block_height*80)
+            h = f.read(80)
+            f.close()
+            if len(h) == 80:
+                h = self.header_from_string(h)
+                return h 
+
+
+    def get_target(self, index):
+
+        max_target = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
+        if index == 0: return 0x1d00ffff, max_target
+
+        first = self.read_header((index-1)*2016)
+        last = self.read_header(index*2016-1)
+        
+        nActualTimespan = last.get('timestamp') - first.get('timestamp')
+        nTargetTimespan = 14*24*60*60
+        nActualTimespan = max(nActualTimespan, nTargetTimespan/4)
+        nActualTimespan = min(nActualTimespan, nTargetTimespan*4)
+
+        bits = last.get('bits') 
+        # convert to bignum
+        MM = 256*256*256
+        a = bits%MM
+        if a < 0x8000:
+            a *= 256
+        target = (a) * pow(2, 8 * (bits/MM - 3))
+
+        # new target
+        new_target = min( max_target, (target * nActualTimespan)/nTargetTimespan )
+        
+        # convert it to bits
+        c = ("%064X"%new_target)[2:]
+        i = 31
+        while c[0:2]=="00":
+            c = c[2:]
+            i -= 1
+
+        c = eval('0x'+c[0:6])
+        if c > 0x800000: 
+            c /= 256
+            i += 1
+
+        new_bits = c + MM * i
+        # print "%3d"%index, "%8x"%bits, "%64X"%new_target, hex(c)[2:].upper(), hex(new_bits)
+        return new_bits, new_target
+
index 8c53c12..c3f5984 100644 (file)
@@ -940,119 +940,3 @@ class WalletSynchronizer(threading.Thread):
                 self.wallet.was_updated = False
 
 
-encode = lambda x: x[::-1].encode('hex')
-decode = lambda x: x.decode('hex')[::-1]
-from bitcoin import Hash, rev_hex, int_to_hex
-
-class WalletVerifier(threading.Thread):
-
-    def __init__(self, wallet, config):
-        threading.Thread.__init__(self)
-        self.daemon = True
-        self.config = config
-        self.wallet = wallet
-        self.interface = self.wallet.interface
-        self.interface.register_channel('verifier')
-        self.validated       = config.get('verified_tx',[])
-        self.merkle_roots    = config.get('merkle_roots',{})
-        self.headers         = config.get('block_headers',{})
-        self.lock = threading.Lock()
-        self.saved = True
-
-    def run(self):
-        requested = []
-
-        while True:
-            txlist = self.wallet.get_tx_hashes()
-
-            for tx in txlist:
-                if tx not in self.validated:
-                    if tx not in requested:
-                        requested.append(tx)
-                        self.request_merkle(tx)
-                        self.saved = False
-                        break
-
-            try:
-                r = self.interface.get_response('verifier',timeout=1)
-            except Queue.Empty:
-                if len(self.validated) == len(txlist) and not self.saved:
-                    print "saving verified transactions"
-                    self.config.set_key('verified_tx', self.validated, True)
-                    self.saved = True
-                continue
-
-            # 3. handle response
-            method = r['method']
-            params = r['params']
-            result = r['result']
-
-            if method == 'blockchain.transaction.get_merkle':
-                tx_hash = params[0]
-                tx_height = result.get('block_height')
-                self.merkle_roots[tx_hash] = self.hash_merkle_root(result['merkle'], tx_hash)
-                # if we already have the header, check merkle root directly
-                header = self.headers.get(tx_height)
-                if header:
-                    self.validated.append(tx_hash)
-                    assert header.get('merkle_root') == self.merkle_roots[tx_hash]
-                self.request_headers(tx_height) 
-
-            elif method == 'blockchain.block.get_header':
-                self.validate_header(result)
-
-
-    def request_merkle(self, tx_hash):
-        self.interface.send([ ('blockchain.transaction.get_merkle',[tx_hash]) ], 'verifier')
-        
-
-    def request_headers(self, tx_height, delta=10):
-        headers_requests = []
-        for height in range(tx_height-delta,tx_height+delta): # we might can request blocks that do not exist yet
-            if height not in self.headers:
-                headers_requests.append( ('blockchain.block.get_header',[height]) )
-        self.interface.send(headers_requests,'verifier')
-
-
-    def validate_header(self, header):
-        """ if there is a previous or a next block in the list, check the hash"""
-        height = header.get('block_height')
-        with self.lock:
-            self.headers[height] = header # detect conflicts
-            prev_header = next_header = None
-            if height-1 in self.headers:
-                prev_header = self.headers[height-1]
-            if height+1 in self.headers:
-                next_header = self.headers[height+1]
-
-        if prev_header:
-            prev_hash = self.hash_header(prev_header)
-            assert prev_hash == header.get('prev_block_hash')
-        if next_header:
-            _hash = self.hash_header(header)
-            assert _hash == next_header.get('prev_block_hash')
-            
-        # check if there are transactions at that height
-        for tx_hash in self.wallet.get_transactions_at_height(height):
-            if tx_hash in self.validated: continue
-            # check if we already have the merkle root
-            merkle_root = self.merkle_roots.get(tx_hash)
-            if merkle_root:
-                self.validated.append(tx_hash)
-                assert header.get('merkle_root') == merkle_root
-
-    def hash_header(self, res):
-        header = int_to_hex(res.get('version'),4) \
-            + rev_hex(res.get('prev_block_hash')) \
-            + rev_hex(res.get('merkle_root')) \
-            + int_to_hex(int(res.get('timestamp')),4) \
-            + int_to_hex(int(res.get('bits')),4) \
-            + int_to_hex(int(res.get('nonce')),4)
-        return rev_hex(Hash(header.decode('hex')).encode('hex'))
-
-    def hash_merkle_root(self, merkle_s, target_hash):
-        h = decode(target_hash)
-        for item in merkle_s:
-            is_left = item[0] == 'L'
-            h = Hash( h + decode(item[1:]) ) if is_left else Hash( decode(item[1:]) + h )
-        return encode(h)
index acfa6f9..56f1bd1 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -61,6 +61,7 @@ setup(name = "Electrum",
                   'electrum.msqr',
                   'electrum.util',
                   'electrum.bitcoin',
+                  'electrum.verifier',
                   'electrum.i18n'],
     description = "Lightweight Bitcoin Wallet",
     author = "thomasv",