check coinbase subsidy in test_nodes
[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 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:
60                 print 'invalid fee'
61             if block['header']['previous_block'] != self.blocks[-1]:
62                 return False
63             if bitcoin_data.hash256(result.decode('hex')) > block['header']['bits'].target:
64                 return False
65             header_hash = bitcoin_data.hash256(bitcoin_data.block_header_type.pack(block['header']))
66             self.blocks.append(header_hash)
67             self.headers[header_hash] = block['header']
68             reactor.callLater(0, self.new_block.happened)
69             return True
70         
71         txs = []
72         for i in xrange(100):
73             fee = i
74             txs.append(dict(
75                 data=bitcoin_data.tx_type.pack(dict(version=1, tx_ins=[], tx_outs=[dict(value=fee*1000 + i, script='hello!'*100)], lock_time=0)).encode('hex'),
76                 fee=fee,
77             ))
78         return {
79             "version" : 2,
80             "previousblockhash" : '%064x' % (self.blocks[-1],),
81             "transactions" : txs,
82             "coinbaseaux" : {
83                 "flags" : "062f503253482f"
84             },
85             "coinbasevalue" : 5000000000 + sum(tx['fee'] for tx in txs),
86             "target" : "0000000000000513c50000000000000000000000000000000000000000000000",
87             "mintime" : 1351655621,
88             "mutable" : [
89                 "time",
90                 "transactions",
91                 "prevblock"
92             ],
93             "noncerange" : "00000000ffffffff",
94             "sigoplimit" : 20000,
95             "sizelimit" : 1000000,
96             "curtime" : 1351659940,
97             "bits" : "21008000",
98             "height" : len(self.blocks),
99         }
100
101 @apply
102 class mm_provider(object):
103     def __getattr__(self, name):
104         print '>>>>>>>', name
105     def rpc_getauxblock(self, request, result1=None, result2=None):
106         if result1 is not None:
107             print result1, result2
108             return True
109         return {
110             "target" : "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", # 2**256*2/3
111             "hash" : "2756ea0315d46dc3d8d974f34380873fc88863845ac01a658ef11bc3b368af52",
112             "chainid" : 1
113         }
114
115 mynet = math.Object(
116     PARENT=networks.nets['litecoin_testnet'],
117     SHARE_PERIOD=3, # seconds
118     CHAIN_LENGTH=20*60//3, # shares
119     REAL_CHAIN_LENGTH=20*60//3, # shares
120     TARGET_LOOKBEHIND=200, # shares
121     SPREAD=12, # blocks
122     IDENTIFIER='cca5e24ec6408b1e'.decode('hex'),
123     PREFIX='ad9614f6466a39cf'.decode('hex'),
124     P2P_PORT=19338,
125     MIN_TARGET=2**256 - 1,
126     MAX_TARGET=2**256 - 1,
127     PERSIST=False,
128     WORKER_PORT=19327,
129     BOOTSTRAP_ADDRS='72.14.191.28'.split(' '),
130     ANNOUNCE_CHANNEL='#p2pool-alt',
131     VERSION_CHECK=lambda v: True,
132 )
133
134 class MiniNode(object):
135     @classmethod
136     @defer.inlineCallbacks
137     def start(cls, net, factory, bitcoind, peer_ports, merged_urls):
138         self = cls()
139         
140         self.n = node.Node(factory, bitcoind, [], [], net)
141         yield self.n.start()
142         
143         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])
144         self.n.p2p_node.start()
145         
146         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)
147         self.wb = wb
148         web_root = resource.Resource()
149         worker_interface.WorkerInterface(wb).attach_to(web_root)
150         self.web_port = reactor.listenTCP(0, server.Site(web_root))
151         
152         defer.returnValue(self)
153     
154     @defer.inlineCallbacks
155     def stop(self):
156         yield self.web_port.stopListening()
157         yield self.n.p2p_node.stop()
158         yield self.n.stop()
159         del self.web_port, self.n
160
161 class Test(unittest.TestCase):
162     @defer.inlineCallbacks
163     def test_node(self):
164         bitd = bitcoind()
165         
166         mm_root = resource.Resource()
167         mm_root.putChild('', jsonrpc.HTTPServer(mm_provider))
168         mm_port = reactor.listenTCP(0, server.Site(mm_root))
169         
170         n = node.Node(bitd, bitd, [], [], mynet)
171         yield n.start()
172         
173         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)
174         web_root = resource.Resource()
175         worker_interface.WorkerInterface(wb).attach_to(web_root)
176         port = reactor.listenTCP(0, server.Site(web_root))
177         
178         proxy = jsonrpc.HTTPProxy('http://127.0.0.1:' + str(port.getHost().port))
179         
180         yield deferral.sleep(3)
181         
182         for i in xrange(100):
183             blah = yield proxy.rpc_getwork()
184             yield proxy.rpc_getwork(blah['data'])
185         
186         
187         yield deferral.sleep(3)
188         
189         assert len(n.tracker.items) == 100
190         assert n.tracker.verified.get_height(n.best_share_var.value) == 100
191         
192         wb.stop()
193         n.stop()
194         
195         yield port.stopListening()
196         del n, wb, web_root, port, proxy
197         import gc
198         gc.collect()
199         gc.collect()
200         gc.collect()
201         
202         yield deferral.sleep(20) # waiting for work_poller to exit
203         yield mm_port.stopListening()
204     #test_node.timeout = 15
205     
206     @defer.inlineCallbacks
207     def test_nodes(self):
208         N = 3
209         SHARES = 600
210         
211         bitd = bitcoind()
212         
213         nodes = []
214         for i in xrange(N):
215             nodes.append((yield MiniNode.start(mynet, bitd, bitd, [mn.n.p2p_node.serverfactory.listen_port.getHost().port for mn in nodes], [])))
216         
217         yield deferral.sleep(3)
218         
219         for i in xrange(SHARES):
220             proxy = jsonrpc.HTTPProxy('http://127.0.0.1:' + str(random.choice(nodes).web_port.getHost().port))
221             blah = yield proxy.rpc_getwork()
222             yield proxy.rpc_getwork(blah['data'])
223             yield deferral.sleep(.05)
224             print i
225             print type(nodes[0].n.tracker.items[nodes[0].n.best_share_var.value])
226         
227         # crawl web pages
228         from p2pool import web
229         stop_event = variable.Event()
230         web2_root = web.get_web_root(nodes[0].wb, tempfile.mkdtemp(), variable.Variable(None), stop_event)
231         web2_port = reactor.listenTCP(0, server.Site(web2_root))
232         for name in web2_root.listNames() + ['web/' + x for x in web2_root.getChildWithDefault('web', None).listNames()]:
233             if name in ['web/graph_data', 'web/share', 'web/share_data']: continue
234             print
235             print name
236             try:
237                 res = yield client.getPage('http://127.0.0.1:%i/%s' % (web2_port.getHost().port, name))
238             except:
239                 import traceback
240                 traceback.print_exc()
241             else:
242                 print repr(res)[:100]
243             print
244         yield web2_port.stopListening()
245         stop_event.happened()
246         del web2_root
247         
248         yield deferral.sleep(3)
249         
250         for i, n in enumerate(nodes):
251             assert len(n.n.tracker.items) == SHARES, (i, len(n.n.tracker.items))
252             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))
253             assert type(n.n.tracker.items[nodes[0].n.best_share_var.value]) is data.Share
254             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
255         
256         for n in nodes:
257             yield n.stop()
258         
259         del nodes, n
260         import gc
261         gc.collect()
262         gc.collect()
263         gc.collect()
264         
265         yield deferral.sleep(20) # waiting for work_poller to exit
266     test_nodes.timeout = 300