NVC protocol v. 60011 support
[p2pool.git] / p2pool / bitcoin / helper.py
1 import sys
2 import time
3
4 from twisted.internet import defer
5
6 import p2pool
7 from p2pool.bitcoin import data as bitcoin_data
8 from p2pool.util import deferral, jsonrpc
9
10 @deferral.retry('Error while checking Bitcoin connection:', 1)
11 @defer.inlineCallbacks
12 def check(bitcoind, net):
13     if not (yield net.PARENT.RPC_CHECK(bitcoind)):
14         print >>sys.stderr, "    Check failed! Make sure that you're connected to the right bitcoind with --bitcoind-rpc-port!"
15         raise deferral.RetrySilentlyException()
16     if not net.VERSION_CHECK((yield bitcoind.rpc_getinfo())['version']):
17         print >>sys.stderr, '    Bitcoin version too old! Upgrade to 0.6.4 or newer!'
18         raise deferral.RetrySilentlyException()
19
20 @deferral.retry('Error getting work from bitcoind:', 3)
21 @defer.inlineCallbacks
22 def getwork(bitcoind, use_getblocktemplate=False):
23     def go():
24         if use_getblocktemplate:
25             return bitcoind.rpc_getblocktemplate(dict(mode='template'))
26         else:
27             return bitcoind.rpc_getmemorypool()
28     try:
29         start = time.time()
30         work = yield go()
31         end = time.time()
32     except jsonrpc.Error_for_code(-32601): # Method not found
33         use_getblocktemplate = not use_getblocktemplate
34         try:
35             start = time.time()
36             work = yield go()
37             end = time.time()
38         except jsonrpc.Error_for_code(-32601): # Method not found
39             print >>sys.stderr, 'Error: Bitcoin version too old! Upgrade to v0.5 or newer!'
40             raise deferral.RetrySilentlyException()
41     packed_transactions = [(x['data'] if isinstance(x, dict) else x).decode('hex') for x in work['transactions']]
42
43     transactions=map(bitcoin_data.tx_type.unpack, packed_transactions)
44     transaction_hashes=map(bitcoin_data.hash256, packed_transactions)
45     txn_timestamp = 0
46     for tx in transactions:
47         if tx.timestamp > txn_timestamp:
48             txn_timestamp = tx.timestamp
49
50     if 'height' not in work:
51         work['height'] = (yield bitcoind.rpc_getblock(work['previousblockhash']))['height'] + 1
52     elif p2pool.DEBUG:
53         assert work['height'] == (yield bitcoind.rpc_getblock(work['previousblockhash']))['height'] + 1
54     defer.returnValue(dict(
55         version=work['version'],
56         previous_block=int(work['previousblockhash'], 16),
57         transactions=transactions,
58         transaction_hashes=transaction_hashes,
59         transaction_fees=[x.get('fee', None) if isinstance(x, dict) else None for x in work['transactions']],
60         subsidy=work['coinbasevalue'],
61         time=work['time'] if 'time' in work else work['curtime'],
62         txn_timestamp=txn_timestamp,
63         bits=bitcoin_data.FloatingIntegerType().unpack(work['bits'].decode('hex')[::-1]) if isinstance(work['bits'], (str, unicode)) else bitcoin_data.FloatingInteger(work['bits']),
64         coinbaseflags=work['coinbaseflags'].decode('hex') if 'coinbaseflags' in work else ''.join(x.decode('hex') for x in work['coinbaseaux'].itervalues()) if 'coinbaseaux' in work else '',
65         height=work['height'],
66         last_update=time.time(),
67         use_getblocktemplate=use_getblocktemplate,
68         latency=end - start,
69     ))
70
71 @deferral.retry('Error submitting primary block: (will retry)', 10, 10)
72 def submit_block_p2p(block, factory, net):
73     if factory.conn.value is None:
74         print >>sys.stderr, 'No bitcoind connection when block submittal attempted! %s%064x' % (net.PARENT.BLOCK_EXPLORER_URL_PREFIX, bitcoin_data.scrypt(bitcoin_data.block_header_type.pack(block['header'])))
75         raise deferral.RetrySilentlyException()
76     factory.conn.value.send_block(block=block)
77
78 @deferral.retry('Error submitting block: (will retry)', 10, 10)
79 @defer.inlineCallbacks
80 def submit_block_rpc(block, ignore_failure, bitcoind, bitcoind_work, net):
81     if bitcoind_work.value['use_getblocktemplate']:
82         result = yield bitcoind.rpc_submitblock(bitcoin_data.block_type.pack(block).encode('hex'))
83         success = result is None
84     else:
85         result = yield bitcoind.rpc_getmemorypool(bitcoin_data.block_type.pack(block).encode('hex'))
86         success = result
87     success_expected = bitcoin_data.scrypt(bitcoin_data.block_header_type.pack(block['header'])) <= block['header']['bits'].target
88     if (not success and success_expected and not ignore_failure) or (success and not success_expected):
89         print >>sys.stderr, 'Block submittal result: %s (%r) Expected: %s' % (success, result, success_expected)
90
91 def submit_block(block, ignore_failure, factory, bitcoind, bitcoind_work, net):
92     submit_block_p2p(block, factory, net)
93     submit_block_rpc(block, ignore_failure, bitcoind, bitcoind_work, net)