change SHARE_PERIOD and SPREAD to new values on switch to new share implementation
[p2pool.git] / p2pool / test / test_node.py
1 from __future__ import division
2
3 import base64
4 import random
5 import tempfile
6
7 from twisted.internet import defer, reactor
8 from twisted.python import failure
9 from twisted.trial import unittest
10 from twisted.web import client, resource, server
11
12 from p2pool import data, node, work
13 from p2pool.bitcoin import data as bitcoin_data, networks, worker_interface
14 from p2pool.util import deferral, jsonrpc, math, variable
15
16 class bitcoind(object): # can be used as p2p factory, p2p protocol, or rpc jsonrpc proxy
17     def __init__(self):
18         self.blocks = [0x000000000000016c169477c25421250ec5d32cf9c6d38538b5de970a2355fd89]
19         self.headers = {0x16c169477c25421250ec5d32cf9c6d38538b5de970a2355fd89: {
20             'nonce': 1853158954,
21             'timestamp': 1351658517,
22             'merkle_root': 2282849479936278423916707524932131168473430114569971665822757638339486597658L,
23             'version': 1,
24             'previous_block': 1048610514577342396345362905164852351970507722694242579238530L,
25             'bits': bitcoin_data.FloatingInteger(bits=0x1a0513c5, target=0x513c50000000000000000000000000000000000000000000000L),
26         }}
27         
28         self.conn = variable.Variable(self)
29         self.new_headers = variable.Event()
30         self.new_block = variable.Event()
31         self.new_tx = variable.Event()
32     
33     # p2p factory
34     
35     def getProtocol(self):
36         return self
37     
38     # p2p protocol
39     
40     def send_block(self, block):
41         pass
42     
43     def send_tx(self, tx):
44         pass
45     
46     def get_block_header(self, block_hash):
47         return self.headers[block_hash]
48     
49     # rpc jsonrpc proxy
50     
51     def rpc_help(self):
52         return '\ngetblock '
53     
54     def rpc_getblock(self, block_hash_hex):
55         block_hash = int(block_hash_hex, 16)
56         return dict(height=self.blocks.index(block_hash))
57     
58     def __getattr__(self, name):
59         if name.startswith('rpc_'):
60             return lambda *args, **kwargs: failure.Failure(jsonrpc.Error_for_code(-32601)('Method not found'))
61     
62     def rpc_getblocktemplate(self, param):
63         if param['mode'] == 'template':
64             pass
65         elif param['mode'] == 'submit':
66             result = param['data']
67             block = bitcoin_data.block_type.unpack(result.decode('hex'))
68             if sum(tx_out['value'] for tx_out in block['txs'][0]['tx_outs']) != sum(tx['tx_outs'][0]['value'] for tx in block['txs'][1:]) + 5000000000:
69                 print 'invalid fee'
70             if block['header']['previous_block'] != self.blocks[-1]:
71                 return False
72             if bitcoin_data.hash256(result.decode('hex')) > block['header']['bits'].target:
73                 return False
74             header_hash = bitcoin_data.hash256(bitcoin_data.block_header_type.pack(block['header']))
75             self.blocks.append(header_hash)
76             self.headers[header_hash] = block['header']
77             reactor.callLater(0, self.new_block.happened)
78             return True
79         else:
80             raise jsonrpc.Error_for_code(-1)('invalid request')
81         
82         txs = []
83         for i in xrange(100):
84             fee = i
85             txs.append(dict(
86                 data=bitcoin_data.tx_type.pack(dict(version=1, tx_ins=[], tx_outs=[dict(value=fee, script='hello!'*100)], lock_time=0)).encode('hex'),
87                 fee=fee,
88             ))
89         return {
90             "version" : 2,
91             "previousblockhash" : '%064x' % (self.blocks[-1],),
92             "transactions" : txs,
93             "coinbaseaux" : {
94                 "flags" : "062f503253482f"
95             },
96             "coinbasevalue" : 5000000000 + sum(tx['fee'] for tx in txs),
97             "target" : "0000000000000513c50000000000000000000000000000000000000000000000",
98             "mintime" : 1351655621,
99             "mutable" : [
100                 "time",
101                 "transactions",
102                 "prevblock"
103             ],
104             "noncerange" : "00000000ffffffff",
105             "sigoplimit" : 20000,
106             "sizelimit" : 1000000,
107             "curtime" : 1351659940,
108             "bits" : "21008000",
109             "height" : len(self.blocks),
110         }
111
112 @apply
113 class mm_provider(object):
114     def __getattr__(self, name):
115         print '>>>>>>>', name
116     def rpc_getauxblock(self, request, result1=None, result2=None):
117         if result1 is not None:
118             print result1, result2
119             return True
120         return {
121             "target" : "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", # 2**256*2/3
122             "hash" : "2756ea0315d46dc3d8d974f34380873fc88863845ac01a658ef11bc3b368af52",
123             "chainid" : 1
124         }
125
126 mynet = math.Object(
127     PARENT=networks.nets['litecoin_testnet'],
128     SHARE_PERIOD=3, # seconds
129     NEW_SHARE_PERIOD=5, # seconds
130     CHAIN_LENGTH=20*60//3, # shares
131     REAL_CHAIN_LENGTH=20*60//3, # shares
132     TARGET_LOOKBEHIND=200, # shares
133     SPREAD=12, # blocks
134     NEW_SPREAD=3, # blocks
135     IDENTIFIER='cca5e24ec6408b1e'.decode('hex'),
136     PREFIX='ad9614f6466a39cf'.decode('hex'),
137     P2P_PORT=19338,
138     MIN_TARGET=2**256 - 1,
139     MAX_TARGET=2**256 - 1,
140     PERSIST=False,
141     WORKER_PORT=19327,
142     BOOTSTRAP_ADDRS='72.14.191.28'.split(' '),
143     ANNOUNCE_CHANNEL='#p2pool-alt',
144     VERSION_CHECK=lambda v: True,
145 )
146
147 class MiniNode(object):
148     @classmethod
149     @defer.inlineCallbacks
150     def start(cls, net, factory, bitcoind, peer_ports, merged_urls):
151         self = cls()
152         
153         self.n = node.Node(factory, bitcoind, [], [], net)
154         yield self.n.start()
155         
156         self.n.p2p_node = node.P2PNode(self.n, port=0, max_incoming_conns=1000000, addr_store={}, connect_addrs=[('127.0.0.1', peer_port) for peer_port in peer_ports])
157         self.n.p2p_node.start()
158         
159         wb = work.WorkerBridge(node=self.n, my_pubkey_hash=random.randrange(2**160), donation_percentage=random.uniform(0, 10), merged_urls=merged_urls, worker_fee=3)
160         self.wb = wb
161         web_root = resource.Resource()
162         worker_interface.WorkerInterface(wb).attach_to(web_root)
163         self.web_port = reactor.listenTCP(0, server.Site(web_root))
164         
165         defer.returnValue(self)
166     
167     @defer.inlineCallbacks
168     def stop(self):
169         yield self.web_port.stopListening()
170         yield self.n.p2p_node.stop()
171         yield self.n.stop()
172         del self.web_port, self.n
173
174 class Test(unittest.TestCase):
175     @defer.inlineCallbacks
176     def test_node(self):
177         bitd = bitcoind()
178         
179         mm_root = resource.Resource()
180         mm_root.putChild('', jsonrpc.HTTPServer(mm_provider))
181         mm_port = reactor.listenTCP(0, server.Site(mm_root))
182         
183         n = node.Node(bitd, bitd, [], [], mynet)
184         yield n.start()
185         
186         wb = work.WorkerBridge(node=n, my_pubkey_hash=42, donation_percentage=2, merged_urls=[('http://127.0.0.1:%i' % (mm_port.getHost().port,), '')], worker_fee=3)
187         web_root = resource.Resource()
188         worker_interface.WorkerInterface(wb).attach_to(web_root)
189         port = reactor.listenTCP(0, server.Site(web_root))
190         
191         proxy = jsonrpc.HTTPProxy('http://127.0.0.1:' + str(port.getHost().port),
192             headers=dict(Authorization='Basic ' + base64.b64encode('user/0:password')))
193         
194         yield deferral.sleep(3)
195         
196         for i in xrange(100):
197             blah = yield proxy.rpc_getwork()
198             yield proxy.rpc_getwork(blah['data'])
199         
200         
201         yield deferral.sleep(3)
202         
203         assert len(n.tracker.items) == 100
204         assert n.tracker.verified.get_height(n.best_share_var.value) == 100
205         
206         wb.stop()
207         n.stop()
208         
209         yield port.stopListening()
210         del n, wb, web_root, port, proxy
211         import gc
212         gc.collect()
213         gc.collect()
214         gc.collect()
215         
216         yield deferral.sleep(20) # waiting for work_poller to exit
217         yield mm_port.stopListening()
218     #test_node.timeout = 15
219     
220     @defer.inlineCallbacks
221     def test_nodes(self):
222         N = 3
223         SHARES = 600
224         
225         bitd = bitcoind()
226         
227         nodes = []
228         for i in xrange(N):
229             nodes.append((yield MiniNode.start(mynet, bitd, bitd, [mn.n.p2p_node.serverfactory.listen_port.getHost().port for mn in nodes], [])))
230         
231         yield deferral.sleep(3)
232         
233         for i in xrange(SHARES):
234             proxy = jsonrpc.HTTPProxy('http://127.0.0.1:' + str(random.choice(nodes).web_port.getHost().port),
235                 headers=dict(Authorization='Basic ' + base64.b64encode('user/0:password')))
236             blah = yield proxy.rpc_getwork()
237             yield proxy.rpc_getwork(blah['data'])
238             yield deferral.sleep(.05)
239             print i
240             print type(nodes[0].n.tracker.items[nodes[0].n.best_share_var.value])
241         
242         # crawl web pages
243         from p2pool import web
244         stop_event = variable.Event()
245         web2_root = web.get_web_root(nodes[0].wb, tempfile.mkdtemp(), variable.Variable(None), stop_event)
246         web2_port = reactor.listenTCP(0, server.Site(web2_root))
247         for name in web2_root.listNames() + ['web/' + x for x in web2_root.getChildWithDefault('web', None).listNames()]:
248             if name in ['web/graph_data', 'web/share', 'web/share_data']: continue
249             print
250             print name
251             try:
252                 res = yield client.getPage('http://127.0.0.1:%i/%s' % (web2_port.getHost().port, name))
253             except:
254                 import traceback
255                 traceback.print_exc()
256             else:
257                 print repr(res)[:100]
258             print
259         yield web2_port.stopListening()
260         stop_event.happened()
261         del web2_root
262         
263         yield deferral.sleep(3)
264         
265         for i, n in enumerate(nodes):
266             assert len(n.n.tracker.items) == SHARES, (i, len(n.n.tracker.items))
267             assert n.n.tracker.verified.get_height(n.n.best_share_var.value) == SHARES, (i, n.n.tracker.verified.get_height(n.n.best_share_var.value))
268             assert type(n.n.tracker.items[nodes[0].n.best_share_var.value]) is (data.Share.SUCCESSOR if data.Share.SUCCESSOR is not None else data.Share)
269             assert type(n.n.tracker.items[n.n.tracker.get_nth_parent_hash(nodes[0].n.best_share_var.value, SHARES - 5)]) is data.Share
270         
271         for n in nodes:
272             yield n.stop()
273         
274         del nodes, n
275         import gc
276         gc.collect()
277         gc.collect()
278         gc.collect()
279         
280         yield deferral.sleep(20) # waiting for work_poller to exit
281     test_nodes.timeout = 300