def __init__(self, net): forest.Tracker.__init__(self, delta_type=forest.get_attributedelta_type(dict(forest.AttributeDelta.attrs, work=lambda share: bitcoin_data.target_to_average_attempts(share.target), min_work=lambda share: bitcoin_data.target_to_average_attempts(share.max_target), ))) self.net = net self.verified = forest.SubsetTracker(delta_type=forest.get_attributedelta_type(dict(forest.AttributeDelta.attrs, work=lambda share: bitcoin_data.target_to_average_attempts(share.target), )), subset_of=self) self.get_cumulative_weights = WeightsSkipList(self)
def get_stale_counts(tracker, share_hash, lookbehind, rates=False): res = {} for share in tracker.get_chain(share_hash, lookbehind - 1): res['good'] = res.get('good', 0) + bitcoin_data.target_to_average_attempts(share.target) s = share.share_data['stale_info'] if s is not None: res[s] = res.get(s, 0) + bitcoin_data.target_to_average_attempts(share.target) if rates: dt = tracker.items[share_hash].timestamp - tracker.items[tracker.get_nth_parent_hash(share_hash, lookbehind - 1)].timestamp res = dict((k, v/dt) for k, v in res.iteritems()) return res
def __init__(self, net, my_share_hashes, my_doa_share_hashes): forest.Tracker.__init__(self, delta_type=forest.get_attributedelta_type(dict(forest.AttributeDelta.attrs, work=lambda share: bitcoin_data.target_to_average_attempts(share.target), min_work=lambda share: bitcoin_data.target_to_average_attempts(share.max_target), ))) self.net = net self.verified = forest.Tracker(delta_type=forest.get_attributedelta_type(dict(forest.AttributeDelta.attrs, work=lambda share: bitcoin_data.target_to_average_attempts(share.target), my_count=lambda share: 1 if share.hash in my_share_hashes else 0, my_doa_count=lambda share: 1 if share.hash in my_doa_share_hashes else 0, my_orphan_announce_count=lambda share: 1 if share.hash in my_share_hashes and share.share_data['stale_info'] == 253 else 0, my_dead_announce_count=lambda share: 1 if share.hash in my_share_hashes and share.share_data['stale_info'] == 254 else 0, )), subset_of=self) self.get_cumulative_weights = WeightsSkipList(self)
def get_desired_version_counts(tracker, best_share_hash, dist): res = {} for share in tracker.get_chain(best_share_hash, dist): res[share.desired_version] = res.get(share.desired_version, 0) + bitcoin_data.target_to_average_attempts( share.target ) return res
def get_delta(self, element): from p2pool.bitcoin import data as bitcoin_data if element is None: return (2**256, {}, 0, 0) # XXX share = self.tracker.shares[element] att = bitcoin_data.target_to_average_attempts(share.target) return 1, {share.new_script: att*(65535-share.donation)}, att*65535, att*share.donation
def from_element(cls, share): return cls(share.hash, 1, bitcoin_data.target_to_average_attempts(share.target), 1 if share.hash in cls.my_share_hashes else 0, 1 if share.hash in cls.my_doa_share_hashes else 0, 1 if share.hash in cls.my_share_hashes and share.share_data['stale_info'] == 253 else 0, 1 if share.hash in cls.my_share_hashes and share.share_data['stale_info'] == 254 else 0, share.previous_hash)
def get_height_work_and_last(self, share_hash): height = 0 work = 0 while share_hash in self.shares: share_hash, work_inc = self.shares[share_hash].previous_hash, bitcoin_data.target_to_average_attempts(self.shares[share_hash].target) height += 1 work += work_inc return height, work, share_hash
def get_expected_payouts(tracker, best_share_hash, block_target, subsidy, net): weights, total_weight, donation_weight = tracker.get_cumulative_weights( best_share_hash, min(tracker.get_height(best_share_hash), net.REAL_CHAIN_LENGTH), 65535 * net.SPREAD * bitcoin_data.target_to_average_attempts(block_target), ) res = dict((script, subsidy * weight // total_weight) for script, weight in weights.iteritems()) res[DONATION_SCRIPT] = res.get(DONATION_SCRIPT, 0) + subsidy - sum(res.itervalues()) return res
def get_pool_attempts_per_second(tracker, previous_share_hash, net, dist=None): if dist is None: dist = net.TARGET_LOOKBEHIND # XXX could be optimized to use nth_parent and cumulative_weights chain = list(itertools.islice(tracker.get_chain_to_root(previous_share_hash), dist)) attempts = sum(bitcoin_data.target_to_average_attempts(share.target) for share in chain[:-1]) time = chain[0].timestamp - chain[-1].timestamp if time == 0: time = 1 return attempts//time
def generate_transaction(cls, tracker, share_data, block_target, desired_timestamp, desired_target, ref_merkle_link, net): previous_share = tracker.shares[share_data['previous_share_hash']] if share_data['previous_share_hash'] is not None else None height, last = tracker.get_height_and_last(share_data['previous_share_hash']) assert height >= net.REAL_CHAIN_LENGTH or last is None if height < net.TARGET_LOOKBEHIND: pre_target3 = net.MAX_TARGET else: attempts_per_second = get_pool_attempts_per_second(tracker, share_data['previous_share_hash'], net.TARGET_LOOKBEHIND, min_work=True, integer=True) pre_target = 2**256//(net.SHARE_PERIOD*attempts_per_second) - 1 if attempts_per_second else 2**256-1 pre_target2 = math.clip(pre_target, (previous_share.max_target*9//10, previous_share.max_target*11//10)) pre_target3 = math.clip(pre_target2, (0, net.MAX_TARGET)) max_bits = bitcoin_data.FloatingInteger.from_target_upper_bound(pre_target3) bits = bitcoin_data.FloatingInteger.from_target_upper_bound(math.clip(desired_target, (pre_target3//10, pre_target3))) weights, total_weight, donation_weight = tracker.get_cumulative_weights(share_data['previous_share_hash'], min(height, net.REAL_CHAIN_LENGTH), 65535*net.SPREAD*bitcoin_data.target_to_average_attempts(block_target), ) assert total_weight == sum(weights.itervalues()) + donation_weight, (total_weight, sum(weights.itervalues()) + donation_weight) amounts = dict((script, share_data['subsidy']*(199*weight)//(200*total_weight)) for script, weight in weights.iteritems()) # 99.5% goes according to weights prior to this share this_script = bitcoin_data.pubkey_hash_to_script2(share_data['pubkey_hash']) amounts[this_script] = amounts.get(this_script, 0) + share_data['subsidy']//200 # 0.5% goes to block finder amounts[DONATION_SCRIPT] = amounts.get(DONATION_SCRIPT, 0) + share_data['subsidy'] - sum(amounts.itervalues()) # all that's left over is the donation weight and some extra satoshis due to rounding if sum(amounts.itervalues()) != share_data['subsidy'] or any(x < 0 for x in amounts.itervalues()): raise ValueError() dests = sorted(amounts.iterkeys(), key=lambda script: (script == DONATION_SCRIPT, amounts[script], script))[-4000:] # block length limit, unlikely to ever be hit share_info = dict( share_data=share_data, far_share_hash=None if last is None and height < 99 else tracker.get_nth_parent_hash(share_data['previous_share_hash'], 99), max_bits=max_bits, bits=bits, timestamp=math.clip(desired_timestamp, ( (previous_share.timestamp + net.SHARE_PERIOD) - (net.SHARE_PERIOD - 1), # = previous_share.timestamp + 1 (previous_share.timestamp + net.SHARE_PERIOD) + (net.SHARE_PERIOD - 1), )) if previous_share is not None else desired_timestamp, ) return share_info, dict( version=1, tx_ins=[dict( previous_output=None, sequence=None, script=share_data['coinbase'].ljust(2, '\x00'), )], tx_outs=[dict(value=amounts[script], script=script) for script in dests if amounts[script] or script == DONATION_SCRIPT] + [dict( value=0, script='\x20' + cls.get_ref_hash(net, share_info, ref_merkle_link), )], lock_time=0, )
def get_delta(self, element): from p2pool.bitcoin import data as bitcoin_data share = self.tracker.items[element] att = bitcoin_data.target_to_average_attempts(share.target) return ( 1, {share.new_script: att * (65535 - share.share_data["donation"])}, att * 65535, att * share.share_data["donation"], )
def get_expected_payouts(tracker, best_share_hash, block_target, subsidy, net): weights, total_weight, donation_weight = tracker.get_cumulative_weights( best_share_hash, min(tracker.get_height(best_share_hash), net.REAL_CHAIN_LENGTH), 65535 * net.SPREAD * bitcoin_data.target_to_average_attempts(block_target)) res = dict((script, subsidy * weight // total_weight) for script, weight in weights.iteritems()) res[DONATION_SCRIPT] = res.get(DONATION_SCRIPT, 0) + subsidy - sum( res.itervalues()) return res
def __init__(self, net): forest.Tracker.__init__( self, delta_type=forest.get_attributedelta_type( dict( forest.AttributeDelta.attrs, work=lambda share: bitcoin_data.target_to_average_attempts( share.target), min_work=lambda share: bitcoin_data. target_to_average_attempts(share.max_target), ))) self.net = net self.verified = forest.SubsetTracker( delta_type=forest.get_attributedelta_type( dict( forest.AttributeDelta.attrs, work=lambda share: bitcoin_data.target_to_average_attempts( share.target), )), subset_of=self) self.get_cumulative_weights = WeightsSkipList(self)
def get_delta(element): from p2pool.bitcoin import data as bitcoin_data share = self.tracker.items[element] att = bitcoin_data.target_to_average_attempts(share.target) #(share_count2, weights2, total_weight2, total_donation_weight2) # share_count2 = 1, # weights2 = {share.new_script: att*(65535-share.share_data['donation'])}, # total_weight2 = att*65535, # total_donation_weight2 = att*share.share_data['donation'] return 1, { share.new_script: att * (65535 - share.share_data['donation']) }, att * 65535, att * share.share_data['donation']
def score(self, share_hash, ht): head_height, last = self.verified.get_height_and_last(share_hash) score2 = 0 attempts = 0 max_height = 0 # XXX should only look past a certain share, not at recent ones share2_hash = self.verified.get_nth_parent_hash(share_hash, min(self.net.CHAIN_LENGTH//2, head_height//2)) if last is not None else share_hash # XXX this must go in the opposite direction for max_height to make sense for share in reversed(list(itertools.islice(self.verified.get_chain_known(share2_hash), self.net.CHAIN_LENGTH))): max_height = max(max_height, ht.get_min_height(share.header['previous_block'])) attempts += bitcoin_data.target_to_average_attempts(share.target) this_score = attempts//(ht.get_highest_height() - max_height + 1) if this_score > score2: score2 = this_score return min(head_height, self.net.CHAIN_LENGTH), score2
def generate_transaction(cls, tracker, share_data, block_target, desired_timestamp, desired_target, ref_merkle_link, desired_other_transaction_hashes_and_fees, net, known_txs=None, last_txout_nonce=0, base_subsidy=None): previous_share = tracker.items[share_data['previous_share_hash']] if share_data['previous_share_hash'] is not None else None height, last = tracker.get_height_and_last(share_data['previous_share_hash']) assert height >= net.REAL_CHAIN_LENGTH or last is None if height < net.TARGET_LOOKBEHIND: pre_target3 = net.MAX_TARGET else: attempts_per_second = get_pool_attempts_per_second(tracker, share_data['previous_share_hash'], net.TARGET_LOOKBEHIND, min_work=True, integer=True) pre_target = 2**256//(net.SHARE_PERIOD*attempts_per_second) - 1 if attempts_per_second else 2**256-1 pre_target2 = math.clip(pre_target, (previous_share.max_target*9//10, previous_share.max_target*11//10)) pre_target3 = math.clip(pre_target2, (net.MIN_TARGET, net.MAX_TARGET)) max_bits = bitcoin_data.FloatingInteger.from_target_upper_bound(pre_target3) bits = bitcoin_data.FloatingInteger.from_target_upper_bound(math.clip(desired_target, (pre_target3//30, pre_target3))) new_transaction_hashes = [] new_transaction_size = 0 transaction_hash_refs = [] other_transaction_hashes = [] past_shares = list(tracker.get_chain(share_data['previous_share_hash'], min(height, 100))) tx_hash_to_this = {} for i, share in enumerate(past_shares): for j, tx_hash in enumerate(share.new_transaction_hashes): if tx_hash not in tx_hash_to_this: tx_hash_to_this[tx_hash] = [1+i, j] # share_count, tx_count for tx_hash, fee in desired_other_transaction_hashes_and_fees: if tx_hash in tx_hash_to_this: this = tx_hash_to_this[tx_hash] else: if known_txs is not None: this_size = bitcoin_data.tx_type.packed_size(known_txs[tx_hash]) if new_transaction_size + this_size > 50000: # only allow 50 kB of new txns/share break new_transaction_size += this_size new_transaction_hashes.append(tx_hash) this = [0, len(new_transaction_hashes)-1] transaction_hash_refs.extend(this) other_transaction_hashes.append(tx_hash) included_transactions = set(other_transaction_hashes) removed_fees = [fee for tx_hash, fee in desired_other_transaction_hashes_and_fees if tx_hash not in included_transactions] definite_fees = sum(0 if fee is None else fee for tx_hash, fee in desired_other_transaction_hashes_and_fees if tx_hash in included_transactions) if None not in removed_fees: share_data = dict(share_data, subsidy=share_data['subsidy'] - sum(removed_fees)) else: assert base_subsidy is not None share_data = dict(share_data, subsidy=base_subsidy + definite_fees) weights, total_weight, donation_weight = tracker.get_cumulative_weights(previous_share.share_data['previous_share_hash'] if previous_share is not None else None, min(height, net.REAL_CHAIN_LENGTH-1), 65535*net.SPREAD*bitcoin_data.target_to_average_attempts(block_target), ) assert total_weight == sum(weights.itervalues()) + donation_weight, (total_weight, sum(weights.itervalues()) + donation_weight) amounts = dict((script, share_data['subsidy']*(199*weight)//(200*total_weight)) for script, weight in weights.iteritems()) # 99.5% goes according to weights prior to this share this_script = bitcoin_data.pubkey_hash_to_script2(share_data['pubkey_hash']) amounts[this_script] = amounts.get(this_script, 0) + share_data['subsidy']//200 # 0.5% goes to block finder amounts[DONATION_SCRIPT] = amounts.get(DONATION_SCRIPT, 0) + share_data['subsidy'] - sum(amounts.itervalues()) # all that's left over is the donation weight and some extra satoshis due to rounding if sum(amounts.itervalues()) != share_data['subsidy'] or any(x < 0 for x in amounts.itervalues()): raise ValueError() dests = sorted(amounts.iterkeys(), key=lambda script: (script == DONATION_SCRIPT, amounts[script], script))[-4000:] # block length limit, unlikely to ever be hit share_info = dict( share_data=share_data, far_share_hash=None if last is None and height < 99 else tracker.get_nth_parent_hash(share_data['previous_share_hash'], 99), max_bits=max_bits, bits=bits, timestamp=math.clip(desired_timestamp, ( (previous_share.timestamp + net.SHARE_PERIOD) - (net.SHARE_PERIOD - 1), # = previous_share.timestamp + 1 (previous_share.timestamp + net.SHARE_PERIOD) + (net.SHARE_PERIOD - 1), )) if previous_share is not None else desired_timestamp, new_transaction_hashes=new_transaction_hashes, transaction_hash_refs=transaction_hash_refs, absheight=((previous_share.absheight if previous_share is not None else 0) + 1) % 2**32, abswork=((previous_share.abswork if previous_share is not None else 0) + bitcoin_data.target_to_average_attempts(bits.target)) % 2**128, ) gentx = dict( version=1, tx_ins=[dict( previous_output=None, sequence=None, script=share_data['coinbase'], )], tx_outs=[dict(value=amounts[script], script=script) for script in dests if amounts[script] or script == DONATION_SCRIPT] + [dict( value=0, script='\x6a\x28' + cls.get_ref_hash(net, share_info, ref_merkle_link) + pack.IntType(64).pack(last_txout_nonce), )], lock_time=0, ) def get_share(header, last_txout_nonce=last_txout_nonce): min_header = dict(header); del min_header['merkle_root'] share = cls(net, None, dict( min_header=min_header, share_info=share_info, ref_merkle_link=dict(branch=[], index=0), last_txout_nonce=last_txout_nonce, hash_link=prefix_to_hash_link(bitcoin_data.tx_type.pack(gentx)[:-32-8-4], cls.gentx_before_refhash), merkle_link=bitcoin_data.calculate_merkle_link([None] + other_transaction_hashes, 0), )) assert share.header == header # checks merkle_root return share return share_info, gentx, other_transaction_hashes, get_share
def generate_transaction(tracker, share_data, block_target, desired_timestamp, net): previous_share_hash = share_data['previous_share_hash'] new_script = share_data['new_script'] subsidy = share_data['subsidy'] donation = share_data['donation'] assert 0 <= donation <= 65535 if len(share_data['coinbase']) > 100: raise ValueError('coinbase too long!') previous_share = tracker.shares[previous_share_hash] if previous_share_hash is not None else None chain_length = getattr(net, 'REAL_CHAIN_LENGTH_FUNC', lambda _: net.REAL_CHAIN_LENGTH)(previous_share.timestamp if previous_share is not None else None) height, last = tracker.get_height_and_last(previous_share_hash) assert height >= chain_length or last is None if height < net.TARGET_LOOKBEHIND: bits = bitcoin_data.FloatingInteger.from_target_upper_bound(net.MAX_TARGET) else: attempts_per_second = get_pool_attempts_per_second(tracker, previous_share_hash, net.TARGET_LOOKBEHIND) pre_target = 2**256//(net.SHARE_PERIOD*attempts_per_second) - 1 pre_target2 = math.clip(pre_target, (previous_share.target*9//10, previous_share.target*11//10)) pre_target3 = math.clip(pre_target2, (0, net.MAX_TARGET)) bits = bitcoin_data.FloatingInteger.from_target_upper_bound(pre_target3) attempts_to_block = bitcoin_data.target_to_average_attempts(block_target) max_att = net.SPREAD * attempts_to_block this_att = min(bitcoin_data.target_to_average_attempts(bits.target), max_att) other_weights, other_total_weight, other_donation_weight = tracker.get_cumulative_weights(previous_share_hash, min(height, chain_length), 65535*max(0, max_att - this_att)) assert other_total_weight == sum(other_weights.itervalues()) + other_donation_weight, (other_total_weight, sum(other_weights.itervalues()) + other_donation_weight) weights, total_weight, donation_weight = math.add_dicts({new_script: this_att*(65535-donation)}, other_weights), this_att*65535 + other_total_weight, this_att*donation + other_donation_weight assert total_weight == sum(weights.itervalues()) + donation_weight, (total_weight, sum(weights.itervalues()) + donation_weight) SCRIPT = '4104ffd03de44a6e11b9917f3a29f9443283d9871c9d743ef30d5eddcd37094b64d1b3d8090496b53256786bf5c82932ec23c3b74d9f05a6f95a8b5529352656664bac'.decode('hex') # 1 satoshi is always donated so that a list of p2pool generated blocks can be easily found by looking at the donation address amounts = dict((script, (subsidy-1)*(199*weight)//(200*total_weight)) for (script, weight) in weights.iteritems()) amounts[new_script] = amounts.get(new_script, 0) + (subsidy-1)//200 amounts[SCRIPT] = amounts.get(SCRIPT, 0) + (subsidy-1)*(199*donation_weight)//(200*total_weight) amounts[SCRIPT] = amounts.get(SCRIPT, 0) + subsidy - sum(amounts.itervalues()) # collect any extra satoshis :P if sum(amounts.itervalues()) != subsidy: raise ValueError() if any(x < 0 for x in amounts.itervalues()): raise ValueError() dests = sorted(amounts.iterkeys(), key=lambda script: (amounts[script], script)) dests = dests[-4000:] # block length limit, unlikely to ever be hit share_info = dict( share_data=share_data, bits=bits, timestamp=math.clip(desired_timestamp, (previous_share.timestamp - 60, previous_share.timestamp + 60)) if previous_share is not None else desired_timestamp, ) return share_info, dict( version=1, tx_ins=[dict( previous_output=None, sequence=None, script=share_data['coinbase'].ljust(2, '\x00'), )], tx_outs=[dict(value=0, script='\x20' + pack.IntType(256).pack(bitcoin_data.hash256(share_info_type.pack(share_info))))] + [dict(value=amounts[script], script=script) for script in dests if amounts[script]], lock_time=0, )
def __sub__(self, other): if self.head == other.head: return self.__class__(other.tail, self.tail, **dict((k, getattr(self, k) - getattr(other, k)) for k in attrs)) elif self.tail == other.tail: return self.__class__(self.head, other.head, **dict((k, getattr(self, k) - getattr(other, k)) for k in attrs)) else: raise AssertionError() def __repr__(self): return '%s(%r, %r%s)' % (self.__class__, self.head, self.tail, ''.join(', %s=%r' % (k, getattr(self, k)) for k in attrs)) ProtoAttributeDelta.attrs = attrs return ProtoAttributeDelta AttributeDelta = get_attributedelta_type(dict( height=lambda share: 1, work=lambda share: bitcoin_data.target_to_average_attempts(share.target), )) class Tracker(object): def __init__(self, shares=[], delta_type=AttributeDelta): self.shares = {} # hash -> share self.reverse_shares = {} # delta.tail -> set of share_hashes self.heads = {} # head hash -> tail_hash self.tails = {} # tail hash -> set of head hashes self.deltas = {} # share_hash -> delta, ref self.reverse_deltas = {} # ref -> set of share_hashes self.ref_generator = itertools.count() self.delta_refs = {} # ref -> delta
def generate_transaction(cls, tracker, share_data, block_target, desired_timestamp, desired_target, ref_merkle_link, desired_other_transaction_hashes_and_fees, net, known_txs=None, last_txout_nonce=0, base_subsidy=None, segwit_data=None): t0 = time.time() previous_share = tracker.items[share_data['previous_share_hash']] if share_data['previous_share_hash'] is not None else None height, last = tracker.get_height_and_last(share_data['previous_share_hash']) assert height >= net.REAL_CHAIN_LENGTH or last is None if height < net.TARGET_LOOKBEHIND: pre_target3 = net.MAX_TARGET else: attempts_per_second = get_pool_attempts_per_second(tracker, share_data['previous_share_hash'], net.TARGET_LOOKBEHIND, min_work=True, integer=True) pre_target = 2**256//(net.SHARE_PERIOD*attempts_per_second) - 1 if attempts_per_second else 2**256-1 pre_target2 = math.clip(pre_target, (previous_share.max_target*9//10, previous_share.max_target*11//10)) pre_target3 = math.clip(pre_target2, (net.MIN_TARGET, net.MAX_TARGET)) max_bits = bitcoin_data.FloatingInteger.from_target_upper_bound(pre_target3) bits = bitcoin_data.FloatingInteger.from_target_upper_bound(math.clip(desired_target, (pre_target3//30, pre_target3))) new_transaction_hashes = [] new_transaction_size = 0 # including witnesses all_transaction_stripped_size = 0 # stripped size all_transaction_real_size = 0 # including witnesses, for statistics new_transaction_weight = 0 all_transaction_weight = 0 transaction_hash_refs = [] other_transaction_hashes = [] t1 = time.time() past_shares = list(tracker.get_chain(share_data['previous_share_hash'], min(height, 100))) tx_hash_to_this = {} for i, share in enumerate(past_shares): for j, tx_hash in enumerate(share.new_transaction_hashes): if tx_hash not in tx_hash_to_this: tx_hash_to_this[tx_hash] = [1+i, j] # share_count, tx_count t2 = time.time() for tx_hash, fee in desired_other_transaction_hashes_and_fees: if known_txs is not None: this_stripped_size = bitcoin_data.tx_id_type.packed_size(known_txs[tx_hash]) this_real_size = bitcoin_data.tx_type.packed_size(known_txs[tx_hash]) this_weight = this_real_size + 3*this_stripped_size else: # we're just verifying someone else's share. We'll calculate sizes in should_punish_reason() this_stripped_size = 0 this_real_size = 0 this_weight = 0 if all_transaction_stripped_size + this_stripped_size + 80 + cls.gentx_size + 500 > net.BLOCK_MAX_SIZE: break if all_transaction_weight + this_weight + 4*80 + cls.gentx_weight + 2000 > net.BLOCK_MAX_WEIGHT: break if tx_hash in tx_hash_to_this: this = tx_hash_to_this[tx_hash] if known_txs is not None: all_transaction_stripped_size += this_stripped_size all_transaction_real_size += this_real_size all_transaction_weight += this_weight else: if known_txs is not None: new_transaction_size += this_real_size all_transaction_stripped_size += this_stripped_size all_transaction_real_size += this_real_size new_transaction_weight += this_weight all_transaction_weight += this_weight new_transaction_hashes.append(tx_hash) this = [0, len(new_transaction_hashes)-1] transaction_hash_refs.extend(this) other_transaction_hashes.append(tx_hash) t3 = time.time() if transaction_hash_refs and max(transaction_hash_refs) < 2**16: transaction_hash_refs = array.array('H', transaction_hash_refs) elif transaction_hash_refs and max(transaction_hash_refs) < 2**32: # in case we see blocks with more than 65536 tx transaction_hash_refs = array.array('L', transaction_hash_refs) t4 = time.time() if all_transaction_stripped_size: print "Generating a share with %i bytes, %i WU (new: %i B, %i WU) in %i tx (%i new), plus est gentx of %i bytes/%i WU" % ( all_transaction_real_size, all_transaction_weight, new_transaction_size, new_transaction_weight, len(other_transaction_hashes), len(new_transaction_hashes), cls.gentx_size, cls.gentx_weight) print "Total block stripped size=%i B, full size=%i B, weight: %i WU" % ( 80+all_transaction_stripped_size+cls.gentx_size, 80+all_transaction_real_size+cls.gentx_size, 3*80+all_transaction_weight+cls.gentx_weight) included_transactions = set(other_transaction_hashes) removed_fees = [fee for tx_hash, fee in desired_other_transaction_hashes_and_fees if tx_hash not in included_transactions] definite_fees = sum(0 if fee is None else fee for tx_hash, fee in desired_other_transaction_hashes_and_fees if tx_hash in included_transactions) if None not in removed_fees: share_data = dict(share_data, subsidy=share_data['subsidy'] - sum(removed_fees)) else: assert base_subsidy is not None share_data = dict(share_data, subsidy=base_subsidy + definite_fees) weights, total_weight, donation_weight = tracker.get_cumulative_weights(previous_share.share_data['previous_share_hash'] if previous_share is not None else None, max(0, min(height, net.REAL_CHAIN_LENGTH) - 1), 65535*net.SPREAD*bitcoin_data.target_to_average_attempts(block_target), ) assert total_weight == sum(weights.itervalues()) + donation_weight, (total_weight, sum(weights.itervalues()) + donation_weight) amounts = dict((script, share_data['subsidy']*(199*weight)//(200*total_weight)) for script, weight in weights.iteritems()) # 99.5% goes according to weights prior to this share this_script = bitcoin_data.pubkey_hash_to_script2(share_data['pubkey_hash']) amounts[this_script] = amounts.get(this_script, 0) + share_data['subsidy']//200 # 0.5% goes to block finder amounts[DONATION_SCRIPT] = amounts.get(DONATION_SCRIPT, 0) + share_data['subsidy'] - sum(amounts.itervalues()) # all that's left over is the donation weight and some extra satoshis due to rounding if sum(amounts.itervalues()) != share_data['subsidy'] or any(x < 0 for x in amounts.itervalues()): raise ValueError() dests = sorted(amounts.iterkeys(), key=lambda script: (script == DONATION_SCRIPT, amounts[script], script))[-4000:] # block length limit, unlikely to ever be hit segwit_activated = is_segwit_activated(cls.VERSION, net) if segwit_data is None and known_txs is None: segwit_activated = False if not(segwit_activated or known_txs is None) and any(bitcoin_data.is_segwit_tx(known_txs[h]) for h in other_transaction_hashes): raise ValueError('segwit transaction included before activation') if segwit_activated and known_txs is not None: share_txs = [(known_txs[h], bitcoin_data.get_txid(known_txs[h]), h) for h in other_transaction_hashes] segwit_data = dict(txid_merkle_link=bitcoin_data.calculate_merkle_link([None] + [tx[1] for tx in share_txs], 0), wtxid_merkle_root=bitcoin_data.merkle_hash([0] + [bitcoin_data.get_wtxid(tx[0], tx[1], tx[2]) for tx in share_txs])) if segwit_activated and segwit_data is not None: witness_reserved_value_str = '[P2Pool]'*4 witness_reserved_value = pack.IntType(256).unpack(witness_reserved_value_str) witness_commitment_hash = bitcoin_data.get_witness_commitment_hash(segwit_data['wtxid_merkle_root'], witness_reserved_value) share_info = dict( share_data=share_data, far_share_hash=None if last is None and height < 99 else tracker.get_nth_parent_hash(share_data['previous_share_hash'], 99), max_bits=max_bits, bits=bits, timestamp=(math.clip(desired_timestamp, ( (previous_share.timestamp + net.SHARE_PERIOD) - (net.SHARE_PERIOD - 1), # = previous_share.timestamp + 1 (previous_share.timestamp + net.SHARE_PERIOD) + (net.SHARE_PERIOD - 1),)) if previous_share is not None else desired_timestamp ) if cls.VERSION < 32 else max(desired_timestamp, (previous_share.timestamp + 1)) if previous_share is not None else desired_timestamp, new_transaction_hashes=new_transaction_hashes, transaction_hash_refs=transaction_hash_refs, absheight=((previous_share.absheight if previous_share is not None else 0) + 1) % 2**32, abswork=((previous_share.abswork if previous_share is not None else 0) + bitcoin_data.target_to_average_attempts(bits.target)) % 2**128, ) if previous_share != None and desired_timestamp > previous_share.timestamp + 180: print "Warning: Previous share's timestamp is %i seconds old." % int(desired_timestamp - previous_share.timestamp) print "Make sure your system clock is accurate, and ensure that you're connected to decent peers." print "If your clock is more than 300 seconds behind, it can result in orphaned shares." print "(It's also possible that this share is just taking a long time to mine.)" if previous_share != None and previous_share.timestamp > int(time.time()) + 3: print "WARNING! Previous share's timestamp is %i seconds in the future. This is not normal." % \ int(previous_share.timestamp - (int(time.time()))) print "Make sure your system clock is accurate. Errors beyond 300 sec result in orphaned shares." if segwit_activated: share_info['segwit_data'] = segwit_data gentx = dict( version=1, tx_ins=[dict( previous_output=None, sequence=None, script=share_data['coinbase'], )], tx_outs=([dict(value=0, script='\x6a\x24\xaa\x21\xa9\xed' + pack.IntType(256).pack(witness_commitment_hash))] if segwit_activated else []) + [dict(value=amounts[script], script=script) for script in dests if amounts[script] or script == DONATION_SCRIPT] + [dict(value=0, script='\x6a\x28' + cls.get_ref_hash(net, share_info, ref_merkle_link) + pack.IntType(64).pack(last_txout_nonce))], lock_time=0, ) if segwit_activated: gentx['marker'] = 0 gentx['flag'] = 1 gentx['witness'] = [[witness_reserved_value_str]] def get_share(header, last_txout_nonce=last_txout_nonce): min_header = dict(header); del min_header['merkle_root'] share = cls(net, None, dict( min_header=min_header, share_info=share_info, ref_merkle_link=dict(branch=[], index=0), last_txout_nonce=last_txout_nonce, hash_link=prefix_to_hash_link(bitcoin_data.tx_id_type.pack(gentx)[:-32-8-4], cls.gentx_before_refhash), merkle_link=bitcoin_data.calculate_merkle_link([None] + other_transaction_hashes, 0), )) assert share.header == header # checks merkle_root return share t5 = time.time() if p2pool.BENCH: print "%8.3f ms for data.py:generate_transaction(). Parts: %8.3f %8.3f %8.3f %8.3f %8.3f " % ( (t5-t0)*1000., (t1-t0)*1000., (t2-t1)*1000., (t3-t2)*1000., (t4-t3)*1000., (t5-t4)*1000.) return share_info, gentx, other_transaction_hashes, get_share
def generate_transaction(cls, tracker, share_data, block_target, desired_timestamp, desired_target, ref_merkle_link, desired_other_transaction_hashes_and_fees, net, known_txs=None, last_txout_nonce=0, base_subsidy=None): previous_share = tracker.items[ share_data['previous_share_hash']] if share_data[ 'previous_share_hash'] is not None else None def get_coinbase_fee(share_data, outpointsnum): # calculate neccessary coinbase fee # coinbase usually seems like this: # # 01000000 - nVersion # 1a184351 - nTimestamp # 01 - Inputs num # 0000000000000000000000000000000000000000000000000000000000000000 - Input hash # ffffffff - Input index (-1) # 0a02732a062f503253482f - Scriptsig # ffffffff - nSequence # 15 - Outpoints num # (User outpoints, 44 bytes per each) # (Donation outpoint, 76 bytes) # P2Pool service outpoint (contains merkle link), 46 bytes # # 1027000000000000 # 25 # 2417cc2063b11fd5255c7e5605780de78163ffc698ed22856bff1a5d880c3c44e400000000 # Giving users some time to upgrade coinbase_size = 50 + ( 1 + len(share_data['coinbase'])) + outpointsnum * 44 + 76 + 46 # if coinbase size is greater than 1000 bytes, it should pay fee (0.01 per 1000 bytes) if coinbase_size > 1000: return int(ceil(coinbase_size / 1000.0) * minout) return 0 if base_subsidy is None: base_subsidy = net.PARENT.SUBSIDY_FUNC(block_target) # current user payout script this_script = share_data['script'] height, last = tracker.get_height_and_last( share_data['previous_share_hash']) assert height >= net.REAL_CHAIN_LENGTH or last is None if height < net.TARGET_LOOKBEHIND: pre_target3 = net.MAX_TARGET else: attempts_per_second = get_pool_attempts_per_second( tracker, share_data['previous_share_hash'], net.TARGET_LOOKBEHIND, min_work=True, integer=True) pre_target = 2**256 // ( net.SHARE_PERIOD * attempts_per_second) - 1 if attempts_per_second else 2**256 - 1 pre_target2 = math.clip(pre_target, (previous_share.max_target * 9 // 10, previous_share.max_target * 11 // 10)) pre_target3 = math.clip(pre_target2, (net.MIN_TARGET, net.MAX_TARGET)) max_bits = bitcoin_data.FloatingInteger.from_target_upper_bound( pre_target3) bits = bitcoin_data.FloatingInteger.from_target_upper_bound( math.clip(desired_target, (pre_target3 // 10, pre_target3))) if p2pool.DEBUG: print print "Share Info Bits Target (DATA)" print bitcoin_data.target_to_difficulty(bits.target) print new_transaction_hashes = [] new_transaction_size = 0 transaction_hash_refs = [] other_transaction_hashes = [] past_shares = list( tracker.get_chain(share_data['previous_share_hash'], min(height, 100))) tx_hash_to_this = {} for i, share in enumerate(past_shares): for j, tx_hash in enumerate(share.new_transaction_hashes): if tx_hash not in tx_hash_to_this: tx_hash_to_this[tx_hash] = [1 + i, j] # share_count, tx_count for tx_hash, fee in desired_other_transaction_hashes_and_fees: if tx_hash in tx_hash_to_this: this = tx_hash_to_this[tx_hash] else: if known_txs is not None: this_size = bitcoin_data.tx_type.packed_size( known_txs[tx_hash]) if new_transaction_size + this_size > 50000: # only allow 50 kB of new txns/share break new_transaction_size += this_size new_transaction_hashes.append(tx_hash) this = [0, len(new_transaction_hashes) - 1] transaction_hash_refs.extend(this) other_transaction_hashes.append(tx_hash) included_transactions = set(other_transaction_hashes) share_data = dict(share_data, subsidy=base_subsidy) raw_weights, total_weight, donation_weight = tracker.get_cumulative_weights( share_data['previous_share_hash'], min(height, net.REAL_CHAIN_LENGTH), 65535 * net.SPREAD * bitcoin_data.target_to_average_attempts(block_target), ) # calculate "raw" subsidy raw_subsidy = share_data['subsidy'] - 4 * minout - get_coinbase_fee( share_data, len(raw_weights) + 1) # calculate "raw" amounts raw_amounts = dict((script, raw_subsidy * weight // total_weight) for script, weight in raw_weights.iteritems()) total_remowed_weight = 0 weights = {} # iterate list and collect all weights, which produces less than 0.01 payout # it's neccessary due to NVC/PPC protocol-level limitations for coinbase outpoint size for x in raw_amounts.keys(): if raw_amounts[x] < minout and x not in [ this_script, DONATION_SCRIPT ]: total_remowed_weight = total_remowed_weight + raw_weights[x] else: weights[x] = raw_weights[x] total_weight = total_weight - total_remowed_weight assert total_weight == sum(weights.itervalues()) + donation_weight, ( total_weight, sum(weights.itervalues()) + donation_weight) # base subsidy value calculated as: # [subsidy - (0.01 for donation + 0.01 for current user + 0.01 for p2pool outpoint) - netfee] my_subsidy = share_data['subsidy'] - 3 * minout - get_coinbase_fee( share_data, len(weights) + 1) # subsidy goes according to weights prior to this share amounts = dict((script, my_subsidy * weight // total_weight) for script, weight in weights.iteritems()) # all that's left over is the donation weight and some extra satoshis due to rounding amounts[DONATION_SCRIPT] = amounts.get( DONATION_SCRIPT, 0) + my_subsidy - sum(amounts.itervalues()) if sum(amounts.itervalues()) != my_subsidy or any( x < 0 for x in amounts.itervalues()): raise ValueError() # add 0.01 coin to donation, to satisfy the protocol amounts[DONATION_SCRIPT] = amounts[DONATION_SCRIPT] + minout # add 0.01 to current user output, to satisfy the protocol amounts[this_script] = amounts.get(this_script, 0) + minout dests = sorted( amounts.iterkeys(), key=lambda script: (script == DONATION_SCRIPT, amounts[script], script))[ -4000:] # block length limit, unlikely to ever be hi share_info = dict( share_data=share_data, far_share_hash=None if last is None and height < 99 else tracker.get_nth_parent_hash(share_data['previous_share_hash'], 99), max_bits=max_bits, bits=bits, timestamp=desired_timestamp, # need better solution # timestamp=math.clip(desired_timestamp, ( # (previous_share.timestamp + net.SHARE_PERIOD) - (net.SHARE_PERIOD - 1), # = previous_share.timestamp + 1 # (previous_share.timestamp + net.SHARE_PERIOD) + (net.SHARE_PERIOD - 1), # )) if previous_share is not None else desired_timestamp, new_transaction_hashes=new_transaction_hashes, transaction_hash_refs=transaction_hash_refs, ) if p2pool.DEBUG: print print "Desired timestamp (DATA)" print desired_timestamp print time.time() print print "Prev Share timestamp (DATA)" print previous_share.timestamp print time.time() print print "Share info timestamp (DATA)" print share_info['timestamp'] print time.time() print gentx = dict( version=1, # coinbase timestamp must be older than share/block timestamp # maybe there are more elegant solution, but this hack works quite well for now timestamp=share_info['timestamp'], tx_ins=[ dict( previous_output=None, sequence=None, script=share_data['coinbase'], ) ], tx_outs=[ dict(value=amounts[script], script=script) for script in dests if amounts[script] or script == DONATION_SCRIPT ] + [ dict( # add 0.01 coin to service output, to satisfy the protocol value=minout, script='\x24' + cls.get_ref_hash(net, share_info, ref_merkle_link) + pack.IntType(32).pack(last_txout_nonce), ) ], lock_time=0, ) def get_share(header, last_txout_nonce=last_txout_nonce): min_header = dict(header) del min_header['merkle_root'] share = cls( net, None, dict( min_header=min_header, share_info=share_info, ref_merkle_link=dict(branch=[], index=0), last_txout_nonce=last_txout_nonce, hash_link=prefix_to_hash_link( bitcoin_data.tx_type.pack(gentx)[:-32 - 4 - 4], cls.gentx_before_refhash), merkle_link=bitcoin_data.calculate_merkle_link( [None] + other_transaction_hashes, 0), )) assert share.header == header # checks merkle_root return share return share_info, gentx, other_transaction_hashes, get_share
def generate_transaction(cls, tracker, share_data, block_target, desired_timestamp, desired_target, ref_merkle_link, other_transaction_hashes, net): previous_share = tracker.items[ share_data['previous_share_hash']] if share_data[ 'previous_share_hash'] is not None else None height, last = tracker.get_height_and_last( share_data['previous_share_hash']) assert height >= net.REAL_CHAIN_LENGTH or last is None if height < net.TARGET_LOOKBEHIND: pre_target3 = net.MAX_TARGET else: attempts_per_second = get_pool_attempts_per_second( tracker, share_data['previous_share_hash'], net.TARGET_LOOKBEHIND, min_work=True, integer=True) pre_target = 2**256 // ( net.SHARE_PERIOD * attempts_per_second) - 1 if attempts_per_second else 2**256 - 1 pre_target2 = math.clip(pre_target, (previous_share.max_target * 9 // 10, previous_share.max_target * 11 // 10)) pre_target3 = math.clip(pre_target2, (0, net.MAX_TARGET)) max_bits = bitcoin_data.FloatingInteger.from_target_upper_bound( pre_target3) bits = bitcoin_data.FloatingInteger.from_target_upper_bound( math.clip(desired_target, (pre_target3 // 10, pre_target3))) weights, total_weight, donation_weight = tracker.get_cumulative_weights( share_data['previous_share_hash'], min(height, net.REAL_CHAIN_LENGTH), 65535 * net.SPREAD * bitcoin_data.target_to_average_attempts(block_target), ) assert total_weight == sum(weights.itervalues()) + donation_weight, ( total_weight, sum(weights.itervalues()) + donation_weight) amounts = dict( (script, share_data['subsidy'] * (199 * weight) // (200 * total_weight)) for script, weight in weights.iteritems( )) # 99.5% goes according to weights prior to this share this_script = bitcoin_data.pubkey_hash_to_script2( share_data['pubkey_hash']) amounts[this_script] = amounts.get( this_script, 0) + share_data['subsidy'] // 200 # 0.5% goes to block finder amounts[DONATION_SCRIPT] = amounts.get( DONATION_SCRIPT, 0 ) + share_data['subsidy'] - sum( amounts.itervalues() ) # all that's left over is the donation weight and some extra satoshis due to rounding if sum(amounts.itervalues()) != share_data['subsidy'] or any( x < 0 for x in amounts.itervalues()): raise ValueError() dests = sorted( amounts.iterkeys(), key=lambda script: (script == DONATION_SCRIPT, amounts[script], script))[ -4000:] # block length limit, unlikely to ever be hit share_info = dict( share_data=share_data, far_share_hash=None if last is None and height < 99 else tracker.get_nth_parent_hash(share_data['previous_share_hash'], 99), max_bits=max_bits, bits=bits, timestamp=math.clip( desired_timestamp, ( (previous_share.timestamp + net.SHARE_PERIOD) - (net.SHARE_PERIOD - 1), # = previous_share.timestamp + 1 (previous_share.timestamp + net.SHARE_PERIOD) + (net.SHARE_PERIOD - 1), )) if previous_share is not None else desired_timestamp, ) gentx = dict( version=1, tx_ins=[ dict( previous_output=None, sequence=None, script=share_data['coinbase'].ljust(2, '\x00'), ) ], tx_outs=[ dict(value=amounts[script], script=script) for script in dests if amounts[script] or script == DONATION_SCRIPT ] + [ dict( value=0, script='\x20' + cls.get_ref_hash(net, share_info, ref_merkle_link), ) ], lock_time=0, ) def get_share(header, transactions): assert transactions[0] == gentx and [ bitcoin_data.hash256(bitcoin_data.tx_type.pack(tx)) for tx in transactions[1:] ] == other_transaction_hashes min_header = dict(header) del min_header['merkle_root'] hash_link = prefix_to_hash_link( bitcoin_data.tx_type.pack(gentx)[:-32 - 4], cls.gentx_before_refhash) merkle_link = bitcoin_data.calculate_merkle_link( [None] + other_transaction_hashes, 0) pow_hash = net.PARENT.POW_FUNC( bitcoin_data.block_header_type.pack(header)) return cls(net, None, dict( min_header=min_header, share_info=share_info, hash_link=hash_link, ref_merkle_link=dict(branch=[], index=0), ), merkle_link=merkle_link, other_txs=transactions[1:] if pow_hash <= header['bits'].target else None) return share_info, gentx, other_transaction_hashes, get_share
def get_delta(self, element): from p2pool.bitcoin import data as bitcoin_data share = self.tracker.items[element] att = bitcoin_data.target_to_average_attempts(share.target) return 1, {share.new_script: att*(65535-share.share_data['donation'])}, att*65535, att*share.share_data['donation']
def remove(self, share_hash): assert isinstance(share_hash, (int, long, type(None))) if share_hash not in self.shares: raise KeyError() share = self.shares[share_hash] del share_hash children = self.reverse_shares.get(share.hash, set()) # move height refs referencing children down to this, so they can be moved up in one step if share.previous_hash in self.reverse_height_refs: if share.previous_hash not in self.tails: for x in list(self.reverse_heights.get(self.reverse_height_refs.get(share.previous_hash, object()), set())): self.get_last(x) for x in list(self.reverse_heights.get(self.reverse_height_refs.get(share.hash, object()), set())): self.get_last(x) assert share.hash not in self.reverse_height_refs, list(self.reverse_heights.get(self.reverse_height_refs.get(share.hash, None), set())) if share.hash in self.heads and share.previous_hash in self.tails: tail = self.heads.pop(share.hash) self.tails[tail].remove(share.hash) if not self.tails[share.previous_hash]: self.tails.pop(share.previous_hash) elif share.hash in self.heads: tail = self.heads.pop(share.hash) self.tails[tail].remove(share.hash) if self.reverse_shares[share.previous_hash] != set([share.hash]): pass # has sibling else: self.tails[tail].add(share.previous_hash) self.heads[share.previous_hash] = tail elif share.previous_hash in self.tails and len(self.reverse_shares[share.previous_hash]) <= 1: heads = self.tails.pop(share.previous_hash) for head in heads: self.heads[head] = share.hash self.tails[share.hash] = set(heads) # move ref pointing to this up if share.previous_hash in self.reverse_height_refs: assert share.hash not in self.reverse_height_refs, list(self.reverse_heights.get(self.reverse_height_refs.get(share.hash, object()), set())) ref = self.reverse_height_refs[share.previous_hash] cur_height, cur_hash, cur_work = self.height_refs[ref] assert cur_hash == share.previous_hash self.height_refs[ref] = cur_height - 1, share.hash, cur_work - bitcoin_data.target_to_average_attempts(share.target) del self.reverse_height_refs[share.previous_hash] self.reverse_height_refs[share.hash] = ref else: raise NotImplementedError() # delete height entry, and ref if it is empty if share.hash in self.heights: _, ref, _ = self.heights.pop(share.hash) self.reverse_heights[ref].remove(share.hash) if not self.reverse_heights[ref]: del self.reverse_heights[ref] _, ref_hash, _ = self.height_refs.pop(ref) del self.reverse_height_refs[ref_hash] self.shares.pop(share.hash) self.reverse_shares[share.previous_hash].remove(share.hash) if not self.reverse_shares[share.previous_hash]: self.reverse_shares.pop(share.previous_hash) self.removed.happened(share)
def generate_transaction(tracker, share_data, block_target, desired_timestamp, net): previous_share_hash = share_data['previous_share_hash'] new_script = share_data['new_script'] subsidy = share_data['subsidy'] donation = share_data['donation'] assert 0 <= donation <= 65535 if len(share_data['coinbase']) > 100: raise ValueError('coinbase too long!') previous_share = tracker.shares[ previous_share_hash] if previous_share_hash is not None else None chain_length = getattr( net, 'REAL_CHAIN_LENGTH_FUNC', lambda _: net.REAL_CHAIN_LENGTH)( previous_share.timestamp if previous_share is not None else None) height, last = tracker.get_height_and_last(previous_share_hash) assert height >= chain_length or last is None if height < net.TARGET_LOOKBEHIND: target = bitcoin_data.FloatingInteger.from_target_upper_bound( net.MAX_TARGET) else: attempts_per_second = get_pool_attempts_per_second( tracker, previous_share_hash, net.TARGET_LOOKBEHIND) pre_target = 2**256 // (net.SHARE_PERIOD * attempts_per_second) - 1 pre_target2 = math.clip(pre_target, (previous_share.target * 9 // 10, previous_share.target * 11 // 10)) pre_target3 = math.clip(pre_target2, (0, net.MAX_TARGET)) target = bitcoin_data.FloatingInteger.from_target_upper_bound( pre_target3) attempts_to_block = bitcoin_data.target_to_average_attempts(block_target) max_att = net.SPREAD * attempts_to_block this_att = min(bitcoin_data.target_to_average_attempts(target), max_att) other_weights, other_total_weight, other_donation_weight = tracker.get_cumulative_weights( previous_share_hash, min(height, chain_length), 65535 * max(0, max_att - this_att)) assert other_total_weight == sum( other_weights.itervalues()) + other_donation_weight, ( other_total_weight, sum(other_weights.itervalues()) + other_donation_weight) weights, total_weight, donation_weight = math.add_dicts( [{ new_script: this_att * (65535 - donation) }, other_weights] ), this_att * 65535 + other_total_weight, this_att * donation + other_donation_weight assert total_weight == sum(weights.itervalues()) + donation_weight, ( total_weight, sum(weights.itervalues()) + donation_weight) SCRIPT = '4104ffd03de44a6e11b9917f3a29f9443283d9871c9d743ef30d5eddcd37094b64d1b3d8090496b53256786bf5c82932ec23c3b74d9f05a6f95a8b5529352656664bac'.decode( 'hex') # 1 satoshi is always donated so that a list of p2pool generated blocks can be easily found by looking at the donation address amounts = dict( (script, (subsidy - 1) * (199 * weight) // (200 * total_weight)) for (script, weight) in weights.iteritems()) amounts[new_script] = amounts.get(new_script, 0) + (subsidy - 1) // 200 amounts[SCRIPT] = amounts.get( SCRIPT, 0) + (subsidy - 1) * (199 * donation_weight) // (200 * total_weight) amounts[SCRIPT] = amounts.get(SCRIPT, 0) + subsidy - sum( amounts.itervalues()) # collect any extra satoshis :P if sum(amounts.itervalues()) != subsidy: raise ValueError() if any(x < 0 for x in amounts.itervalues()): raise ValueError() dests = sorted(amounts.iterkeys(), key=lambda script: (amounts[script], script)) dests = dests[-4000:] # block length limit, unlikely to ever be hit share_info = dict( share_data=share_data, target=target, timestamp=math.clip(desired_timestamp, (previous_share.timestamp - 60, previous_share.timestamp + 60)) if previous_share is not None else desired_timestamp, ) return share_info, dict( version=1, tx_ins=[ dict( previous_output=None, sequence=None, script=share_data['coinbase'].ljust(2, '\x00'), ) ], tx_outs=[ dict(value=0, script='\x20' + bitcoin_data.HashType().pack( share_info_type.hash256(share_info))) ] + [ dict(value=amounts[script], script=script) for script in dests if amounts[script] ], lock_time=0, )
def from_element(cls, share): return cls(share.hash, 1, bitcoin_data.target_to_average_attempts(share.target), share.previous_hash)
def generate_transaction(tracker, previous_share_hash, new_script, subsidy, nonce, block_target, net): height, last = tracker.get_height_and_last(previous_share_hash) if height < net.TARGET_LOOKBEHIND: target = bitcoin_data.FloatingIntegerType().truncate_to(2**256//2**20 - 1) else: attempts_per_second = get_pool_attempts_per_second(tracker, previous_share_hash, net) pre_target = 2**256//(net.SHARE_PERIOD*attempts_per_second) - 1 previous_share = tracker.shares[previous_share_hash] if previous_share_hash is not None else None pre_target2 = math.clip(pre_target, (previous_share.target*9//10, previous_share.target*11//10)) pre_target3 = math.clip(pre_target2, (0, net.MAX_TARGET)) target = bitcoin_data.FloatingIntegerType().truncate_to(pre_target3) attempts_to_block = bitcoin_data.target_to_average_attempts(block_target) max_weight = net.SPREAD * attempts_to_block ''' class fake_share(object): pass fake_share.new_script = new_script fake_share.target = target dest_weights = {} total_weight = 0 for share in itertools.chain([fake_share], itertools.islice(tracker.get_chain_to_root(previous_share_hash), net.CHAIN_LENGTH)): weight = bitcoin_data.target_to_average_attempts(share.target) if weight > max_weight - total_weight: weight = max_weight - total_weight dest_weights[share.new_script] = dest_weights.get(share.new_script, 0) + weight total_weight += weight if total_weight == max_weight: break ''' this_weight = min(bitcoin_data.target_to_average_attempts(target), max_weight) other_weights, other_weights_total = tracker.get_cumulative_weights(previous_share_hash, min(height, net.CHAIN_LENGTH), max(0, max_weight - this_weight)) dest_weights, total_weight = math.add_dicts([{new_script: this_weight}, other_weights]), this_weight + other_weights_total total_weight = sum(dest_weights.itervalues()) amounts = dict((script, subsidy*(396*weight)//(400*total_weight)) for (script, weight) in dest_weights.iteritems()) amounts[new_script] = amounts.get(new_script, 0) + subsidy*2//400 amounts[net.SCRIPT] = amounts.get(net.SCRIPT, 0) + subsidy*2//400 amounts[net.SCRIPT] = amounts.get(net.SCRIPT, 0) + subsidy - sum(amounts.itervalues()) # collect any extra if sum(amounts.itervalues()) != subsidy: raise ValueError() if any(x < 0 for x in amounts.itervalues()): raise ValueError() pre_dests = sorted(amounts.iterkeys(), key=lambda script: (amounts[script], script)) pre_dests = pre_dests[-4000:] # block length limit, unlikely to ever be hit dests = sorted(pre_dests, key=lambda script: (script == new_script, script)) assert dests[-1] == new_script return dict( version=1, tx_ins=[dict( previous_output=None, sequence=None, script=coinbase_type.pack(dict( identifier=net.IDENTIFIER, share_data=dict( previous_share_hash=previous_share_hash, nonce=nonce, target=target, ), )), )], tx_outs=[dict(value=amounts[script], script=script) for script in dests if amounts[script]], lock_time=0, )
def generate_transaction(cls, tracker, share_data, block_target, desired_timestamp, desired_target, ref_merkle_link, desired_other_transaction_hashes_and_fees, net, known_txs=None, last_txout_nonce=0, base_subsidy=None): previous_share = tracker.items[share_data['previous_share_hash']] if share_data['previous_share_hash'] is not None else None def get_coinbase_fee(share_data, outpointsnum): # calculate neccessary coinbase fee # coinbase usually seems like this: # # 01000000 - nVersion # 1a184351 - nTimestamp # 01 - Inputs num # 0000000000000000000000000000000000000000000000000000000000000000 - Input hash # ffffffff - Input index (-1) # 0a02732a062f503253482f - Scriptsig # ffffffff - nSequence # 15 - Outpoints num # (User outpoints, 44 bytes per each) # (Donation outpoint, 76 bytes) # P2Pool service outpoint (contains merkle link), 46 bytes # # 1027000000000000 # 25 # 2417cc2063b11fd5255c7e5605780de78163ffc698ed22856bff1a5d880c3c44e400000000 # Giving users some time to upgrade coinbase_size = 50 + (1 + len(share_data['coinbase'])) + outpointsnum * 44 + 76 + 46 # if coinbase size is greater than 1000 bytes, it should pay fee (0.01 per 1000 bytes) if coinbase_size > 1000: return int(ceil(coinbase_size / 1000.0) * minout) return 0 if base_subsidy is None: base_subsidy = net.PARENT.SUBSIDY_FUNC(block_target) # current user payout script this_script = share_data['script'] height, last = tracker.get_height_and_last(share_data['previous_share_hash']) assert height >= net.REAL_CHAIN_LENGTH or last is None if height < net.TARGET_LOOKBEHIND: pre_target3 = net.MAX_TARGET else: attempts_per_second = get_pool_attempts_per_second(tracker, share_data['previous_share_hash'], net.TARGET_LOOKBEHIND, min_work=True, integer=True) pre_target = 2**256//(net.SHARE_PERIOD*attempts_per_second) - 1 if attempts_per_second else 2**256-1 pre_target2 = math.clip(pre_target, (previous_share.max_target*9//10, previous_share.max_target*11//10)) pre_target3 = math.clip(pre_target2, (net.MIN_TARGET, net.MAX_TARGET)) max_bits = bitcoin_data.FloatingInteger.from_target_upper_bound(pre_target3) bits = bitcoin_data.FloatingInteger.from_target_upper_bound(math.clip(desired_target, (pre_target3//10, pre_target3))) if p2pool.DEBUG: print print "Share Info Bits Target (DATA)" print bitcoin_data.target_to_difficulty(bits.target) print new_transaction_hashes = [] new_transaction_size = 0 transaction_hash_refs = [] other_transaction_hashes = [] past_shares = list(tracker.get_chain(share_data['previous_share_hash'], min(height, 100))) tx_hash_to_this = {} for i, share in enumerate(past_shares): for j, tx_hash in enumerate(share.new_transaction_hashes): if tx_hash not in tx_hash_to_this: tx_hash_to_this[tx_hash] = [1+i, j] # share_count, tx_count for tx_hash, fee in desired_other_transaction_hashes_and_fees: if tx_hash in tx_hash_to_this: this = tx_hash_to_this[tx_hash] else: if known_txs is not None: this_size = bitcoin_data.tx_type.packed_size(known_txs[tx_hash]) if new_transaction_size + this_size > 50000: # only allow 50 kB of new txns/share break new_transaction_size += this_size new_transaction_hashes.append(tx_hash) this = [0, len(new_transaction_hashes)-1] transaction_hash_refs.extend(this) other_transaction_hashes.append(tx_hash) included_transactions = set(other_transaction_hashes) share_data = dict(share_data, subsidy=base_subsidy) raw_weights, total_weight, donation_weight = tracker.get_cumulative_weights(share_data['previous_share_hash'], min(height, net.REAL_CHAIN_LENGTH), 65535*net.SPREAD*bitcoin_data.target_to_average_attempts(block_target), ) # calculate "raw" subsidy raw_subsidy = share_data['subsidy'] - 4 * minout - get_coinbase_fee(share_data, len(raw_weights) + 1) # calculate "raw" amounts raw_amounts = dict((script, raw_subsidy*weight//total_weight) for script, weight in raw_weights.iteritems()) total_remowed_weight = 0 weights = {} # iterate list and collect all weights, which produces less than 0.01 payout # it's neccessary due to NVC/PPC protocol-level limitations for coinbase outpoint size for x in raw_amounts.keys(): if raw_amounts[x] < minout and x not in [this_script, DONATION_SCRIPT]: total_remowed_weight = total_remowed_weight + raw_weights[x] else: weights[x] = raw_weights[x] total_weight = total_weight - total_remowed_weight assert total_weight == sum(weights.itervalues()) + donation_weight, (total_weight, sum(weights.itervalues()) + donation_weight) # base subsidy value calculated as: # [subsidy - (0.01 for donation + 0.01 for current user + 0.01 for p2pool outpoint) - netfee] my_subsidy = share_data['subsidy'] - 3 * minout - get_coinbase_fee(share_data, len(weights) + 1) # subsidy goes according to weights prior to this share amounts = dict((script, my_subsidy*weight//total_weight) for script, weight in weights.iteritems()) # all that's left over is the donation weight and some extra satoshis due to rounding amounts[DONATION_SCRIPT] = amounts.get(DONATION_SCRIPT, 0) + my_subsidy - sum(amounts.itervalues()) if sum(amounts.itervalues()) != my_subsidy or any(x < 0 for x in amounts.itervalues()): raise ValueError() # add 0.01 coin to donation, to satisfy the protocol amounts[DONATION_SCRIPT] = amounts[DONATION_SCRIPT] + minout # add 0.01 to current user output, to satisfy the protocol amounts[this_script] = amounts.get(this_script, 0) + minout dests = sorted(amounts.iterkeys(), key=lambda script: (script == DONATION_SCRIPT, amounts[script], script))[-4000:] # block length limit, unlikely to ever be hi share_info = dict( share_data=share_data, far_share_hash=None if last is None and height < 99 else tracker.get_nth_parent_hash(share_data['previous_share_hash'], 99), max_bits=max_bits, bits=bits, timestamp=desired_timestamp, # need better solution # timestamp=math.clip(desired_timestamp, ( # (previous_share.timestamp + net.SHARE_PERIOD) - (net.SHARE_PERIOD - 1), # = previous_share.timestamp + 1 # (previous_share.timestamp + net.SHARE_PERIOD) + (net.SHARE_PERIOD - 1), # )) if previous_share is not None else desired_timestamp, new_transaction_hashes=new_transaction_hashes, transaction_hash_refs=transaction_hash_refs, ) if p2pool.DEBUG: print print "Desired timestamp (DATA)" print desired_timestamp print time.time() print print "Prev Share timestamp (DATA)" print previous_share.timestamp print time.time() print print "Share info timestamp (DATA)" print share_info['timestamp'] print time.time() print gentx = dict( version=1, # coinbase timestamp must be older than share/block timestamp # maybe there are more elegant solution, but this hack works quite well for now timestamp=share_info['timestamp'], tx_ins=[dict( previous_output=None, sequence=None, script=share_data['coinbase'], )], tx_outs=[dict(value=amounts[script], script=script) for script in dests if amounts[script] or script == DONATION_SCRIPT] + [dict( # add 0.01 coin to service output, to satisfy the protocol value=minout, script='\x24' + cls.get_ref_hash(net, share_info, ref_merkle_link) + pack.IntType(32).pack(last_txout_nonce), )], lock_time=0, ) def get_share(header, last_txout_nonce=last_txout_nonce): min_header = dict(header); del min_header['merkle_root'] share = cls(net, None, dict( min_header=min_header, share_info=share_info, ref_merkle_link=dict(branch=[], index=0), last_txout_nonce=last_txout_nonce, hash_link=prefix_to_hash_link(bitcoin_data.tx_type.pack(gentx)[:-32-4-4], cls.gentx_before_refhash), merkle_link=bitcoin_data.calculate_merkle_link([None] + other_transaction_hashes, 0), )) assert share.header == header # checks merkle_root return share return share_info, gentx, other_transaction_hashes, get_share
def remove(self, share_hash): assert isinstance(share_hash, (int, long, type(None))) if share_hash not in self.shares: raise KeyError() share = self.shares[share_hash] del share_hash children = self.reverse_shares.get(share.hash, set()) # move height refs referencing children down to this, so they can be moved up in one step if share.previous_hash in self.reverse_height_refs: if share.previous_hash not in self.tails: for x in list( self.reverse_heights.get( self.reverse_height_refs.get( share.previous_hash, object()), set())): self.get_last(x) for x in list( self.reverse_heights.get( self.reverse_height_refs.get(share.hash, object()), set())): self.get_last(x) assert share.hash not in self.reverse_height_refs, list( self.reverse_heights.get( self.reverse_height_refs.get(share.hash, None), set())) if share.hash in self.heads and share.previous_hash in self.tails: tail = self.heads.pop(share.hash) self.tails[tail].remove(share.hash) if not self.tails[share.previous_hash]: self.tails.pop(share.previous_hash) elif share.hash in self.heads: tail = self.heads.pop(share.hash) self.tails[tail].remove(share.hash) if self.reverse_shares[share.previous_hash] != set([share.hash]): pass # has sibling else: self.tails[tail].add(share.previous_hash) self.heads[share.previous_hash] = tail elif share.previous_hash in self.tails and len( self.reverse_shares[share.previous_hash]) <= 1: heads = self.tails.pop(share.previous_hash) for head in heads: self.heads[head] = share.hash self.tails[share.hash] = set(heads) # move ref pointing to this up if share.previous_hash in self.reverse_height_refs: assert share.hash not in self.reverse_height_refs, list( self.reverse_heights.get( self.reverse_height_refs.get(share.hash, object()), set())) ref = self.reverse_height_refs[share.previous_hash] cur_height, cur_hash, cur_work = self.height_refs[ref] assert cur_hash == share.previous_hash self.height_refs[ ref] = cur_height - 1, share.hash, cur_work - bitcoin_data.target_to_average_attempts( share.target) del self.reverse_height_refs[share.previous_hash] self.reverse_height_refs[share.hash] = ref else: raise NotImplementedError() # delete height entry, and ref if it is empty if share.hash in self.heights: _, ref, _ = self.heights.pop(share.hash) self.reverse_heights[ref].remove(share.hash) if not self.reverse_heights[ref]: del self.reverse_heights[ref] _, ref_hash, _ = self.height_refs.pop(ref) del self.reverse_height_refs[ref_hash] self.shares.pop(share.hash) self.reverse_shares[share.previous_hash].remove(share.hash) if not self.reverse_shares[share.previous_hash]: self.reverse_shares.pop(share.previous_hash) self.removed.happened(share)
def get_desired_version_counts(tracker, best_share_hash, dist): res = {} for share in tracker.get_chain(best_share_hash, dist): res[share.desired_version] = res.get(share.desired_version, 0) + bitcoin_data.target_to_average_attempts(share.target) return res
def _get_height_jump(self, share_hash): if share_hash in self.heights: height_to1, ref, work_inc1 = self.heights[share_hash] height_to2, share_hash, work_inc2 = self.height_refs[ref] height_inc = height_to1 + height_to2 work_inc = work_inc1 + work_inc2 else: height_inc, share_hash, work_inc = 1, self.shares[share_hash].previous_hash, bitcoin_data.target_to_average_attempts(self.shares[share_hash].target) return height_inc, share_hash, work_inc