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