''' Implementation of Bitcoin's p2p protocol ''' from __future__ import division import hashlib import random import struct import time import traceback from twisted.internet import defer, protocol, reactor from . import data as bitcoin_data from p2pool.util import variable, datachunker, deferral class BaseProtocol(protocol.Protocol): def connectionMade(self): self.dataReceived = datachunker.DataChunker(self.dataReceiver()) def dataReceiver(self): while True: start = '' while start != self._prefix: start = (start + (yield 1))[-len(self._prefix):] command = (yield 12).rstrip('\0') length, = struct.unpack('= 12: raise ValueError('command too long') type_ = getattr(self, "message_" + command, None) if type_ is None: raise ValueError('invalid command') payload = type_.pack(payload2) if self.use_checksum: checksum = hashlib.sha256(hashlib.sha256(payload).digest()).digest()[:4] else: checksum = '' if self.compress: payload = zlib.compress(payload) data = self._prefix + struct.pack('<12sI', command, len(payload)) + checksum + payload self.transport.write(data) def __getattr__(self, attr): prefix = 'send_' if attr.startswith(prefix): command = attr[len(prefix):] return lambda **payload2: self.sendPacket(command, payload2) #return protocol.Protocol.__getattr__(self, attr) raise AttributeError(attr) class Protocol(BaseProtocol): def __init__(self, net): self._prefix = net.BITCOIN_P2P_PREFIX version = 0 compress = False @property def use_checksum(self): return self.version >= 209 null_order = '\0'*60 def connectionMade(self): BaseProtocol.connectionMade(self) self.send_version( version=32200, services=1, time=int(time.time()), addr_to=dict( services=1, address=self.transport.getPeer().host, port=self.transport.getPeer().port, ), addr_from=dict( services=1, address=self.transport.getHost().host, port=self.transport.getHost().port, ), nonce=random.randrange(2**64), sub_version_num='', start_height=0, ) message_version = bitcoin_data.ComposedType([ ('version', bitcoin_data.StructType(' 10: step *= 2 continue break chain = list(self.tracker.get_chain_known(highest_head)) if chain: have.append(chain[-1].hash) if not have: have.append(0) if have == last: yield deferral.sleep(1) last = None continue last = have good_tails = [x for x in self.tracker.tails if x is not None] self.request(have, random.choice(good_tails) if good_tails else None) for tail in self.tracker.tails: if tail is None: continue self.request([], tail) try: yield self.factory.new_headers.get_deferred(timeout=1) except defer.TimeoutError: pass def heard_headers(self, headers): header2s = map(HeaderWrapper, headers) for header2 in header2s: self.tracker.add(header2) if header2s: if self.tracker.get_height_and_last(header2s[-1].hash)[1] is None: self.most_recent = header2s[-1].hash if random.random() < .6: self.request([header2s[-1].hash], None) print len(self.tracker.shares) def request(self, have, last): #print "REQ", ('[' + ', '.join(map(hex, have)) + ']', hex(last) if last is not None else None) if self.factory.conn.value is not None: self.factory.conn.value.send_getheaders(version=1, have=have, last=last) #@defer.inlineCallbacks #XXX should defer def getHeight(self, block_hash): height, last = self.tracker.get_height_and_last(block_hash) if last is not None: self.request([], last) #raise ValueError(last) return height, last if __name__ == '__main__': factory = ClientFactory(bitcoin_data.Mainnet) reactor.connectTCP('127.0.0.1', 8333, factory) h = HeightTracker(factory) @repr @apply @defer.inlineCallbacks def think(): while True: yield deferral.sleep(1) try: print h.getHeight(0xa285c3cb2a90ac7194cca034512748289e2526d9d7ae6ee7523) except Exception, e: traceback.print_exc() reactor.run()