def get_global_stats(): # averaged over last hour if node.tracker.get_height(node.best_share_var.value) < 10: return None lookbehind = min(node.tracker.get_height(node.best_share_var.value), 3600//node.net.SHARE_PERIOD) nonstale_hash_rate = p2pool_data.get_pool_attempts_per_second(node.tracker, node.best_share_var.value, lookbehind) stale_prop = p2pool_data.get_average_stale_prop(node.tracker, node.best_share_var.value, lookbehind) diff = bitcoin_data.target_to_difficulty(wb.current_work.value['bits'].target) return dict( pool_nonstale_hash_rate=nonstale_hash_rate, pool_hash_rate=nonstale_hash_rate/(1 - stale_prop), pool_stale_prop=stale_prop, min_difficulty=bitcoin_data.target_to_difficulty(node.tracker.items[node.best_share_var.value].max_target), network_block_difficulty=diff, network_hashrate=(diff * 2**32 // node.net.PARENT.BLOCK_PERIOD), )
def get_global_stats(): # averaged over last hour if node.tracker.get_height(node.best_share_var.value) < 10: return None lookbehind = min(node.tracker.get_height(node.best_share_var.value), 3600//node.net.SHARE_PERIOD) nonstale_hash_rate = p2pool_data.get_pool_attempts_per_second(node.tracker, node.best_share_var.value, lookbehind) stale_prop = p2pool_data.get_average_stale_prop(node.tracker, node.best_share_var.value, lookbehind) return dict( pool_nonstale_hash_rate=nonstale_hash_rate, pool_hash_rate=nonstale_hash_rate/(1 - stale_prop), pool_stale_prop=stale_prop, min_difficulty=bitcoin_data.target_to_difficulty(node.tracker.items[node.best_share_var.value].max_target), )
def get_global_stats(): # averaged over last hour lookbehind = 3600//net.SHARE_PERIOD if tracker.get_height(current_work.value['best_share_hash']) < lookbehind: return None nonstale_hash_rate = p2pool_data.get_pool_attempts_per_second(tracker, current_work.value['best_share_hash'], lookbehind) stale_prop = p2pool_data.get_average_stale_prop(tracker, current_work.value['best_share_hash'], lookbehind) return dict( pool_nonstale_hash_rate=nonstale_hash_rate, pool_hash_rate=nonstale_hash_rate/(1 - stale_prop), pool_stale_prop=stale_prop, min_difficulty=bitcoin_data.target_to_difficulty(tracker.shares[current_work.value['best_share_hash']].max_target), )
def get_user_details(self, user): desired_pseudoshare_target = None if '+' in user: user, desired_pseudoshare_difficulty_str = user.rsplit('+', 1) try: desired_pseudoshare_target = bitcoin_data.difficulty_to_target(float(desired_pseudoshare_difficulty_str)) except: pass desired_share_target = 2**256 - 1 if '/' in user: user, min_diff_str = user.rsplit('/', 1) try: desired_share_target = bitcoin_data.difficulty_to_target(float(min_diff_str)) except: pass if p2pool.DEBUG: print print "Desired Share Target (WORK)" print bitcoin_data.target_to_difficulty(desired_share_target) print current_txouts = self.node.get_current_txouts() current_pay_out = current_txouts.get(bitcoin_data.pubkey_to_script2(self.my_pubkey), 0)*1e-6 if current_pay_out == 0 or random.uniform(0, 100) < self.worker_fee: script = bitcoin_data.pubkey_to_script2(self.my_pubkey) else: try: script = bitcoin_data.pubkey_hash_to_script2(bitcoin_data.address_to_pubkey_hash(user, self.node.net.PARENT)) except: # XXX blah script = bitcoin_data.pubkey_to_script2(self.my_pubkey) return user, script, desired_share_target, desired_pseudoshare_target
def compute(request): state = current_work.value user = worker_interface.get_username(request) payout_script = get_payout_script_from_username(user) if payout_script is None or random.uniform(0, 100) < args.worker_fee: payout_script = my_script if len(p2p_node.peers) == 0 and net.PERSIST: raise jsonrpc.Error(-12345, u'p2pool is not connected to any peers') if state['best_share_hash'] is None and net.PERSIST: raise jsonrpc.Error(-12345, u'p2pool is downloading shares') if time.time() > current_work2.value['last_update'] + 60: raise jsonrpc.Error(-12345, u'lost contact with bitcoind') previous_share = None if state['best_share_hash'] is None else tracker.shares[state['best_share_hash']] subsidy = current_work2.value['subsidy'] share_info, generate_tx = p2pool_data.generate_transaction( tracker=tracker, share_data=dict( previous_share_hash=state['best_share_hash'], coinbase='' if state['aux_work'] is None else '\xfa\xbemm' + bitcoin_data.HashType().pack(state['aux_work']['hash'])[::-1] + struct.pack('<ii', 1, 0), nonce=run_identifier + struct.pack('<Q', random.randrange(2**64)), new_script=payout_script, subsidy=subsidy, donation=math.perfect_round(65535*args.donation_percentage/100), stale_frac=(lambda shares, stales: 255 if shares == 0 else math.perfect_round(254*stales/shares) )(*get_share_counts()), ), block_target=state['target'], desired_timestamp=int(time.time() - current_work2.value['clock_offset']), net=net, ) print 'New work for worker %s! Difficulty: %.06f Payout if block: %.6f %s Total block value: %.6f %s including %i transactions' % ( user, bitcoin_data.target_to_difficulty(share_info['target']), (sum(t['value'] for t in generate_tx['tx_outs'] if t['script'] == payout_script) - subsidy//200)*1e-8, net.BITCOIN_SYMBOL, subsidy*1e-8, net.BITCOIN_SYMBOL, len(current_work2.value['transactions']), ) transactions = [generate_tx] + list(current_work2.value['transactions']) merkle_root = bitcoin_data.merkle_hash(transactions) merkle_root_to_transactions[merkle_root] = share_info, transactions, time.time() return bitcoin_getwork.BlockAttempt(state['version'], state['previous_block'], merkle_root, current_work2.value['time'], state['target'], share_info['target']), state['best_share_hash']
def get_local_stats(): if node.tracker.get_height(node.best_share_var.value) < 10: return None lookbehind = min(node.tracker.get_height(node.best_share_var.value), 3600//node.net.SHARE_PERIOD) global_stale_prop = p2pool_data.get_average_stale_prop(node.tracker, node.best_share_var.value, lookbehind) my_unstale_count = sum(1 for share in node.tracker.get_chain(node.best_share_var.value, lookbehind) if share.hash in wb.my_share_hashes) my_orphan_count = sum(1 for share in node.tracker.get_chain(node.best_share_var.value, lookbehind) if share.hash in wb.my_share_hashes and share.share_data['stale_info'] == 'orphan') my_doa_count = sum(1 for share in node.tracker.get_chain(node.best_share_var.value, lookbehind) if share.hash in wb.my_share_hashes and share.share_data['stale_info'] == 'doa') my_share_count = my_unstale_count + my_orphan_count + my_doa_count my_stale_count = my_orphan_count + my_doa_count my_stale_prop = my_stale_count/my_share_count if my_share_count != 0 else None my_work = sum(bitcoin_data.target_to_average_attempts(share.target) for share in node.tracker.get_chain(node.best_share_var.value, lookbehind - 1) if share.hash in wb.my_share_hashes) actual_time = (node.tracker.items[node.best_share_var.value].timestamp - node.tracker.items[node.tracker.get_nth_parent_hash(node.best_share_var.value, lookbehind - 1)].timestamp) share_att_s = my_work / actual_time miner_hash_rates, miner_dead_hash_rates = wb.get_local_rates() (stale_orphan_shares, stale_doa_shares), shares, _ = wb.get_stale_counts() miner_last_difficulties = {} for addr in wb.last_work_shares.value: miner_last_difficulties[addr] = bitcoin_data.target_to_difficulty(wb.last_work_shares.value[addr].target) return dict( my_hash_rates_in_last_hour=dict( note="DEPRECATED", nonstale=share_att_s, rewarded=share_att_s/(1 - global_stale_prop), actual=share_att_s/(1 - my_stale_prop) if my_stale_prop is not None else 0, # 0 because we don't have any shares anyway ), my_share_counts_in_last_hour=dict( shares=my_share_count, unstale_shares=my_unstale_count, stale_shares=my_stale_count, orphan_stale_shares=my_orphan_count, doa_stale_shares=my_doa_count, ), my_stale_proportions_in_last_hour=dict( stale=my_stale_prop, orphan_stale=my_orphan_count/my_share_count if my_share_count != 0 else None, dead_stale=my_doa_count/my_share_count if my_share_count != 0 else None, ), miner_hash_rates=miner_hash_rates, miner_dead_hash_rates=miner_dead_hash_rates, miner_last_difficulties=miner_last_difficulties, efficiency_if_miner_perfect=(1 - stale_orphan_shares/shares)/(1 - global_stale_prop) if shares else None, # ignores dead shares because those are miner's fault and indicated by pseudoshare rejection efficiency=(1 - (stale_orphan_shares+stale_doa_shares)/shares)/(1 - global_stale_prop) if shares else None, peers=dict( incoming=sum(1 for peer in node.p2p_node.peers.itervalues() if peer.incoming), outgoing=sum(1 for peer in node.p2p_node.peers.itervalues() if not peer.incoming), ), shares=dict( total=shares, orphan=stale_orphan_shares, dead=stale_doa_shares, ), uptime=time.time() - start_time, attempts_to_share=bitcoin_data.target_to_average_attempts(node.tracker.items[node.best_share_var.value].max_target), attempts_to_block=bitcoin_data.target_to_average_attempts(node.bitcoind_work.value['bits'].target), block_value=node.bitcoind_work.value['subsidy']*1e-8, warnings=p2pool_data.get_warnings(node.tracker, node.best_share_var.value, node.net, bitcoind_getinfo_var.value, node.bitcoind_work.value), donation_proportion=wb.donation_percentage/100, version=p2pool.__version__, protocol_version=p2p.Protocol.VERSION, fee=wb.worker_fee, )
def get_work(self, pubkey, desired_share_target, desired_pseudoshare_target): if (self.node.p2p_node is None or len(self.node.p2p_node.peers) == 0) and self.node.net.PERSIST: raise jsonrpc.Error_for_code(-12345)(u'p2pool is not connected to any peers') if self.node.best_share_var.value is None and self.node.net.PERSIST: raise jsonrpc.Error_for_code(-12345)(u'p2pool is downloading shares') if time.time() > self.current_work.value['last_update'] + 60: raise jsonrpc.Error_for_code(-12345)(u'lost contact with bitcoind') if self.merged_work.value: tree, size = bitcoin_data.make_auxpow_tree(self.merged_work.value) mm_hashes = [self.merged_work.value.get(tree.get(i), dict(hash=0))['hash'] for i in xrange(size)] mm_data = '\xfa\xbemm' + bitcoin_data.aux_pow_coinbase_type.pack(dict( merkle_root=bitcoin_data.merkle_hash(mm_hashes), size=size, nonce=0, )) mm_later = [(aux_work, mm_hashes.index(aux_work['hash']), mm_hashes) for chain_id, aux_work in self.merged_work.value.iteritems()] else: mm_data = '' mm_later = [] tx_hashes = [bitcoin_data.hash256(bitcoin_data.tx_type.pack(tx)) for tx in self.current_work.value['transactions']] tx_map = dict(zip(tx_hashes, self.current_work.value['transactions'])) txn_timestamp = self.current_work.value['txn_timestamp'] #print #print txn_timestamp #print if self.node.best_share_var.value is None: share_type = p2pool_data.Share else: previous_share = self.node.tracker.items[self.node.best_share_var.value] previous_share_type = type(previous_share) if previous_share_type.SUCCESSOR is None or self.node.tracker.get_height(previous_share.hash) < self.node.net.CHAIN_LENGTH: share_type = previous_share_type else: successor_type = previous_share_type.SUCCESSOR counts = p2pool_data.get_desired_version_counts(self.node.tracker, self.node.tracker.get_nth_parent_hash(previous_share.hash, self.node.net.CHAIN_LENGTH*9//10), self.node.net.CHAIN_LENGTH//10) # Share -> NewShare only valid if 85% of hashes in [net.CHAIN_LENGTH*9//10, net.CHAIN_LENGTH] for new version if counts.get(successor_type.VERSION, 0) > sum(counts.itervalues())*95//100: share_type = successor_type else: share_type = previous_share_type if True: subsidy = self.node.net.PARENT.SUBSIDY_FUNC(self.current_work.value['bits'].target) desired_timestamp = int(time.time() + 0.5) share_info, gentx, other_transaction_hashes, get_share = share_type.generate_transaction( tracker=self.node.tracker, share_data=dict( previous_share_hash=self.node.best_share_var.value, coinbase=(script.create_push_script([ self.current_work.value['height'], ] + ([mm_data] if mm_data else []) + [ ]) + self.current_work.value['coinbaseflags'])[:100], nonce=random.randrange(2**32), pubkey=pubkey, subsidy=self.current_work.value['subsidy'], donation=math.perfect_round(65535*self.donation_percentage/100), stale_info=(lambda (orphans, doas), total, (orphans_recorded_in_chain, doas_recorded_in_chain): 'orphan' if orphans > orphans_recorded_in_chain else 'doa' if doas > doas_recorded_in_chain else None )(*self.get_stale_counts()), desired_version=14, ), block_target=self.current_work.value['bits'].target, desired_timestamp=desired_timestamp if txn_timestamp < desired_timestamp else txn_timestamp + 1, desired_target=desired_share_target, ref_merkle_link=dict(branch=[], index=0), desired_other_transaction_hashes_and_fees=zip(tx_hashes, self.current_work.value['transaction_fees']), net=self.node.net, known_txs=tx_map, base_subsidy=subsidy ) packed_gentx = bitcoin_data.tx_type.pack(gentx) other_transactions = [tx_map[tx_hash] for tx_hash in other_transaction_hashes] mm_later = [(dict(aux_work, target=aux_work['target'] if aux_work['target'] != 'p2pool' else share_info['bits'].target), index, hashes) for aux_work, index, hashes in mm_later] if desired_pseudoshare_target is None: target = 2**256-1 if len(self.recent_shares_ts_work) == 50: hash_rate = sum(work for ts, work in self.recent_shares_ts_work[1:])//(self.recent_shares_ts_work[-1][0] - self.recent_shares_ts_work[0][0]) if hash_rate: target = min(target, int(2**256/hash_rate)) else: target = desired_pseudoshare_target target = max(target, share_info['bits'].target) for aux_work, index, hashes in mm_later: target = max(target, aux_work['target']) target = math.clip(target, self.node.net.PARENT.SANE_TARGET_RANGE) getwork_time = time.time() lp_count = self.new_work_event.times merkle_link = bitcoin_data.calculate_merkle_link([None] + other_transaction_hashes, 0) print 'New work for worker! Difficulty: %.06f Share difficulty: %.06f Total block value: %.6f %s including %i transactions' % ( bitcoin_data.target_to_difficulty(target), bitcoin_data.target_to_difficulty(share_info['bits'].target), self.current_work.value['subsidy']*1e-6, self.node.net.PARENT.SYMBOL, len(self.current_work.value['transactions']), ) ba = dict( version=min(self.current_work.value['version'], 3), previous_block=self.current_work.value['previous_block'], merkle_link=merkle_link, coinb1=packed_gentx[:-4-4], coinb2=packed_gentx[-4:], timestamp=gentx['timestamp'], bits=self.current_work.value['bits'], share_target=target, ) received_header_hashes = set() def got_response(header, user, coinbase_nonce): assert len(coinbase_nonce) == self.COINBASE_NONCE_LENGTH == 4 new_packed_gentx = packed_gentx[:-4-4] + coinbase_nonce + packed_gentx[-4:] if coinbase_nonce != '\0'*self.COINBASE_NONCE_LENGTH else packed_gentx new_gentx = bitcoin_data.tx_type.unpack(new_packed_gentx) if coinbase_nonce != '\0'*self.COINBASE_NONCE_LENGTH else gentx header_hash = self.node.net.PARENT.BLOCKHASH_FUNC(bitcoin_data.block_header_type.pack(header)) pow_hash = self.node.net.PARENT.POW_FUNC(bitcoin_data.block_header_type.pack(header)) try: if header['timestamp'] > new_gentx['timestamp'] + 3600: print print header['timestamp'], '>', new_gentx['timestamp'] + 3600 print 'Coinbase timestamp is too early!' print return if header['timestamp'] < new_gentx['timestamp']: print print header['timestamp'], '<', new_gentx['timestamp'] print 'Block header timestamp is before coinbase timestamp!' print return if pow_hash <= header['bits'].target or p2pool.DEBUG: helper.submit_block(dict(header=header, txs=[new_gentx] + other_transactions, signature=''), False, self.node.factory, self.node.bitcoind, self.node.bitcoind_work, self.node.net) if pow_hash <= header['bits'].target: print print 'GOT BLOCK FROM MINER! Passing to bitcoind! %s%064x' % (self.node.net.PARENT.BLOCK_EXPLORER_URL_PREFIX, header_hash) print except: log.err(None, 'Error while processing potential block:') user, _, _, _ = self.get_user_details(user) assert header['previous_block'] == ba['previous_block'] assert header['merkle_root'] == bitcoin_data.check_merkle_link(bitcoin_data.hash256(new_packed_gentx), merkle_link) assert header['bits'] == ba['bits'] on_time = self.new_work_event.times == lp_count for aux_work, index, hashes in mm_later: try: if pow_hash <= aux_work['target'] or p2pool.DEBUG: df = deferral.retry('Error submitting merged block: (will retry)', 10, 10)(aux_work['merged_proxy'].rpc_getauxblock)( pack.IntType(256, 'big').pack(aux_work['hash']).encode('hex'), bitcoin_data.aux_pow_type.pack(dict( merkle_tx=dict( tx=new_gentx, block_hash=header_hash, merkle_link=merkle_link, ), merkle_link=bitcoin_data.calculate_merkle_link(hashes, index), parent_block_header=header, )).encode('hex'), ) @df.addCallback def _(result, aux_work=aux_work): if result != (pow_hash <= aux_work['target']): print >>sys.stderr, 'Merged block submittal result: %s Expected: %s' % (result, pow_hash <= aux_work['target']) else: print 'Merged block submittal result: %s' % (result,) @df.addErrback def _(err): log.err(err, 'Error submitting merged block:') except: log.err(None, 'Error while processing merged mining POW:') if pow_hash <= share_info['bits'].target and header_hash not in received_header_hashes: share = get_share(header, pack.IntType(32).unpack(coinbase_nonce)) print 'GOT SHARE! %s %s prev %s age %.2fs%s' % ( user, p2pool_data.format_hash(share.hash), p2pool_data.format_hash(share.previous_hash), time.time() - getwork_time, ' DEAD ON ARRIVAL' if not on_time else '', ) self.my_share_hashes.add(share.hash) if not on_time: self.my_doa_share_hashes.add(share.hash) self.node.tracker.add(share) self.node.set_best_share() try: if (pow_hash <= header['bits'].target or p2pool.DEBUG) and self.node.p2p_node is not None: self.node.p2p_node.broadcast_share(share.hash) except: log.err(None, 'Error forwarding block solution:') self.share_received.happened(bitcoin_data.target_to_average_attempts(share.target), not on_time) if pow_hash > target: print 'Worker %s submitted share with hash > target:' % (user,) print ' Hash: %56x' % (pow_hash,) print ' Target: %56x' % (target,) elif header_hash in received_header_hashes: print >>sys.stderr, 'Worker %s submitted share more than once!' % (user,) else: received_header_hashes.add(header_hash) self.pseudoshare_received.happened(bitcoin_data.target_to_average_attempts(target), not on_time, user) self.recent_shares_ts_work.append((time.time(), bitcoin_data.target_to_average_attempts(target))) while len(self.recent_shares_ts_work) > 50: self.recent_shares_ts_work.pop(0) self.local_rate_monitor.add_datum(dict(work=bitcoin_data.target_to_average_attempts(target), dead=not on_time, user=user)) return on_time return ba, got_response
def got_response(header, user, coinbase_nonce): assert len(coinbase_nonce) == self.COINBASE_NONCE_LENGTH new_packed_gentx = packed_gentx[:-self.COINBASE_NONCE_LENGTH-4] + coinbase_nonce + packed_gentx[-4:] if coinbase_nonce != '\0'*self.COINBASE_NONCE_LENGTH else packed_gentx new_gentx = bitcoin_data.tx_type.unpack(new_packed_gentx) if coinbase_nonce != '\0'*self.COINBASE_NONCE_LENGTH else gentx header_hash = bitcoin_data.hash256(bitcoin_data.block_header_type.pack(header)) pow_hash = self.node.net.PARENT.POW_FUNC(bitcoin_data.block_header_type.pack(header)) try: if pow_hash <= header['bits'].target or p2pool.DEBUG: helper.submit_block(dict(header=header, txs=[new_gentx] + other_transactions), False, self.node.factory, self.node.bitcoind, self.node.bitcoind_work, self.node.net) if pow_hash <= header['bits'].target: print print 'GOT BLOCK FROM MINER! Passing to bitcoind! %s%064x' % (self.node.net.PARENT.BLOCK_EXPLORER_URL_PREFIX, header_hash) print except: log.err(None, 'Error while processing potential block:') user, _, _, _ = self.get_user_details(user) assert header['previous_block'] == ba['previous_block'] assert header['merkle_root'] == bitcoin_data.check_merkle_link(bitcoin_data.hash256(new_packed_gentx), merkle_link) assert header['bits'] == ba['bits'] on_time = self.new_work_event.times == lp_count for aux_work, index, hashes in mm_later: try: if pow_hash <= aux_work['target'] or p2pool.DEBUG: df = deferral.retry('Error submitting merged block: (will retry)', 10, 10)(aux_work['merged_proxy'].rpc_getauxblock)( pack.IntType(256, 'big').pack(aux_work['hash']).encode('hex'), bitcoin_data.aux_pow_type.pack(dict( merkle_tx=dict( tx=new_gentx, block_hash=header_hash, merkle_link=merkle_link, ), merkle_link=bitcoin_data.calculate_merkle_link(hashes, index), parent_block_header=header, )).encode('hex'), ) @df.addCallback def _(result, aux_work=aux_work): if result != (pow_hash <= aux_work['target']): print >>sys.stderr, 'Merged block submittal result: %s Expected: %s' % (result, pow_hash <= aux_work['target']) else: print 'Merged block submittal result: %s' % (result,) @df.addErrback def _(err): log.err(err, 'Error submitting merged block:') except: log.err(None, 'Error while processing merged mining POW:') if pow_hash <= share_info['bits'].target and header_hash not in received_header_hashes: last_txout_nonce = pack.IntType(8*self.COINBASE_NONCE_LENGTH).unpack(coinbase_nonce) share = get_share(header, last_txout_nonce) print 'GOT SHARE! %s %s prev %s age %.2fs%s' % ( user, p2pool_data.format_hash(share.hash), p2pool_data.format_hash(share.previous_hash), time.time() - getwork_time, ' DEAD ON ARRIVAL' if not on_time else '', ) self.my_share_hashes.add(share.hash) if not on_time: self.my_doa_share_hashes.add(share.hash) self.node.tracker.add(share) self.node.set_best_share() try: if (pow_hash <= header['bits'].target or p2pool.DEBUG) and self.node.p2p_node is not None: self.node.p2p_node.broadcast_share(share.hash) except: log.err(None, 'Error forwarding block solution:') self.share_received.happened(bitcoin_data.target_to_average_attempts(share.target), not on_time, share.hash) network_diff = bitcoin_data.target_to_difficulty(header['bits'].target) target_diff = bitcoin_data.target_to_difficulty(target) share_diff = bitcoin_data.target_to_difficulty(pow_hash) share = { 'username': user, 'remoteHost': '127.0.0.1', 'blockHeight': block_height, 'networkDiff': network_diff, 'targetDiff': target_diff, 'shareDiff': share_diff, 'ok': True, } if pow_hash > target: print 'Worker %s submitted share with hash > target:' % (user,) print ' Hash: %56x' % (pow_hash,) print ' Target: %56x' % (target,) share['ok'] = False share['rejectReason'] = 'high-hash' elif header_hash in received_header_hashes: print >>sys.stderr, 'Worker %s submitted share more than once!' % (user,) share['ok'] = False share['rejectReason'] = 'duplicate' else: received_header_hashes.add(header_hash) self.pseudoshare_received.happened(bitcoin_data.target_to_average_attempts(target), not on_time, user) self.recent_shares_ts_work.append((time.time(), bitcoin_data.target_to_average_attempts(target))) while len(self.recent_shares_ts_work) > 50: self.recent_shares_ts_work.pop(0) self.local_rate_monitor.add_datum(dict(work=bitcoin_data.target_to_average_attempts(target), dead=not on_time, user=user, share_target=share_info['bits'].target)) self.local_addr_rate_monitor.add_datum(dict(work=bitcoin_data.target_to_average_attempts(target), pubkey_hash=pubkey_hash)) self.logShare(share) return on_time
def get_web_root(wb, datadir_path, bitcoind_getinfo_var, stop_event=variable.Event(), static_dir=None): node = wb.node start_time = time.time() web_root = resource.Resource() def get_users(): height, last = node.tracker.get_height_and_last(node.best_share_var.value) weights, total_weight, donation_weight = node.tracker.get_cumulative_weights(node.best_share_var.value, min(height, 720), 65535*2**256) res = {} for script in sorted(weights, key=lambda s: weights[s]): res[bitcoin_data.script2_to_address(script, node.net.PARENT)] = weights[script]/total_weight return res def get_current_scaled_txouts(scale, trunc=0): txouts = node.get_current_txouts() total = sum(txouts.itervalues()) results = dict((script, value*scale//total) for script, value in txouts.iteritems()) if trunc > 0: total_random = 0 random_set = set() for s in sorted(results, key=results.__getitem__): if results[s] >= trunc: break total_random += results[s] random_set.add(s) if total_random: winner = math.weighted_choice((script, results[script]) for script in random_set) for script in random_set: del results[script] results[winner] = total_random if sum(results.itervalues()) < int(scale): results[math.weighted_choice(results.iteritems())] += int(scale) - sum(results.itervalues()) return results def get_patron_sendmany(total=None, trunc='0.01'): if total is None: return 'need total argument. go to patron_sendmany/<TOTAL>' total = int(float(total)*1e5) trunc = int(float(trunc)*1e5) return json.dumps(dict( (bitcoin_data.script2_to_address(script, node.net.PARENT), value/1e5) for script, value in get_current_scaled_txouts(total, trunc).iteritems() if bitcoin_data.script2_to_address(script, node.net.PARENT) is not None )) def get_global_stats(): # averaged over last hour if node.tracker.get_height(node.best_share_var.value) < 10: return None lookbehind = min(node.tracker.get_height(node.best_share_var.value), 3600//node.net.SHARE_PERIOD) nonstale_hash_rate = p2pool_data.get_pool_attempts_per_second(node.tracker, node.best_share_var.value, lookbehind) stale_prop = p2pool_data.get_average_stale_prop(node.tracker, node.best_share_var.value, lookbehind) diff = bitcoin_data.target_to_difficulty(wb.current_work.value['bits'].target) return dict( pool_nonstale_hash_rate=nonstale_hash_rate, pool_hash_rate=nonstale_hash_rate/(1 - stale_prop), pool_stale_prop=stale_prop, min_difficulty=bitcoin_data.target_to_difficulty(node.tracker.items[node.best_share_var.value].max_target), network_block_difficulty=diff, network_hashrate=(diff * 2**32 // node.net.PARENT.BLOCK_PERIOD), ) def get_local_stats(): if node.tracker.get_height(node.best_share_var.value) < 10: return None lookbehind = min(node.tracker.get_height(node.best_share_var.value), 3600//node.net.SHARE_PERIOD) global_stale_prop = p2pool_data.get_average_stale_prop(node.tracker, node.best_share_var.value, lookbehind) my_unstale_count = sum(1 for share in node.tracker.get_chain(node.best_share_var.value, lookbehind) if share.hash in wb.my_share_hashes) my_orphan_count = sum(1 for share in node.tracker.get_chain(node.best_share_var.value, lookbehind) if share.hash in wb.my_share_hashes and share.share_data['stale_info'] == 'orphan') my_doa_count = sum(1 for share in node.tracker.get_chain(node.best_share_var.value, lookbehind) if share.hash in wb.my_share_hashes and share.share_data['stale_info'] == 'doa') my_share_count = my_unstale_count + my_orphan_count + my_doa_count my_stale_count = my_orphan_count + my_doa_count my_stale_prop = my_stale_count/my_share_count if my_share_count != 0 else None my_work = sum(bitcoin_data.target_to_average_attempts(share.target) for share in node.tracker.get_chain(node.best_share_var.value, lookbehind - 1) if share.hash in wb.my_share_hashes) actual_time = (node.tracker.items[node.best_share_var.value].timestamp - node.tracker.items[node.tracker.get_nth_parent_hash(node.best_share_var.value, lookbehind - 1)].timestamp) share_att_s = my_work / actual_time miner_hash_rates, miner_dead_hash_rates = wb.get_local_rates() (stale_orphan_shares, stale_doa_shares), shares, _ = wb.get_stale_counts() miner_last_difficulties = {} for addr in wb.last_work_shares.value: miner_last_difficulties[addr] = bitcoin_data.target_to_difficulty(wb.last_work_shares.value[addr].target) return dict( my_hash_rates_in_last_hour=dict( note="DEPRECATED", nonstale=share_att_s, rewarded=share_att_s/(1 - global_stale_prop), actual=share_att_s/(1 - my_stale_prop) if my_stale_prop is not None else 0, # 0 because we don't have any shares anyway ), my_share_counts_in_last_hour=dict( shares=my_share_count, unstale_shares=my_unstale_count, stale_shares=my_stale_count, orphan_stale_shares=my_orphan_count, doa_stale_shares=my_doa_count, ), my_stale_proportions_in_last_hour=dict( stale=my_stale_prop, orphan_stale=my_orphan_count/my_share_count if my_share_count != 0 else None, dead_stale=my_doa_count/my_share_count if my_share_count != 0 else None, ), miner_hash_rates=miner_hash_rates, miner_dead_hash_rates=miner_dead_hash_rates, miner_last_difficulties=miner_last_difficulties, efficiency_if_miner_perfect=(1 - stale_orphan_shares/shares)/(1 - global_stale_prop) if shares else None, # ignores dead shares because those are miner's fault and indicated by pseudoshare rejection efficiency=(1 - (stale_orphan_shares+stale_doa_shares)/shares)/(1 - global_stale_prop) if shares else None, peers=dict( incoming=sum(1 for peer in node.p2p_node.peers.itervalues() if peer.incoming), outgoing=sum(1 for peer in node.p2p_node.peers.itervalues() if not peer.incoming), ), shares=dict( total=shares, orphan=stale_orphan_shares, dead=stale_doa_shares, ), uptime=time.time() - start_time, attempts_to_share=bitcoin_data.target_to_average_attempts(node.tracker.items[node.best_share_var.value].max_target), attempts_to_block=bitcoin_data.target_to_average_attempts(node.bitcoind_work.value['bits'].target), block_value=node.bitcoind_work.value['subsidy']*1e-5, warnings=p2pool_data.get_warnings(node.tracker, node.best_share_var.value, node.net, bitcoind_getinfo_var.value, node.bitcoind_work.value), donation_proportion=wb.donation_percentage/100, version=p2pool.__version__, protocol_version=p2p.Protocol.VERSION, fee=wb.worker_fee, ) class WebInterface(deferred_resource.DeferredResource): def __init__(self, func, mime_type='application/json', args=()): deferred_resource.DeferredResource.__init__(self) self.func, self.mime_type, self.args = func, mime_type, args def getChild(self, child, request): return WebInterface(self.func, self.mime_type, self.args + (child,)) @defer.inlineCallbacks def render_GET(self, request): request.setHeader('Content-Type', self.mime_type) request.setHeader('Access-Control-Allow-Origin', '*') res = yield self.func(*self.args) defer.returnValue(json.dumps(res) if self.mime_type == 'application/json' else res) def decent_height(): return min(node.tracker.get_height(node.best_share_var.value), 720) web_root.putChild('rate', WebInterface(lambda: p2pool_data.get_pool_attempts_per_second(node.tracker, node.best_share_var.value, decent_height())/(1-p2pool_data.get_average_stale_prop(node.tracker, node.best_share_var.value, decent_height())))) web_root.putChild('difficulty', WebInterface(lambda: bitcoin_data.target_to_difficulty(node.tracker.items[node.best_share_var.value].max_target))) web_root.putChild('users', WebInterface(get_users)) web_root.putChild('user_stales', WebInterface(lambda: dict((bitcoin_data.pubkey_hash_to_address(ph, node.net.PARENT), prop) for ph, prop in p2pool_data.get_user_stale_props(node.tracker, node.best_share_var.value, node.tracker.get_height(node.best_share_var.value)).iteritems()))) web_root.putChild('fee', WebInterface(lambda: wb.worker_fee)) web_root.putChild('current_payouts', WebInterface(lambda: dict((bitcoin_data.script2_to_address(script, node.net.PARENT), value/1e8) for script, value in node.get_current_txouts().iteritems()))) web_root.putChild('patron_sendmany', WebInterface(get_patron_sendmany, 'text/plain')) web_root.putChild('global_stats', WebInterface(get_global_stats)) web_root.putChild('local_stats', WebInterface(get_local_stats)) web_root.putChild('peer_addresses', WebInterface(lambda: ' '.join('%s%s' % (peer.transport.getPeer().host, ':'+str(peer.transport.getPeer().port) if peer.transport.getPeer().port != node.net.P2P_PORT else '') for peer in node.p2p_node.peers.itervalues()))) web_root.putChild('peer_txpool_sizes', WebInterface(lambda: dict(('%s:%i' % (peer.transport.getPeer().host, peer.transport.getPeer().port), peer.remembered_txs_size) for peer in node.p2p_node.peers.itervalues()))) web_root.putChild('pings', WebInterface(defer.inlineCallbacks(lambda: defer.returnValue( dict([(a, (yield b)) for a, b in [( '%s:%i' % (peer.transport.getPeer().host, peer.transport.getPeer().port), defer.inlineCallbacks(lambda peer=peer: defer.returnValue( min([(yield peer.do_ping().addCallback(lambda x: x/0.001).addErrback(lambda fail: None)) for i in xrange(3)]) ))() ) for peer in list(node.p2p_node.peers.itervalues())] ]) )))) web_root.putChild('peer_versions', WebInterface(lambda: dict(('%s:%i' % peer.addr, peer.other_sub_version) for peer in node.p2p_node.peers.itervalues()))) web_root.putChild('payout_addr', WebInterface(lambda: bitcoin_data.pubkey_hash_to_address(wb.my_pubkey_hash, node.net.PARENT))) web_root.putChild('payout_addrs', WebInterface(lambda: list(('%s' % bitcoin_data.pubkey_hash_to_address(add, node.net.PARENT)) for add in wb.pubkeys.keys))) web_root.putChild('recent_blocks', WebInterface(lambda: [dict( ts=s.timestamp, hash='%064x' % s.header_hash, number=p2pool_data.parse_bip0034(s.share_data['coinbase'])[0], share='%064x' % s.hash, ) for s in node.tracker.get_chain(node.best_share_var.value, min(node.tracker.get_height(node.best_share_var.value), 24*60*60//node.net.SHARE_PERIOD)) if s.pow_hash <= s.header['bits'].target])) web_root.putChild('uptime', WebInterface(lambda: time.time() - start_time)) web_root.putChild('stale_rates', WebInterface(lambda: p2pool_data.get_stale_counts(node.tracker, node.best_share_var.value, decent_height(), rates=True))) new_root = resource.Resource() web_root.putChild('web', new_root) stat_log = [] if os.path.exists(os.path.join(datadir_path, 'stats')): try: with open(os.path.join(datadir_path, 'stats'), 'rb') as f: stat_log = json.loads(f.read()) except: log.err(None, 'Error loading stats:') def update_stat_log(): while stat_log and stat_log[0]['time'] < time.time() - 24*60*60: stat_log.pop(0) lookbehind = 3600//node.net.SHARE_PERIOD if node.tracker.get_height(node.best_share_var.value) < lookbehind: return None global_stale_prop = p2pool_data.get_average_stale_prop(node.tracker, node.best_share_var.value, lookbehind) (stale_orphan_shares, stale_doa_shares), shares, _ = wb.get_stale_counts() miner_hash_rates, miner_dead_hash_rates = wb.get_local_rates() my_current_payout=0.0 for add in wb.pubkeys.keys: my_current_payout+=node.get_current_txouts().get(bitcoin_data.pubkey_hash_to_script2(add), 0)*1e-5 stat_log.append(dict( time=time.time(), pool_hash_rate=p2pool_data.get_pool_attempts_per_second(node.tracker, node.best_share_var.value, lookbehind)/(1-global_stale_prop), pool_stale_prop=global_stale_prop, local_hash_rates=miner_hash_rates, local_dead_hash_rates=miner_dead_hash_rates, shares=shares, stale_shares=stale_orphan_shares + stale_doa_shares, stale_shares_breakdown=dict(orphan=stale_orphan_shares, doa=stale_doa_shares), current_payout=my_current_payout, peers=dict( incoming=sum(1 for peer in node.p2p_node.peers.itervalues() if peer.incoming), outgoing=sum(1 for peer in node.p2p_node.peers.itervalues() if not peer.incoming), ), attempts_to_share=bitcoin_data.target_to_average_attempts(node.tracker.items[node.best_share_var.value].max_target), attempts_to_block=bitcoin_data.target_to_average_attempts(node.bitcoind_work.value['bits'].target), block_value=node.bitcoind_work.value['subsidy']*1e-5, )) with open(os.path.join(datadir_path, 'stats'), 'wb') as f: f.write(json.dumps(stat_log)) x = deferral.RobustLoopingCall(update_stat_log) x.start(5*60) stop_event.watch(x.stop) new_root.putChild('log', WebInterface(lambda: stat_log)) def get_share(share_hash_str): if int(share_hash_str, 16) not in node.tracker.items: return None share = node.tracker.items[int(share_hash_str, 16)] return dict( parent='%064x' % share.previous_hash, far_parent='%064x' % share.share_info['far_share_hash'], children=['%064x' % x for x in sorted(node.tracker.reverse.get(share.hash, set()), key=lambda sh: -len(node.tracker.reverse.get(sh, set())))], # sorted from most children to least children type_name=type(share).__name__, local=dict( verified=share.hash in node.tracker.verified.items, time_first_seen=start_time if share.time_seen == 0 else share.time_seen, peer_first_received_from=share.peer_addr, ), share_data=dict( timestamp=share.timestamp, target=share.target, max_target=share.max_target, payout_address=bitcoin_data.script2_to_address(share.new_script, node.net.PARENT), donation=share.share_data['donation']/65535, stale_info=share.share_data['stale_info'], nonce=share.share_data['nonce'], desired_version=share.share_data['desired_version'], absheight=share.absheight, abswork=share.abswork, ), block=dict( hash='%064x' % share.header_hash, header=dict( version=share.header['version'], previous_block='%064x' % share.header['previous_block'], merkle_root='%064x' % share.header['merkle_root'], timestamp=share.header['timestamp'], target=share.header['bits'].target, nonce=share.header['nonce'], ), gentx=dict( hash='%064x' % share.gentx_hash, coinbase=share.share_data['coinbase'].ljust(2, '\x00').encode('hex'), value=share.share_data['subsidy']*1e-5, last_txout_nonce='%016x' % share.contents['last_txout_nonce'], ), other_transaction_hashes=['%064x' % x for x in share.get_other_tx_hashes(node.tracker)], ), ) def get_share_address(share_hash_str): if int(share_hash_str, 16) not in node.tracker.items: return None share = node.tracker.items[int(share_hash_str, 16)] return bitcoin_data.script2_to_address(share.new_script, node.net.PARENT) new_root.putChild('payout_address', WebInterface(lambda share_hash_str: get_share_address(share_hash_str))) new_root.putChild('share', WebInterface(lambda share_hash_str: get_share(share_hash_str))) new_root.putChild('heads', WebInterface(lambda: ['%064x' % x for x in node.tracker.heads])) new_root.putChild('verified_heads', WebInterface(lambda: ['%064x' % x for x in node.tracker.verified.heads])) new_root.putChild('tails', WebInterface(lambda: ['%064x' % x for t in node.tracker.tails for x in node.tracker.reverse.get(t, set())])) new_root.putChild('verified_tails', WebInterface(lambda: ['%064x' % x for t in node.tracker.verified.tails for x in node.tracker.verified.reverse.get(t, set())])) new_root.putChild('best_share_hash', WebInterface(lambda: '%064x' % node.best_share_var.value)) new_root.putChild('my_share_hashes', WebInterface(lambda: ['%064x' % my_share_hash for my_share_hash in wb.my_share_hashes])) def get_share_data(share_hash_str): if int(share_hash_str, 16) not in node.tracker.items: return '' share = node.tracker.items[int(share_hash_str, 16)] return p2pool_data.share_type.pack(share.as_share()) new_root.putChild('share_data', WebInterface(lambda share_hash_str: get_share_data(share_hash_str), 'application/octet-stream')) new_root.putChild('currency_info', WebInterface(lambda: dict( symbol=node.net.PARENT.SYMBOL, block_explorer_url_prefix=node.net.PARENT.BLOCK_EXPLORER_URL_PREFIX, address_explorer_url_prefix=node.net.PARENT.ADDRESS_EXPLORER_URL_PREFIX, tx_explorer_url_prefix=node.net.PARENT.TX_EXPLORER_URL_PREFIX, ))) new_root.putChild('version', WebInterface(lambda: p2pool.__version__)) hd_path = os.path.join(datadir_path, 'graph_db') hd_data = _atomic_read(hd_path) hd_obj = {} if hd_data is not None: try: hd_obj = json.loads(hd_data) except Exception: log.err(None, 'Error reading graph database:') dataview_descriptions = { 'last_hour': graph.DataViewDescription(150, 60*60), 'last_day': graph.DataViewDescription(300, 60*60*24), 'last_week': graph.DataViewDescription(300, 60*60*24*7), 'last_month': graph.DataViewDescription(300, 60*60*24*30), 'last_year': graph.DataViewDescription(300, 60*60*24*365.25), } hd = graph.HistoryDatabase.from_obj({ 'local_hash_rate': graph.DataStreamDescription(dataview_descriptions, is_gauge=False), 'local_dead_hash_rate': graph.DataStreamDescription(dataview_descriptions, is_gauge=False), 'local_share_hash_rates': graph.DataStreamDescription(dataview_descriptions, is_gauge=False, multivalues=True, multivalue_undefined_means_0=True, default_func=graph.make_multivalue_migrator(dict(good='local_share_hash_rate', dead='local_dead_share_hash_rate', orphan='local_orphan_share_hash_rate'), post_func=lambda bins: [dict((k, (v[0] - (sum(bin.get(rem_k, (0, 0))[0] for rem_k in ['dead', 'orphan']) if k == 'good' else 0), v[1])) for k, v in bin.iteritems()) for bin in bins])), 'pool_rates': graph.DataStreamDescription(dataview_descriptions, multivalues=True, multivalue_undefined_means_0=True), 'current_payout': graph.DataStreamDescription(dataview_descriptions), 'current_payouts': graph.DataStreamDescription(dataview_descriptions, multivalues=True), 'peers': graph.DataStreamDescription(dataview_descriptions, multivalues=True, default_func=graph.make_multivalue_migrator(dict(incoming='incoming_peers', outgoing='outgoing_peers'))), 'miner_hash_rates': graph.DataStreamDescription(dataview_descriptions, is_gauge=False, multivalues=True), 'miner_dead_hash_rates': graph.DataStreamDescription(dataview_descriptions, is_gauge=False, multivalues=True), 'desired_version_rates': graph.DataStreamDescription(dataview_descriptions, multivalues=True, multivalue_undefined_means_0=True), 'traffic_rate': graph.DataStreamDescription(dataview_descriptions, is_gauge=False, multivalues=True), 'getwork_latency': graph.DataStreamDescription(dataview_descriptions), 'memory_usage': graph.DataStreamDescription(dataview_descriptions), }, hd_obj) x = deferral.RobustLoopingCall(lambda: _atomic_write(hd_path, json.dumps(hd.to_obj()))) x.start(100) stop_event.watch(x.stop) @wb.pseudoshare_received.watch def _(work, dead, user): t = time.time() hd.datastreams['local_hash_rate'].add_datum(t, work) if dead: hd.datastreams['local_dead_hash_rate'].add_datum(t, work) if user is not None: hd.datastreams['miner_hash_rates'].add_datum(t, {user: work}) if dead: hd.datastreams['miner_dead_hash_rates'].add_datum(t, {user: work}) @wb.share_received.watch def _(work, dead, share_hash): t = time.time() if not dead: hd.datastreams['local_share_hash_rates'].add_datum(t, dict(good=work)) else: hd.datastreams['local_share_hash_rates'].add_datum(t, dict(dead=work)) def later(): res = node.tracker.is_child_of(share_hash, node.best_share_var.value) if res is None: res = False # share isn't connected to sharechain? assume orphaned if res and dead: # share was DOA, but is now in sharechain # move from dead to good hd.datastreams['local_share_hash_rates'].add_datum(t, dict(dead=-work, good=work)) elif not res and not dead: # share wasn't DOA, and isn't in sharechain # move from good to orphan hd.datastreams['local_share_hash_rates'].add_datum(t, dict(good=-work, orphan=work)) reactor.callLater(200, later) @node.p2p_node.traffic_happened.watch def _(name, bytes): hd.datastreams['traffic_rate'].add_datum(time.time(), {name: bytes}) def add_point(): if node.tracker.get_height(node.best_share_var.value) < 10: return None lookbehind = min(node.net.CHAIN_LENGTH, 60*60//node.net.SHARE_PERIOD, node.tracker.get_height(node.best_share_var.value)) t = time.time() pool_rates = p2pool_data.get_stale_counts(node.tracker, node.best_share_var.value, lookbehind, rates=True) pool_total = sum(pool_rates.itervalues()) hd.datastreams['pool_rates'].add_datum(t, pool_rates) current_txouts = node.get_current_txouts() my_current_payouts = 0.0 for add in wb.pubkeys.keys: my_current_payouts += current_txouts.get(bitcoin_data.pubkey_hash_to_script2(add), 0)*1e-5 hd.datastreams['current_payout'].add_datum(t, my_current_payouts) miner_hash_rates, miner_dead_hash_rates = wb.get_local_rates() current_txouts_by_address = dict((bitcoin_data.script2_to_address(script, node.net.PARENT), amount) for script, amount in current_txouts.iteritems()) hd.datastreams['current_payouts'].add_datum(t, dict((user, current_txouts_by_address[user]*1e-5) for user in miner_hash_rates if user in current_txouts_by_address)) hd.datastreams['peers'].add_datum(t, dict( incoming=sum(1 for peer in node.p2p_node.peers.itervalues() if peer.incoming), outgoing=sum(1 for peer in node.p2p_node.peers.itervalues() if not peer.incoming), )) vs = p2pool_data.get_desired_version_counts(node.tracker, node.best_share_var.value, lookbehind) vs_total = sum(vs.itervalues()) hd.datastreams['desired_version_rates'].add_datum(t, dict((str(k), v/vs_total*pool_total) for k, v in vs.iteritems())) try: hd.datastreams['memory_usage'].add_datum(t, memory.resident()) except: if p2pool.DEBUG: traceback.print_exc() x = deferral.RobustLoopingCall(add_point) x.start(5) stop_event.watch(x.stop) @node.bitcoind_work.changed.watch def _(new_work): hd.datastreams['getwork_latency'].add_datum(time.time(), new_work['latency']) new_root.putChild('graph_data', WebInterface(lambda source, view: hd.datastreams[source].dataviews[view].get_data(time.time()))) if static_dir is None: static_dir = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), 'web-static') web_root.putChild('static', static.File(static_dir)) return web_root
def get_web_root(wb, datadir_path, bitcoind_warning_var): node = wb.node start_time = time.time() web_root = resource.Resource() def get_users(): height, last = node.tracker.get_height_and_last(node.best_share_var.value) weights, total_weight, donation_weight = node.tracker.get_cumulative_weights(node.best_share_var.value, min(height, 720), 65535*2**256) res = {} for script in sorted(weights, key=lambda s: weights[s]): res[bitcoin_data.script2_to_address(script, node.net.PARENT)] = weights[script]/total_weight return res def get_current_scaled_txouts(scale, trunc=0): txouts = node.get_current_txouts() total = sum(txouts.itervalues()) results = dict((script, value*scale//total) for script, value in txouts.iteritems()) if trunc > 0: total_random = 0 random_set = set() for s in sorted(results, key=results.__getitem__): if results[s] >= trunc: break total_random += results[s] random_set.add(s) if total_random: winner = math.weighted_choice((script, results[script]) for script in random_set) for script in random_set: del results[script] results[winner] = total_random if sum(results.itervalues()) < int(scale): results[math.weighted_choice(results.iteritems())] += int(scale) - sum(results.itervalues()) return results def get_patron_sendmany(total=None, trunc='0.01'): if total is None: return 'need total argument. go to patron_sendmany/<TOTAL>' total = int(float(total)*1e8) trunc = int(float(trunc)*1e8) return json.dumps(dict( (bitcoin_data.script2_to_address(script, node.net.PARENT), value/1e8) for script, value in get_current_scaled_txouts(total, trunc).iteritems() if bitcoin_data.script2_to_address(script, node.net.PARENT) is not None )) def get_local_rates(): miner_hash_rates = {} miner_dead_hash_rates = {} datums, dt = wb.local_rate_monitor.get_datums_in_last() for datum in datums: miner_hash_rates[datum['user']] = miner_hash_rates.get(datum['user'], 0) + datum['work']/dt if datum['dead']: miner_dead_hash_rates[datum['user']] = miner_dead_hash_rates.get(datum['user'], 0) + datum['work']/dt return miner_hash_rates, miner_dead_hash_rates def get_global_stats(): # averaged over last hour if node.tracker.get_height(node.best_share_var.value) < 10: return None lookbehind = min(node.tracker.get_height(node.best_share_var.value), 3600//node.net.SHARE_PERIOD) nonstale_hash_rate = p2pool_data.get_pool_attempts_per_second(node.tracker, node.best_share_var.value, lookbehind) stale_prop = p2pool_data.get_average_stale_prop(node.tracker, node.best_share_var.value, lookbehind) return dict( pool_nonstale_hash_rate=nonstale_hash_rate, pool_hash_rate=nonstale_hash_rate/(1 - stale_prop), pool_stale_prop=stale_prop, min_difficulty=bitcoin_data.target_to_difficulty(node.tracker.items[node.best_share_var.value].max_target), ) def get_local_stats(): if node.tracker.get_height(node.best_share_var.value) < 10: return None lookbehind = min(node.tracker.get_height(node.best_share_var.value), 3600//node.net.SHARE_PERIOD) global_stale_prop = p2pool_data.get_average_stale_prop(node.tracker, node.best_share_var.value, lookbehind) my_unstale_count = sum(1 for share in node.tracker.get_chain(node.best_share_var.value, lookbehind) if share.hash in wb.my_share_hashes) my_orphan_count = sum(1 for share in node.tracker.get_chain(node.best_share_var.value, lookbehind) if share.hash in wb.my_share_hashes and share.share_data['stale_info'] == 'orphan') my_doa_count = sum(1 for share in node.tracker.get_chain(node.best_share_var.value, lookbehind) if share.hash in wb.my_share_hashes and share.share_data['stale_info'] == 'doa') my_share_count = my_unstale_count + my_orphan_count + my_doa_count my_stale_count = my_orphan_count + my_doa_count my_stale_prop = my_stale_count/my_share_count if my_share_count != 0 else None my_work = sum(bitcoin_data.target_to_average_attempts(share.target) for share in node.tracker.get_chain(node.best_share_var.value, lookbehind - 1) if share.hash in wb.my_share_hashes) actual_time = (node.tracker.items[node.best_share_var.value].timestamp - node.tracker.items[node.tracker.get_nth_parent_hash(node.best_share_var.value, lookbehind - 1)].timestamp) share_att_s = my_work / actual_time miner_hash_rates, miner_dead_hash_rates = get_local_rates() (stale_orphan_shares, stale_doa_shares), shares, _ = wb.get_stale_counts() return dict( my_hash_rates_in_last_hour=dict( note="DEPRECATED", nonstale=share_att_s, rewarded=share_att_s/(1 - global_stale_prop), actual=share_att_s/(1 - my_stale_prop) if my_stale_prop is not None else 0, # 0 because we don't have any shares anyway ), my_share_counts_in_last_hour=dict( shares=my_share_count, unstale_shares=my_unstale_count, stale_shares=my_stale_count, orphan_stale_shares=my_orphan_count, doa_stale_shares=my_doa_count, ), my_stale_proportions_in_last_hour=dict( stale=my_stale_prop, orphan_stale=my_orphan_count/my_share_count if my_share_count != 0 else None, dead_stale=my_doa_count/my_share_count if my_share_count != 0 else None, ), miner_hash_rates=miner_hash_rates, miner_dead_hash_rates=miner_dead_hash_rates, efficiency_if_miner_perfect=(1 - stale_orphan_shares/shares)/(1 - global_stale_prop) if shares else None, # ignores dead shares because those are miner's fault and indicated by pseudoshare rejection efficiency=(1 - (stale_orphan_shares+stale_doa_shares)/shares)/(1 - global_stale_prop) if shares else None, peers=dict( incoming=sum(1 for peer in node.p2p_node.peers.itervalues() if peer.incoming), outgoing=sum(1 for peer in node.p2p_node.peers.itervalues() if not peer.incoming), ), shares=dict( total=shares, orphan=stale_orphan_shares, dead=stale_doa_shares, ), uptime=time.time() - start_time, attempts_to_share=bitcoin_data.target_to_average_attempts(node.tracker.items[node.best_share_var.value].max_target), attempts_to_block=bitcoin_data.target_to_average_attempts(node.bitcoind_work.value['bits'].target), block_value=node.bitcoind_work.value['subsidy']*1e-8, warnings=p2pool_data.get_warnings(node.tracker, node.best_share_var.value, node.net, bitcoind_warning_var.value, node.bitcoind_work.value), donation_proportion=wb.donation_percentage/100, ) class WebInterface(deferred_resource.DeferredResource): def __init__(self, func, mime_type='application/json', args=()): deferred_resource.DeferredResource.__init__(self) self.func, self.mime_type, self.args = func, mime_type, args def getChild(self, child, request): return WebInterface(self.func, self.mime_type, self.args + (child,)) @defer.inlineCallbacks def render_GET(self, request): request.setHeader('Content-Type', self.mime_type) request.setHeader('Access-Control-Allow-Origin', '*') res = yield self.func(*self.args) defer.returnValue(json.dumps(res) if self.mime_type == 'application/json' else res) web_root.putChild('rate', WebInterface(lambda: p2pool_data.get_pool_attempts_per_second(node.tracker, node.best_share_var.value, 720)/(1-p2pool_data.get_average_stale_prop(node.tracker, node.best_share_var.value, 720)))) web_root.putChild('difficulty', WebInterface(lambda: bitcoin_data.target_to_difficulty(node.tracker.items[node.best_share_var.value].max_target))) web_root.putChild('users', WebInterface(get_users)) web_root.putChild('user_stales', WebInterface(lambda: dict((bitcoin_data.pubkey_hash_to_address(ph, node.net.PARENT), prop) for ph, prop in p2pool_data.get_user_stale_props(node.tracker, node.best_share_var.value, node.tracker.get_height(node.best_share_var.value)).iteritems()))) web_root.putChild('fee', WebInterface(lambda: wb.worker_fee)) web_root.putChild('current_payouts', WebInterface(lambda: dict((bitcoin_data.script2_to_address(script, node.net.PARENT), value/1e8) for script, value in node.get_current_txouts().iteritems()))) web_root.putChild('patron_sendmany', WebInterface(get_patron_sendmany, 'text/plain')) web_root.putChild('global_stats', WebInterface(get_global_stats)) web_root.putChild('local_stats', WebInterface(get_local_stats)) web_root.putChild('peer_addresses', WebInterface(lambda: ['%s:%i' % (peer.transport.getPeer().host, peer.transport.getPeer().port) for peer in node.p2p_node.peers.itervalues()])) web_root.putChild('peer_txpool_sizes', WebInterface(lambda: dict(('%s:%i' % (peer.transport.getPeer().host, peer.transport.getPeer().port), peer.remembered_txs_size) for peer in node.p2p_node.peers.itervalues()))) web_root.putChild('pings', WebInterface(defer.inlineCallbacks(lambda: defer.returnValue( dict([(a, (yield b)) for a, b in [( '%s:%i' % (peer.transport.getPeer().host, peer.transport.getPeer().port), defer.inlineCallbacks(lambda peer=peer: defer.returnValue( min([(yield peer.do_ping().addCallback(lambda x: x/0.001).addErrback(lambda fail: None)) for i in xrange(3)]) ))() ) for peer in list(node.p2p_node.peers.itervalues())] ]) )))) web_root.putChild('peer_versions', WebInterface(lambda: dict(('%s:%i' % peer.addr, peer.other_sub_version) for peer in node.p2p_node.peers.itervalues()))) web_root.putChild('payout_addr', WebInterface(lambda: bitcoin_data.pubkey_hash_to_address(wb.my_pubkey_hash, node.net.PARENT))) web_root.putChild('recent_blocks', WebInterface(lambda: [dict( ts=s.timestamp, hash='%064x' % s.header_hash, number=pack.IntType(24).unpack(s.share_data['coinbase'][1:4]), share='%064x' % s.hash, ) for s in node.tracker.get_chain(node.best_share_var.value, min(node.tracker.get_height(node.best_share_var.value), 24*60*60//node.net.SHARE_PERIOD)) if s.pow_hash <= s.header['bits'].target])) web_root.putChild('uptime', WebInterface(lambda: time.time() - start_time)) web_root.putChild('stale_rates', WebInterface(lambda: p2pool_data.get_stale_counts(node.tracker, node.best_share_var.value, 720, rates=True))) new_root = resource.Resource() web_root.putChild('web', new_root) stat_log = [] if os.path.exists(os.path.join(datadir_path, 'stats')): try: with open(os.path.join(datadir_path, 'stats'), 'rb') as f: stat_log = json.loads(f.read()) except: log.err(None, 'Error loading stats:') def update_stat_log(): while stat_log and stat_log[0]['time'] < time.time() - 24*60*60: stat_log.pop(0) lookbehind = 3600//node.net.SHARE_PERIOD if node.tracker.get_height(node.best_share_var.value) < lookbehind: return None global_stale_prop = p2pool_data.get_average_stale_prop(node.tracker, node.best_share_var.value, lookbehind) (stale_orphan_shares, stale_doa_shares), shares, _ = wb.get_stale_counts() miner_hash_rates, miner_dead_hash_rates = get_local_rates() stat_log.append(dict( time=time.time(), pool_hash_rate=p2pool_data.get_pool_attempts_per_second(node.tracker, node.best_share_var.value, lookbehind)/(1-global_stale_prop), pool_stale_prop=global_stale_prop, local_hash_rates=miner_hash_rates, local_dead_hash_rates=miner_dead_hash_rates, shares=shares, stale_shares=stale_orphan_shares + stale_doa_shares, stale_shares_breakdown=dict(orphan=stale_orphan_shares, doa=stale_doa_shares), current_payout=node.get_current_txouts().get(bitcoin_data.pubkey_hash_to_script2(wb.my_pubkey_hash), 0)*1e-8, peers=dict( incoming=sum(1 for peer in node.p2p_node.peers.itervalues() if peer.incoming), outgoing=sum(1 for peer in node.p2p_node.peers.itervalues() if not peer.incoming), ), attempts_to_share=bitcoin_data.target_to_average_attempts(node.tracker.items[node.best_share_var.value].max_target), attempts_to_block=bitcoin_data.target_to_average_attempts(node.bitcoind_work.value['bits'].target), block_value=node.bitcoind_work.value['subsidy']*1e-8, )) with open(os.path.join(datadir_path, 'stats'), 'wb') as f: f.write(json.dumps(stat_log)) task.LoopingCall(update_stat_log).start(5*60) new_root.putChild('log', WebInterface(lambda: stat_log)) def get_share(share_hash_str): if int(share_hash_str, 16) not in node.tracker.items: return None share = node.tracker.items[int(share_hash_str, 16)] return dict( parent='%064x' % share.previous_hash, children=['%064x' % x for x in sorted(node.tracker.reverse.get(share.hash, set()), key=lambda sh: -len(node.tracker.reverse.get(sh, set())))], # sorted from most children to least children local=dict( verified=share.hash in node.tracker.verified.items, time_first_seen=start_time if share.time_seen == 0 else share.time_seen, peer_first_received_from=share.peer.addr if share.peer is not None else None, ), share_data=dict( timestamp=share.timestamp, target=share.target, max_target=share.max_target, payout_address=bitcoin_data.script2_to_address(share.new_script, node.net.PARENT), donation=share.share_data['donation']/65535, stale_info=share.share_data['stale_info'], nonce=share.share_data['nonce'], desired_version=share.share_data['desired_version'], ), block=dict( hash='%064x' % share.header_hash, header=dict( version=share.header['version'], previous_block='%064x' % share.header['previous_block'], merkle_root='%064x' % share.header['merkle_root'], timestamp=share.header['timestamp'], target=share.header['bits'].target, nonce=share.header['nonce'], ), gentx=dict( hash='%064x' % share.gentx_hash, coinbase=share.share_data['coinbase'].ljust(2, '\x00').encode('hex'), value=share.share_data['subsidy']*1e-8, ), txn_count_range=[len(share.other_txs), len(share.other_txs)] if share.other_txs is not None else 1 if len(share.merkle_link['branch']) == 0 else [2**len(share.merkle_link['branch'])//2+1, 2**len(share.merkle_link['branch'])], ), ) new_root.putChild('share', WebInterface(lambda share_hash_str: get_share(share_hash_str))) new_root.putChild('heads', WebInterface(lambda: ['%064x' % x for x in node.tracker.heads])) new_root.putChild('verified_heads', WebInterface(lambda: ['%064x' % x for x in node.tracker.verified.heads])) new_root.putChild('tails', WebInterface(lambda: ['%064x' % x for t in node.tracker.tails for x in node.tracker.reverse.get(t, set())])) new_root.putChild('verified_tails', WebInterface(lambda: ['%064x' % x for t in node.tracker.verified.tails for x in node.tracker.verified.reverse.get(t, set())])) new_root.putChild('best_share_hash', WebInterface(lambda: '%064x' % node.best_share_var.value)) def get_share_data(share_hash_str): if int(share_hash_str, 16) not in node.tracker.items: return '' share = node.tracker.items[int(share_hash_str, 16)] return p2pool_data.share_type.pack(share.as_share1a()) new_root.putChild('share_data', WebInterface(lambda share_hash_str: get_share_data(share_hash_str), 'application/octet-stream')) new_root.putChild('currency_info', WebInterface(lambda: dict( symbol=node.net.PARENT.SYMBOL, block_explorer_url_prefix=node.net.PARENT.BLOCK_EXPLORER_URL_PREFIX, address_explorer_url_prefix=node.net.PARENT.ADDRESS_EXPLORER_URL_PREFIX, ))) new_root.putChild('version', WebInterface(lambda: p2pool.__version__)) hd_path = os.path.join(datadir_path, 'graph_db') hd_data = _atomic_read(hd_path) hd_obj = {} if hd_data is not None: try: hd_obj = json.loads(hd_data) except Exception: log.err(None, 'Error reading graph database:') dataview_descriptions = { 'last_hour': graph.DataViewDescription(150, 60*60), 'last_day': graph.DataViewDescription(300, 60*60*24), 'last_week': graph.DataViewDescription(300, 60*60*24*7), 'last_month': graph.DataViewDescription(300, 60*60*24*30), 'last_year': graph.DataViewDescription(300, 60*60*24*365.25), } def build_desired_rates(ds_name, ds_desc, dv_name, dv_desc, obj): if not obj: last_bin_end = 0 bins = dv_desc.bin_count*[{}] else: pool_rates = obj['pool_rates'][dv_name] desired_versions = obj['desired_versions'][dv_name] def get_total_pool_rate(t): n = int((pool_rates['last_bin_end'] - t)/dv_desc.bin_width) if n < 0 or n >= dv_desc.bin_count: return None total = sum(x[0] for x in pool_rates['bins'][n].values()) count = math.mean(x[1] for x in pool_rates['bins'][n].values()) if count == 0: return None return total/count last_bin_end = desired_versions['last_bin_end'] bins = [dict((name, (total*get_total_pool_rate(last_bin_end - (i+1/2)*dv_desc.bin_width), count)) for name, (total, count) in desired_versions['bins'][i].iteritems()) for i in xrange(dv_desc.bin_count)] return graph.DataView(dv_desc, ds_desc, last_bin_end, bins) hd = graph.HistoryDatabase.from_obj({ 'local_hash_rate': graph.DataStreamDescription(dataview_descriptions, is_gauge=False), 'local_dead_hash_rate': graph.DataStreamDescription(dataview_descriptions, is_gauge=False), 'local_share_hash_rate': graph.DataStreamDescription(dataview_descriptions, is_gauge=False), 'local_dead_share_hash_rate': graph.DataStreamDescription(dataview_descriptions, is_gauge=False), 'pool_rates': graph.DataStreamDescription(dataview_descriptions, multivalues=True, multivalue_undefined_means_0=True), 'current_payout': graph.DataStreamDescription(dataview_descriptions), 'current_payouts': graph.DataStreamDescription(dataview_descriptions, multivalues=True), 'incoming_peers': graph.DataStreamDescription(dataview_descriptions), 'outgoing_peers': graph.DataStreamDescription(dataview_descriptions), 'miner_hash_rates': graph.DataStreamDescription(dataview_descriptions, is_gauge=False, multivalues=True), 'miner_dead_hash_rates': graph.DataStreamDescription(dataview_descriptions, is_gauge=False, multivalues=True), 'desired_versions': graph.DataStreamDescription(dataview_descriptions, multivalues=True, multivalue_undefined_means_0=True), 'desired_version_rates': graph.DataStreamDescription(dataview_descriptions, multivalues=True, multivalue_undefined_means_0=True, default_func=build_desired_rates), 'traffic_rate': graph.DataStreamDescription(dataview_descriptions, is_gauge=False, multivalues=True), 'getwork_latency': graph.DataStreamDescription(dataview_descriptions), }, hd_obj) task.LoopingCall(lambda: _atomic_write(hd_path, json.dumps(hd.to_obj()))).start(100) @wb.pseudoshare_received.watch def _(work, dead, user): t = time.time() hd.datastreams['local_hash_rate'].add_datum(t, work) if dead: hd.datastreams['local_dead_hash_rate'].add_datum(t, work) if user is not None: hd.datastreams['miner_hash_rates'].add_datum(t, {user: work}) if dead: hd.datastreams['miner_dead_hash_rates'].add_datum(t, {user: work}) @wb.share_received.watch def _(work, dead): t = time.time() hd.datastreams['local_share_hash_rate'].add_datum(t, work) if dead: hd.datastreams['local_dead_share_hash_rate'].add_datum(t, work) @node.p2p_node.traffic_happened.watch def _(name, bytes): hd.datastreams['traffic_rate'].add_datum(time.time(), {name: bytes}) def add_point(): if node.tracker.get_height(node.best_share_var.value) < 10: return None lookbehind = min(node.net.CHAIN_LENGTH, 60*60//node.net.SHARE_PERIOD, node.tracker.get_height(node.best_share_var.value)) t = time.time() pool_rates = p2pool_data.get_stale_counts(node.tracker, node.best_share_var.value, lookbehind, rates=True) pool_total = sum(pool_rates.itervalues()) hd.datastreams['pool_rates'].add_datum(t, pool_rates) current_txouts = node.get_current_txouts() hd.datastreams['current_payout'].add_datum(t, current_txouts.get(bitcoin_data.pubkey_hash_to_script2(wb.my_pubkey_hash), 0)*1e-8) miner_hash_rates, miner_dead_hash_rates = get_local_rates() current_txouts_by_address = dict((bitcoin_data.script2_to_address(script, node.net.PARENT), amount) for script, amount in current_txouts.iteritems()) hd.datastreams['current_payouts'].add_datum(t, dict((user, current_txouts_by_address[user]*1e-8) for user in miner_hash_rates if user in current_txouts_by_address)) hd.datastreams['incoming_peers'].add_datum(t, sum(1 for peer in node.p2p_node.peers.itervalues() if peer.incoming)) hd.datastreams['outgoing_peers'].add_datum(t, sum(1 for peer in node.p2p_node.peers.itervalues() if not peer.incoming)) vs = p2pool_data.get_desired_version_counts(node.tracker, node.best_share_var.value, lookbehind) vs_total = sum(vs.itervalues()) hd.datastreams['desired_versions'].add_datum(t, dict((str(k), v/vs_total) for k, v in vs.iteritems())) hd.datastreams['desired_version_rates'].add_datum(t, dict((str(k), v/vs_total*pool_total) for k, v in vs.iteritems())) task.LoopingCall(add_point).start(5) @node.bitcoind_work.changed.watch def _(new_work): hd.datastreams['getwork_latency'].add_datum(time.time(), new_work['latency']) new_root.putChild('graph_data', WebInterface(lambda source, view: hd.datastreams[source].dataviews[view].get_data(time.time()))) web_root.putChild('static', static.File(os.path.join(os.path.dirname(sys.argv[0]), 'web-static'))) return web_root
def get_work(self, pubkey_hash, desired_share_target, desired_pseudoshare_target): if self.node.best_share_var.value is None and self.node.net.PERSIST: raise jsonrpc.Error_for_code(-12345)(u"p2pool is downloading shares") if self.merged_work.value: tree, size = bitcoin_data.make_auxpow_tree(self.merged_work.value) mm_hashes = [self.merged_work.value.get(tree.get(i), dict(hash=0))["hash"] for i in xrange(size)] mm_data = "\xfa\xbemm" + bitcoin_data.aux_pow_coinbase_type.pack( dict(merkle_root=bitcoin_data.merkle_hash(mm_hashes), size=size, nonce=0) ) mm_later = [ (aux_work, mm_hashes.index(aux_work["hash"]), mm_hashes) for chain_id, aux_work in self.merged_work.value.iteritems() ] else: mm_data = "" mm_later = [] tx_hashes = [ bitcoin_data.hash256(bitcoin_data.tx_type.pack(tx)) for tx in self.current_work.value["transactions"] ] tx_map = dict(zip(tx_hashes, self.current_work.value["transactions"])) previous_share = ( self.node.tracker.items[self.node.best_share_var.value] if self.node.best_share_var.value is not None else None ) if previous_share is None: share_type = p2pool_data.Share else: previous_share_type = type(previous_share) if ( previous_share_type.SUCCESSOR is None or self.node.tracker.get_height(previous_share.hash) < self.node.net.CHAIN_LENGTH ): share_type = previous_share_type else: successor_type = previous_share_type.SUCCESSOR counts = p2pool_data.get_desired_version_counts( self.node.tracker, self.node.tracker.get_nth_parent_hash(previous_share.hash, self.node.net.CHAIN_LENGTH * 9 // 10), self.node.net.CHAIN_LENGTH // 10, ) upgraded = counts.get(successor_type.VERSION, 0) / sum(counts.itervalues()) if upgraded > 0.65: print "Switchover imminent. Upgraded: %.3f%% Threshold: %.3f%%" % (upgraded * 100, 95) print # Share -> NewShare only valid if 95% of hashes in [net.CHAIN_LENGTH*9//10, net.CHAIN_LENGTH] for new version if counts.get(successor_type.VERSION, 0) > sum(counts.itervalues()) * 95 // 100: share_type = successor_type else: share_type = previous_share_type if desired_share_target is None: desired_share_target = 2 ** 256 - 1 local_hash_rate = self._estimate_local_hash_rate() if local_hash_rate is not None: desired_share_target = min( desired_share_target, bitcoin_data.average_attempts_to_target(local_hash_rate * self.node.net.SHARE_PERIOD / 0.0167), ) # limit to 1.67% of pool shares by modulating share difficulty local_addr_rates = self.get_local_addr_rates() lookbehind = 3600 // self.node.net.SHARE_PERIOD block_subsidy = self.node.bitcoind_work.value["subsidy"] if previous_share is not None and self.node.tracker.get_height(previous_share.hash) > lookbehind: expected_payout_per_block = ( local_addr_rates.get(pubkey_hash, 0) / p2pool_data.get_pool_attempts_per_second( self.node.tracker, self.node.best_share_var.value, lookbehind ) * block_subsidy * (1 - self.donation_percentage / 100) ) # XXX doesn't use global stale rate to compute pool hash if expected_payout_per_block < self.node.net.PARENT.DUST_THRESHOLD: desired_share_target = min( desired_share_target, bitcoin_data.average_attempts_to_target( ( bitcoin_data.target_to_average_attempts(self.node.bitcoind_work.value["bits"].target) * self.node.net.SPREAD ) * self.node.net.PARENT.DUST_THRESHOLD / block_subsidy ), ) if True: share_info, gentx, other_transaction_hashes, get_share = share_type.generate_transaction( tracker=self.node.tracker, share_data=dict( previous_share_hash=self.node.best_share_var.value, coinbase=( script.create_push_script( [self.current_work.value["height"]] + ([mm_data] if mm_data else []) + [] ) + self.current_work.value["coinbaseflags"] )[:100], nonce=random.randrange(2 ** 32), pubkey_hash=pubkey_hash, subsidy=self.current_work.value["subsidy"], donation=math.perfect_round(65535 * self.donation_percentage / 100), stale_info=( lambda (orphans, doas), total, (orphans_recorded_in_chain, doas_recorded_in_chain): "orphan" if orphans > orphans_recorded_in_chain else "doa" if doas > doas_recorded_in_chain else None )(*self.get_stale_counts()), desired_version=( share_type.SUCCESSOR if share_type.SUCCESSOR is not None else share_type ).VOTING_VERSION, ), block_target=self.current_work.value["bits"].target, desired_timestamp=int(time.time() + 0.5), desired_target=desired_share_target, ref_merkle_link=dict(branch=[], index=0), desired_other_transaction_hashes_and_fees=zip(tx_hashes, self.current_work.value["transaction_fees"]), net=self.node.net, known_txs=tx_map, base_subsidy=self.node.net.PARENT.SUBSIDY_FUNC(self.current_work.value["height"]), ) packed_gentx = bitcoin_data.tx_type.pack(gentx) other_transactions = [tx_map[tx_hash] for tx_hash in other_transaction_hashes] mm_later = [ ( dict( aux_work, target=aux_work["target"] if aux_work["target"] != "p2pool" else share_info["bits"].target ), index, hashes, ) for aux_work, index, hashes in mm_later ] if desired_pseudoshare_target is None: target = 2 ** 256 - 1 local_hash_rate = self._estimate_local_hash_rate() if local_hash_rate is not None: target = min( target, bitcoin_data.average_attempts_to_target(local_hash_rate * 3) ) # limit to 1 share response every 3 seconds by modulating pseudoshare difficulty else: target = desired_pseudoshare_target target = max(target, share_info["bits"].target) for aux_work, index, hashes in mm_later: target = max(target, aux_work["target"]) target = math.clip(target, self.node.net.PARENT.SANE_TARGET_RANGE) getwork_time = time.time() lp_count = self.new_work_event.times merkle_link = bitcoin_data.calculate_merkle_link([None] + other_transaction_hashes, 0) print "New work for worker! Difficulty: %.09f Share difficulty: %.09f Total block value: %.6f %s including %i transactions" % ( bitcoin_data.target_to_difficulty(target), bitcoin_data.target_to_difficulty(share_info["bits"].target), self.current_work.value["subsidy"] * 1e-8, self.node.net.PARENT.SYMBOL, len(self.current_work.value["transactions"]), ) ba = dict( version=min(self.current_work.value["version"], 2), previous_block=self.current_work.value["previous_block"], merkle_link=merkle_link, coinb1=packed_gentx[: -self.COINBASE_NONCE_LENGTH - 4], coinb2=packed_gentx[-4:], timestamp=self.current_work.value["time"], bits=self.current_work.value["bits"], share_target=target, ) received_header_hashes = set() def got_response(header, user, coinbase_nonce): assert len(coinbase_nonce) == self.COINBASE_NONCE_LENGTH new_packed_gentx = ( packed_gentx[: -self.COINBASE_NONCE_LENGTH - 4] + coinbase_nonce + packed_gentx[-4:] if coinbase_nonce != "\0" * self.COINBASE_NONCE_LENGTH else packed_gentx ) new_gentx = ( bitcoin_data.tx_type.unpack(new_packed_gentx) if coinbase_nonce != "\0" * self.COINBASE_NONCE_LENGTH else gentx ) header_hash = bitcoin_data.hash256(bitcoin_data.block_header_type.pack(header)) pow_hash = self.node.net.PARENT.POW_FUNC(bitcoin_data.block_header_type.pack(header)) try: if pow_hash <= header["bits"].target or p2pool.DEBUG: helper.submit_block( dict(header=header, txs=[new_gentx] + other_transactions), False, self.node.factory, self.node.bitcoind, self.node.bitcoind_work, self.node.net, ) if pow_hash <= header["bits"].target: print print "GOT BLOCK FROM MINER! Passing to bitcoind! %s%064x" % ( self.node.net.PARENT.BLOCK_EXPLORER_URL_PREFIX, header_hash, ) print except: log.err(None, "Error while processing potential block:") user, _, _, _ = self.get_user_details(user) assert header["previous_block"] == ba["previous_block"] assert header["merkle_root"] == bitcoin_data.check_merkle_link( bitcoin_data.hash256(new_packed_gentx), merkle_link ) assert header["bits"] == ba["bits"] # Check momentum using midhash (who knows why it is called that) and birthday values midhash = hashlib.sha256(hashlib.sha256(bitcoin_data.block_header_type.pack(header)[:80]).digest()).digest() # print 'MIDHASH: {0}'.format(midhash.encode('hex')) # print 'A: {0}'.format(header['birthdayA']) # print 'B: {0}'.format(header['birthdayB']) momentumc = rewardcoin_momentum.checkMomentum(midhash, header["birthdayA"], header["birthdayB"]) # print momentumc if momentumc == False: print "Invalid Momentum from Client!" return False on_time = self.new_work_event.times == lp_count for aux_work, index, hashes in mm_later: try: if pow_hash <= aux_work["target"] or p2pool.DEBUG: df = deferral.retry("Error submitting merged block: (will retry)", 10, 10)( aux_work["merged_proxy"].rpc_getauxblock )( pack.IntType(256, "big").pack(aux_work["hash"]).encode("hex"), bitcoin_data.aux_pow_type.pack( dict( merkle_tx=dict(tx=new_gentx, block_hash=header_hash, merkle_link=merkle_link), merkle_link=bitcoin_data.calculate_merkle_link(hashes, index), parent_block_header=header, ) ).encode("hex"), ) @df.addCallback def _(result, aux_work=aux_work): if result != (pow_hash <= aux_work["target"]): print >> sys.stderr, "Merged block submittal result: %s Expected: %s" % ( result, pow_hash <= aux_work["target"], ) else: print "Merged block submittal result: %s" % (result,) @df.addErrback def _(err): log.err(err, "Error submitting merged block:") except: log.err(None, "Error while processing merged mining POW:") if pow_hash <= share_info["bits"].target and header_hash not in received_header_hashes: last_txout_nonce = pack.IntType(8 * self.COINBASE_NONCE_LENGTH).unpack(coinbase_nonce) share = get_share(header, last_txout_nonce) print "GOT SHARE! %s %s prev %s age %.2fs%s" % ( user, p2pool_data.format_hash(share.hash), p2pool_data.format_hash(share.previous_hash), time.time() - getwork_time, " DEAD ON ARRIVAL" if not on_time else "", ) self.my_share_hashes.add(share.hash) if not on_time: self.my_doa_share_hashes.add(share.hash) self.node.tracker.add(share) self.node.set_best_share() try: if (pow_hash <= header["bits"].target or p2pool.DEBUG) and self.node.p2p_node is not None: self.node.p2p_node.broadcast_share(share.hash) except: log.err(None, "Error forwarding block solution:") self.share_received.happened( bitcoin_data.target_to_average_attempts(share.target), not on_time, share.hash ) if pow_hash > target: print "Worker %s submitted share with hash > target:" % (user,) print " Hash: %56x" % (pow_hash,) print " Target: %56x" % (target,) elif header_hash in received_header_hashes: print >> sys.stderr, "Worker %s submitted share more than once!" % (user,) else: received_header_hashes.add(header_hash) self.pseudoshare_received.happened(bitcoin_data.target_to_average_attempts(target), not on_time, user) self.recent_shares_ts_work.append((time.time(), bitcoin_data.target_to_average_attempts(target))) while len(self.recent_shares_ts_work) > 50: self.recent_shares_ts_work.pop(0) self.local_rate_monitor.add_datum( dict( work=bitcoin_data.target_to_average_attempts(target), dead=not on_time, user=user, share_target=share_info["bits"].target, ) ) self.local_addr_rate_monitor.add_datum( dict(work=bitcoin_data.target_to_average_attempts(target), pubkey_hash=pubkey_hash) ) return on_time return ba, got_response
def get_work(self, pubkey_hash, desired_share_target, desired_pseudoshare_target): if (self.node.p2p_node is None or len(self.node.p2p_node.peers) == 0) and self.node.net.PERSIST: raise jsonrpc.Error_for_code(-12345)(u"p2pool is not connected to any peers") if self.node.best_share_var.value is None and self.node.net.PERSIST: raise jsonrpc.Error_for_code(-12345)(u"p2pool is downloading shares") if time.time() > self.current_work.value["last_update"] + 60: raise jsonrpc.Error_for_code(-12345)(u"lost contact with bitcoind") if self.merged_work.value: tree, size = bitcoin_data.make_auxpow_tree(self.merged_work.value) mm_hashes = [self.merged_work.value.get(tree.get(i), dict(hash=0))["hash"] for i in xrange(size)] mm_data = "\xfa\xbemm" + bitcoin_data.aux_pow_coinbase_type.pack( dict(merkle_root=bitcoin_data.merkle_hash(mm_hashes), size=size, nonce=0) ) mm_later = [ (aux_work, mm_hashes.index(aux_work["hash"]), mm_hashes) for chain_id, aux_work in self.merged_work.value.iteritems() ] else: mm_data = "" mm_later = [] tx_hashes = [ bitcoin_data.hash256(bitcoin_data.tx_type.pack(tx)) for tx in self.current_work.value["transactions"] ] tx_map = dict(zip(tx_hashes, self.current_work.value["transactions"])) if self.node.best_share_var.value is None: share_type = p2pool_data.Share else: previous_share = self.node.tracker.items[self.node.best_share_var.value] previous_share_type = type(previous_share) if ( previous_share_type.SUCCESSOR is None or self.node.tracker.get_height(previous_share.hash) < self.node.net.CHAIN_LENGTH ): share_type = previous_share_type else: successor_type = previous_share_type.SUCCESSOR counts = p2pool_data.get_desired_version_counts( self.node.tracker, self.node.tracker.get_nth_parent_hash(previous_share.hash, self.node.net.CHAIN_LENGTH * 9 // 10), self.node.net.CHAIN_LENGTH // 10, ) # Share -> NewShare only valid if 85% of hashes in [net.CHAIN_LENGTH*9//10, net.CHAIN_LENGTH] for new version if counts.get(successor_type.VERSION, 0) > sum(counts.itervalues()) * 95 // 100: share_type = successor_type else: share_type = previous_share_type if True: share_info, gentx, other_transaction_hashes, get_share = share_type.generate_transaction( tracker=self.node.tracker, share_data=dict( previous_share_hash=self.node.best_share_var.value, coinbase=( script.create_push_script( [self.current_work.value["height"]] + ([mm_data] if mm_data else []) + [] ) + self.current_work.value["coinbaseflags"] )[:100], nonce=random.randrange(2 ** 32), pubkey_hash=pubkey_hash, subsidy=self.current_work.value["subsidy"], donation=math.perfect_round(65535 * self.donation_percentage / 100), stale_info=( lambda (orphans, doas), total, (orphans_recorded_in_chain, doas_recorded_in_chain): "orphan" if orphans > orphans_recorded_in_chain else "doa" if doas > doas_recorded_in_chain else None )(*self.get_stale_counts()), desired_version=share_type.SUCCESSOR.VERSION if share_type.SUCCESSOR is not None else share_type.VERSION, ), block_target=self.current_work.value["bits"].target, desired_timestamp=int(time.time() + 0.5), desired_target=desired_share_target, ref_merkle_link=dict(branch=[], index=0), desired_other_transaction_hashes=tx_hashes, net=self.node.net, known_txs=tx_map, ) transactions = [gentx] + [tx_map[tx_hash] for tx_hash in other_transaction_hashes] mm_later = [ ( dict( aux_work, target=aux_work["target"] if aux_work["target"] != "p2pool" else share_info["bits"].target ), index, hashes, ) for aux_work, index, hashes in mm_later ] if desired_pseudoshare_target is None: target = 2 ** 256 - 1 if len(self.recent_shares_ts_work) == 50: hash_rate = sum(work for ts, work in self.recent_shares_ts_work[1:]) // ( self.recent_shares_ts_work[-1][0] - self.recent_shares_ts_work[0][0] ) if hash_rate: target = min(target, int(2 ** 256 / hash_rate)) else: target = desired_pseudoshare_target target = max(target, share_info["bits"].target) for aux_work, index, hashes in mm_later: target = max(target, aux_work["target"]) target = math.clip(target, self.node.net.PARENT.SANE_TARGET_RANGE) getwork_time = time.time() lp_count = self.new_work_event.times merkle_link = bitcoin_data.calculate_merkle_link( [bitcoin_data.hash256(bitcoin_data.tx_type.pack(tx)) for tx in transactions], 0 ) print "New work for worker! Difficulty: %.06f Share difficulty: %.06f Total block value: %.6f %s including %i transactions" % ( bitcoin_data.target_to_difficulty(target), bitcoin_data.target_to_difficulty(share_info["bits"].target), self.current_work.value["subsidy"] * 1e-8, self.node.net.PARENT.SYMBOL, len(self.current_work.value["transactions"]), ) ba = bitcoin_getwork.BlockAttempt( version=min(self.current_work.value["version"], 2), previous_block=self.current_work.value["previous_block"], merkle_root=bitcoin_data.check_merkle_link( bitcoin_data.hash256(bitcoin_data.tx_type.pack(transactions[0])), merkle_link ), timestamp=self.current_work.value["time"], bits=self.current_work.value["bits"], share_target=target, ) received_header_hashes = set() def got_response(header, request): header_hash = bitcoin_data.hash256(bitcoin_data.block_header_type.pack(header)) pow_hash = self.node.net.PARENT.POW_FUNC(bitcoin_data.block_header_type.pack(header)) try: if pow_hash <= header["bits"].target or p2pool.DEBUG: helper.submit_block( dict(header=header, txs=transactions), False, self.node.factory, self.node.bitcoind, self.node.bitcoind_work, self.node.net, ) if pow_hash <= header["bits"].target: print print "GOT BLOCK FROM MINER! Passing to bitcoind! %s%064x" % ( self.node.net.PARENT.BLOCK_EXPLORER_URL_PREFIX, header_hash, ) print except: log.err(None, "Error while processing potential block:") user, _, _, _ = self.get_user_details(request) assert header["previous_block"] == ba.previous_block assert header["merkle_root"] == ba.merkle_root assert header["bits"] == ba.bits on_time = self.new_work_event.times == lp_count for aux_work, index, hashes in mm_later: try: if pow_hash <= aux_work["target"] or p2pool.DEBUG: df = deferral.retry("Error submitting merged block: (will retry)", 10, 10)( aux_work["merged_proxy"].rpc_getauxblock )( pack.IntType(256, "big").pack(aux_work["hash"]).encode("hex"), bitcoin_data.aux_pow_type.pack( dict( merkle_tx=dict(tx=transactions[0], block_hash=header_hash, merkle_link=merkle_link), merkle_link=bitcoin_data.calculate_merkle_link(hashes, index), parent_block_header=header, ) ).encode("hex"), ) @df.addCallback def _(result, aux_work=aux_work): if result != (pow_hash <= aux_work["target"]): print >> sys.stderr, "Merged block submittal result: %s Expected: %s" % ( result, pow_hash <= aux_work["target"], ) else: print "Merged block submittal result: %s" % (result,) @df.addErrback def _(err): log.err(err, "Error submitting merged block:") except: log.err(None, "Error while processing merged mining POW:") if pow_hash <= share_info["bits"].target and header_hash not in received_header_hashes: share = get_share(header, transactions) print "GOT SHARE! %s %s prev %s age %.2fs%s" % ( request.getUser(), p2pool_data.format_hash(share.hash), p2pool_data.format_hash(share.previous_hash), time.time() - getwork_time, " DEAD ON ARRIVAL" if not on_time else "", ) self.my_share_hashes.add(share.hash) if not on_time: self.my_doa_share_hashes.add(share.hash) self.node.tracker.add(share) if not p2pool.DEBUG: self.node.tracker.verified.add(share) self.node.set_best_share() try: if (pow_hash <= header["bits"].target or p2pool.DEBUG) and self.node.p2p_node is not None: self.node.p2p_node.broadcast_share(share.hash) except: log.err(None, "Error forwarding block solution:") self.share_received.happened(bitcoin_data.target_to_average_attempts(share.target), not on_time) if pow_hash > target: print "Worker %s submitted share with hash > target:" % (request.getUser(),) print " Hash: %56x" % (pow_hash,) print " Target: %56x" % (target,) elif header_hash in received_header_hashes: print >> sys.stderr, "Worker %s @ %s submitted share more than once!" % ( request.getUser(), request.getClientIP(), ) else: received_header_hashes.add(header_hash) self.pseudoshare_received.happened(bitcoin_data.target_to_average_attempts(target), not on_time, user) self.recent_shares_ts_work.append((time.time(), bitcoin_data.target_to_average_attempts(target))) while len(self.recent_shares_ts_work) > 50: self.recent_shares_ts_work.pop(0) self.local_rate_monitor.add_datum( dict(work=bitcoin_data.target_to_average_attempts(target), dead=not on_time, user=user) ) return on_time return ba, got_response
def get_work(self, pubkey_hash, desired_share_target, desired_pseudoshare_target): if len(self.p2p_node.peers) == 0 and self.net.PERSIST: raise jsonrpc.Error_for_code(-12345)(u'p2pool is not connected to any peers') if self.best_share_var.value is None and self.net.PERSIST: raise jsonrpc.Error_for_code(-12345)(u'p2pool is downloading shares') if time.time() > self.current_work.value['last_update'] + 60: raise jsonrpc.Error_for_code(-12345)(u'lost contact with bitcoind') if self.merged_work.value: tree, size = bitcoin_data.make_auxpow_tree(self.merged_work.value) mm_hashes = [self.merged_work.value.get(tree.get(i), dict(hash=0))['hash'] for i in xrange(size)] mm_data = '\xfa\xbemm' + bitcoin_data.aux_pow_coinbase_type.pack(dict( merkle_root=bitcoin_data.merkle_hash(mm_hashes), size=size, nonce=0, )) mm_later = [(aux_work, mm_hashes.index(aux_work['hash']), mm_hashes) for chain_id, aux_work in self.merged_work.value.iteritems()] else: mm_data = '' mm_later = [] tx_hashes = [bitcoin_data.hash256(bitcoin_data.tx_type.pack(tx)) for tx in self.current_work.value['transactions']] tx_map = dict(zip(tx_hashes, self.current_work.value['transactions'])) if True: share_info, gentx, other_transaction_hashes, get_share = p2pool_data.Share.generate_transaction( tracker=self.tracker, share_data=dict( previous_share_hash=self.best_share_var.value, coinbase=(script.create_push_script([ self.current_work.value['height'], ] + ([mm_data] if mm_data else []) + [ ]) + self.current_work.value['coinbaseflags'])[:100], nonce=random.randrange(2**32), pubkey_hash=pubkey_hash, subsidy=self.current_work.value['subsidy'], donation=math.perfect_round(65535*self.donation_percentage/100), stale_info=(lambda (orphans, doas), total, (orphans_recorded_in_chain, doas_recorded_in_chain): 'orphan' if orphans > orphans_recorded_in_chain else 'doa' if doas > doas_recorded_in_chain else None )(*self.get_stale_counts()), desired_version=5, ), block_target=self.current_work.value['bits'].target, desired_timestamp=int(time.time() + 0.5), desired_target=desired_share_target, ref_merkle_link=dict(branch=[], index=0), other_transaction_hashes=tx_hashes, net=self.net, ) transactions = [gentx] + [tx_map[tx_hash] for tx_hash in other_transaction_hashes] mm_later = [(dict(aux_work, target=aux_work['target'] if aux_work['target'] != 'p2pool' else share_info['bits'].target), index, hashes) for aux_work, index, hashes in mm_later] if desired_pseudoshare_target is None: target = 2**256-1 if len(self.recent_shares_ts_work) == 50: hash_rate = sum(work for ts, work in self.recent_shares_ts_work[1:])//(self.recent_shares_ts_work[-1][0] - self.recent_shares_ts_work[0][0]) if hash_rate: target = min(target, int(2**256/hash_rate)) else: target = desired_pseudoshare_target target = max(target, share_info['bits'].target) for aux_work, index, hashes in mm_later: target = max(target, aux_work['target']) target = math.clip(target, self.net.PARENT.SANE_TARGET_RANGE) getwork_time = time.time() lp_count = self.new_work_event.times merkle_link = bitcoin_data.calculate_merkle_link([bitcoin_data.hash256(bitcoin_data.tx_type.pack(tx)) for tx in transactions], 0) print 'New work for worker! Difficulty: %.06f Share difficulty: %.06f Total block value: %.6f %s including %i transactions' % ( bitcoin_data.target_to_difficulty(target), bitcoin_data.target_to_difficulty(share_info['bits'].target), self.current_work.value['subsidy']*1e-8, self.net.PARENT.SYMBOL, len(self.current_work.value['transactions']), ) ba = bitcoin_getwork.BlockAttempt( version=min(self.current_work.value['version'], 2), previous_block=self.current_work.value['previous_block'], merkle_root=bitcoin_data.check_merkle_link(bitcoin_data.hash256(bitcoin_data.tx_type.pack(transactions[0])), merkle_link), timestamp=self.current_work.value['time'], bits=self.current_work.value['bits'], share_target=target, ) received_header_hashes = set() def got_response(header, request): header_hash = bitcoin_data.hash256(bitcoin_data.block_header_type.pack(header)) pow_hash = self.net.PARENT.POW_FUNC(bitcoin_data.block_header_type.pack(header)) try: if pow_hash <= header['bits'].target or p2pool.DEBUG: self.submit_block(dict(header=header, txs=transactions), ignore_failure=False) if pow_hash <= header['bits'].target: print print 'GOT BLOCK FROM MINER! Passing to bitcoind! %s%064x' % (self.net.PARENT.BLOCK_EXPLORER_URL_PREFIX, header_hash) print except: log.err(None, 'Error while processing potential block:') user, _, _, _ = self.get_user_details(request) assert header['previous_block'] == ba.previous_block assert header['merkle_root'] == ba.merkle_root assert header['bits'] == ba.bits on_time = self.new_work_event.times == lp_count for aux_work, index, hashes in mm_later: try: if pow_hash <= aux_work['target'] or p2pool.DEBUG: df = deferral.retry('Error submitting merged block: (will retry)', 10, 10)(aux_work['merged_proxy'].rpc_getauxblock)( pack.IntType(256, 'big').pack(aux_work['hash']).encode('hex'), bitcoin_data.aux_pow_type.pack(dict( merkle_tx=dict( tx=transactions[0], block_hash=header_hash, merkle_link=merkle_link, ), merkle_link=bitcoin_data.calculate_merkle_link(hashes, index), parent_block_header=header, )).encode('hex'), ) @df.addCallback def _(result, aux_work=aux_work): if result != (pow_hash <= aux_work['target']): print >>sys.stderr, 'Merged block submittal result: %s Expected: %s' % (result, pow_hash <= aux_work['target']) else: print 'Merged block submittal result: %s' % (result,) @df.addErrback def _(err): log.err(err, 'Error submitting merged block:') except: log.err(None, 'Error while processing merged mining POW:') if pow_hash <= share_info['bits'].target and header_hash not in received_header_hashes: share = get_share(header, transactions) print 'GOT SHARE! %s %s prev %s age %.2fs%s' % ( request.getUser(), p2pool_data.format_hash(share.hash), p2pool_data.format_hash(share.previous_hash), time.time() - getwork_time, ' DEAD ON ARRIVAL' if not on_time else '', ) self.my_share_hashes.add(share.hash) if not on_time: self.my_doa_share_hashes.add(share.hash) self.tracker.add(share) if not p2pool.DEBUG: self.tracker.verified.add(share) self.set_best_share() try: if pow_hash <= header['bits'].target or p2pool.DEBUG: self.broadcast_share(share.hash) except: log.err(None, 'Error forwarding block solution:') self.share_received.happened(bitcoin_data.target_to_average_attempts(share.target), not on_time) if pow_hash > target: print 'Worker %s submitted share with hash > target:' % (request.getUser(),) print ' Hash: %56x' % (pow_hash,) print ' Target: %56x' % (target,) elif header_hash in received_header_hashes: print >>sys.stderr, 'Worker %s @ %s submitted share more than once!' % (request.getUser(), request.getClientIP()) else: received_header_hashes.add(header_hash) self.pseudoshare_received.happened(bitcoin_data.target_to_average_attempts(target), not on_time, user) self.recent_shares_ts_work.append((time.time(), bitcoin_data.target_to_average_attempts(target))) while len(self.recent_shares_ts_work) > 50: self.recent_shares_ts_work.pop(0) self.local_rate_monitor.add_datum(dict(work=bitcoin_data.target_to_average_attempts(target), dead=not on_time, user=user)) return on_time return ba, got_response
def get_work(self, pubkey_hash, desired_share_target, desired_pseudoshare_target): global print_throttle if (self.node.p2p_node is None or len(self.node.p2p_node.peers) == 0) and self.node.net.PERSIST: raise jsonrpc.Error_for_code(-12345)( u'p2pool is not connected to any peers') if self.node.best_share_var.value is None and self.node.net.PERSIST: raise jsonrpc.Error_for_code(-12345)( u'p2pool is downloading shares') if self.merged_work.value: tree, size = bitcoin_data.make_auxpow_tree(self.merged_work.value) mm_hashes = [ self.merged_work.value.get(tree.get(i), dict(hash=0))['hash'] for i in xrange(size) ] mm_data = '\xfa\xbemm' + bitcoin_data.aux_pow_coinbase_type.pack( dict( merkle_root=bitcoin_data.merkle_hash(mm_hashes), size=size, nonce=0, )) mm_later = [ (aux_work, mm_hashes.index(aux_work['hash']), mm_hashes) for chain_id, aux_work in self.merged_work.value.iteritems() ] else: mm_data = '' mm_later = [] tx_hashes = [ bitcoin_data.hash256(bitcoin_data.tx_type.pack(tx)) for tx in self.current_work.value['transactions'] ] tx_map = dict(zip(tx_hashes, self.current_work.value['transactions'])) previous_share = self.node.tracker.items[ self.node.best_share_var. value] if self.node.best_share_var.value is not None else None if previous_share is None: share_type = p2pool_data.Share else: previous_share_type = type(previous_share) if previous_share_type.SUCCESSOR is None or self.node.tracker.get_height( previous_share.hash) < self.node.net.CHAIN_LENGTH: share_type = previous_share_type else: successor_type = previous_share_type.SUCCESSOR counts = p2pool_data.get_desired_version_counts( self.node.tracker, self.node.tracker.get_nth_parent_hash( previous_share.hash, self.node.net.CHAIN_LENGTH * 9 // 10), self.node.net.CHAIN_LENGTH // 10) upgraded = counts.get(successor_type.VERSION, 0) / sum( counts.itervalues()) if upgraded > .65: print 'Switchover imminent. Upgraded: %.3f%% Threshold: %.3f%%' % ( upgraded * 100, 95) # Share -> NewShare only valid if 95% of hashes in [net.CHAIN_LENGTH*9//10, net.CHAIN_LENGTH] for new version if counts.get(successor_type.VERSION, 0) > sum(counts.itervalues()) * 95 // 100: share_type = successor_type else: share_type = previous_share_type if desired_share_target is None: desired_share_target = 2**256 - 1 local_hash_rate = self._estimate_local_hash_rate() if local_hash_rate is not None: desired_share_target = min( desired_share_target, bitcoin_data.average_attempts_to_target( local_hash_rate * self.node.net.SHARE_PERIOD / 0.0167) ) # limit to 1.67% of pool shares by modulating share difficulty local_addr_rates = self.get_local_addr_rates() lookbehind = 3600 // self.node.net.SHARE_PERIOD block_subsidy = self.node.bitcoind_work.value['subsidy'] if previous_share is not None and self.node.tracker.get_height( previous_share.hash) > lookbehind: expected_payout_per_block = local_addr_rates.get(pubkey_hash, 0)/p2pool_data.get_pool_attempts_per_second(self.node.tracker, self.node.best_share_var.value, lookbehind) \ * block_subsidy*(1-self.donation_percentage/100) # XXX doesn't use global stale rate to compute pool hash if expected_payout_per_block < self.node.net.PARENT.DUST_THRESHOLD: desired_share_target = min( desired_share_target, bitcoin_data.average_attempts_to_target( (bitcoin_data.target_to_average_attempts( self.node.bitcoind_work.value['bits'].target) * self.node.net.SPREAD) * self.node.net.PARENT.DUST_THRESHOLD / block_subsidy)) if True: share_info, gentx, other_transaction_hashes, get_share = share_type.generate_transaction( tracker=self.node.tracker, share_data=dict( previous_share_hash=self.node.best_share_var.value, coinbase=(script.create_push_script([ self.current_work.value['height'], ] + ([mm_data] if mm_data else []) + []) + self.current_work.value['coinbaseflags'])[:100], nonce=random.randrange(2**32), pubkey_hash=pubkey_hash, subsidy=self.current_work.value['subsidy'], donation=math.perfect_round( 65535 * self.donation_percentage / 100), stale_info=( lambda (orphans, doas), total, (orphans_recorded_in_chain, doas_recorded_in_chain ): 'orphan' if orphans > orphans_recorded_in_chain else 'doa' if doas > doas_recorded_in_chain else None)( *self.get_stale_counts()), desired_version=(share_type.SUCCESSOR if share_type.SUCCESSOR is not None else share_type).VOTING_VERSION, ), block_target=self.current_work.value['bits'].target, desired_timestamp=int(time.time() + 0.5), desired_target=desired_share_target, ref_merkle_link=dict(branch=[], index=0), desired_other_transaction_hashes_and_fees=zip( tx_hashes, self.current_work.value['transaction_fees']), net=self.node.net, known_txs=tx_map, base_subsidy=self.node.net.PARENT.SUBSIDY_FUNC( self.current_work.value['height']), ) packed_gentx = bitcoin_data.tx_type.pack(gentx) other_transactions = [ tx_map[tx_hash] for tx_hash in other_transaction_hashes ] mm_later = [ (dict(aux_work, target=aux_work['target'] if aux_work['target'] != 'p2pool' else share_info['bits'].target), index, hashes) for aux_work, index, hashes in mm_later ] if desired_pseudoshare_target is None: target = 2**256 - 1 local_hash_rate = self._estimate_local_hash_rate() if local_hash_rate is not None: target = min( target, bitcoin_data.average_attempts_to_target(local_hash_rate * 1) ) # limit to 1 share response every second by modulating pseudoshare difficulty else: target = desired_pseudoshare_target target = max(target, share_info['bits'].target) for aux_work, index, hashes in mm_later: target = max(target, aux_work['target']) target = math.clip(target, self.node.net.PARENT.SANE_TARGET_RANGE) getwork_time = time.time() lp_count = self.new_work_event.times merkle_link = bitcoin_data.calculate_merkle_link( [None] + other_transaction_hashes, 0) if print_throttle is 0.0: print_throttle = time.time() else: current_time = time.time() if (current_time - print_throttle) > 5.0: print 'New work for worker! Difficulty: %.06f Share difficulty: %.06f Total block value: %.6f %s including %i transactions' % ( bitcoin_data.target_to_difficulty(target), bitcoin_data.target_to_difficulty( share_info['bits'].target), self.current_work.value['subsidy'] * 1e-8, self.node.net.PARENT.SYMBOL, len(self.current_work.value['transactions']), ) print_throttle = time.time() #need this for stats self.last_work_shares.value[bitcoin_data.pubkey_hash_to_address( pubkey_hash, self.node.net.PARENT)] = share_info['bits'] ba = dict( version=min(self.current_work.value['version'], 536870919), previous_block=self.current_work.value['previous_block'], merkle_link=merkle_link, coinb1=packed_gentx[:-self.COINBASE_NONCE_LENGTH - 4], coinb2=packed_gentx[-4:], timestamp=self.current_work.value['time'], bits=self.current_work.value['bits'], share_target=target, ) received_header_hashes = set() def got_response(header, user, coinbase_nonce): assert len(coinbase_nonce) == self.COINBASE_NONCE_LENGTH new_packed_gentx = packed_gentx[:-self.COINBASE_NONCE_LENGTH - 4] + coinbase_nonce + packed_gentx[ -4:] if coinbase_nonce != '\0' * self.COINBASE_NONCE_LENGTH else packed_gentx new_gentx = bitcoin_data.tx_type.unpack( new_packed_gentx ) if coinbase_nonce != '\0' * self.COINBASE_NONCE_LENGTH else gentx header_hash = bitcoin_data.hash256( bitcoin_data.block_header_type.pack(header)) pow_hash = self.node.net.PARENT.POW_FUNC( bitcoin_data.block_header_type.pack(header)) try: if pow_hash <= header['bits'].target or p2pool.DEBUG: helper.submit_block( dict(header=header, txs=[new_gentx] + other_transactions), False, self.node.factory, self.node.bitcoind, self.node.bitcoind_work, self.node.net) if pow_hash <= header['bits'].target: print print 'GOT BLOCK FROM MINER! Passing to bitcoind! %s%064x' % ( self.node.net.PARENT.BLOCK_EXPLORER_URL_PREFIX, header_hash) print except: log.err(None, 'Error while processing potential block:') user, _, _, _ = self.get_user_details(user) assert header['previous_block'] == ba['previous_block'] assert header['merkle_root'] == bitcoin_data.check_merkle_link( bitcoin_data.hash256(new_packed_gentx), merkle_link) assert header['bits'] == ba['bits'] on_time = self.new_work_event.times == lp_count for aux_work, index, hashes in mm_later: try: if pow_hash <= aux_work['target'] or p2pool.DEBUG: df = deferral.retry( 'Error submitting merged block: (will retry)', 10, 10)(aux_work['merged_proxy'].rpc_getauxblock)( pack.IntType(256, 'big').pack( aux_work['hash']).encode('hex'), bitcoin_data.aux_pow_type.pack( dict( merkle_tx=dict( tx=new_gentx, block_hash=header_hash, merkle_link=merkle_link, ), merkle_link=bitcoin_data. calculate_merkle_link(hashes, index), parent_block_header=header, )).encode('hex'), ) @df.addCallback def _(result, aux_work=aux_work): if result != (pow_hash <= aux_work['target']): print >> sys.stderr, 'Merged block submittal result: %s Expected: %s' % ( result, pow_hash <= aux_work['target']) else: print 'Merged block submittal result: %s' % ( result, ) @df.addErrback def _(err): log.err(err, 'Error submitting merged block:') except: log.err(None, 'Error while processing merged mining POW:') if pow_hash <= share_info[ 'bits'].target and header_hash not in received_header_hashes: last_txout_nonce = pack.IntType( 8 * self.COINBASE_NONCE_LENGTH).unpack(coinbase_nonce) share = get_share(header, last_txout_nonce) print 'GOT SHARE! %s %s prev %s age %.2fs%s' % ( user, p2pool_data.format_hash(share.hash), p2pool_data.format_hash(share.previous_hash), time.time() - getwork_time, ' DEAD ON ARRIVAL' if not on_time else '', ) self.my_share_hashes.add(share.hash) if not on_time: self.my_doa_share_hashes.add(share.hash) self.node.tracker.add(share) self.node.set_best_share() try: if (pow_hash <= header['bits'].target or p2pool.DEBUG) and self.node.p2p_node is not None: self.node.p2p_node.broadcast_share(share.hash) except: log.err(None, 'Error forwarding block solution:') self.share_received.happened( bitcoin_data.target_to_average_attempts(share.target), not on_time, share.hash) if pow_hash > target: print 'Worker %s submitted share with hash > target:' % ( user, ) print ' Hash: %56x' % (pow_hash, ) print ' Target: %56x' % (target, ) elif header_hash in received_header_hashes: print >> sys.stderr, 'Worker %s submitted share more than once!' % ( user, ) else: received_header_hashes.add(header_hash) self.pseudoshare_received.happened( bitcoin_data.target_to_average_attempts(target), not on_time, user) self.recent_shares_ts_work.append( (time.time(), bitcoin_data.target_to_average_attempts(target))) while len(self.recent_shares_ts_work) > 50: self.recent_shares_ts_work.pop(0) self.local_rate_monitor.add_datum( dict(work=bitcoin_data.target_to_average_attempts(target), dead=not on_time, user=user, share_target=share_info['bits'].target)) self.local_addr_rate_monitor.add_datum( dict(work=bitcoin_data.target_to_average_attempts(target), pubkey_hash=pubkey_hash)) return on_time return ba, got_response
def get_web_root(wb, datadir_path, bitcoind_getinfo_var, stop_event=variable.Event()): node = wb.node start_time = time.time() web_root = resource.Resource() def get_users(): height, last = node.tracker.get_height_and_last(node.best_share_var.value) weights, total_weight, donation_weight = node.tracker.get_cumulative_weights(node.best_share_var.value, min(height, 720), 65535*2**256) res = {} for script in sorted(weights, key=lambda s: weights[s]): res[bitcoin_data.script2_to_address(script, node.net.PARENT)] = weights[script]/total_weight return res def get_current_scaled_txouts(scale, trunc=0): txouts = node.get_current_txouts() total = sum(txouts.itervalues()) results = dict((script, value*scale//total) for script, value in txouts.iteritems()) if trunc > 0: total_random = 0 random_set = set() for s in sorted(results, key=results.__getitem__): if results[s] >= trunc: break total_random += results[s] random_set.add(s) if total_random: winner = math.weighted_choice((script, results[script]) for script in random_set) for script in random_set: del results[script] results[winner] = total_random if sum(results.itervalues()) < int(scale): results[math.weighted_choice(results.iteritems())] += int(scale) - sum(results.itervalues()) return results def get_patron_sendmany(total=None, trunc='0.01'): if total is None: return 'need total argument. go to patron_sendmany/<TOTAL>' total = int(float(total)*1e8) trunc = int(float(trunc)*1e8) return json.dumps(dict( (bitcoin_data.script2_to_address(script, node.net.PARENT), value/1e8) for script, value in get_current_scaled_txouts(total, trunc).iteritems() if bitcoin_data.script2_to_address(script, node.net.PARENT) is not None )) def get_global_stats(): # averaged over last hour if node.tracker.get_height(node.best_share_var.value) < 10: return None lookbehind = min(node.tracker.get_height(node.best_share_var.value), 3600//node.net.SHARE_PERIOD) nonstale_hash_rate = p2pool_data.get_pool_attempts_per_second(node.tracker, node.best_share_var.value, lookbehind) stale_prop = p2pool_data.get_average_stale_prop(node.tracker, node.best_share_var.value, lookbehind) diff = bitcoin_data.target_to_difficulty(wb.current_work.value['bits'].target) return dict( pool_nonstale_hash_rate=nonstale_hash_rate, pool_hash_rate=nonstale_hash_rate/(1 - stale_prop), pool_stale_prop=stale_prop, min_difficulty=bitcoin_data.target_to_difficulty(node.tracker.items[node.best_share_var.value].max_target), network_block_difficulty=diff, network_hashrate=(diff * 2**32 // node.net.PARENT.BLOCK_PERIOD), ) def get_local_stats(): if node.tracker.get_height(node.best_share_var.value) < 10: return None lookbehind = min(node.tracker.get_height(node.best_share_var.value), 3600//node.net.SHARE_PERIOD) global_stale_prop = p2pool_data.get_average_stale_prop(node.tracker, node.best_share_var.value, lookbehind) my_unstale_count = sum(1 for share in node.tracker.get_chain(node.best_share_var.value, lookbehind) if share.hash in wb.my_share_hashes) my_orphan_count = sum(1 for share in node.tracker.get_chain(node.best_share_var.value, lookbehind) if share.hash in wb.my_share_hashes and share.share_data['stale_info'] == 'orphan') my_doa_count = sum(1 for share in node.tracker.get_chain(node.best_share_var.value, lookbehind) if share.hash in wb.my_share_hashes and share.share_data['stale_info'] == 'doa') my_share_count = my_unstale_count + my_orphan_count + my_doa_count my_stale_count = my_orphan_count + my_doa_count my_stale_prop = my_stale_count/my_share_count if my_share_count != 0 else None my_work = sum(bitcoin_data.target_to_average_attempts(share.target) for share in node.tracker.get_chain(node.best_share_var.value, lookbehind - 1) if share.hash in wb.my_share_hashes) actual_time = (node.tracker.items[node.best_share_var.value].timestamp - node.tracker.items[node.tracker.get_nth_parent_hash(node.best_share_var.value, lookbehind - 1)].timestamp) share_att_s = my_work / actual_time miner_hash_rates, miner_dead_hash_rates = wb.get_local_rates() (stale_orphan_shares, stale_doa_shares), shares, _ = wb.get_stale_counts() miner_last_difficulties = {} for addr in wb.last_work_shares.value: miner_last_difficulties[addr] = bitcoin_data.target_to_difficulty(wb.last_work_shares.value[addr].target) return dict( my_hash_rates_in_last_hour=dict( note="DEPRECATED", nonstale=share_att_s, rewarded=share_att_s/(1 - global_stale_prop), actual=share_att_s/(1 - my_stale_prop) if my_stale_prop is not None else 0, # 0 because we don't have any shares anyway ), my_share_counts_in_last_hour=dict( shares=my_share_count, unstale_shares=my_unstale_count, stale_shares=my_stale_count, orphan_stale_shares=my_orphan_count, doa_stale_shares=my_doa_count, ), my_stale_proportions_in_last_hour=dict( stale=my_stale_prop, orphan_stale=my_orphan_count/my_share_count if my_share_count != 0 else None, dead_stale=my_doa_count/my_share_count if my_share_count != 0 else None, ), miner_hash_rates=miner_hash_rates, miner_dead_hash_rates=miner_dead_hash_rates, miner_last_difficulties=miner_last_difficulties, efficiency_if_miner_perfect=(1 - stale_orphan_shares/shares)/(1 - global_stale_prop) if shares else None, # ignores dead shares because those are miner's fault and indicated by pseudoshare rejection efficiency=(1 - (stale_orphan_shares+stale_doa_shares)/shares)/(1 - global_stale_prop) if shares else None, peers=dict( incoming=sum(1 for peer in node.p2p_node.peers.itervalues() if peer.incoming), outgoing=sum(1 for peer in node.p2p_node.peers.itervalues() if not peer.incoming), ), shares=dict( total=shares, orphan=stale_orphan_shares, dead=stale_doa_shares, ), uptime=time.time() - start_time, attempts_to_share=bitcoin_data.target_to_average_attempts(node.tracker.items[node.best_share_var.value].max_target), attempts_to_block=bitcoin_data.target_to_average_attempts(node.bitcoind_work.value['bits'].target), block_value=node.bitcoind_work.value['subsidy']*1e-8, warnings=p2pool_data.get_warnings(node.tracker, node.best_share_var.value, node.net, bitcoind_getinfo_var.value, node.bitcoind_work.value), donation_proportion=wb.donation_percentage/100, version=p2pool.__version__, protocol_version=p2p.Protocol.VERSION, fee=wb.worker_fee, ) class WebInterface(deferred_resource.DeferredResource): def __init__(self, func, mime_type='application/json', args=()): deferred_resource.DeferredResource.__init__(self) self.func, self.mime_type, self.args = func, mime_type, args def getChild(self, child, request): return WebInterface(self.func, self.mime_type, self.args + (child,)) @defer.inlineCallbacks def render_GET(self, request): request.setHeader('Content-Type', self.mime_type) request.setHeader('Access-Control-Allow-Origin', '*') res = yield self.func(*self.args) defer.returnValue(json.dumps(res) if self.mime_type == 'application/json' else res) def decent_height(): return min(node.tracker.get_height(node.best_share_var.value), 720) web_root.putChild('rate', WebInterface(lambda: p2pool_data.get_pool_attempts_per_second(node.tracker, node.best_share_var.value, decent_height())/(1-p2pool_data.get_average_stale_prop(node.tracker, node.best_share_var.value, decent_height())))) web_root.putChild('difficulty', WebInterface(lambda: bitcoin_data.target_to_difficulty(node.tracker.items[node.best_share_var.value].max_target))) web_root.putChild('users', WebInterface(get_users)) web_root.putChild('user_stales', WebInterface(lambda: dict((bitcoin_data.pubkey_hash_to_address(ph, node.net.PARENT), prop) for ph, prop in p2pool_data.get_user_stale_props(node.tracker, node.best_share_var.value, node.tracker.get_height(node.best_share_var.value)).iteritems()))) web_root.putChild('fee', WebInterface(lambda: wb.worker_fee)) web_root.putChild('current_payouts', WebInterface(lambda: dict((bitcoin_data.script2_to_address(script, node.net.PARENT), value/1e8) for script, value in node.get_current_txouts().iteritems()))) web_root.putChild('patron_sendmany', WebInterface(get_patron_sendmany, 'text/plain')) web_root.putChild('global_stats', WebInterface(get_global_stats)) web_root.putChild('local_stats', WebInterface(get_local_stats)) web_root.putChild('peer_addresses', WebInterface(lambda: ' '.join('%s%s' % (peer.transport.getPeer().host, ':'+str(peer.transport.getPeer().port) if peer.transport.getPeer().port != node.net.P2P_PORT else '') for peer in node.p2p_node.peers.itervalues()))) web_root.putChild('peer_txpool_sizes', WebInterface(lambda: dict(('%s:%i' % (peer.transport.getPeer().host, peer.transport.getPeer().port), peer.remembered_txs_size) for peer in node.p2p_node.peers.itervalues()))) web_root.putChild('pings', WebInterface(defer.inlineCallbacks(lambda: defer.returnValue( dict([(a, (yield b)) for a, b in [( '%s:%i' % (peer.transport.getPeer().host, peer.transport.getPeer().port), defer.inlineCallbacks(lambda peer=peer: defer.returnValue( min([(yield peer.do_ping().addCallback(lambda x: x/0.001).addErrback(lambda fail: None)) for i in xrange(3)]) ))() ) for peer in list(node.p2p_node.peers.itervalues())] ]) )))) web_root.putChild('peer_versions', WebInterface(lambda: dict(('%s:%i' % peer.addr, peer.other_sub_version) for peer in node.p2p_node.peers.itervalues()))) web_root.putChild('payout_addr', WebInterface(lambda: bitcoin_data.pubkey_hash_to_address(wb.my_pubkey_hash, node.net.PARENT))) def height_from_coinbase(coinbase): opcode = ord(coinbase[0]) if len(coinbase) > 0 else 0 if opcode >= 1 and opcode <= 75: return pack.IntType(opcode*8).unpack(coinbase[1:opcode+1]) if opcode == 76: return pack.IntType(8).unpack(coinbase[1:2]) if opcode == 77: return pack.IntType(8).unpack(coinbase[1:3]) if opcode == 78: return pack.IntType(8).unpack(coinbase[1:5]) if opcode >= 79 and opcode <= 96: return opcode - 80 return None web_root.putChild('recent_blocks', WebInterface(lambda: [dict( ts=s.timestamp, hash='%064x' % s.header_hash, number=height_from_coinbase(s.share_data['coinbase']), share='%064x' % s.hash, ) for s in node.tracker.get_chain(node.best_share_var.value, min(node.tracker.get_height(node.best_share_var.value), 24*60*60//node.net.SHARE_PERIOD)) if s.pow_hash <= s.header['bits'].target])) web_root.putChild('uptime', WebInterface(lambda: time.time() - start_time)) web_root.putChild('stale_rates', WebInterface(lambda: p2pool_data.get_stale_counts(node.tracker, node.best_share_var.value, decent_height(), rates=True))) new_root = resource.Resource() web_root.putChild('web', new_root) stat_log = [] if os.path.exists(os.path.join(datadir_path, 'stats')): try: with open(os.path.join(datadir_path, 'stats'), 'rb') as f: stat_log = json.loads(f.read()) except: log.err(None, 'Error loading stats:') def update_stat_log(): while stat_log and stat_log[0]['time'] < time.time() - 24*60*60: stat_log.pop(0) lookbehind = 3600//node.net.SHARE_PERIOD if node.tracker.get_height(node.best_share_var.value) < lookbehind: return None global_stale_prop = p2pool_data.get_average_stale_prop(node.tracker, node.best_share_var.value, lookbehind) (stale_orphan_shares, stale_doa_shares), shares, _ = wb.get_stale_counts() miner_hash_rates, miner_dead_hash_rates = wb.get_local_rates() stat_log.append(dict( time=time.time(), pool_hash_rate=p2pool_data.get_pool_attempts_per_second(node.tracker, node.best_share_var.value, lookbehind)/(1-global_stale_prop), pool_stale_prop=global_stale_prop, local_hash_rates=miner_hash_rates, local_dead_hash_rates=miner_dead_hash_rates, shares=shares, stale_shares=stale_orphan_shares + stale_doa_shares, stale_shares_breakdown=dict(orphan=stale_orphan_shares, doa=stale_doa_shares), current_payout=node.get_current_txouts().get(bitcoin_data.pubkey_hash_to_script2(wb.my_pubkey_hash), 0)*1e-8, peers=dict( incoming=sum(1 for peer in node.p2p_node.peers.itervalues() if peer.incoming), outgoing=sum(1 for peer in node.p2p_node.peers.itervalues() if not peer.incoming), ), attempts_to_share=bitcoin_data.target_to_average_attempts(node.tracker.items[node.best_share_var.value].max_target), attempts_to_block=bitcoin_data.target_to_average_attempts(node.bitcoind_work.value['bits'].target), block_value=node.bitcoind_work.value['subsidy']*1e-8, )) with open(os.path.join(datadir_path, 'stats'), 'wb') as f: f.write(json.dumps(stat_log)) x = deferral.RobustLoopingCall(update_stat_log) x.start(5*60) stop_event.watch(x.stop) new_root.putChild('log', WebInterface(lambda: stat_log)) def get_share(share_hash_str): if int(share_hash_str, 16) not in node.tracker.items: return None share = node.tracker.items[int(share_hash_str, 16)] return dict( parent='%064x' % share.previous_hash, children=['%064x' % x for x in sorted(node.tracker.reverse.get(share.hash, set()), key=lambda sh: -len(node.tracker.reverse.get(sh, set())))], # sorted from most children to least children type_name=type(share).__name__, local=dict( verified=share.hash in node.tracker.verified.items, time_first_seen=start_time if share.time_seen == 0 else share.time_seen, peer_first_received_from=share.peer_addr, ), share_data=dict( timestamp=share.timestamp, target=share.target, max_target=share.max_target, payout_address=bitcoin_data.script2_to_address(share.new_script, node.net.PARENT), donation=share.share_data['donation']/65535, stale_info=share.share_data['stale_info'], nonce=share.share_data['nonce'], desired_version=share.share_data['desired_version'], absheight=share.absheight, abswork=share.abswork, ), block=dict( hash='%064x' % share.header_hash, header=dict( version=share.header['version'], previous_block='%064x' % share.header['previous_block'], merkle_root='%064x' % share.header['merkle_root'], timestamp=share.header['timestamp'], target=share.header['bits'].target, nonce=share.header['nonce'], ), gentx=dict( hash='%064x' % share.gentx_hash, coinbase=share.share_data['coinbase'].ljust(2, '\x00').encode('hex'), value=share.share_data['subsidy']*1e-8, last_txout_nonce='%016x' % share.contents['last_txout_nonce'], ), other_transaction_hashes=['%064x' % x for x in share.get_other_tx_hashes(node.tracker)], ), ) new_root.putChild('share', WebInterface(lambda share_hash_str: get_share(share_hash_str))) new_root.putChild('heads', WebInterface(lambda: ['%064x' % x for x in node.tracker.heads])) new_root.putChild('verified_heads', WebInterface(lambda: ['%064x' % x for x in node.tracker.verified.heads])) new_root.putChild('tails', WebInterface(lambda: ['%064x' % x for t in node.tracker.tails for x in node.tracker.reverse.get(t, set())])) new_root.putChild('verified_tails', WebInterface(lambda: ['%064x' % x for t in node.tracker.verified.tails for x in node.tracker.verified.reverse.get(t, set())])) new_root.putChild('best_share_hash', WebInterface(lambda: '%064x' % node.best_share_var.value)) new_root.putChild('my_share_hashes', WebInterface(lambda: ['%064x' % my_share_hash for my_share_hash in wb.my_share_hashes])) def get_share_data(share_hash_str): if int(share_hash_str, 16) not in node.tracker.items: return '' share = node.tracker.items[int(share_hash_str, 16)] return p2pool_data.share_type.pack(share.as_share1a()) new_root.putChild('share_data', WebInterface(lambda share_hash_str: get_share_data(share_hash_str), 'application/octet-stream')) new_root.putChild('currency_info', WebInterface(lambda: dict( symbol=node.net.PARENT.SYMBOL, block_explorer_url_prefix=node.net.PARENT.BLOCK_EXPLORER_URL_PREFIX, address_explorer_url_prefix=node.net.PARENT.ADDRESS_EXPLORER_URL_PREFIX, tx_explorer_url_prefix=node.net.PARENT.TX_EXPLORER_URL_PREFIX, ))) new_root.putChild('version', WebInterface(lambda: p2pool.__version__)) hd_path = os.path.join(datadir_path, 'graph_db') hd_data = _atomic_read(hd_path) hd_obj = {} if hd_data is not None: try: hd_obj = json.loads(hd_data) except Exception: log.err(None, 'Error reading graph database:') dataview_descriptions = { 'last_hour': graph.DataViewDescription(150, 60*60), 'last_day': graph.DataViewDescription(300, 60*60*24), 'last_week': graph.DataViewDescription(300, 60*60*24*7), 'last_month': graph.DataViewDescription(300, 60*60*24*30), 'last_year': graph.DataViewDescription(300, 60*60*24*365.25), } hd = graph.HistoryDatabase.from_obj({ 'local_hash_rate': graph.DataStreamDescription(dataview_descriptions, is_gauge=False), 'local_dead_hash_rate': graph.DataStreamDescription(dataview_descriptions, is_gauge=False), 'local_share_hash_rates': graph.DataStreamDescription(dataview_descriptions, is_gauge=False, multivalues=True, multivalue_undefined_means_0=True, default_func=graph.make_multivalue_migrator(dict(good='local_share_hash_rate', dead='local_dead_share_hash_rate', orphan='local_orphan_share_hash_rate'), post_func=lambda bins: [dict((k, (v[0] - (sum(bin.get(rem_k, (0, 0))[0] for rem_k in ['dead', 'orphan']) if k == 'good' else 0), v[1])) for k, v in bin.iteritems()) for bin in bins])), 'pool_rates': graph.DataStreamDescription(dataview_descriptions, multivalues=True, multivalue_undefined_means_0=True), 'current_payout': graph.DataStreamDescription(dataview_descriptions), 'current_payouts': graph.DataStreamDescription(dataview_descriptions, multivalues=True), 'peers': graph.DataStreamDescription(dataview_descriptions, multivalues=True, default_func=graph.make_multivalue_migrator(dict(incoming='incoming_peers', outgoing='outgoing_peers'))), 'miner_hash_rates': graph.DataStreamDescription(dataview_descriptions, is_gauge=False, multivalues=True), 'miner_dead_hash_rates': graph.DataStreamDescription(dataview_descriptions, is_gauge=False, multivalues=True), 'desired_version_rates': graph.DataStreamDescription(dataview_descriptions, multivalues=True, multivalue_undefined_means_0=True), 'traffic_rate': graph.DataStreamDescription(dataview_descriptions, is_gauge=False, multivalues=True), 'getwork_latency': graph.DataStreamDescription(dataview_descriptions), 'memory_usage': graph.DataStreamDescription(dataview_descriptions), }, hd_obj) x = deferral.RobustLoopingCall(lambda: _atomic_write(hd_path, json.dumps(hd.to_obj()))) x.start(100) stop_event.watch(x.stop) @wb.pseudoshare_received.watch def _(work, dead, user): t = time.time() hd.datastreams['local_hash_rate'].add_datum(t, work) if dead: hd.datastreams['local_dead_hash_rate'].add_datum(t, work) if user is not None: hd.datastreams['miner_hash_rates'].add_datum(t, {user: work}) if dead: hd.datastreams['miner_dead_hash_rates'].add_datum(t, {user: work}) @wb.share_received.watch def _(work, dead, share_hash): t = time.time() if not dead: hd.datastreams['local_share_hash_rates'].add_datum(t, dict(good=work)) else: hd.datastreams['local_share_hash_rates'].add_datum(t, dict(dead=work)) def later(): res = node.tracker.is_child_of(share_hash, node.best_share_var.value) if res is None: res = False # share isn't connected to sharechain? assume orphaned if res and dead: # share was DOA, but is now in sharechain # move from dead to good hd.datastreams['local_share_hash_rates'].add_datum(t, dict(dead=-work, good=work)) elif not res and not dead: # share wasn't DOA, and isn't in sharechain # move from good to orphan hd.datastreams['local_share_hash_rates'].add_datum(t, dict(good=-work, orphan=work)) reactor.callLater(200, later) @node.p2p_node.traffic_happened.watch def _(name, bytes): hd.datastreams['traffic_rate'].add_datum(time.time(), {name: bytes}) def add_point(): if node.tracker.get_height(node.best_share_var.value) < 10: return None lookbehind = min(node.net.CHAIN_LENGTH, 60*60//node.net.SHARE_PERIOD, node.tracker.get_height(node.best_share_var.value)) t = time.time() pool_rates = p2pool_data.get_stale_counts(node.tracker, node.best_share_var.value, lookbehind, rates=True) pool_total = sum(pool_rates.itervalues()) hd.datastreams['pool_rates'].add_datum(t, pool_rates) current_txouts = node.get_current_txouts() hd.datastreams['current_payout'].add_datum(t, current_txouts.get(bitcoin_data.pubkey_hash_to_script2(wb.my_pubkey_hash), 0)*1e-8) miner_hash_rates, miner_dead_hash_rates = wb.get_local_rates() current_txouts_by_address = dict((bitcoin_data.script2_to_address(script, node.net.PARENT), amount) for script, amount in current_txouts.iteritems()) hd.datastreams['current_payouts'].add_datum(t, dict((user, current_txouts_by_address[user]*1e-8) for user in miner_hash_rates if user in current_txouts_by_address)) hd.datastreams['peers'].add_datum(t, dict( incoming=sum(1 for peer in node.p2p_node.peers.itervalues() if peer.incoming), outgoing=sum(1 for peer in node.p2p_node.peers.itervalues() if not peer.incoming), )) vs = p2pool_data.get_desired_version_counts(node.tracker, node.best_share_var.value, lookbehind) vs_total = sum(vs.itervalues()) hd.datastreams['desired_version_rates'].add_datum(t, dict((str(k), v/vs_total*pool_total) for k, v in vs.iteritems())) try: hd.datastreams['memory_usage'].add_datum(t, memory.resident()) except: if p2pool.DEBUG: traceback.print_exc() x = deferral.RobustLoopingCall(add_point) x.start(5) stop_event.watch(x.stop) @node.bitcoind_work.changed.watch def _(new_work): hd.datastreams['getwork_latency'].add_datum(time.time(), new_work['latency']) new_root.putChild('graph_data', WebInterface(lambda source, view: hd.datastreams[source].dataviews[view].get_data(time.time()))) web_root.putChild('static', static.File(os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), 'web-static'))) return web_root
def get_web_root(tracker, current_work, current_work2, get_current_txouts, datadir_path, net, get_stale_counts, my_pubkey_hash, local_rate_monitor, worker_fee, p2p_node, my_share_hashes, recent_blocks, pseudoshare_received, share_received): start_time = time.time() web_root = resource.Resource() def get_users(): height, last = tracker.get_height_and_last(current_work.value['best_share_hash']) weights, total_weight, donation_weight = tracker.get_cumulative_weights(current_work.value['best_share_hash'], min(height, 720), 65535*2**256) res = {} for script in sorted(weights, key=lambda s: weights[s]): res[bitcoin_data.script2_to_human(script, net.PARENT)] = weights[script]/total_weight return json.dumps(res) def get_current_scaled_txouts(scale, trunc=0): txouts = get_current_txouts() total = sum(txouts.itervalues()) results = dict((script, value*scale//total) for script, value in txouts.iteritems()) if trunc > 0: total_random = 0 random_set = set() for s in sorted(results, key=results.__getitem__): if results[s] >= trunc: break total_random += results[s] random_set.add(s) if total_random: winner = math.weighted_choice((script, results[script]) for script in random_set) for script in random_set: del results[script] results[winner] = total_random if sum(results.itervalues()) < int(scale): results[math.weighted_choice(results.iteritems())] += int(scale) - sum(results.itervalues()) return results def get_patron_sendmany(total=None, trunc='0.01'): if total is None: return 'need total argument. go to patron_sendmany/<TOTAL>' total = int(float(total)*1e8) trunc = int(float(trunc)*1e8) return dict( (bitcoin_data.script2_to_address(script, net.PARENT), value/1e8) for script, value in get_current_scaled_txouts(total, trunc).iteritems() if bitcoin_data.script2_to_address(script, net.PARENT) is not None ) def get_global_stats(): # averaged over last hour lookbehind = 3600//net.SHARE_PERIOD if tracker.get_height(current_work.value['best_share_hash']) < lookbehind: return None nonstale_hash_rate = p2pool_data.get_pool_attempts_per_second(tracker, current_work.value['best_share_hash'], lookbehind) stale_prop = p2pool_data.get_average_stale_prop(tracker, current_work.value['best_share_hash'], lookbehind) return dict( pool_nonstale_hash_rate=nonstale_hash_rate, pool_hash_rate=nonstale_hash_rate/(1 - stale_prop), pool_stale_prop=stale_prop, ) def get_local_stats(): lookbehind = 3600//net.SHARE_PERIOD if tracker.get_height(current_work.value['best_share_hash']) < lookbehind: return None global_stale_prop = p2pool_data.get_average_stale_prop(tracker, current_work.value['best_share_hash'], lookbehind) my_unstale_count = sum(1 for share in tracker.get_chain(current_work.value['best_share_hash'], lookbehind) if share.hash in my_share_hashes) my_orphan_count = sum(1 for share in tracker.get_chain(current_work.value['best_share_hash'], lookbehind) if share.hash in my_share_hashes and share.share_data['stale_info'] == 253) my_doa_count = sum(1 for share in tracker.get_chain(current_work.value['best_share_hash'], lookbehind) if share.hash in my_share_hashes and share.share_data['stale_info'] == 254) my_share_count = my_unstale_count + my_orphan_count + my_doa_count my_stale_count = my_orphan_count + my_doa_count my_stale_prop = my_stale_count/my_share_count if my_share_count != 0 else None my_work = sum(bitcoin_data.target_to_average_attempts(share.target) for share in tracker.get_chain(current_work.value['best_share_hash'], lookbehind - 1) if share.hash in my_share_hashes) actual_time = (tracker.shares[current_work.value['best_share_hash']].timestamp - tracker.shares[tracker.get_nth_parent_hash(current_work.value['best_share_hash'], lookbehind - 1)].timestamp) share_att_s = my_work / actual_time miner_hash_rates = {} miner_dead_hash_rates = {} datums, dt = local_rate_monitor.get_datums_in_last() for datum in datums: miner_hash_rates[datum['user']] = miner_hash_rates.get(datum['user'], 0) + datum['work']/dt if datum['dead']: miner_dead_hash_rates[datum['user']] = miner_dead_hash_rates.get(datum['user'], 0) + datum['work']/dt (stale_orphan_shares, stale_doa_shares), shares, _ = get_stale_counts() return dict( my_hash_rates_in_last_hour=dict( note="DEPRECATED", nonstale=share_att_s, rewarded=share_att_s/(1 - global_stale_prop), actual=share_att_s/(1 - my_stale_prop) if my_stale_prop is not None else 0, # 0 because we don't have any shares anyway ), my_share_counts_in_last_hour=dict( shares=my_share_count, unstale_shares=my_unstale_count, stale_shares=my_stale_count, orphan_stale_shares=my_orphan_count, doa_stale_shares=my_doa_count, ), my_stale_proportions_in_last_hour=dict( stale=my_stale_prop, orphan_stale=my_orphan_count/my_share_count if my_share_count != 0 else None, dead_stale=my_doa_count/my_share_count if my_share_count != 0 else None, ), miner_hash_rates=miner_hash_rates, miner_dead_hash_rates=miner_dead_hash_rates, efficiency_if_miner_perfect=(1 - stale_orphan_shares/shares)/(1 - global_stale_prop) if shares else None, # ignores dead shares because those are miner's fault and indicated by pseudoshare rejection ) class WebInterface(resource.Resource): def __init__(self, func, mime_type='application/json', args=()): resource.Resource.__init__(self) self.func, self.mime_type, self.args = func, mime_type, args def getChild(self, child, request): return WebInterface(self.func, self.mime_type, self.args + (child,)) def render_GET(self, request): request.setHeader('Content-Type', self.mime_type) request.setHeader('Access-Control-Allow-Origin', '*') res = self.func(*self.args) return json.dumps(res) if self.mime_type == 'application/json' else res web_root.putChild('rate', WebInterface(lambda: p2pool_data.get_pool_attempts_per_second(tracker, current_work.value['best_share_hash'], 720)/(1-p2pool_data.get_average_stale_prop(tracker, current_work.value['best_share_hash'], 720)))) web_root.putChild('difficulty', WebInterface(lambda: bitcoin_data.target_to_difficulty(tracker.shares[current_work.value['best_share_hash']].max_target))) web_root.putChild('users', WebInterface(get_users)) web_root.putChild('fee', WebInterface(lambda: worker_fee)) web_root.putChild('current_payouts', WebInterface(lambda: dict((bitcoin_data.script2_to_address(script, net.PARENT), value/1e8) for script, value in get_current_txouts().iteritems()))) web_root.putChild('patron_sendmany', WebInterface(get_patron_sendmany, 'text/plain')) web_root.putChild('global_stats', WebInterface(get_global_stats)) web_root.putChild('local_stats', WebInterface(get_local_stats)) web_root.putChild('peer_addresses', WebInterface(lambda: ' '.join(peer.transport.getPeer().host + (':' + str(peer.transport.getPeer().port) if peer.transport.getPeer().port != net.P2P_PORT else '') for peer in p2p_node.peers.itervalues()), 'text/plain')) web_root.putChild('peer_versions', WebInterface(lambda: ''.join('%s:%i ' % peer.addr + peer.other_sub_version + '\n' for peer in p2p_node.peers.itervalues()), 'text/plain')) web_root.putChild('payout_addr', WebInterface(lambda: bitcoin_data.pubkey_hash_to_address(my_pubkey_hash, net.PARENT))) web_root.putChild('recent_blocks', WebInterface(lambda: recent_blocks)) web_root.putChild('uptime', WebInterface(lambda: time.time() - start_time)) new_root = resource.Resource() web_root.putChild('web', new_root) stat_log = [] if os.path.exists(os.path.join(datadir_path, 'stats')): try: with open(os.path.join(datadir_path, 'stats'), 'rb') as f: stat_log = json.loads(f.read()) except: log.err(None, 'Error loading stats:') def update_stat_log(): while stat_log and stat_log[0]['time'] < time.time() - 24*60*60: stat_log.pop(0) lookbehind = 3600//net.SHARE_PERIOD if tracker.get_height(current_work.value['best_share_hash']) < lookbehind: return None global_stale_prop = p2pool_data.get_average_stale_prop(tracker, current_work.value['best_share_hash'], lookbehind) (stale_orphan_shares, stale_doa_shares), shares, _ = get_stale_counts() miner_hash_rates = {} miner_dead_hash_rates = {} datums, dt = local_rate_monitor.get_datums_in_last() for datum in datums: miner_hash_rates[datum['user']] = miner_hash_rates.get(datum['user'], 0) + datum['work']/dt if datum['dead']: miner_dead_hash_rates[datum['user']] = miner_dead_hash_rates.get(datum['user'], 0) + datum['work']/dt stat_log.append(dict( time=time.time(), pool_hash_rate=p2pool_data.get_pool_attempts_per_second(tracker, current_work.value['best_share_hash'], lookbehind)/(1-global_stale_prop), pool_stale_prop=global_stale_prop, local_hash_rates=miner_hash_rates, local_dead_hash_rates=miner_dead_hash_rates, shares=shares, stale_shares=stale_orphan_shares + stale_doa_shares, stale_shares_breakdown=dict(orphan=stale_orphan_shares, doa=stale_doa_shares), current_payout=get_current_txouts().get(bitcoin_data.pubkey_hash_to_script2(my_pubkey_hash), 0)*1e-8, peers=dict( incoming=sum(1 for peer in p2p_node.peers.itervalues() if peer.incoming), outgoing=sum(1 for peer in p2p_node.peers.itervalues() if not peer.incoming), ), attempts_to_share=bitcoin_data.target_to_average_attempts(tracker.shares[current_work.value['best_share_hash']].max_target), attempts_to_block=bitcoin_data.target_to_average_attempts(current_work.value['bits'].target), block_value=current_work2.value['subsidy']*1e-8, )) with open(os.path.join(datadir_path, 'stats'), 'wb') as f: f.write(json.dumps(stat_log)) task.LoopingCall(update_stat_log).start(5*60) new_root.putChild('log', WebInterface(lambda: stat_log)) def get_share(share_hash_str): if int(share_hash_str, 16) not in tracker.shares: return None share = tracker.shares[int(share_hash_str, 16)] return dict( parent='%064x' % share.previous_hash, children=['%064x' % x for x in sorted(tracker.reverse_shares.get(share.hash, set()), key=lambda sh: -len(tracker.reverse_shares.get(sh, set())))], # sorted from most children to least children local=dict( verified=share.hash in tracker.verified.shares, time_first_seen=start_time if share.time_seen == 0 else share.time_seen, peer_first_received_from=share.peer.addr if share.peer is not None else None, ), share_data=dict( timestamp=share.timestamp, target=share.target, max_target=share.max_target, payout_address=bitcoin_data.script2_to_address(share.new_script, net.PARENT), donation=share.share_data['donation']/65535, stale_info=share.share_data['stale_info'], nonce=share.share_data['nonce'], ), block=dict( hash='%064x' % share.header_hash, header=dict( version=share.header['version'], previous_block='%064x' % share.header['previous_block'], merkle_root='%064x' % share.header['merkle_root'], timestamp=share.header['timestamp'], target=share.header['bits'].target, nonce=share.header['nonce'], ), gentx=dict( hash='%064x' % share.gentx_hash, coinbase=share.share_data['coinbase'].ljust(2, '\x00').encode('hex'), value=share.share_data['subsidy']*1e-8, ), txn_count_range=[len(share.other_txs), len(share.other_txs)] if share.other_txs is not None else 1 if len(share.merkle_link['branch']) == 0 else [2**len(share.merkle_link['branch'])//2+1, 2**len(share.merkle_link['branch'])], ), ) new_root.putChild('share', WebInterface(lambda share_hash_str: get_share(share_hash_str))) new_root.putChild('heads', WebInterface(lambda: ['%064x' % x for x in tracker.heads])) new_root.putChild('verified_heads', WebInterface(lambda: ['%064x' % x for x in tracker.verified.heads])) new_root.putChild('tails', WebInterface(lambda: ['%064x' % x for t in tracker.tails for x in tracker.reverse_shares.get(t, set())])) new_root.putChild('verified_tails', WebInterface(lambda: ['%064x' % x for t in tracker.verified.tails for x in tracker.verified.reverse_shares.get(t, set())])) new_root.putChild('best_share_hash', WebInterface(lambda: '%064x' % current_work.value['best_share_hash'])) class Explorer(resource.Resource): def render_GET(self, request): return 'moved to /static/explorer.html' def getChild(self, child, request): return self new_root.putChild('explorer', Explorer()) hd_path = os.path.join(datadir_path, 'graph_db') hd_data = _atomic_read(hd_path) hd_obj = {} if hd_data is not None: try: hd_obj = json.loads(hd_data) except Exception: log.err(None, 'Error reading graph database:') dataview_descriptions = { 'last_hour': graph.DataViewDescription(150, 60*60), 'last_day': graph.DataViewDescription(300, 60*60*24), 'last_week': graph.DataViewDescription(300, 60*60*24*7), 'last_month': graph.DataViewDescription(300, 60*60*24*30), 'last_year': graph.DataViewDescription(300, 60*60*24*365.25), } def combine_and_keep_largest(*dicts): res = {} for d in dicts: for k, v in d.iteritems(): res[k] = res.get(k, 0) + v return dict((k, v) for k, v in sorted(res.iteritems(), key=lambda (k, v): v)[-30:] if v) hd = graph.HistoryDatabase.from_obj({ 'local_hash_rate': graph.DataStreamDescription(False, dataview_descriptions), 'local_dead_hash_rate': graph.DataStreamDescription(False, dataview_descriptions), 'local_share_hash_rate': graph.DataStreamDescription(False, dataview_descriptions), 'local_dead_share_hash_rate': graph.DataStreamDescription(False, dataview_descriptions), 'pool_rate': graph.DataStreamDescription(True, dataview_descriptions), 'pool_stale_rate': graph.DataStreamDescription(True, dataview_descriptions), 'current_payout': graph.DataStreamDescription(True, dataview_descriptions), 'incoming_peers': graph.DataStreamDescription(True, dataview_descriptions), 'outgoing_peers': graph.DataStreamDescription(True, dataview_descriptions), 'miner_hash_rates': graph.DataStreamDescription(False, dataview_descriptions, {}, combine_and_keep_largest, math.mult_dict), 'miner_dead_hash_rates': graph.DataStreamDescription(False, dataview_descriptions, {}, combine_and_keep_largest, math.mult_dict), }, hd_obj) task.LoopingCall(lambda: _atomic_write(hd_path, json.dumps(hd.to_obj()))).start(100) @pseudoshare_received.watch def _(work, dead, user, had_vip_pass): t = time.time() hd.datastreams['local_hash_rate'].add_datum(t, work) if dead: hd.datastreams['local_dead_hash_rate'].add_datum(t, work) if user is not None: hd.datastreams['miner_hash_rates'].add_datum(t, {user: work}) if dead: hd.datastreams['miner_dead_hash_rates'].add_datum(t, {user: work}) @share_received.watch def _(work, dead): t = time.time() hd.datastreams['local_share_hash_rate'].add_datum(t, work) if dead: hd.datastreams['local_dead_share_hash_rate'].add_datum(t, work) def add_point(): if tracker.get_height(current_work.value['best_share_hash']) < 720: return nonstalerate = p2pool_data.get_pool_attempts_per_second(tracker, current_work.value['best_share_hash'], 720) poolrate = nonstalerate / (1 - p2pool_data.get_average_stale_prop(tracker, current_work.value['best_share_hash'], 720)) t = time.time() hd.datastreams['pool_rate'].add_datum(t, poolrate) hd.datastreams['pool_stale_rate'].add_datum(t, poolrate - nonstalerate) hd.datastreams['current_payout'].add_datum(t, get_current_txouts().get(bitcoin_data.pubkey_hash_to_script2(my_pubkey_hash), 0)*1e-8) hd.datastreams['incoming_peers'].add_datum(t, sum(1 for peer in p2p_node.peers.itervalues() if peer.incoming)) hd.datastreams['outgoing_peers'].add_datum(t, sum(1 for peer in p2p_node.peers.itervalues() if not peer.incoming)) task.LoopingCall(add_point).start(5) new_root.putChild('graph_data', WebInterface(lambda source, view: hd.datastreams[source].dataviews[view].get_data(time.time()))) web_root.putChild('static', static.File(os.path.join(os.path.dirname(sys.argv[0]), 'web-static'))) return web_root
def get_web_root(wb, datadir_path, bitcoind_getinfo_var, stop_event=variable.Event()): node = wb.node start_time = time.time() web_root = resource.Resource() def get_users(): height, last = node.tracker.get_height_and_last(node.best_share_var.value) weights, total_weight, donation_weight = node.tracker.get_cumulative_weights( node.best_share_var.value, min(height, 720), 65535 * 2 ** 256 ) res = {} for script in sorted(weights, key=lambda s: weights[s]): res[bitcoin_data.script2_to_address(script, node.net.PARENT)] = weights[script] / total_weight return res def get_current_scaled_txouts(scale, trunc=0): txouts = node.get_current_txouts() total = sum(txouts.itervalues()) results = dict((script, value * scale // total) for script, value in txouts.iteritems()) if trunc > 0: total_random = 0 random_set = set() for s in sorted(results, key=results.__getitem__): if results[s] >= trunc: break total_random += results[s] random_set.add(s) if total_random: winner = math.weighted_choice((script, results[script]) for script in random_set) for script in random_set: del results[script] results[winner] = total_random if sum(results.itervalues()) < int(scale): results[math.weighted_choice(results.iteritems())] += int(scale) - sum(results.itervalues()) return results def get_patron_sendmany(total=None, trunc="0.01"): if total is None: return "need total argument. go to patron_sendmany/<TOTAL>" total = int(float(total) * 1e8) trunc = int(float(trunc) * 1e8) return json.dumps( dict( (bitcoin_data.script2_to_address(script, node.net.PARENT), value / 1e8) for script, value in get_current_scaled_txouts(total, trunc).iteritems() if bitcoin_data.script2_to_address(script, node.net.PARENT) is not None ) ) def get_global_stats(): # averaged over last hour if node.tracker.get_height(node.best_share_var.value) < 10: return None lookbehind = min(node.tracker.get_height(node.best_share_var.value), 3600 // node.net.SHARE_PERIOD) nonstale_hash_rate = p2pool_data.get_pool_attempts_per_second( node.tracker, node.best_share_var.value, lookbehind ) stale_prop = p2pool_data.get_average_stale_prop(node.tracker, node.best_share_var.value, lookbehind) return dict( pool_nonstale_hash_rate=nonstale_hash_rate, pool_hash_rate=nonstale_hash_rate / (1 - stale_prop), pool_stale_prop=stale_prop, min_difficulty=bitcoin_data.target_to_difficulty(node.tracker.items[node.best_share_var.value].max_target), ) def get_local_stats(): if node.tracker.get_height(node.best_share_var.value) < 10: return None lookbehind = min(node.tracker.get_height(node.best_share_var.value), 3600 // node.net.SHARE_PERIOD) global_stale_prop = p2pool_data.get_average_stale_prop(node.tracker, node.best_share_var.value, lookbehind) my_unstale_count = sum( 1 for share in node.tracker.get_chain(node.best_share_var.value, lookbehind) if share.hash in wb.my_share_hashes ) my_orphan_count = sum( 1 for share in node.tracker.get_chain(node.best_share_var.value, lookbehind) if share.hash in wb.my_share_hashes and share.share_data["stale_info"] == "orphan" ) my_doa_count = sum( 1 for share in node.tracker.get_chain(node.best_share_var.value, lookbehind) if share.hash in wb.my_share_hashes and share.share_data["stale_info"] == "doa" ) my_share_count = my_unstale_count + my_orphan_count + my_doa_count my_stale_count = my_orphan_count + my_doa_count my_stale_prop = my_stale_count / my_share_count if my_share_count != 0 else None my_work = sum( bitcoin_data.target_to_average_attempts(share.target) for share in node.tracker.get_chain(node.best_share_var.value, lookbehind - 1) if share.hash in wb.my_share_hashes ) actual_time = ( node.tracker.items[node.best_share_var.value].timestamp - node.tracker.items[node.tracker.get_nth_parent_hash(node.best_share_var.value, lookbehind - 1)].timestamp ) share_att_s = my_work / actual_time miner_hash_rates, miner_dead_hash_rates = wb.get_local_rates() (stale_orphan_shares, stale_doa_shares), shares, _ = wb.get_stale_counts() return dict( my_hash_rates_in_last_hour=dict( note="DEPRECATED", nonstale=share_att_s, rewarded=share_att_s / (1 - global_stale_prop), actual=share_att_s / (1 - my_stale_prop) if my_stale_prop is not None else 0, # 0 because we don't have any shares anyway ), my_share_counts_in_last_hour=dict( shares=my_share_count, unstale_shares=my_unstale_count, stale_shares=my_stale_count, orphan_stale_shares=my_orphan_count, doa_stale_shares=my_doa_count, ), my_stale_proportions_in_last_hour=dict( stale=my_stale_prop, orphan_stale=my_orphan_count / my_share_count if my_share_count != 0 else None, dead_stale=my_doa_count / my_share_count if my_share_count != 0 else None, ), miner_hash_rates=miner_hash_rates, miner_dead_hash_rates=miner_dead_hash_rates, efficiency_if_miner_perfect=(1 - stale_orphan_shares / shares) / (1 - global_stale_prop) if shares else None, # ignores dead shares because those are miner's fault and indicated by pseudoshare rejection efficiency=(1 - (stale_orphan_shares + stale_doa_shares) / shares) / (1 - global_stale_prop) if shares else None, peers=dict( incoming=sum(1 for peer in node.p2p_node.peers.itervalues() if peer.incoming), outgoing=sum(1 for peer in node.p2p_node.peers.itervalues() if not peer.incoming), ), shares=dict(total=shares, orphan=stale_orphan_shares, dead=stale_doa_shares), uptime=time.time() - start_time, attempts_to_share=bitcoin_data.target_to_average_attempts( node.tracker.items[node.best_share_var.value].max_target ), attempts_to_block=bitcoin_data.target_to_average_attempts(node.bitcoind_work.value["bits"].target), block_value=node.bitcoind_work.value["subsidy"] * 1e-8, warnings=p2pool_data.get_warnings( node.tracker, node.best_share_var.value, node.net, bitcoind_getinfo_var.value, node.bitcoind_work.value ), donation_proportion=wb.donation_percentage / 100, version=p2pool.__version__, protocol_version=p2p.Protocol.VERSION, fee=wb.worker_fee, ) class WebInterface(deferred_resource.DeferredResource): def __init__(self, func, mime_type="application/json", args=()): deferred_resource.DeferredResource.__init__(self) self.func, self.mime_type, self.args = func, mime_type, args def getChild(self, child, request): return WebInterface(self.func, self.mime_type, self.args + (child,)) @defer.inlineCallbacks def render_GET(self, request): request.setHeader("Content-Type", self.mime_type) request.setHeader("Access-Control-Allow-Origin", "*") res = yield self.func(*self.args) defer.returnValue(json.dumps(res) if self.mime_type == "application/json" else res) def decent_height(): return min(node.tracker.get_height(node.best_share_var.value), 720) web_root.putChild( "rate", WebInterface( lambda: p2pool_data.get_pool_attempts_per_second(node.tracker, node.best_share_var.value, decent_height()) / (1 - p2pool_data.get_average_stale_prop(node.tracker, node.best_share_var.value, decent_height())) ), ) web_root.putChild( "difficulty", WebInterface( lambda: bitcoin_data.target_to_difficulty(node.tracker.items[node.best_share_var.value].max_target) ), ) web_root.putChild("users", WebInterface(get_users)) web_root.putChild( "user_stales", WebInterface( lambda: dict( (bitcoin_data.pubkey_hash_to_address(ph, node.net.PARENT), prop) for ph, prop in p2pool_data.get_user_stale_props( node.tracker, node.best_share_var.value, node.tracker.get_height(node.best_share_var.value) ).iteritems() ) ), ) web_root.putChild("fee", WebInterface(lambda: wb.worker_fee)) web_root.putChild( "current_payouts", WebInterface( lambda: dict( (bitcoin_data.script2_to_address(script, node.net.PARENT), value / 1e8) for script, value in node.get_current_txouts().iteritems() ) ), ) web_root.putChild("patron_sendmany", WebInterface(get_patron_sendmany, "text/plain")) web_root.putChild("global_stats", WebInterface(get_global_stats)) web_root.putChild("local_stats", WebInterface(get_local_stats)) web_root.putChild( "peer_addresses", WebInterface( lambda: " ".join( "%s%s" % ( peer.transport.getPeer().host, ":" + str(peer.transport.getPeer().port) if peer.transport.getPeer().port != node.net.P2P_PORT else "", ) for peer in node.p2p_node.peers.itervalues() ) ), ) web_root.putChild( "peer_txpool_sizes", WebInterface( lambda: dict( ("%s:%i" % (peer.transport.getPeer().host, peer.transport.getPeer().port), peer.remembered_txs_size) for peer in node.p2p_node.peers.itervalues() ) ), ) web_root.putChild( "pings", WebInterface( defer.inlineCallbacks( lambda: defer.returnValue( dict( [ (a, (yield b)) for a, b in [ ( "%s:%i" % (peer.transport.getPeer().host, peer.transport.getPeer().port), defer.inlineCallbacks( lambda peer=peer: defer.returnValue( min( [ ( yield peer.do_ping() .addCallback(lambda x: x / 0.001) .addErrback(lambda fail: None) ) for i in xrange(3) ] ) ) )(), ) for peer in list(node.p2p_node.peers.itervalues()) ] ] ) ) ) ), ) web_root.putChild( "peer_versions", WebInterface( lambda: dict(("%s:%i" % peer.addr, peer.other_sub_version) for peer in node.p2p_node.peers.itervalues()) ), ) web_root.putChild( "payout_addr", WebInterface(lambda: bitcoin_data.pubkey_hash_to_address(wb.my_pubkey_hash, node.net.PARENT)) ) web_root.putChild( "recent_blocks", WebInterface( lambda: [ dict( ts=s.timestamp, hash="%064x" % s.header_hash, number=p2pool_data.parse_bip0034(s.share_data["coinbase"])[0], share="%064x" % s.hash, ) for s in node.tracker.get_chain( node.best_share_var.value, min(node.tracker.get_height(node.best_share_var.value), 24 * 60 * 60 // node.net.SHARE_PERIOD), ) if s.pow_hash <= s.header["bits"].target ] ), ) web_root.putChild("uptime", WebInterface(lambda: time.time() - start_time)) web_root.putChild( "stale_rates", WebInterface( lambda: p2pool_data.get_stale_counts(node.tracker, node.best_share_var.value, decent_height(), rates=True) ), ) new_root = resource.Resource() web_root.putChild("web", new_root) stat_log = [] if os.path.exists(os.path.join(datadir_path, "stats")): try: with open(os.path.join(datadir_path, "stats"), "rb") as f: stat_log = json.loads(f.read()) except: log.err(None, "Error loading stats:") def update_stat_log(): while stat_log and stat_log[0]["time"] < time.time() - 24 * 60 * 60: stat_log.pop(0) lookbehind = 3600 // node.net.SHARE_PERIOD if node.tracker.get_height(node.best_share_var.value) < lookbehind: return None global_stale_prop = p2pool_data.get_average_stale_prop(node.tracker, node.best_share_var.value, lookbehind) (stale_orphan_shares, stale_doa_shares), shares, _ = wb.get_stale_counts() miner_hash_rates, miner_dead_hash_rates = wb.get_local_rates() stat_log.append( dict( time=time.time(), pool_hash_rate=p2pool_data.get_pool_attempts_per_second( node.tracker, node.best_share_var.value, lookbehind ) / (1 - global_stale_prop), pool_stale_prop=global_stale_prop, local_hash_rates=miner_hash_rates, local_dead_hash_rates=miner_dead_hash_rates, shares=shares, stale_shares=stale_orphan_shares + stale_doa_shares, stale_shares_breakdown=dict(orphan=stale_orphan_shares, doa=stale_doa_shares), current_payout=node.get_current_txouts().get(bitcoin_data.pubkey_hash_to_script2(wb.my_pubkey_hash), 0) * 1e-8, peers=dict( incoming=sum(1 for peer in node.p2p_node.peers.itervalues() if peer.incoming), outgoing=sum(1 for peer in node.p2p_node.peers.itervalues() if not peer.incoming), ), attempts_to_share=bitcoin_data.target_to_average_attempts( node.tracker.items[node.best_share_var.value].max_target ), attempts_to_block=bitcoin_data.target_to_average_attempts(node.bitcoind_work.value["bits"].target), block_value=node.bitcoind_work.value["subsidy"] * 1e-8, ) ) with open(os.path.join(datadir_path, "stats"), "wb") as f: f.write(json.dumps(stat_log)) x = deferral.RobustLoopingCall(update_stat_log) x.start(5 * 60) stop_event.watch(x.stop) new_root.putChild("log", WebInterface(lambda: stat_log)) def get_share(share_hash_str): if int(share_hash_str, 16) not in node.tracker.items: return None share = node.tracker.items[int(share_hash_str, 16)] return dict( parent="%064x" % share.previous_hash, children=[ "%064x" % x for x in sorted( node.tracker.reverse.get(share.hash, set()), key=lambda sh: -len(node.tracker.reverse.get(sh, set())), ) ], # sorted from most children to least children type_name=type(share).__name__, local=dict( verified=share.hash in node.tracker.verified.items, time_first_seen=start_time if share.time_seen == 0 else share.time_seen, peer_first_received_from=share.peer_addr, ), share_data=dict( timestamp=share.timestamp, target=share.target, max_target=share.max_target, payout_address=bitcoin_data.script2_to_address(share.new_script, node.net.PARENT), donation=share.share_data["donation"] / 65535, stale_info=share.share_data["stale_info"], nonce=share.share_data["nonce"], desired_version=share.share_data["desired_version"], absheight=share.absheight, abswork=share.abswork, ), block=dict( hash="%064x" % share.header_hash, header=dict( version=share.header["version"], previous_block="%064x" % share.header["previous_block"], merkle_root="%064x" % share.header["merkle_root"], timestamp=share.header["timestamp"], target=share.header["bits"].target, nonce=share.header["nonce"], ), gentx=dict( hash="%064x" % share.gentx_hash, coinbase=share.share_data["coinbase"].ljust(2, "\x00").encode("hex"), value=share.share_data["subsidy"] * 1e-8, last_txout_nonce="%016x" % share.contents["last_txout_nonce"], ), other_transaction_hashes=["%064x" % x for x in share.get_other_tx_hashes(node.tracker)], ), ) new_root.putChild("share", WebInterface(lambda share_hash_str: get_share(share_hash_str))) new_root.putChild("heads", WebInterface(lambda: ["%064x" % x for x in node.tracker.heads])) new_root.putChild("verified_heads", WebInterface(lambda: ["%064x" % x for x in node.tracker.verified.heads])) new_root.putChild( "tails", WebInterface(lambda: ["%064x" % x for t in node.tracker.tails for x in node.tracker.reverse.get(t, set())]), ) new_root.putChild( "verified_tails", WebInterface( lambda: [ "%064x" % x for t in node.tracker.verified.tails for x in node.tracker.verified.reverse.get(t, set()) ] ), ) new_root.putChild("best_share_hash", WebInterface(lambda: "%064x" % node.best_share_var.value)) new_root.putChild( "my_share_hashes", WebInterface(lambda: ["%064x" % my_share_hash for my_share_hash in wb.my_share_hashes]) ) def get_share_data(share_hash_str): if int(share_hash_str, 16) not in node.tracker.items: return "" share = node.tracker.items[int(share_hash_str, 16)] return p2pool_data.share_type.pack(share.as_share()) new_root.putChild( "share_data", WebInterface(lambda share_hash_str: get_share_data(share_hash_str), "application/octet-stream") ) new_root.putChild( "currency_info", WebInterface( lambda: dict( symbol=node.net.PARENT.SYMBOL, block_explorer_url_prefix=node.net.PARENT.BLOCK_EXPLORER_URL_PREFIX, address_explorer_url_prefix=node.net.PARENT.ADDRESS_EXPLORER_URL_PREFIX, tx_explorer_url_prefix=node.net.PARENT.TX_EXPLORER_URL_PREFIX, ) ), ) new_root.putChild("version", WebInterface(lambda: p2pool.__version__)) hd_path = os.path.join(datadir_path, "graph_db") hd_data = _atomic_read(hd_path) hd_obj = {} if hd_data is not None: try: hd_obj = json.loads(hd_data) except Exception: log.err(None, "Error reading graph database:") dataview_descriptions = { "last_hour": graph.DataViewDescription(150, 60 * 60), "last_day": graph.DataViewDescription(300, 60 * 60 * 24), "last_week": graph.DataViewDescription(300, 60 * 60 * 24 * 7), "last_month": graph.DataViewDescription(300, 60 * 60 * 24 * 30), "last_year": graph.DataViewDescription(300, 60 * 60 * 24 * 365.25), } hd = graph.HistoryDatabase.from_obj( { "local_hash_rate": graph.DataStreamDescription(dataview_descriptions, is_gauge=False), "local_dead_hash_rate": graph.DataStreamDescription(dataview_descriptions, is_gauge=False), "local_share_hash_rates": graph.DataStreamDescription( dataview_descriptions, is_gauge=False, multivalues=True, multivalue_undefined_means_0=True, default_func=graph.make_multivalue_migrator( dict( good="local_share_hash_rate", dead="local_dead_share_hash_rate", orphan="local_orphan_share_hash_rate", ), post_func=lambda bins: [ dict( ( k, ( v[0] - ( sum(bin.get(rem_k, (0, 0))[0] for rem_k in ["dead", "orphan"]) if k == "good" else 0 ), v[1], ), ) for k, v in bin.iteritems() ) for bin in bins ], ), ), "pool_rates": graph.DataStreamDescription( dataview_descriptions, multivalues=True, multivalue_undefined_means_0=True ), "current_payout": graph.DataStreamDescription(dataview_descriptions), "current_payouts": graph.DataStreamDescription(dataview_descriptions, multivalues=True), "peers": graph.DataStreamDescription( dataview_descriptions, multivalues=True, default_func=graph.make_multivalue_migrator(dict(incoming="incoming_peers", outgoing="outgoing_peers")), ), "miner_hash_rates": graph.DataStreamDescription(dataview_descriptions, is_gauge=False, multivalues=True), "miner_dead_hash_rates": graph.DataStreamDescription( dataview_descriptions, is_gauge=False, multivalues=True ), "desired_version_rates": graph.DataStreamDescription( dataview_descriptions, multivalues=True, multivalue_undefined_means_0=True ), "traffic_rate": graph.DataStreamDescription(dataview_descriptions, is_gauge=False, multivalues=True), "getwork_latency": graph.DataStreamDescription(dataview_descriptions), "memory_usage": graph.DataStreamDescription(dataview_descriptions), }, hd_obj, ) x = deferral.RobustLoopingCall(lambda: _atomic_write(hd_path, json.dumps(hd.to_obj()))) x.start(100) stop_event.watch(x.stop) @wb.pseudoshare_received.watch def _(work, dead, user): t = time.time() hd.datastreams["local_hash_rate"].add_datum(t, work) if dead: hd.datastreams["local_dead_hash_rate"].add_datum(t, work) if user is not None: hd.datastreams["miner_hash_rates"].add_datum(t, {user: work}) if dead: hd.datastreams["miner_dead_hash_rates"].add_datum(t, {user: work}) @wb.share_received.watch def _(work, dead, share_hash): t = time.time() if not dead: hd.datastreams["local_share_hash_rates"].add_datum(t, dict(good=work)) else: hd.datastreams["local_share_hash_rates"].add_datum(t, dict(dead=work)) def later(): res = node.tracker.is_child_of(share_hash, node.best_share_var.value) if res is None: res = False # share isn't connected to sharechain? assume orphaned if res and dead: # share was DOA, but is now in sharechain # move from dead to good hd.datastreams["local_share_hash_rates"].add_datum(t, dict(dead=-work, good=work)) elif not res and not dead: # share wasn't DOA, and isn't in sharechain # move from good to orphan hd.datastreams["local_share_hash_rates"].add_datum(t, dict(good=-work, orphan=work)) reactor.callLater(200, later) @node.p2p_node.traffic_happened.watch def _(name, bytes): hd.datastreams["traffic_rate"].add_datum(time.time(), {name: bytes}) def add_point(): if node.tracker.get_height(node.best_share_var.value) < 10: return None lookbehind = min( node.net.CHAIN_LENGTH, 60 * 60 // node.net.SHARE_PERIOD, node.tracker.get_height(node.best_share_var.value) ) t = time.time() pool_rates = p2pool_data.get_stale_counts(node.tracker, node.best_share_var.value, lookbehind, rates=True) pool_total = sum(pool_rates.itervalues()) hd.datastreams["pool_rates"].add_datum(t, pool_rates) current_txouts = node.get_current_txouts() hd.datastreams["current_payout"].add_datum( t, current_txouts.get(bitcoin_data.pubkey_hash_to_script2(wb.my_pubkey_hash), 0) * 1e-8 ) miner_hash_rates, miner_dead_hash_rates = wb.get_local_rates() current_txouts_by_address = dict( (bitcoin_data.script2_to_address(script, node.net.PARENT), amount) for script, amount in current_txouts.iteritems() ) hd.datastreams["current_payouts"].add_datum( t, dict( (user, current_txouts_by_address[user] * 1e-8) for user in miner_hash_rates if user in current_txouts_by_address ), ) hd.datastreams["peers"].add_datum( t, dict( incoming=sum(1 for peer in node.p2p_node.peers.itervalues() if peer.incoming), outgoing=sum(1 for peer in node.p2p_node.peers.itervalues() if not peer.incoming), ), ) vs = p2pool_data.get_desired_version_counts(node.tracker, node.best_share_var.value, lookbehind) vs_total = sum(vs.itervalues()) hd.datastreams["desired_version_rates"].add_datum( t, dict((str(k), v / vs_total * pool_total) for k, v in vs.iteritems()) ) try: hd.datastreams["memory_usage"].add_datum(t, memory.resident()) except: if p2pool.DEBUG: traceback.print_exc() x = deferral.RobustLoopingCall(add_point) x.start(5) stop_event.watch(x.stop) @node.bitcoind_work.changed.watch def _(new_work): hd.datastreams["getwork_latency"].add_datum(time.time(), new_work["latency"]) new_root.putChild( "graph_data", WebInterface(lambda source, view: hd.datastreams[source].dataviews[view].get_data(time.time())) ) web_root.putChild("static", static.File(os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), "web-static"))) return web_root
def get_work(self, pubkey_hash, desired_share_target, desired_pseudoshare_target): if self.node.best_share_var.value is None and self.node.net.PERSIST: raise jsonrpc.Error_for_code(-12345)(u'p2pool is downloading shares') if self.merged_work.value: tree, size = bitcoin_data.make_auxpow_tree(self.merged_work.value) mm_hashes = [self.merged_work.value.get(tree.get(i), dict(hash=0))['hash'] for i in xrange(size)] mm_data = '\xfa\xbemm' + bitcoin_data.aux_pow_coinbase_type.pack(dict( merkle_root=bitcoin_data.merkle_hash(mm_hashes), size=size, nonce=0, )) mm_later = [(aux_work, mm_hashes.index(aux_work['hash']), mm_hashes) for chain_id, aux_work in self.merged_work.value.iteritems()] else: mm_data = '' mm_later = [] tx_hashes = [bitcoin_data.hash256(bitcoin_data.tx_type.pack(tx)) for tx in self.current_work.value['transactions']] tx_map = dict(zip(tx_hashes, self.current_work.value['transactions'])) previous_share = self.node.tracker.items[self.node.best_share_var.value] if self.node.best_share_var.value is not None else None if previous_share is None: share_type = p2pool_data.Share else: previous_share_type = type(previous_share) if previous_share_type.SUCCESSOR is None or self.node.tracker.get_height(previous_share.hash) < self.node.net.CHAIN_LENGTH: share_type = previous_share_type else: successor_type = previous_share_type.SUCCESSOR counts = p2pool_data.get_desired_version_counts(self.node.tracker, self.node.tracker.get_nth_parent_hash(previous_share.hash, self.node.net.CHAIN_LENGTH*9//10), self.node.net.CHAIN_LENGTH//10) upgraded = counts.get(successor_type.VERSION, 0)/sum(counts.itervalues()) if upgraded > .65: print 'Switchover imminent. Upgraded: %.3f%% Threshold: %.3f%%' % (upgraded*100, 95) print # Share -> NewShare only valid if 95% of hashes in [net.CHAIN_LENGTH*9//10, net.CHAIN_LENGTH] for new version if counts.get(successor_type.VERSION, 0) > sum(counts.itervalues())*95//100: share_type = successor_type else: share_type = previous_share_type if desired_share_target is None: desired_share_target = 2**256-1 # will be reduced to SANE_TARGET_RANGE later local_hash_rate = self._estimate_local_hash_rate() if local_hash_rate is not None: desired_share_target = min(desired_share_target, bitcoin_data.average_attempts_to_target(local_hash_rate * self.node.net.SHARE_PERIOD / 0.0167)) # limit to 1.67% of pool shares by modulating share difficulty local_addr_rates = self.get_local_addr_rates() lookbehind = 3600//self.node.net.SHARE_PERIOD block_subsidy = self.node.bitcoind_work.value['subsidy'] if previous_share is not None and self.node.tracker.get_height(previous_share.hash) > lookbehind: expected_payout_per_block = local_addr_rates.get(pubkey_hash, 0)/p2pool_data.get_pool_attempts_per_second(self.node.tracker, self.node.best_share_var.value, lookbehind) \ * block_subsidy*(1-self.donation_percentage/100) # XXX doesn't use global stale rate to compute pool hash if expected_payout_per_block < self.node.net.PARENT.DUST_THRESHOLD: desired_share_target = min(desired_share_target, bitcoin_data.average_attempts_to_target((bitcoin_data.target_to_average_attempts(self.node.bitcoind_work.value['bits'].target)*self.node.net.SPREAD)*self.node.net.PARENT.DUST_THRESHOLD/block_subsidy) ) if True: share_info, gentx, other_transaction_hashes, get_share = share_type.generate_transaction( tracker=self.node.tracker, share_data=dict( previous_share_hash=self.node.best_share_var.value, coinbase=(script.create_push_script([ self.current_work.value['height'], ] + ([mm_data] if mm_data else []) + [ ]) + self.current_work.value['coinbaseflags'])[:100], nonce=random.randrange(2**32), pubkey_hash=pubkey_hash, subsidy=self.current_work.value['subsidy'], donation=math.perfect_round(65535*self.donation_percentage/100), stale_info=(lambda (orphans, doas), total, (orphans_recorded_in_chain, doas_recorded_in_chain): 'orphan' if orphans > orphans_recorded_in_chain else 'doa' if doas > doas_recorded_in_chain else None )(*self.get_stale_counts()), desired_version=(share_type.SUCCESSOR if share_type.SUCCESSOR is not None else share_type).VOTING_VERSION, ), block_target=self.current_work.value['bits'].target, desired_timestamp=int(time.time() + 0.5), desired_target=desired_share_target, ref_merkle_link=dict(branch=[], index=0), desired_other_transaction_hashes_and_fees=zip(tx_hashes, self.current_work.value['transaction_fees']), net=self.node.net, known_txs=tx_map, base_subsidy=self.node.net.PARENT.SUBSIDY_FUNC(self.current_work.value['height']), ) packed_gentx = bitcoin_data.tx_type.pack(gentx) other_transactions = [tx_map[tx_hash] for tx_hash in other_transaction_hashes] mm_later = [(dict(aux_work, target=aux_work['target'] if aux_work['target'] == 'p2pool' else share_info['bits'].target), index, hashes) for aux_work, index, hashes in mm_later] if desired_pseudoshare_target is None: target = 2**256-1 local_hash_rate = self._estimate_local_hash_rate() if local_hash_rate is not None: target = min(target, bitcoin_data.average_attempts_to_target(local_hash_rate * 1)) # limit to 1 share response every second by modulating pseudoshare difficulty else: target = desired_pseudoshare_target # target = max(target, share_info['bits'].target) for aux_work, index, hashes in mm_later: target = max(target, aux_work['target']) target = math.clip(target, self.node.net.PARENT.SANE_TARGET_RANGE) getwork_time = time.time() lp_count = self.new_work_event.times merkle_link = bitcoin_data.calculate_merkle_link([None] + other_transaction_hashes, 0) print 'New work for worker! Difficulty: %.06f Share difficulty: %.08f Total block value: %.6f %s including %i transactions' % ( bitcoin_data.target_to_difficulty(target), bitcoin_data.target_to_difficulty(share_info['bits'].target), self.current_work.value['subsidy']*1e-8, self.node.net.PARENT.SYMBOL, len(self.current_work.value['transactions']), ) ba = dict( version=min(self.current_work.value['version'], 2), previous_block=self.current_work.value['previous_block'], merkle_link=merkle_link, coinb1=packed_gentx[:-self.COINBASE_NONCE_LENGTH-4], coinb2=packed_gentx[-4:], timestamp=self.current_work.value['time'], bits=self.current_work.value['bits'], share_target=target, ) received_header_hashes = set() def got_response(header, user, coinbase_nonce): assert len(coinbase_nonce) == self.COINBASE_NONCE_LENGTH new_packed_gentx = packed_gentx[:-self.COINBASE_NONCE_LENGTH-4] + coinbase_nonce + packed_gentx[-4:] if coinbase_nonce != '\0'*self.COINBASE_NONCE_LENGTH else packed_gentx new_gentx = bitcoin_data.tx_type.unpack(new_packed_gentx) if coinbase_nonce != '\0'*self.COINBASE_NONCE_LENGTH else gentx header_hash = bitcoin_data.hash256(bitcoin_data.block_header_type.pack(header)) pow_hash = self.node.net.PARENT.POW_FUNC(bitcoin_data.block_header_type.pack(header)) try: if pow_hash <= header['bits'].target or p2pool.DEBUG: helper.submit_block(dict(header=header, txs=[new_gentx] + other_transactions), False, self.node.factory, self.node.bitcoind, self.node.bitcoind_work, self.node.net) if pow_hash <= header['bits'].target: print print 'GOT BLOCK FROM MINER! Passing to bitcoind! %s%064x' % (self.node.net.PARENT.BLOCK_EXPLORER_URL_PREFIX, header_hash) print except: log.err(None, 'Error while processing potential block:') user, _, _, _ = self.get_user_details(user) assert header['previous_block'] == ba['previous_block'] assert header['merkle_root'] == bitcoin_data.check_merkle_link(bitcoin_data.hash256(new_packed_gentx), merkle_link) assert header['bits'] == ba['bits'] on_time = self.new_work_event.times == lp_count for aux_work, index, hashes in mm_later: try: if pow_hash <= aux_work['target'] or p2pool.DEBUG: df = deferral.retry('Error submitting merged block: (will retry)', 10, 10)(aux_work['merged_proxy'].rpc_getauxblock)( pack.IntType(256, 'big').pack(aux_work['hash']).encode('hex'), # neoscrypt uses little endian only # pack.IntType(256, 'little').pack(aux_work['hash']).encode('hex'), bitcoin_data.aux_pow_type.pack(dict( merkle_tx=dict( tx=new_gentx, block_hash=header_hash, merkle_link=merkle_link, ), merkle_link=bitcoin_data.calculate_merkle_link(hashes, index), parent_block_header=header, )).encode('hex'), ) @df.addCallback def _(result, aux_work=aux_work): if result != (pow_hash <= aux_work['target']): print >>sys.stderr, 'Merged block submittal result: %s Expected: %s' % (result, pow_hash <= aux_work['target']) else: print 'Merged block submittal result: %s' % (result,) @df.addErrback def _(err): log.err(err, 'Error submitting merged block:') except: log.err(None, 'Error while processing merged mining POW:') if pow_hash <= share_info['bits'].target and header_hash not in received_header_hashes: last_txout_nonce = pack.IntType(8*self.COINBASE_NONCE_LENGTH).unpack(coinbase_nonce) share = get_share(header, last_txout_nonce) print 'GOT SHARE! %s %s prev %s age %.2fs%s' % ( user, p2pool_data.format_hash(share.hash), p2pool_data.format_hash(share.previous_hash), time.time() - getwork_time, ' DEAD ON ARRIVAL' if not on_time else '', ) self.my_share_hashes.add(share.hash) if not on_time: self.my_doa_share_hashes.add(share.hash) self.node.tracker.add(share) self.node.set_best_share() try: if (pow_hash <= header['bits'].target or p2pool.DEBUG) and self.node.p2p_node is not None: self.node.p2p_node.broadcast_share(share.hash) except: log.err(None, 'Error forwarding block solution:') self.share_received.happened(bitcoin_data.target_to_average_attempts(share.target), not on_time, share.hash) if p2pool.DEBUG: print 'Hash: %X' % (pow_hash) print 'Target: %X' % (target) if pow_hash > target: print 'Worker %s submitted share with hash > target:' % (user,) elif header_hash in received_header_hashes: print >>sys.stderr, 'Worker %s submitted share more than once!' % (user,) else: received_header_hashes.add(header_hash) self.pseudoshare_received.happened(bitcoin_data.target_to_average_attempts(target), not on_time, user) self.recent_shares_ts_work.append((time.time(), bitcoin_data.target_to_average_attempts(target))) while len(self.recent_shares_ts_work) > 50: self.recent_shares_ts_work.pop(0) self.local_rate_monitor.add_datum(dict(work=bitcoin_data.target_to_average_attempts(target), dead=not on_time, user=user, share_target=share_info['bits'].target)) self.local_addr_rate_monitor.add_datum(dict(work=bitcoin_data.target_to_average_attempts(target), pubkey_hash=pubkey_hash)) return on_time return ba, got_response
def get_local_stats(): if node.tracker.get_height(node.best_share_var.value) < 10: return None lookbehind = min(node.tracker.get_height(node.best_share_var.value), 3600//node.net.SHARE_PERIOD) global_stale_prop = p2pool_data.get_average_stale_prop(node.tracker, node.best_share_var.value, lookbehind) my_unstale_count = sum(1 for share in node.tracker.get_chain(node.best_share_var.value, lookbehind) if share.hash in wb.my_share_hashes) my_orphan_count = sum(1 for share in node.tracker.get_chain(node.best_share_var.value, lookbehind) if share.hash in wb.my_share_hashes and share.share_data['stale_info'] == 'orphan') my_doa_count = sum(1 for share in node.tracker.get_chain(node.best_share_var.value, lookbehind) if share.hash in wb.my_share_hashes and share.share_data['stale_info'] == 'doa') my_share_count = my_unstale_count + my_orphan_count + my_doa_count my_stale_count = my_orphan_count + my_doa_count my_stale_prop = my_stale_count/my_share_count if my_share_count != 0 else None my_work = sum(bitcoin_data.target_to_average_attempts(share.target) for share in node.tracker.get_chain(node.best_share_var.value, lookbehind - 1) if share.hash in wb.my_share_hashes) actual_time = (node.tracker.items[node.best_share_var.value].timestamp - node.tracker.items[node.tracker.get_nth_parent_hash(node.best_share_var.value, lookbehind - 1)].timestamp) share_att_s = my_work / actual_time miner_hash_rates, miner_dead_hash_rates = wb.get_local_rates() (stale_orphan_shares, stale_doa_shares), shares, _ = wb.get_stale_counts() miner_last_difficulties = {} for addr in wb.last_work_shares.value: miner_last_difficulties[addr] = bitcoin_data.target_to_difficulty(wb.last_work_shares.value[addr].target) return dict( my_hash_rates_in_last_hour=dict( note="DEPRECATED", nonstale=share_att_s, rewarded=share_att_s/(1 - global_stale_prop), actual=share_att_s/(1 - my_stale_prop) if my_stale_prop is not None else 0, # 0 because we don't have any shares anyway ), my_share_counts_in_last_hour=dict( shares=my_share_count, unstale_shares=my_unstale_count, stale_shares=my_stale_count, orphan_stale_shares=my_orphan_count, doa_stale_shares=my_doa_count, ), my_stale_proportions_in_last_hour=dict( stale=my_stale_prop, orphan_stale=my_orphan_count/my_share_count if my_share_count != 0 else None, dead_stale=my_doa_count/my_share_count if my_share_count != 0 else None, ), miner_hash_rates=miner_hash_rates, miner_dead_hash_rates=miner_dead_hash_rates, miner_last_difficulties=miner_last_difficulties, efficiency_if_miner_perfect=(1 - stale_orphan_shares/shares)/(1 - global_stale_prop) if shares else None, # ignores dead shares because those are miner's fault and indicated by pseudoshare rejection efficiency=(1 - (stale_orphan_shares+stale_doa_shares)/shares)/(1 - global_stale_prop) if shares else None, peers=dict( incoming=sum(1 for peer in node.p2p_node.peers.itervalues() if peer.incoming), outgoing=sum(1 for peer in node.p2p_node.peers.itervalues() if not peer.incoming), ), shares=dict( total=shares, orphan=stale_orphan_shares, dead=stale_doa_shares, ), uptime=time.time() - start_time, attempts_to_share=bitcoin_data.target_to_average_attempts(node.tracker.items[node.best_share_var.value].max_target), attempts_to_block=bitcoin_data.target_to_average_attempts(node.bitcoind_work.value['bits'].target), block_value=node.bitcoind_work.value['subsidy']*1e-5, warnings=p2pool_data.get_warnings(node.tracker, node.best_share_var.value, node.net, bitcoind_getinfo_var.value, node.bitcoind_work.value), donation_proportion=wb.donation_percentage/100, version=p2pool.__version__, protocol_version=p2p.Protocol.VERSION, fee=wb.worker_fee, )