Workaround for bug in Python's math.log function
[bitcoin:eloipool.git] / bitcoin / node.py
1 # Eloipool - Python Bitcoin pool server
2 # Copyright (C) 2011-2012  Luke Dashjr <luke-jr+eloipool@utopios.org>
3 #
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License as
6 # published by the Free Software Foundation, either version 3 of the
7 # License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU Affero General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17 from .varlen import varlenEncode
18 import asynchat
19 from binascii import b2a_hex
20 from collections import deque
21 import logging
22 import networkserver
23 import re
24 import socket
25 from struct import pack, unpack
26 from time import time
27 from util import dblsha, tryErr
28
29 MAX_PACKET_PAYLOAD = 0x200000
30
31 def makeNetAddr(addr):
32         timestamp = pack('<L', int(time()))
33         aIP = pack('>BBBB', *map(int, addr[0].split('.')))
34         aPort = pack('>H', addr[1])
35         return timestamp + b'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xff\xff' + aIP + aPort
36
37 class BitcoinLink(networkserver.SocketHandler):
38         logger = logging.getLogger('BitcoinLink')
39         
40         def __init__(self, *a, **ka):
41                 dest = ka.pop('dest', None)
42                 if dest:
43                         # Initiate outbound connection
44                         try:
45                                 if ':' not in dest[0]:
46                                         dest = ('::ffff:' + dest[0],) + tuple(x for x in dest[1:])
47                         except:
48                                 pass
49                         sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
50                         sock.connect(dest)
51                         ka['sock'] = sock
52                         ka['addr'] = dest
53                 super().__init__(*a, **ka)
54                 self.dest = dest
55                 self.sentVersion = False
56                 self.changeTask(None)  # FIXME: TEMPORARY
57                 if dest:
58                         self.pushVersion()
59         
60         def handle_readbuf(self):
61                 netid = self.server.netid
62                 while self.ac_in_buffer:
63                         if self.ac_in_buffer[:4] != netid:
64                                 p = self.ac_in_buffer.find(netid)
65                                 if p == -1:
66                                         p = asynchat.find_prefix_at_end(self.ac_in_buffer, netid)
67                                         if p:
68                                                 self.ac_in_buffer = self.ac_in_buffer[-p:]
69                                         else:
70                                                 self.ac_in_buffer = b''
71                                         break
72                                 self.ac_in_buffer = self.ac_in_buffer[p:]
73                         
74                         cmd = self.ac_in_buffer[4:0x10].rstrip(b'\0').decode('utf8')
75                         payloadLen = unpack('<L', self.ac_in_buffer[0x10:0x14])[0]
76                         if payloadLen > MAX_PACKET_PAYLOAD:
77                                 raise RuntimeError('Packet payload is too long (%d bytes)' % (payloadLen,))
78                         payloadEnd = payloadLen + 0x18
79                         if len(self.ac_in_buffer) < payloadEnd:
80                                 # Don't have the whole packet yet
81                                 break
82                         
83                         method = 'doCmd_' + cmd
84                         cksum = self.ac_in_buffer[0x14:0x18]
85                         payload = self.ac_in_buffer[0x18:payloadEnd]
86                         self.ac_in_buffer = self.ac_in_buffer[payloadEnd:]
87                         
88                         realcksum = dblsha(payload)[:4]
89                         if realcksum != cksum:
90                                 self.logger.debug('Wrong checksum on `%s\' message (%s vs actual:%s); ignoring' % (cmd, b2a_hex(cksum), b2a_hex(realcksum)))
91                                 return
92                         
93                         if hasattr(self, method):
94                                 getattr(self, method)(payload)
95         
96         def pushMessage(self, *a, **ka):
97                 self.push(self.server.makeMessage(*a, **ka))
98         
99         def makeVersion(self):
100                 r = pack('<lQq26s26sQ',
101                         60000,              # version
102                         0,                  # services bitfield
103                         int(time()),        # timestamp
104                         b'',                # FIXME: other-side address
105                         b'',                # FIXME: my-side address
106                         self.server.nonce,  # nonce
107                 )
108                 UA = self.server.userAgent
109                 r += varlenEncode(len(UA)) + UA
110                 r += b'\0\0\0\0'         # start_height
111                 return r
112         
113         def pushVersion(self):
114                 if self.sentVersion:
115                         return
116                 self.pushMessage('version', self.makeVersion())
117                 self.sentVersion = True
118         
119         def doCmd_version(self, payload):
120                 # FIXME: check for loopbacks
121                 self.pushVersion()
122                 # FIXME: don't send verack to ancient clients
123                 self.pushMessage('verack')
124
125 class BitcoinNode(networkserver.AsyncSocketServer):
126         logger = logging.getLogger('BitcoinNode')
127         
128         waker = True
129         
130         def __init__(self, netid, *a, **ka):
131                 ka.setdefault('RequestHandlerClass', BitcoinLink)
132                 super().__init__(*a, **ka)
133                 self.netid = netid
134                 self.userAgent = b'/BitcoinNode:0.1/'
135                 self.nonce = 0  # FIXME
136                 self._om = deque()
137         
138         def pre_schedule(self):
139                 OM = self._om
140                 while OM:
141                         m = OM.popleft()
142                         CB = 0
143                         for c in self._fd.values():
144                                 try:
145                                         c.push(m)
146                                 except:
147                                         pass
148                                 else:
149                                         CB += 1
150                         cmd = m[4:0x10].rstrip(b'\0').decode('utf8')
151                         self.logger.info('Sent `%s\' to %d nodes' % (cmd, CB))
152         
153         def makeMessage(self, cmd, payload = b''):
154                 cmd = cmd.encode('utf8')
155                 assert len(cmd) <= 12
156                 cmd += b'\0' * (12 - len(cmd))
157                 
158                 cksum = dblsha(payload)[:4]
159                 payloadLen = pack('<L', len(payload))
160                 return self.netid + cmd + payloadLen + cksum + payload
161         
162         def submitBlock(self, payload):
163                 self._om.append(self.makeMessage('block', payload))
164                 self.wakeup()