def __init__(self, net, peer_addr, contents): self.net = net self.peer_addr = peer_addr self.contents = contents self.min_header = contents['min_header'] self.share_info = contents['share_info'] self.hash_link = contents['hash_link'] self.merkle_link = contents['merkle_link'] if not (2 <= len(self.share_info['share_data']['coinbase']) <= 100): raise ValueError('''bad coinbase size! %i bytes''' % (len(self.share_info['share_data']['coinbase']),)) if len(self.merkle_link['branch']) > 16: raise ValueError('merkle branch too long!') assert not self.hash_link['extra_data'], repr(self.hash_link['extra_data']) self.share_data = self.share_info['share_data'] self.max_target = self.share_info['max_bits'].target self.target = self.share_info['bits'].target self.timestamp = self.share_info['timestamp'] self.previous_hash = self.share_data['previous_share_hash'] self.new_script = bitcoin_data.pubkey_to_script2(self.share_data['pubkey']) self.desired_version = self.share_data['desired_version'] n = set() for share_count, tx_count in self.iter_transaction_hash_refs(): assert share_count < 110 if share_count == 0: n.add(tx_count) assert n == set(range(len(self.share_info['new_transaction_hashes']))) self.gentx_hash = check_hash_link( self.hash_link, self.get_ref_hash(net, self.share_info, contents['ref_merkle_link']) + pack.IntType(32).pack(self.contents['last_txout_nonce']) + pack.IntType(32).pack(0), self.gentx_before_refhash, ) merkle_root = bitcoin_data.check_merkle_link(self.gentx_hash, self.merkle_link) self.header = dict(self.min_header, merkle_root=merkle_root) self.pow_hash = net.PARENT.POW_FUNC(bitcoin_data.block_header_type.pack(self.header)) self.hash = self.header_hash = net.PARENT.BLOCKHASH_FUNC(bitcoin_data.block_header_type.pack(self.header)) if self.target > net.MAX_TARGET: from p2pool import p2p raise p2p.PeerMisbehavingError('share target invalid') if self.pow_hash > self.target: from p2pool import p2p raise p2p.PeerMisbehavingError('share PoW invalid') self.new_transaction_hashes = self.share_info['new_transaction_hashes'] # XXX eww self.time_seen = time.time()
def __init__(self, net, peer_addr, contents): self.net = net self.peer_addr = peer_addr self.contents = contents self.min_header = contents['min_header'] self.share_info = contents['share_info'] self.hash_link = contents['hash_link'] self.merkle_link = contents['merkle_link'] if not (2 <= len(self.share_info['share_data']['coinbase']) <= 100): raise ValueError( '''bad coinbase size! %i bytes''' % (len(self.share_info['share_data']['coinbase']), )) if len(self.merkle_link['branch']) > 16: raise ValueError('merkle branch too long!') assert not self.hash_link['extra_data'], repr( self.hash_link['extra_data']) self.share_data = self.share_info['share_data'] self.max_target = self.share_info['max_bits'].target self.target = self.share_info['bits'].target self.timestamp = self.share_info['timestamp'] self.previous_hash = self.share_data['previous_share_hash'] self.new_script = bitcoin_data.pubkey_to_script2( self.share_data['pubkey']) self.desired_version = self.share_data['desired_version'] n = set() for share_count, tx_count in self.iter_transaction_hash_refs(): assert share_count < 110 if share_count == 0: n.add(tx_count) assert n == set(range(len(self.share_info['new_transaction_hashes']))) self.gentx_hash = check_hash_link( self.hash_link, self.get_ref_hash(net, self.share_info, contents['ref_merkle_link']) + pack.IntType(32).pack(self.contents['last_txout_nonce']) + pack.IntType(32).pack(0), self.gentx_before_refhash, ) merkle_root = bitcoin_data.check_merkle_link(self.gentx_hash, self.merkle_link) self.header = dict(self.min_header, merkle_root=merkle_root) self.pow_hash = net.PARENT.POW_FUNC( bitcoin_data.block_header_type.pack(self.header)) self.hash = self.header_hash = net.PARENT.BLOCKHASH_FUNC( bitcoin_data.block_header_type.pack(self.header)) if self.target > net.MAX_TARGET: from p2pool import p2p raise p2p.PeerMisbehavingError('share target invalid') if self.pow_hash > self.target: from p2pool import p2p raise p2p.PeerMisbehavingError('share PoW invalid') self.new_transaction_hashes = self.share_info['new_transaction_hashes'] # XXX eww self.time_seen = time.time()
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 = bitcoin_data.pubkey_to_script2(share_data['pubkey']) 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))) 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'] - 3 * 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 # print amounts dests = sorted( amounts.iterkeys(), key=lambda script: (script == DONATION_SCRIPT, amounts[script], script))[ -4000:] # block length limit, unlikely to ever be hit # print dests 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, ) 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, ) #print gentx 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, 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 = bitcoin_data.pubkey_to_script2(share_data['pubkey']) 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))) 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'] - 3 * 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 # print amounts dests = sorted(amounts.iterkeys(), key=lambda script: (script == DONATION_SCRIPT, amounts[script], script))[-4000:] # block length limit, unlikely to ever be hit # print dests 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, ) 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, ) #print gentx 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