''' Implementation of Bitcoin's p2p protocol ''' from __future__ import division import hashlib import random import struct import time from twisted.internet import defer, protocol, reactor, task from twisted.python import log import p2pool from . import data as bitcoin_data, getwork from p2pool.util import variable, datachunker, deferral, forest class TooLong(Exception): pass 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(' self.max_payload_length: print 'length too large' continue if self.use_checksum: checksum = yield 4 else: checksum = None payload = yield length if checksum is not None: if hashlib.sha256(hashlib.sha256(payload).digest()).digest()[:4] != checksum: print 'invalid hash for', repr(command), checksum.encode('hex') if checksum is not None else None, repr(payload[:100].encode('hex')), len(payload) continue type_ = getattr(self, 'message_' + command, None) if type_ is None: if p2pool.DEBUG: print 'no type for', repr(command) continue try: payload2 = type_.unpack(payload) except: print 'RECV', command, checksum.encode('hex') if checksum is not None else None, repr(payload.encode('hex')), len(payload) log.err(None, 'Error parsing message: (see RECV line)') continue handler = getattr(self, 'handle_' + command, None) if handler is None: if p2pool.DEBUG: print 'no handler for', repr(command) continue try: handler(**payload2) except: print 'RECV', command, repr(payload2)[:100] log.err(None, 'Error handling message: (see RECV line)') continue self.gotPacket() def gotPacket(self): pass def sendPacket(self, command, payload2): if len(command) >= 12: raise ValueError('command too long') type_ = getattr(self, 'message_' + command, None) if type_ is None: raise ValueError('invalid command') #print 'SEND', command, repr(payload2)[:500] payload = type_.pack(payload2) if len(payload) > self.max_payload_length: raise TooLong('payload too long') if self.use_checksum: checksum = hashlib.sha256(hashlib.sha256(payload).digest()).digest()[:4] else: checksum = '' 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 max_payload_length = 1000000 @property def use_checksum(self): return self.version >= 209 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('= self._last_notified_size + 100: print 'Have %i/%i block headers' % (len(self._tracker.shares), self._backlog_needed) self._last_notified_size = len(self._tracker.shares) def _heard_block(self, block_hash): self._request(block_hash) @defer.inlineCallbacks def _request(self, last): if last in self._tracker.shares: return if last in self._requested: return self._requested.add(last) (yield self._factory.getProtocol()).send_getheaders(version=1, have=[], last=last) def get_height_rel_highest(self, block_hash): # callers: highest height can change during yields! height, last = self._tracker.get_height_and_last(block_hash) if last not in self._tracker.tails: return -1e300 return height - max(self._tracker.get_height(head_hash) for head_hash in self._tracker.tails[last]) def stop(self): self._factory.new_headers.unwatch(self._watch1) self._factory.new_block.unwatch(self._watch2) self._clear_task.stop() self._think_task.stop() self._think2_task.stop() if __name__ == '__main__': from . import networks factory = ClientFactory(networks.BitcoinMainnet) reactor.connectTCP('127.0.0.1', 8333, factory) @repr @apply @defer.inlineCallbacks def think(): try: print (yield (yield factory.getProtocol()).get_block(0x000000000000003aaaf7638f9f9c0d0c60e8b0eb817dcdb55fd2b1964efc5175)) except defer.TimeoutError: print "timeout" reactor.stop() reactor.run()