setup.py
[p2pool.git] / p2pool.py
1 from __future__ import division
2
3 import bitcoin_p2p
4 import conv
5
6 chain_id_type = bitcoin_p2p.ComposedType([
7     ('last_p2pool_block_hash', bitcoin_p2p.HashType()),
8     ('bits', bitcoin_p2p.StructType('<I')),
9 ])
10
11 coinbase_type = bitcoin_p2p.ComposedType([
12     ('identifier', bitcoin_p2p.StructType('<Q')),
13     ('last_p2pool_block_hash', bitcoin_p2p.HashType()),
14     ('previous_p2pool_share_hash', bitcoin_p2p.HashType()),
15     ('subsidy', bitcoin_p2p.StructType('<Q')),
16     ('last_share_index', bitcoin_p2p.StructType('<I')),
17     ('nonce', bitcoin_p2p.StructType('<Q')),
18 ])
19
20 merkle_branch = bitcoin_p2p.ListType(bitcoin_p2p.ComposedType([
21     ('side', bitcoin_p2p.StructType('<B')),
22     ('hash', bitcoin_p2p.HashType()),
23 ]))
24
25 share1 = bitcoin_p2p.ComposedType([
26     ('header', bitcoin_p2p.block_header),
27     ('gentx', bitcoin_p2p.ComposedType([
28         ('tx', bitcoin_p2p.tx),
29         ('merkle_branch', merkle_branch),
30     ])),
31 ])
32
33 def calculate_merkle_branch(txn_list, index):
34     hash_list = [(bitcoin_p2p.doublesha(bitcoin_p2p.tx.pack(data)), i == index, []) for i, data in enumerate(txn_list)]
35     
36     while len(hash_list) > 1:
37         hash_list = [
38             (
39                 bitcoin_p2p.doublesha(bitcoin_p2p.merkle_record.pack(dict(left=left, right=right))),
40                 left_f or right_f,
41                 (left_l if left_f else right_l) + [dict(side=1, hash=right) if left_f else dict(side=0, hash=left)],
42             )
43             for (left, left_f, left_l), (right, right_f, right_l) in
44                 zip(hash_list[::2], hash_list[1::2] + [hash_list[::2][-1]])
45         ]
46     
47     assert check_merkle_branch(txn_list[index], hash_list[0][2]) == hash_list[0][0]
48     
49     return hash_list[0][2]
50
51 def check_merkle_branch(txn, branch):
52     hash_ = bitcoin_p2p.doublesha(bitcoin_p2p.tx.pack(txn))
53     for step in branch:
54         if not step['side']:
55             hash_ = bitcoin_p2p.doublesha(bitcoin_p2p.merkle_record.pack(dict(left=step['hash'], right=hash_)))
56         else:
57             hash_ = bitcoin_p2p.doublesha(bitcoin_p2p.merkle_record.pack(dict(left=hash_, right=step['hash'])))
58     return hash_
59
60 def txns_to_gentx(txns):
61     return dict(
62         tx=txns[0],
63         merkle_branch=calculate_merkle_branch(txns, 0),
64     )
65
66 class Share(object):
67     def __init__(self, header, txns=None, gentx=None):
68         self.header = header
69         self.hash = bitcoin_p2p.block_hash(header)
70         
71         self.txns = txns
72         if txns is None:
73             if gentx is not None:
74                 self.gentx = gentx
75                 if check_merkle_branch(gentx['tx'], gentx['merkle_branch']) != header['merkle_root']:
76                     #print '%x' % check_merkle_branch(gentx['tx'], gentx['merkle_branch'])
77                     #print '%x' % header['merkle_root']
78                     raise ValueError("gentx doesn't match header")
79             else:
80                 raise ValueError('need either txns or gentx')
81         else:
82             self.gentx = txns_to_gentx(txns)
83             if gentx is not None:
84                 if gentx != self.gentx:
85                     raise ValueError('invalid gentx')
86         
87         self.coinbase = coinbase_type.unpack(self.gentx['tx']['tx_ins'][0]['script'], ignore_extra=True)
88         self.previous_share_hash = self.coinbase['previous_p2pool_share_hash'] if self.coinbase['previous_p2pool_share_hash'] != 2**256 - 1 else None
89         self.chain_id_data = chain_id_type.pack(dict(last_p2pool_block_hash=self.coinbase['last_p2pool_block_hash'], bits=self.header['bits']))
90     
91     def as_block(self):
92         if self.txns is None:
93             raise ValueError('share does not contain all txns')
94         return dict(header=self.header, txns=self.txns)
95     
96     def as_share1(self):
97         return dict(header=self.header, gentx=self.gentx)
98     
99     def check(self, chain, height, previous_share2, net):
100         if self.chain_id_data != chain.chain_id_data:
101             raise ValueError('wrong chain')
102         if self.hash > net.TARGET_MULTIPLIER*conv.bits_to_target(self.header['bits']):
103             raise ValueError('not enough work!')
104         
105         t = self.gentx['tx']
106         t2, shares = generate_transaction(
107             last_p2pool_block_hash=chain.last_p2pool_block_hash,
108             previous_share2=previous_share2,
109             add_script=t['tx_outs'][self.coinbase['last_share_index']]['script'],
110             subsidy=self.coinbase['subsidy'],
111             nonce=self.coinbase['nonce'],
112             net=net,
113         )
114         if t2 != t:
115             raise ValueError('invalid generate txn')
116         
117         return Share2(self, shares, height)
118
119 class Share2(object):
120     '''Share with associated data'''
121     
122     def __init__(self, share, shares, height):
123         self.share = share
124         self.shares = map(intern, shares)
125         self.height = height
126         self.shared = False
127     
128     def flag_shared(self):
129         self.shared = True
130
131 def generate_transaction(last_p2pool_block_hash, previous_share2, add_script, subsidy, nonce, net):
132     shares = (previous_share2.shares if previous_share2 is not None else [net.SCRIPT]*net.SPREAD)[1:-1] + [add_script, add_script]
133     
134     dest_weights = {}
135     for script in shares:
136         dest_weights[script] = dest_weights.get(script, 0) + 1
137     total_weight = sum(dest_weights.itervalues())
138     
139     amounts = dict((script, subsidy*weight*63//(64*total_weight)) for (script, weight) in dest_weights.iteritems())
140     amounts[net.SCRIPT] = amounts.get(net.SCRIPT, 0) + subsidy//64 # prevent fake previous p2pool blocks
141     amounts[net.SCRIPT] = amounts.get(net.SCRIPT, 0) + subsidy - sum(amounts.itervalues()) # collect any extra
142     
143     dests = sorted(amounts.iterkeys())
144     
145     return dict(
146         version=1,
147         tx_ins=[dict(
148             previous_output=dict(index=4294967295, hash=0),
149             sequence=4294967295,
150             script=coinbase_type.pack(dict(
151                 identifier=net.IDENTIFIER,
152                 last_p2pool_block_hash=last_p2pool_block_hash,
153                 previous_p2pool_share_hash=previous_share2.share.hash if previous_share2 is not None else 2**256 - 1,
154                 subsidy=subsidy,
155                 last_share_index=dests.index(add_script),
156                 nonce=nonce,
157             )),
158         )],
159         tx_outs=[dict(value=amounts[script], script=script) for script in dests if amounts[script]],
160         lock_time=0,
161     ), shares
162
163
164 # TARGET_MULTIPLIER needs to be less than the current difficulty to prevent miner clients from missing shares
165
166 class Testnet(object):
167     TARGET_MULTIPLIER = 30
168     SPREAD = 30
169     ROOT_BLOCK = 0x3575d1e7b40fe37ad12d41169a1012d26df5f3c35486e2abfbe9d2c
170     SCRIPT = '410489175c7658845fd7c33d61029ebf4042e8386443ff6e6628fdb5ac938c31072dc61cee691ae1e8355c3a87cb4813cc9bf036fdb09078d35eacf9e9ab52374ebeac'.decode('hex')
171     IDENTIFIER = 0x808330dc87e313b7
172
173 class Main(object):
174     TARGET_MULTIPLIER = SPREAD = 600
175     ROOT_BLOCK = 0xf78e83f63fd0f4e0f584d3bc2c7010f679834cd8886d61876d
176     SCRIPT = '410441ccbae5ca6ecfaa014028b0c49df2cd5588cb6058ac260d650bc13c9ec466f95c7a6d80a3ea7f7b8e2e87e49b96081e9b20415b06433d7a5b6a156b58690d96ac'.decode('hex')
177     IDENTIFIER = 0x49ddc0b4938708ad