changing layout, lots of things. currently broken
[p2pool.git] / p2pool / data.py
1 from __future__ import division
2
3 from bitcoin import data as bitcoin_data
4
5 chain_id_type = bitcoin_data.ComposedType([
6     ('last_p2pool_block_hash', bitcoin_data.HashType()),
7     ('bits', bitcoin_data.StructType('<I')),
8 ])
9
10 share_data_type = bitcoin_data.ComposedType([
11     ('last_p2pool_block_hash', bitcoin_data.HashType()),
12     ('previous_p2pool_share_hash', bitcoin_data.HashType()),
13     ('nonce', bitcoin_data.VarStrType()),
14 ])
15
16 coinbase_type = bitcoin_data.ComposedType([
17     ('identifier', bitcoin_data.StructType('<Q')),
18     ('share_data', share_data_type),
19 ])
20
21 merkle_branch_type = bitcoin_data.ListType(bitcoin_data.ComposedType([
22     ('side', bitcoin_data.StructType('<B')),
23     ('hash', bitcoin_data.HashType()),
24 ]))
25
26 gentx_info_type = bitcoin_data.ComposedType([
27     ('share_info', bitcoin_data.ComposedType([
28         ('share_data', share_data_type),
29         ('new_script', bitcoin_data.VarStrType()),
30         ('subsidy', bitcoin_data.StructType('<Q')),
31     ])),
32     ('merkle_branch', merkle_branch_type),
33 ])
34
35 share1_type = bitcoin_data.ComposedType([
36     ('header', bitcoin_data.block_header_type),
37     ('gentx_info', gentx_info_type),
38 ])
39
40 def calculate_merkle_branch(txs, index):
41     hash_list = [(bitcoin_data.tx_hash(tx), i == index, []) for i, tx in enumerate(txs)]
42     
43     while len(hash_list) > 1:
44         hash_list = [
45             (
46                 bitcoin_data.doublesha(bitcoin_data.merkle_record_type.pack(dict(left=left, right=right))),
47                 left_f or right_f,
48                 (left_l if left_f else right_l) + [dict(side=1, hash=right) if left_f else dict(side=0, hash=left)],
49             )
50             for (left, left_f, left_l), (right, right_f, right_l) in
51                 zip(hash_list[::2], hash_list[1::2] + [hash_list[::2][-1]])
52         ]
53     
54     assert hash_list[0][1]
55     assert check_merkle_branch(txs[index], hash_list[0][2]) == hash_list[0][0]
56     
57     return hash_list[0][2]
58
59 def check_merkle_branch(tx, branch):
60     hash_ = bitcoin_data.tx_hash(tx)
61     for step in branch:
62         if not step['side']:
63             hash_ = bitcoin_data.doublesha(bitcoin_data.merkle_record_type.pack(dict(left=step['hash'], right=hash_)))
64         else:
65             hash_ = bitcoin_data.doublesha(bitcoin_data.merkle_record_type.pack(dict(left=hash_, right=step['hash'])))
66     return hash_
67
68 def txs_to_gentx_info(txs):
69     return dict(
70         share_info=dict(
71             share_data=coinbase_type.unpack(txs[0]['tx_ins'][0]['script'])['share_data'],
72             subsidy=sum(tx_out['value'] for tx_out in txs[0]['tx_outs']),
73             new_script=txs[0]['tx_outs'][-1]['script'],
74         ),
75         merkle_branch=calculate_merkle_branch(txs, 0),
76     )
77
78 def share_info_to_gentx_and_shares(share_info, chain, net):
79     return generate_transaction(
80         last_p2pool_block_hash=share_info['share_data']['last_p2pool_block_hash'],
81         previous_share2=chain.share2s[share_info['share_data']['previous_p2pool_share_hash']],
82         nonce=share_info['share_data']['nonce'],
83         new_script=share_info['new_script'],
84         subsidy=share_info['subsidy'],
85         net=net,
86     )
87
88 def gentx_info_to_gentx_shares_and_merkle_root(gentx_info, chain, net):
89     gentx, shares = share_info_to_gentx_and_shares(gentx_info['share_info'], chain, net)
90     return gentx, shares, check_merkle_branch(gentx, gentx_info['merkle_branch'])
91
92 class Share(object):
93     def __init__(self, header, txs=None, gentx_info=None):
94         if txs is not None:
95             if bitcoin_data.merkle_hash(txs) != header['merkle_root']:
96                 raise ValueError("txs don't match header")
97         
98         if gentx_info is None:
99             if txs is None:
100                 raise ValueError('need either txs or gentx_info')
101             
102             gentx_info = txs_to_gentx_info(txs)
103         
104         coinbase = gentx_info['share_info']['coinbase']
105         
106         self.header = header
107         self.txs = txs
108         self.gentx_info = gentx_info
109         self.hash = bitcoin_data.block_hash(header)
110         self.previous_share_hash = coinbase['previous_p2pool_share_hash'] if coinbase['previous_p2pool_share_hash'] != 2**256 - 1 else None
111         self.chain_id_data = chain_id_type.pack(dict(last_p2pool_block_hash=coinbase['last_p2pool_block_hash'], bits=header['bits']))
112     
113     def as_block(self):
114         if self.txs is None:
115             raise ValueError('share does not contain all txs')
116         
117         return dict(header=self.header, txs=self.txs)
118     
119     def as_share1(self):
120         return dict(header=self.header, gentx_info=self.gentx_info)
121     
122     def check(self, chain, height, previous_share2, net):
123         if self.chain_id_data != chain.chain_id_data:
124             raise ValueError('wrong chain')
125         
126         if self.hash > net.TARGET_MULTIPLIER*bitcoin_data.bits_to_target(self.header['bits']):
127             raise ValueError('not enough work!')
128         
129         gentx, shares, merkle_root = gentx_info_to_gentx_shares_and_merkle_root(self.gentx_info, chain, net)
130         
131         if merkle_root != self.header['merkle_root']:
132             raise ValueError("gentx doesn't match header")
133         
134         return Share2(self, shares, height)
135
136 class Share2(object):
137     '''Share with associated data'''
138     
139     def __init__(self, share, shares, height):
140         self.share = share
141         self.shares = shares
142         self.height = height
143         
144         self.shared = False
145     
146     def flag_shared(self):
147         self.shared = True
148
149 def generate_transaction(last_p2pool_block_hash, previous_share2, new_script, subsidy, nonce, net):
150     shares = (previous_share2.shares if previous_share2 is not None else [net.SCRIPT]*net.SPREAD)[1:-1] + [new_script, new_script]
151     
152     dest_weights = {}
153     for script in shares:
154         dest_weights[script] = dest_weights.get(script, 0) + 1
155     total_weight = sum(dest_weights.itervalues())
156     
157     amounts = dict((script, subsidy*weight*63//(64*total_weight)) for (script, weight) in dest_weights.iteritems())
158     amounts[net.SCRIPT] = amounts.get(net.SCRIPT, 0) + subsidy//64 # prevent fake previous p2pool blocks
159     amounts[net.SCRIPT] = amounts.get(net.SCRIPT, 0) + subsidy - sum(amounts.itervalues()) # collect any extra
160     
161     dests = sorted(amounts.iterkeys(), key=lambda script: (script == new_script, script))
162     assert dests[-1] == new_script
163     
164     return dict(
165         version=1,
166         tx_ins=[dict(
167             previous_output=dict(index=4294967295, hash=0),
168             sequence=4294967295,
169             script=coinbase_type.pack(dict(
170                 identifier=net.IDENTIFIER,
171                 share_data=dict(
172                     last_p2pool_block_hash=last_p2pool_block_hash,
173                     previous_p2pool_share_hash=previous_share2.share.hash if previous_share2 is not None else 2**256 - 1,
174                     nonce=nonce,
175                 ),
176             )),
177         )],
178         tx_outs=[dict(value=amounts[script], script=script) for script in dests if amounts[script]],
179         lock_time=0,
180     ), shares
181
182
183 class Tracker(object):
184     def __init__(self):
185         self.shares = {} # hash -> share
186         self.reverse_shares = {} # previous_hash -> share_hash
187         self.heads = {} # hash -> (height, tail hash)
188         self.heads = set()
189     
190     def add_share(self, share):
191         if share.hash in self.shares:
192             return # XXX
193         
194         self.shares[share.hash] = share
195         self.reverse_shares.setdefault(share.previous_hash, set()).add(share.hash)
196         
197         if self.reverse_shares.get(share.hash, set()):
198             pass # not a head
199         else:
200             self.heads.add(share.hash)
201             if share.previous_hash in self.heads:
202                 self.heads.remove(share.previous_hash)
203
204 if __name__ == '__main__':
205     class FakeShare(object):
206         def __init__(self, hash, previous_hash):
207             self.hash = hash
208             self.previous_hash = previous_hash
209     
210     t = Tracker()
211     
212     t.add_share(FakeShare(1, 2))
213     print t.heads
214     t.add_share(FakeShare(4, 0))
215     print t.heads
216     t.add_share(FakeShare(3, 4))
217     print t.heads
218
219 # TARGET_MULTIPLIER needs to be less than the current difficulty to prevent miner clients from missing shares
220
221 class Testnet(object):
222     TARGET_MULTIPLIER = SPREAD = 30
223     ROOT_BLOCK = 0xd5070cd4f2987ad2191af71393731a2b143f094f7b84c9e6aa9a6a
224     SCRIPT = '410403ad3dee8ab3d8a9ce5dd2abfbe7364ccd9413df1d279bf1a207849310465b0956e5904b1155ecd17574778f9949589ebfd4fb33ce837c241474a225cf08d85dac'.decode('hex')
225     IDENTIFIER = 0x1ae3479e4eb6700a
226     PREFIX = 'd19778c812754854'.decode('hex')
227     ADDRS_TABLE = 'addrs_testnet'
228
229 class Main(object):
230     TARGET_MULTIPLIER = SPREAD = 600
231     ROOT_BLOCK = 0x11a22c6e314b1a3f44cbbf50246187a37756ea8af4d41c43a8d6
232     SCRIPT = '4104ffd03de44a6e11b9917f3a29f9443283d9871c9d743ef30d5eddcd37094b64d1b3d8090496b53256786bf5c82932ec23c3b74d9f05a6f95a8b5529352656664bac'.decode('hex')
233     IDENTIFIER = 0x7452839666e1f8f8
234     PREFIX = '2d4224bf18c87b87'.decode('hex')
235     ADDRS_TABLE = 'addrs'