from __future__ import division
import struct
-import StringIO
+import cStringIO as StringIO
import hashlib
import warnings
def unpack(self, data):
obj = self._unpack(data)
- data2 = self._pack(obj)
- if data2 != data:
- assert self._unpack(data2) == obj
+ if __debug__:
+ data2 = self._pack(obj)
+ if data2 != data:
+ assert self._unpack(data2) == obj
return obj
data = file.read(1)
if len(data) != 1:
raise EarlyEnd()
- first, = struct.unpack('<B', data)
- if first == 0xff: desc = '<Q'
- elif first == 0xfe: desc = '<I'
- elif first == 0xfd: desc = '<H'
- else: return first
+ first = ord(data)
+ if first < 0xfd:
+ return first
+ elif first == 0xfd:
+ desc = '<H'
+ elif first == 0xfe:
+ desc = '<I'
+ elif first == 0xff:
+ desc = '<Q'
+ else:
+ raise AssertionError()
length = struct.calcsize(desc)
data = file.read(length)
if len(data) != length:
raise ValueError('int too large for varint')
class VarStrType(Type):
+ _inner_size = VarIntType()
+
def read(self, file):
- length = VarIntType().read(file)
+ length = self._inner_size.read(file)
res = file.read(length)
if len(res) != length:
raise EarlyEnd('var str not long enough %r' % ((length, len(res), res),))
return res
def write(self, file, item):
- VarIntType().write(file, len(item))
+ self._inner_size.write(file, len(item))
file.write(item)
class FixedStrType(Type):
return int(data[::-1].encode('hex'), 16)
def write(self, file, item):
- if item >= 2**256:
+ if not 0 <= item < 2**256:
raise ValueError("invalid hash value")
if item != 0 and item < 2**160:
- warnings.warn("very low hash value - maybe you meant to use ShortHashType?")
+ warnings.warn("very low hash value - maybe you meant to use ShortHashType? %x" % (item,))
file.write(('%064x' % (item,)).decode('hex')[::-1])
class ShortHashType(Type):
file.write(('%040x' % (item,)).decode('hex')[::-1])
class ListType(Type):
+ _inner_size = VarIntType()
+
def __init__(self, type):
self.type = type
def read(self, file):
- length = VarIntType().read(file)
+ length = self._inner_size.read(file)
return [self.type.read(file) for i in xrange(length)]
def write(self, file, item):
- VarIntType().write(file, len(item))
+ self._inner_size.write(file, len(item))
for subitem in item:
self.type.write(file, subitem)
+class FastLittleEndianUnsignedInteger(Type):
+ def read(self, file):
+ data = map(ord, file.read(4))
+ return data[0] + (data[1] << 8) + (data[2] << 16) + (data[3] << 24)
+
+ def write(self, file, item):
+ StructType("<I").write(file, item)
+
class StructType(Type):
def __init__(self, desc):
self.desc = desc
class FloatingIntegerType(Type):
# redundancy doesn't matter here because bitcoin checks binary bits against its own computed bits
# so it will always be encoded 'normally' in blocks (they way bitcoin does it)
+ _inner = StructType("<I")
+ _inner = FastLittleEndianUnsignedInteger()
def read(self, file):
- data = FixedStrType(4).read(file)
- target = self._bits_to_target(data)
- if self._target_to_bits(target) != data:
- raise ValueError("bits in non-canonical form")
+ bits = self._inner.read(file)
+ target = self._bits_to_target(bits)
+ if __debug__:
+ if self._target_to_bits(target) != bits:
+ raise ValueError("bits in non-canonical form")
return target
def write(self, file, item):
- FixedStrType(4).write(file, self._target_to_bits(item))
+ self._inner.write(file, self._target_to_bits(item))
def truncate_to(self, x):
return self._bits_to_target(self._target_to_bits(x, _check=False))
- def _bits_to_target(self, bits, _check=True):
- assert len(bits) == 4, repr(bits)
- target1 = self._bits_to_target1(bits)
- target2 = self._bits_to_target2(bits)
- if target1 != target2:
- raise ValueError()
- if _check:
- if self._target_to_bits(target1, _check=False) != bits:
- raise ValueError()
- return target1
+ def _bits_to_target(self, bits2):
+ target = math.shift_left(bits2 & 0x00ffffff, 8 * ((bits2 >> 24) - 3))
+ assert target == self._bits_to_target1(struct.pack("<I", bits2))
+ assert self._target_to_bits(target, _check=False) == bits2
+ return target
def _bits_to_target1(self, bits):
bits = bits[::-1]
length = ord(bits[0])
return bases.string_to_natural((bits[1:] + "\0"*length)[:length])
- def _bits_to_target2(self, bits):
- bits = struct.unpack("<I", bits)[0]
- return math.shift_left(bits & 0x00ffffff, 8 * ((bits >> 24) - 3))
-
def _target_to_bits(self, target, _check=True):
n = bases.natural_to_string(target)
if n and ord(n[0]) >= 128:
n = "\x00" + n
- bits = (chr(len(n)) + (n + 3*chr(0))[:3])[::-1]
+ bits2 = (chr(len(n)) + (n + 3*chr(0))[:3])[::-1]
+ bits = struct.unpack("<I", bits2)[0]
if _check:
- if self._bits_to_target(bits, _check=False) != target:
+ if self._bits_to_target(bits) != target:
raise ValueError(repr((target, self._bits_to_target(bits, _check=False))))
return bits
def handle_verack(self):
self.version = self.version_after
- # connection ready
+ self.ready()
+
+ def ready(self):
self.check_order = deferral.GenericDeferrer(2**256, lambda id, order: self.send_checkorder(id=id, order=order))
self.submit_order = deferral.GenericDeferrer(2**256, lambda id, order: self.send_submitorder(id=id, order=order))
self.get_block = deferral.ReplyMatcher(lambda hash: self.send_getdata(requests=[dict(type='block', hash=hash)]))
message_getblocks = bitcoin_data.ComposedType([
('version', bitcoin_data.StructType('<I')),
('have', bitcoin_data.ListType(bitcoin_data.HashType())),
- ('last', bitcoin_data.HashType()),
+ ('last', bitcoin_data.PossiblyNone(0, bitcoin_data.HashType())),
])
message_getheaders = bitcoin_data.ComposedType([
('version', bitcoin_data.StructType('<I')),
('have', bitcoin_data.ListType(bitcoin_data.HashType())),
- ('last', bitcoin_data.HashType()),
+ ('last', bitcoin_data.PossiblyNone(0, bitcoin_data.HashType())),
])
message_getaddr = bitcoin_data.ComposedType([])
message_checkorder = bitcoin_data.ComposedType([
self.get_block.got_response(bitcoin_data.block_header_type.hash256(block['header']), block)
message_headers = bitcoin_data.ComposedType([
- ('headers', bitcoin_data.ListType(bitcoin_data.block_header_type)),
+ ('headers', bitcoin_data.ListType(bitcoin_data.block_type)),
])
def handle_headers(self, headers):
for header in headers:
- self.get_block_header.got_response(bitcoin_data.block_hash(header), header)
+ header = header['header']
+ print header
+ self.get_block_header.got_response(bitcoin_data.block_header_type.hash256(header), header)
message_reply = bitcoin_data.ComposedType([
('hash', bitcoin_data.HashType()),
('reply', bitcoin_data.EnumType(bitcoin_data.StructType('<I'), {'success': 0, 'failure': 1, 'denied': 2})),
- ('script', bitcoin_data.VarStrType()),
+ ('script', bitcoin_data.PossiblyNone("", bitcoin_data.VarStrType())),
])
def handle_reply(self, hash, reply, script):
self.check_order.got_response(hash, dict(reply=reply, script=script))
class Tracker(object):
- def __init__(self, net):
- self.net = net
-
+ def __init__(self):
self.shares = {} # hash -> share
self.reverse_shares = {} # previous_share_hash -> set of share_hashes
class OkayTracker(Tracker):
def __init__(self, net):
- Tracker.__init__(self, net)
- self.verified = set()
+ Tracker.__init__(self)
+ self.net = net
+ self.verified = Tracker()
"""
self.okay_cache = {} # hash -> height
"""
def think(self):
desired = set()
- best = None
- best_score = 0
+
+ # for each overall head, attempt verification
+ # if it fails, attempt on parent, and repeat
+ # if no successful verification because of lack of parents, request parent
for head in self.heads:
- height, last = self.get_height_and_last(head)
- #if height > 1:#, self.shares[head]
- if last is not None and height < 2*self.net.CHAIN_LENGTH:
- desired.add((random.choice(self.reverse_shares[last]).peers, last)) # XXX
- continue
- #first_to_verify = math.nth(self.get_chain(head), self.net.CHAIN_LENGTH - 1)
- to_verify = []
- last_good_hash = None
- for share_hash in self.get_chain_known(head):
- if share_hash in self.verified:
- last_good = share_hash
+ head_height, last = self.get_height_and_last(head)
+ if head_height < a and last is not None:
+ # request more
+
+ for share in itertools.islice(self.get_chain_known(head), None if last is None else head_height - self.net.CHAIN_LENGTH): # XXX change length for None
+ in share in self.verified.shares:
break
- to_verify.append(share_hash)
- # recheck for 2*chain_length
- for share_hash in reversed(to_verify):
- share = self.shares[share_hash]
try:
share.check(self, self.net)
except:
print "Share check failed:"
traceback.print_exc()
print
+ else:
+ self.verified.add_share(share_hash)
break
- self.verified.add(share_hash)
- last_good_hash = share_hash
-
- new_height, last = self.get_height_and_last(last_good_hash)
-
- print head, height, last, new_height
- score = new_height
-
- if score > best_score:
- best = head
- best_score = score
- print "BEST", best
+ # try to get at least CHAIN_LENGTH height for each verified head, requesting parents if needed
+ for head in self.verified.heads:
+ head_height, last = self.verified.get_height_and_last(head)
+ a
+
+ # decide best verified head
+ def score(share_hash):
+ share = self.verified.shares[share_hash]
+ head_height, last = self.verified.get_height_and_last(share)
+ return (min(head_height, net.CHAIN_LENGTH), RECENTNESS)
+ best = max(self.verified.heads, key=score)
+
return best, desired
-class Chain(object):
- def __init__(self):
- pass
-
-def get_chain_descriptor(tracker, start):
- for item in tracker.get_chain(self.net.CHAIN_LENGTH):
- a
- pass
-
class Mainnet(bitcoin_data.Mainnet):
SHARE_PERIOD = 5 # seconds
def shift_left(n, m):
# python: :(
- if m < 0:
- return n >> -m
- return n << m
+ if m >= 0:
+ return n << m
+ return n >> -m
def clip(x, (low, high)):
if x < low: