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