Merge branch 'master' into optional_donation
authorForrest Voight <forrest.voight@gmail.com>
Thu, 24 Nov 2011 17:59:46 +0000 (12:59 -0500)
committerForrest Voight <forrest.voight@gmail.com>
Thu, 24 Nov 2011 17:59:46 +0000 (12:59 -0500)
Conflicts:
p2pool/data.py
p2pool/main.py

1  2 
p2pool/bitcoin/data.py
p2pool/data.py
p2pool/main.py
p2pool/p2p.py

diff --combined p2pool/bitcoin/data.py
@@@ -105,12 -105,9 +105,9 @@@ class Type(object)
      
      def hash256(self, obj):
          return HashType().unpack(hashlib.sha256(hashlib.sha256(self.pack(obj)).digest()).digest())
-     ltc_scrypt = None
+     
      def scrypt(self, obj):
-         # dynamically import ltc_scrypt so you will only get an error on runtime
-         if (not self.ltc_scrypt):
-             self.ltc_scrypt = __import__('ltc_scrypt')
+         import ltc_scrypt
          return HashType().unpack(self.ltc_scrypt.getPoWHash(self.pack(obj)))
  
  class VarIntType(Type):
@@@ -439,12 -436,10 +436,12 @@@ tx_type = ComposedType(
      ('lock_time', StructType('<I')),
  ])
  
 +merkle_branch_type = ListType(HashType())
 +
  merkle_tx_type = ComposedType([
      ('tx', tx_type),
      ('block_hash', HashType()),
 -    ('merkle_branch', ListType(HashType())),
 +    ('merkle_branch', merkle_branch_type),
      ('index', StructType('<i')),
  ])
  
@@@ -464,7 -459,7 +461,7 @@@ block_type = ComposedType(
  
  aux_pow_type = ComposedType([
      ('merkle_tx', merkle_tx_type),
 -    ('merkle_branch', ListType(HashType())),
 +    ('merkle_branch', merkle_branch_type),
      ('index', StructType('<i')),
      ('parent_block_header', block_header_type),
  ])
@@@ -484,42 -479,9 +481,42 @@@ def merkle_hash(tx_list)
              for left, right in zip(hash_list[::2], hash_list[1::2] + [None])]
      return hash_list[0]
  
 +def calculate_merkle_branch(txs, index):
 +    # XXX optimize this
 +    
 +    hash_list = [(tx_type.hash256(tx), i == index, []) for i, tx in enumerate(txs)]
 +    
 +    while len(hash_list) > 1:
 +        hash_list = [
 +            (
 +                merkle_record_type.hash256(dict(left=left, right=right)),
 +                left_f or right_f,
 +                (left_l if left_f else right_l) + [dict(side=1, hash=right) if left_f else dict(side=0, hash=left)],
 +            )
 +            for (left, left_f, left_l), (right, right_f, right_l) in
 +                zip(hash_list[::2], hash_list[1::2] + [hash_list[::2][-1]])
 +        ]
 +    
 +    res = [x['hash'] for x in hash_list[0][2]]
 +    
 +    assert hash_list[0][1]
 +    assert check_merkle_branch(txs[index], index, res) == hash_list[0][0]
 +    assert index == sum(k*2**i for i, k in enumerate([1-x['side'] for x in hash_list[0][2]]))
 +    
 +    return res
 +
 +def check_merkle_branch(tx, index, merkle_branch):
 +    return reduce(lambda c, (i, h): merkle_record_type.hash256(
 +        dict(left=h, right=c) if 2**i & index else
 +        dict(left=c, right=h)
 +    ), enumerate(merkle_branch), tx_type.hash256(tx))
 +
  def target_to_average_attempts(target):
      return 2**256//(target + 1)
  
 +def target_to_difficulty(target):
 +    return (0xffff0000 * 2**(256-64) + 1)/(target + 1)
 +
  # tx
  
  def tx_get_sigop_count(tx):
@@@ -876,31 -838,3 +873,3 @@@ if __name__ == '__main__'
              for a in x[1]:
                  print str(a).rjust(10),
          print
- # network definitions
- class Mainnet(object):
-     BITCOIN_P2P_PREFIX = 'f9beb4d9'.decode('hex')
-     BITCOIN_P2P_PORT = 8333
-     BITCOIN_ADDRESS_VERSION = 0
-     BITCOIN_RPC_PORT = 8332
-     BITCOIN_RPC_CHECK = staticmethod(defer.inlineCallbacks(lambda bitcoind: defer.returnValue(
-         'name_firstupdate' not in (yield bitcoind.rpc_help()) and
-         'ixcoinaddress' not in (yield bitcoind.rpc_help()) and
-         not (yield bitcoind.rpc_getinfo())['testnet']
-     )))
-     BITCOIN_SUBSIDY_FUNC = staticmethod(lambda height: 50*100000000 >> (height + 1)//210000)
-     BITCOIN_SYMBOL = 'BTC'
- class Testnet(object):
-     BITCOIN_P2P_PREFIX = 'fabfb5da'.decode('hex')
-     BITCOIN_P2P_PORT = 18333
-     BITCOIN_ADDRESS_VERSION = 111
-     BITCOIN_RPC_PORT = 8332
-     BITCOIN_RPC_CHECK = staticmethod(defer.inlineCallbacks(lambda bitcoind: defer.returnValue(
-         'name_firstupdate' not in (yield bitcoind.rpc_help()) and
-         'ixcoinaddress' not in (yield bitcoind.rpc_help()) and
-         (yield bitcoind.rpc_getinfo())['testnet']
-     )))
-     BITCOIN_SUBSIDY_FUNC = staticmethod(lambda height: 50*100000000 >> (height + 1)//210000)
-     BITCOIN_SYMBOL = 'tBTC'
diff --combined p2pool/data.py
@@@ -9,7 -9,7 +9,7 @@@ from twisted.python import lo
  
  import p2pool
  from p2pool import skiplists
- from p2pool.bitcoin import data as bitcoin_data, script, namecoin, ixcoin, i0coin, solidcoin, litecoin
+ from p2pool.bitcoin import data as bitcoin_data, script, networks
  from p2pool.util import memoize, expiring_dict, math
  
  
@@@ -50,32 -50,6 +50,32 @@@ share1b_type = bitcoin_data.ComposedTyp
      ('other_txs', bitcoin_data.ListType(bitcoin_data.tx_type)),
  ])
  
 +new_share_data_type = bitcoin_data.ComposedType([
 +    ('previous_share_hash', bitcoin_data.PossiblyNoneType(0, bitcoin_data.HashType())),
 +    ('coinbase', bitcoin_data.VarStrType()),
 +    ('nonce', bitcoin_data.VarStrType()),
 +    ('new_script', bitcoin_data.VarStrType()),
 +    ('subsidy', bitcoin_data.StructType('<Q')),
 +    ('donation', bitcoin_data.StructType('<H')),
 +])
 +
 +new_share_info_type = bitcoin_data.ComposedType([
 +    ('new_share_data', new_share_data_type),
 +    ('target', bitcoin_data.FloatingIntegerType()),
 +])
 +
 +new_share1a_type = bitcoin_data.ComposedType([
 +    ('header', bitcoin_data.block_header_type),
 +    ('share_info', new_share_info_type),
 +    ('merkle_branch', bitcoin_data.merkle_branch_type),
 +])
 +
 +new_share1b_type = bitcoin_data.ComposedType([
 +    ('header', bitcoin_data.block_header_type),
 +    ('share_info', new_share_info_type),
 +    ('other_txs', bitcoin_data.ListType(bitcoin_data.tx_type)),
 +])
 +
  def calculate_merkle_branch(txs, index):
      hash_list = [(bitcoin_data.tx_type.hash256(tx), i == index, []) for i, tx in enumerate(txs)]
      
@@@ -104,6 -78,13 +104,6 @@@ def check_merkle_branch(tx, branch)
              hash_ = bitcoin_data.merkle_record_type.hash256(dict(left=hash_, right=step['hash']))
      return hash_
  
 -def gentx_to_share_info(gentx):
 -    return dict(
 -        share_data=coinbase_type.unpack(gentx['tx_ins'][0]['script'])['share_data'],
 -        subsidy=sum(tx_out['value'] for tx_out in gentx['tx_outs']),
 -        new_script=gentx['tx_outs'][-1]['script'],
 -    )
 -
  def share_info_to_gentx(share_info, block_target, tracker, net):
      return generate_transaction(
          tracker=tracker,
          nonce=share_info['share_data']['nonce'],
          block_target=block_target,
          net=net,
 -    )
 +    )[1]
  
  class Share(object):
 +    donation = 0
 +    
      @classmethod
      def from_block(cls, block, net):
          return cls(net, block['header'], gentx_to_share_info(block['txs'][0]), other_txs=block['txs'][1:])
          if len(self.nonce) > 100:
              raise ValueError('nonce too long!')
          
-         # use scrypt for Litecoin
-         if (getattr(net, 'BITCOIN_POW_SCRYPT', False)):
-             self.bitcoin_hash = bitcoin_data.block_header_type.scrypt(header)
+         self.bitcoin_hash = net.BITCOIN_POW_FUNC(header)
+         
+         if net.BITCOIN_POW_FUNC is bitcoin_data.block_header_type.scrypt:
+             # compatibility hack
              self.hash = share1a_type.scrypt(self.as_share1a())
          else:
-             self.bitcoin_hash = bitcoin_data.block_header_type.hash256(header)
              self.hash = share1a_type.hash256(self.as_share1a())
          
          if self.bitcoin_hash > self.target:
          return dict(header=self.header, share_info=self.share_info, merkle_branch=self.merkle_branch)
      
      def as_share1b(self):
 +        if self.other_txs is None:
 +            raise ValueError('share does not contain all txs')
 +        
          return dict(header=self.header, share_info=self.share_info, other_txs=self.other_txs)
      
      def check(self, tracker, now, net):
      def __repr__(self):
          return '<Share %s>' % (' '.join('%s=%r' % (k, getattr(self, k)) for k in self.__slots__),)
  
 +class NewShare(Share):
 +    def __init__(self, net, header, new_share_info, merkle_branch=None, other_txs=None):
 +        if merkle_branch is None and other_txs is None:
 +            raise ValueError('need either merkle_branch or other_txs')
 +        if other_txs is not None:
 +            new_merkle_branch = bitcoin_data.calculate_merkle_branch([dict(version=0, tx_ins=[], tx_outs=[], lock_time=0)] + other_txs, 0)
 +            if merkle_branch is not None:
 +                if merke_branch != new_merkle_branch:
 +                    raise ValueError('invalid merkle_branch and other_txs')
 +            merkle_branch = new_merkle_branch
 +        
 +        if len(merkle_branch) > 16:
 +            raise ValueError('merkle_branch too long!')
 +        
 +        self.header = header
 +        self.previous_block = header['previous_block']
 +        self.share_info = new_share_info
 +        self.merkle_branch = merkle_branch
 +        self.other_txs = other_txs
 +        
 +        self.timestamp = self.header['timestamp']
 +        
 +        self.share_data = self.share_info['new_share_data']
 +        self.new_script = self.share_data['new_script']
 +        self.subsidy = self.share_data['subsidy']
 +        
 +        if len(self.new_script) > 100:
 +            raise ValueError('new_script too long!')
 +        
 +        self.previous_hash = self.previous_share_hash = self.share_data['previous_share_hash']
 +        self.target = self.share_info['target']
 +        self.nonce = self.share_data['nonce']
 +        
 +        if len(self.nonce) > 100:
 +            raise ValueError('nonce too long!')
 +        
 +        if getattr(net, 'BITCOIN_POW_SCRYPT', False):
 +            self.bitcoin_hash = bitcoin_data.block_header_type.scrypt(header)
 +        else:
 +            self.bitcoin_hash = bitcoin_data.block_header_type.hash256(header)
 +        
 +        self.hash = new_share1a_type.hash256(self.as_share1a())
 +        
 +        if self.bitcoin_hash > self.target:
 +            print 'hash %x' % self.bitcoin_hash
 +            print 'targ %x' % self.target
 +            raise ValueError('not enough work!')
 +        
 +        if script.get_sigop_count(self.new_script) > 1:
 +            raise ValueError('too many sigops!')
 +        
 +        # XXX eww
 +        self.time_seen = time.time()
 +        self.shared = False
 +        self.stored = False
 +        self.peer = None
 +    
 +    def check(self, tracker, now, net):
 +        import time
 +        if self.previous_share_hash is not None:
 +            if self.header['timestamp'] <= math.median((s.timestamp for s in itertools.islice(tracker.get_chain_to_root(self.previous_share_hash), 11)), use_float=False):
 +                raise ValueError('share from too far in the past!')
 +        
 +        if self.header['timestamp'] > now + 2*60*60:
 +            raise ValueError('share from too far in the future!')
 +        
 +        new_share_info, gentx = new_generate_transaction(tracker, self.share_info['new_share_data'], self.header['target'], net)
 +        if new_share_info != self.share_info:
 +            raise ValueError()
 +        
 +        if len(gentx['tx_ins'][0]['script']) > 100:
 +            raise ValueError('''coinbase too large! %i bytes''' % (len(gentx['tx_ins'][0]['script']),))
 +        
 +        if bitcoin_data.check_merkle_branch(gentx, 0, self.merkle_branch) != self.header['merkle_root']:
 +            raise ValueError('''gentx doesn't match header via merkle_branch''')
 +        
 +        if self.other_txs is not None:
 +            if bitcoin_data.merkle_hash([gentx] + self.other_txs) != self.header['merkle_root']:
 +                raise ValueError('''gentx doesn't match header via other_txs''')
 +            
 +            if len(bitcoin_data.block_type.pack(dict(header=self.header, txs=[gentx] + self.other_txs))) > 1000000 - 1000:
 +                raise ValueError('''block size too large''')
 +
  def get_pool_attempts_per_second(tracker, previous_share_hash, net, dist=None):
      if dist is None:
          dist = net.TARGET_LOOKBEHIND
@@@ -344,13 -237,13 +344,13 @@@ def generate_transaction(tracker, previ
          target = bitcoin_data.FloatingInteger.from_target_upper_bound(pre_target3)
      
      attempts_to_block = bitcoin_data.target_to_average_attempts(block_target)
 -    max_weight = net.SPREAD * attempts_to_block
 +    max_weight = 65535 * net.SPREAD * attempts_to_block
 +    
 +    this_weight = 65535 * min(bitcoin_data.target_to_average_attempts(target), max_weight)
 +    other_weights, other_weights_total, other_donation_weight_total = tracker.get_cumulative_weights(previous_share_hash, min(height, net.CHAIN_LENGTH), max(0, max_weight - this_weight))
 +    dest_weights, total_weight, donation_weight_total = math.add_dicts([{new_script: this_weight}, other_weights]), this_weight + other_weights_total, other_donation_weight_total
 +    assert total_weight == sum(dest_weights.itervalues()) + donation_weight_total
      
 -    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
 -    assert total_weight == sum(dest_weights.itervalues())
 -
      if net.SCRIPT:
          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 = dict((script, subsidy*(398*weight)//(400*total_weight)) for (script, weight) in dest_weights.iteritems())
          amounts[new_script] = amounts.get(new_script, 0) + subsidy*2//400
          amounts[new_script] = amounts.get(new_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()):
      dests = sorted(pre_dests, key=lambda script: (script == new_script, script))
      assert dests[-1] == new_script
      
 -    return dict(
 +    share_data = dict(
 +        previous_share_hash=previous_share_hash,
 +        nonce=nonce,
 +        target=target,
 +    )
 +    
 +    share_info = dict(
 +        share_data=share_data,
 +        new_script=new_script,
 +        subsidy=subsidy,
 +    )
 +    
 +    return share_info, 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,
 -                ),
 +                share_data=share_data,
              )),
          )],
          tx_outs=[dict(value=amounts[script], script=script) for script in dests if amounts[script]],
          lock_time=0,
      )
  
 +def new_generate_transaction(tracker, new_share_data, block_target, net):
 +    previous_share_hash = new_share_data['previous_share_hash']
 +    new_script = new_share_data['new_script']
 +    subsidy = new_share_data['subsidy']
 +    donation = new_share_data['donation']
 +    assert 0 <= donation <= 65535
 +    
 +    height, last = tracker.get_height_and_last(previous_share_hash)
 +    assert height >= net.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)
 +        previous_share = tracker.shares[previous_share_hash] if previous_share_hash is not None else None
 +        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, net.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)
 +    
 +    amounts = dict((script, subsidy*(199*weight)//(200*total_weight)) for (script, weight) in weights.iteritems())
 +    amounts[new_script] = amounts.get(new_script, 0) + subsidy//200
 +    amounts[net.SCRIPT] = amounts.get(net.SCRIPT, 0) + subsidy*(199*donation_weight)//(200*total_weight)
 +    amounts[net.SCRIPT] = amounts.get(net.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()):
 +        print amounts
 +        import code
 +        d = globals()
 +        d.update(locals())
 +        code.interact(local=d)
 +        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
 +    
 +    new_share_info = dict(
 +        new_share_data=new_share_data,
 +        target=target,
 +    )
 +    
 +    return new_share_info, dict(
 +        version=1,
 +        tx_ins=[dict(
 +            previous_output=None,
 +            sequence=None,
 +            script=new_share_data['coinbase'],
 +        )],
 +        tx_outs=[dict(value=0, script=bitcoin_data.HashType().pack(new_share_info_type.hash256(new_share_info)))] + [dict(value=amounts[script], script=script) for script in dests if amounts[script]],
 +        lock_time=0,
 +    )
  
  
  class OkayTracker(bitcoin_data.Tracker):
@@@ -667,14 -488,6 +667,14 @@@ class ShareStore(object)
                              verified_hash = int(data_hex, 16)
                              yield 'verified_hash', verified_hash
                              verified_hashes.add(verified_hash)
 +                        elif type_id == 3:
 +                            share = NewShare.from_share1a(new_share1a_type.unpack(data_hex.decode('hex')), self.net)
 +                            yield 'share', share
 +                            share_hashes.add(share.hash)
 +                        elif type_id == 4:
 +                            share = NewShare.from_share1b(new_share1b_type.unpack(data_hex.decode('hex')), self.net)
 +                            yield 'share', share
 +                            share_hashes.add(share.hash)
                          else:
                              raise NotImplementedError("share type %i" % (type_id,))
                      except Exception:
          return filename
      
      def add_share(self, share):
 -        if share.bitcoin_hash <= share.header['target']:
 +        if isinstance(share, NewShare) and share.bitcoin_hash <= share.header['target']:
 +            type_id, data = 4, new_share1b_type.pack(share.as_share1b())
 +        elif isinstance(share, NewShare):
 +            type_id, data = 3, new_share1a_type.pack(share.as_share1a())
 +        elif share.bitcoin_hash <= share.header['target']:
              type_id, data = 1, share1b_type.pack(share.as_share1b())
          else:
              type_id, data = 0, share1a_type.pack(share.as_share1a())
              os.remove(filename)
              print "REMOVED", filename
  
- class BitcoinMainnet(bitcoin_data.Mainnet):
+ class BitcoinMainnet(networks.BitcoinMainnet):
      SHARE_PERIOD = 10 # seconds
      CHAIN_LENGTH = 24*60*60//5 # shares
      TARGET_LOOKBEHIND = 200 # shares
      NAME = 'bitcoin'
      P2P_PORT = 9333
      MAX_TARGET = 2**256//2**32 - 1
 -    PERSIST = True
 +    PERSIST = False
      WORKER_PORT = 9332
  
- class BitcoinTestnet(bitcoin_data.Testnet):
+ class BitcoinTestnet(networks.BitcoinTestnet):
      SHARE_PERIOD = 1 # seconds
      CHAIN_LENGTH = 24*60*60//5 # shares
      TARGET_LOOKBEHIND = 200 # shares
      PERSIST = False
      WORKER_PORT = 19332
  
- class NamecoinMainnet(namecoin.Mainnet):
+ class NamecoinMainnet(networks.NamecoinMainnet):
      SHARE_PERIOD = 10 # seconds
      CHAIN_LENGTH = 24*60*60//10 # shares
      TARGET_LOOKBEHIND = 3600//10 # shares
      PERSIST = True
      WORKER_PORT = 9331
  
- class NamecoinTestnet(namecoin.Testnet):
+ class NamecoinTestnet(networks.NamecoinTestnet):
      SHARE_PERIOD = 1 # seconds
      CHAIN_LENGTH = 24*60*60//5 # shares
      TARGET_LOOKBEHIND = 200 # shares
      PERSIST = False
      WORKER_PORT = 19331
  
- class IxcoinMainnet(ixcoin.Mainnet):
+ class IxcoinMainnet(networks.IxcoinMainnet):
      SHARE_PERIOD = 10 # seconds
      CHAIN_LENGTH = 24*60*60//10 # shares
      TARGET_LOOKBEHIND = 3600//10 # shares
      PERSIST = True
      WORKER_PORT = 9330
  
- class IxcoinTestnet(ixcoin.Testnet):
+ class IxcoinTestnet(networks.IxcoinTestnet):
      SHARE_PERIOD = 1 # seconds
      CHAIN_LENGTH = 24*60*60//5 # shares
      TARGET_LOOKBEHIND = 200 # shares
      PERSIST = False
      WORKER_PORT = 19330
  
- class I0coinMainnet(i0coin.Mainnet):
+ class I0coinMainnet(networks.I0coinMainnet):
      SHARE_PERIOD = 10 # seconds
      CHAIN_LENGTH = 24*60*60//10 # shares
      TARGET_LOOKBEHIND = 3600//10 # shares
      PERSIST = False
      WORKER_PORT = 9329
  
- class I0coinTestnet(i0coin.Testnet):
+ class I0coinTestnet(networks.I0coinTestnet):
      SHARE_PERIOD = 1 # seconds
      CHAIN_LENGTH = 24*60*60//5 # shares
      TARGET_LOOKBEHIND = 200 # shares
      PERSIST = False
      WORKER_PORT = 19329
  
- class SolidcoinMainnet(solidcoin.Mainnet):
+ class SolidcoinMainnet(networks.SolidcoinMainnet):
      SHARE_PERIOD = 10
      CHAIN_LENGTH = 24*60*60//10 # shares
      TARGET_LOOKBEHIND = 3600//10 # shares
      SPREAD = 3 # blocks
-     SCRIPT = bitcoin_data.pubkey_hash_to_script2(bitcoin_data.address_to_pubkey_hash('sMKZ1yxHETxPYKh4Z2anWnwZDJZU7ztroy', solidcoin.Mainnet))
+     SCRIPT = bitcoin_data.pubkey_hash_to_script2(bitcoin_data.address_to_pubkey_hash('sMKZ1yxHETxPYKh4Z2anWnwZDJZU7ztroy', networks.SolidcoinMainnet))
      IDENTIFIER = '9cc9c421cca258cd'.decode('hex')
      PREFIX = 'c059125b8070f00a'.decode('hex')
      NAME = 'solidcoin'
      PERSIST = True
      WORKER_PORT = 9328
  
- class LitecoinMainnet(litecoin.Mainnet):
+ class LitecoinMainnet(networks.LitecoinMainnet):
      SHARE_PERIOD = 10 # seconds
      CHAIN_LENGTH = 24*60*60//5 # shares
      TARGET_LOOKBEHIND = 200 # shares
      PERSIST = True
      WORKER_PORT = 9327
  
- class LitecoinTestnet(litecoin.Testnet):
+ class LitecoinTestnet(networks.LitecoinTestnet):
      SHARE_PERIOD = 1 # seconds
      CHAIN_LENGTH = 24*60*60//5 # shares
      TARGET_LOOKBEHIND = 200 # shares
diff --combined p2pool/main.py
@@@ -251,7 -251,7 +251,7 @@@ def main(args)
                      continue
                  #if p2pool_init.DEBUG:
                  #    print "Sending share %s to %r" % (p2pool.format_hash(share.hash), peer.addr)
 -                peer.send_shares([share])
 +                peer.sendShares([share])
              share.flag_shared()
          
          def p2p_shares(shares, peer=None):
                          break
                      shares.append(share)
              print 'Sending %i shares to %s:%i' % (len(shares), peer.addr[0], peer.addr[1])
 -            peer.send_shares(shares, full=True)
 +            peer.sendShares(shares, full=True)
          
          print 'Joining p2pool network using TCP port %i...' % (args.p2pool_port,)
          
                  frac = stale_shares/shares
                  return 2*struct.pack('<H', int(65535*frac + .5))
              subsidy = current_work2.value['subsidy']
 -            generate_tx = p2pool.generate_transaction(
 -                tracker=tracker,
 -                previous_share_hash=state['best_share_hash'],
 -                new_script=payout_script,
 -                subsidy=subsidy,
 -                nonce=run_identifier + struct.pack('<H', random.randrange(2**16)) + aux_str + get_stale_frac(),
 -                block_target=state['target'],
 -                net=args.net,
 -            )
 -            print 'New work for worker! Difficulty: %.06f Payout if block: %.6f %s Total block value: %.6f %s including %i transactions' % (0xffff*2**208/p2pool.coinbase_type.unpack(generate_tx['tx_ins'][0]['script'])['share_data']['target'], (generate_tx['tx_outs'][-1]['value']-subsidy//200)*1e-8, args.net.BITCOIN_SYMBOL, subsidy*1e-8, args.net.BITCOIN_SYMBOL, len(current_work2.value['transactions']))
 -            #print 'Target: %x' % (p2pool.coinbase_type.unpack(generate_tx['tx_ins'][0]['script'])['share_data']['target'],)
 -            #, have', shares.count(my_script) - 2, 'share(s) in the current chain. Fee:', sum(tx.value_in - tx.value_out for tx in extra_txs)/100000000
 -            transactions = [generate_tx] + list(current_work2.value['transactions'])
 -            merkle_root = bitcoin.data.merkle_hash(transactions)
 -            merkle_root_to_transactions[merkle_root] = transactions # will stay for 1000 seconds
              
              timestamp = int(time.time() - current_work2.value['clock_offset'])
              if state['best_share_hash'] is not None:
                  if timestamp2 > timestamp:
                      print 'Toff', timestamp2 - timestamp
                      timestamp = timestamp2
 -            target2 = p2pool.coinbase_type.unpack(generate_tx['tx_ins'][0]['script'])['share_data']['target']
 -            times[p2pool.coinbase_type.unpack(generate_tx['tx_ins'][0]['script'])['share_data']['nonce']] = time.time()
 +            
 +            if timestamp > 42e2:
 +                is_new = True
 +                new_share_info, generate_tx = p2pool.new_generate_transaction(
 +                    tracker=tracker,
 +                    new_share_data=dict(
 +                        previous_share_hash=state['best_share_hash'],
 +                        coinbase=aux_str,
 +                        nonce=run_identifier + struct.pack('<Q', random.randrange(2**64)) + get_stale_frac(),
 +                        new_script=payout_script,
 +                        subsidy=subsidy,
 +                        donation=math.perfect_round(65535*args.donation_percentage/100),
 +                    ),
 +                    block_target=state['target'],
 +                    net=args.net,
 +                )
 +            else:
 +                is_new = False
 +                share_info, generate_tx = p2pool.generate_transaction(
 +                    tracker=tracker,
 +                    previous_share_hash=state['best_share_hash'],
 +                    new_script=payout_script,
 +                    subsidy=subsidy,
 +                    nonce=run_identifier + struct.pack('<H', random.randrange(2**16)) + aux_str + get_stale_frac(),
 +                    block_target=state['target'],
 +                    net=args.net,
 +                )
 +            
 +            print 'New work for worker! Difficulty: %.06f Payout if block: %.6f %s Total block value: %.6f %s including %i transactions' % (bitcoin.data.target_to_difficulty((new_share_info if is_new else share_info)['target']), (generate_tx['tx_outs'][-1]['value']-subsidy//200)*1e-8, args.net.BITCOIN_SYMBOL, subsidy*1e-8, args.net.BITCOIN_SYMBOL, len(current_work2.value['transactions']))
 +            #print 'Target: %x' % (p2pool.coinbase_type.unpack(generate_tx['tx_ins'][0]['script'])['share_data']['target'],)
 +            #, have', shares.count(my_script) - 2, 'share(s) in the current chain. Fee:', sum(tx.value_in - tx.value_out for tx in extra_txs)/100000000
 +            transactions = [generate_tx] + list(current_work2.value['transactions'])
 +            merkle_root = bitcoin.data.merkle_hash(transactions)
 +            merkle_root_to_transactions[merkle_root] = is_new, new_share_info if is_new else share_info, transactions
 +            
 +            target2 = (new_share_info if is_new else share_info)['target']
 +            times[merkle_root] = time.time()
              #print 'SENT', 2**256//p2pool.coinbase_type.unpack(generate_tx['tx_ins'][0]['script'])['share_data']['target']
              return bitcoin.getwork.BlockAttempt(state['version'], state['previous_block'], merkle_root, timestamp, state['target'], target2)
          
              try:
                  # match up with transactions
                  header = bitcoin.getwork.decode_data(data)
 -                transactions = merkle_root_to_transactions.get(header['merkle_root'], None)
 -                if transactions is None:
 +                xxx = merkle_root_to_transactions.get(header['merkle_root'], None)
 +                if xxx is None:
                      print '''Couldn't link returned work's merkle root with its transactions - should only happen if you recently restarted p2pool'''
                      return False
 -                block = dict(header=header, txs=transactions)
 -                hash_ = bitcoin.data.block_header_type.hash256(block['header'])
 +                is_new, share_info, transactions = xxx
 +                new_share_info = share_info
 +                
 +                hash_ = bitcoin.data.block_header_type.hash256(header)
 +                
-                 pow = bitcoin.data.block_header_type.scrypt(header) if getattr(args.net, 'BITCOIN_POW_SCRYPT', False) else hash_
+                 pow_hash = args.net.BITCOIN_POW_FUNC(block['header'])
                  
-                 if pow <= header['target'] or p2pool_init.DEBUG:
 -                if pow_hash <= block['header']['target'] or p2pool_init.DEBUG:
++                if pow_hash <= header['target'] or p2pool_init.DEBUG:
                      if factory.conn.value is not None:
 -                        factory.conn.value.send_block(block=block)
 +                        factory.conn.value.send_block(block=dict(header=header, txs=transactions))
                      else:
                          print 'No bitcoind connection! Erp!'
-                     if pow <= header['target']:
 -                    if pow_hash <= block['header']['target']:
++                    if pow_hash <= header['target']:
                          print
                          print 'GOT BLOCK! Passing to bitcoind! bitcoin: %x' % (hash_,)
                          print
                  
-                 if current_work.value['aux_work'] is not None and pow <= current_work.value['aux_work']['target']:
+                 if current_work.value['aux_work'] is not None and pow_hash <= current_work.value['aux_work']['target']:
                      try:
                          aux_pow = dict(
                              merkle_tx=dict(
                      except:
                          log.err(None, 'Error while processing merged mining POW:')
                  
 -                target = p2pool.coinbase_type.unpack(transactions[0]['tx_ins'][0]['script'])['share_data']['target']
 +                target = new_share_info['target']
-                 if pow > target:
-                     print 'Worker submitted share with hash > target:\nhash  : %x\ntarget: %x' % (pow, target)
+                 if pow_hash > target:
+                     print 'Worker submitted share with hash > target:\nhash  : %x\ntarget: %x' % (pow_hash, target)
                      return False
 -                share = p2pool.Share.from_block(block, args.net)
 +                if is_new:
 +                    share = p2pool.NewShare(args.net, header, new_share_info, other_txs=transactions[1:])
 +                else:
 +                    share = p2pool.Share(args.net, header, share_info, other_txs=transactions[1:])
                  my_shares.add(share.hash)
                  if share.previous_hash != current_work.value['best_share_hash']:
                      doa_shares.add(share.hash)
 -                print 'GOT SHARE! %s %s prev %s age %.2fs' % (user, p2pool.format_hash(share.hash), p2pool.format_hash(share.previous_hash), time.time() - times[share.nonce]) + (' DEAD ON ARRIVAL' if share.previous_hash != current_work.value['best_share_hash'] else '')
 +                print 'GOT SHARE! %s %s prev %s age %.2fs' % (user, p2pool.format_hash(share.hash), p2pool.format_hash(share.previous_hash), time.time() - times[header['merkle_root']]) + (' DEAD ON ARRIVAL' if share.previous_hash != current_work.value['best_share_hash'] else '')
                  good = share.previous_hash == current_work.value['best_share_hash']
                  # maybe revert back to tracker being non-blocking so 'good' can be more accurate?
                  p2p_shares([share])
          
          def get_users():
              height, last = tracker.get_height_and_last(current_work.value['best_share_hash'])
 -            weights, total_weight = tracker.get_cumulative_weights(current_work.value['best_share_hash'], min(height, 720), 2**256)
 +            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, args.net)] = weights[script]/total_weight
                      height, last = tracker.get_height_and_last(current_work.value['best_share_hash'])
                      if height > 2:
                          att_s = p2pool.get_pool_attempts_per_second(tracker, current_work.value['best_share_hash'], args.net, min(height - 1, 720))
 -                        weights, total_weight = tracker.get_cumulative_weights(current_work.value['best_share_hash'], min(height, 720), 2**100)
 +                        weights, total_weight, donation_weight = tracker.get_cumulative_weights(current_work.value['best_share_hash'], min(height, 720), 65535*2**256)
                          shares, stale_doa_shares, stale_not_doa_shares = get_share_counts(True)
                          stale_shares = stale_doa_shares + stale_not_doa_shares
                          fracs = [read_stale_frac(share) for share in itertools.islice(tracker.get_chain_known(current_work.value['best_share_hash']), 120) if read_stale_frac(share) is not None]
@@@ -752,9 -726,6 +752,9 @@@ def run()
      parser.add_argument('--merged-userpass',
          help='merge daemon user and password, separated by a colon. Example: ncuser:ncpass',
          type=str, action='store', default=None, dest='merged_userpass')
 +    parser.add_argument('--give-author', metavar='DONATION_PERCENTAGE',
 +        help='percentage amount to donate to author of p2pool. Default: 0.5',
 +        type=float, action='store', default=0.5, dest='donation_percentage')
      
      p2pool_group = parser.add_argument_group('p2pool interface')
      p2pool_group.add_argument('--p2pool-port', metavar='PORT',
diff --combined p2pool/p2p.py
@@@ -206,25 -206,13 +206,20 @@@ class Protocol(bitcoin_p2p.BaseProtocol
      def handle_share0s(self, hashes):
          self.node.handle_share_hashes(hashes, self)
      
 +    message_shares = bitcoin_data.ComposedType([
 +        ('share1as', bitcoin_data.ListType(p2pool_data.new_share1a_type)),
 +        ('share1bs', bitcoin_data.ListType(p2pool_data.new_share1b_type)),
 +    ])
 +    def handle_shares(self):
 +        xxx
 +    
      message_share1as = bitcoin_data.ComposedType([
          ('share1as', bitcoin_data.ListType(p2pool_data.share1a_type)),
      ])
      def handle_share1as(self, share1as):
          shares = []
          for share1a in share1as:
-             # use scrypt for Litecoin
-             if (getattr(self.node.net, 'BITCOIN_POW_SCRYPT', False)):
-                 hash_ = bitcoin_data.block_header_type.scrypt(share1a['header']);
-             else:
-                 hash_ = bitcoin_data.block_header_type.hash256(share1a['header'])
-             if hash_ <= share1a['header']['target']:
+             if self.node.net.BITCOIN_POW_FUNC(share1a['header']) <= share1a['header']['target']:
                  print 'Dropping peer %s:%i due to invalid share' % self.addr
                  self.transport.loseConnection()
                  return
      def handle_share1bs(self, share1bs):
          shares = []
          for share1b in share1bs:
-             # use scrypt for Litecoin
-             if (getattr(self.node.net, 'BITCOIN_POW_SCRYPT', False)):
-                 hash_ = bitcoin_data.block_header_type.scrypt(share1b['header']);
-             else:
-                 hash_ = bitcoin_data.block_header_type.hash256(share1b['header'])
-             if not hash_ <= share1b['header']['target']:
+             if not self.node.net.BITCOIN_POW_FUNC(share1a['header']) <= share1b['header']['target']:
                  print 'Dropping peer %s:%i due to invalid share' % self.addr
                  self.transport.loseConnection()
                  return
              shares.append(share)
          self.node.handle_shares(shares, self)
      
 -    def send_shares(self, shares, full=False):
 -        share1bs = []
 +    def sendShares(self, shares, full=False):
          share0s = []
          share1as = []
 +        share1bs = []
 +        new_share1as = []
 +        new_share1bs = []
          # XXX doesn't need to send full block when it's not urgent
          # eg. when getting history
          for share in shares:
 -            if share.bitcoin_hash <= share.header['target']:
 -                share1bs.append(share.as_share1b())
 +            if isinstance(share, p2pool_data.NewShare):
 +                if share.bitcoin_hash <= share.header['target']:
 +                    new_share1bs.append(share.as_share1b())
 +                else:
 +                    if self.mode == 0 and not full:
 +                        share0s.append(share.hash)
 +                    elif self.mode == 1 or full:
 +                        new_share1as.append(share.as_share1a())
 +                    else:
 +                        raise ValueError(self.mode)
              else:
 -                if self.mode == 0 and not full:
 -                    share0s.append(share.hash)
 -                elif self.mode == 1 or full:
 -                    share1as.append(share.as_share1a())
 +                if share.bitcoin_hash <= share.header['target']:
 +                    share1bs.append(share.as_share1b())
                  else:
 -                    raise ValueError(self.mode)
 +                    if self.mode == 0 and not full:
 +                        share0s.append(share.hash)
 +                    elif self.mode == 1 or full:
 +                        share1as.append(share.as_share1a())
 +                    else:
 +                        raise ValueError(self.mode)
          def att(f, **kwargs):
              try:
                  f(**kwargs)
              except bitcoin_p2p.TooLong:
                  att(f, **dict((k, v[:len(v)//2]) for k, v in kwargs.iteritems()))
                  att(f, **dict((k, v[len(v)//2:]) for k, v in kwargs.iteritems()))
 -        if share1bs: att(self.send_share1bs, share1bs=share1bs)
          if share0s: att(self.send_share0s, hashes=share0s)
          if share1as: att(self.send_share1as, share1as=share1as)
 +        if share1bs: att(self.send_share1bs, share1bs=share1bs)
 +        if new_share1as or new_share1bs: att(self.send_shares, share1as=new_share1as, share1bs=new_share1bs)
      
      def connectionLost(self, reason):
          if self.node_var_watch is not None: