commit b018e0ae536d4dad500dd7300af149bc886acb18
parent e5c19b64afe6775ad86700523ae5fa508d6fc521
Author: ThomasV <thomasv@gitorious>
Date: Wed, 24 Oct 2012 21:45:45 +0200
simple payment verification: check targets, use block headers file.
Diffstat:
6 files changed, 275 insertions(+), 122 deletions(-)
diff --git a/lib/__init__.py b/lib/__init__.py
@@ -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
diff --git a/lib/bitcoin.py b/lib/bitcoin.py
@@ -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 #####################
diff --git a/lib/interface.py b/lib/interface.py
@@ -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
@@ -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
+
diff --git a/lib/wallet.py b/lib/wallet.py
@@ -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)
diff --git 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",