from __future__ import division
+import itertools
+
from bitcoin import data as bitcoin_data
+class CompressedList(bitcoin_data.Type):
+ def __init__(self, inner):
+ self.inner = inner
+
+ def read(self, file):
+ values = bitcoin_data.ListType(self.inner).read(file)
+ if values != sorted(set(values)):
+ raise ValueError("invalid values")
+ references = bitcoin_data.ListType(bitcoin_data.VarIntType()).read(file)
+ return [values[reference] for reference in references]
+
+ def write(self, file, item):
+ values = sorted(set(item))
+ values_map = dict((value, i) for i, value in enumerate(values))
+ bitcoin_data.ListType(self.inner).write(file, values)
+ bitcoin_data.ListType(bitcoin_data.VarIntType()).write(file, [values_map[subitem] for subitem in item])
+
+
+merkle_branch_type = bitcoin_data.ListType(bitcoin_data.ComposedType([
+ ('side', bitcoin_data.StructType('<B')), # enum?
+ ('hash', bitcoin_data.HashType()),
+]))
+
+
share_data_type = bitcoin_data.ComposedType([
- ('previous_p2pool_share_hash', bitcoin_data.HashType()),
- ('bits2', bitcoin_data.FixedStrType(4)),
+ ('previous_share_hash', bitcoin_data.PossiblyNone(0, bitcoin_data.HashType())),
+ ('previous_shares_hash', bitcoin_data.HashType()),
+ ('target2', bitcoin_data.FloatingIntegerType()),
('nonce', bitcoin_data.VarStrType()),
])
+
coinbase_type = bitcoin_data.ComposedType([
('identifier', bitcoin_data.StructType('<Q')),
('share_data', share_data_type),
])
-merkle_branch_type = bitcoin_data.ListType(bitcoin_data.ComposedType([
- ('side', bitcoin_data.StructType('<B')),
- ('hash', bitcoin_data.HashType()),
-]))
+share_info_type = bitcoin_data.ComposedType([
+ ('share_data', share_data_type),
+ ('new_script', bitcoin_data.VarStrType()),
+ ('subsidy', bitcoin_data.StructType('<Q')),
+])
+
-gentx_info_type = bitcoin_data.ComposedType([
- ('share_info', bitcoin_data.ComposedType([
- ('share_data', share_data_type),
- ('new_script', bitcoin_data.VarStrType()),
- ('subsidy', bitcoin_data.StructType('<Q')),
- ])),
+share1a_type = bitcoin_data.ComposedType([
+ ('header', bitcoin_data.block_header_type), # merkle_header not completely needed
+ ('share_info', share_info_type),
('merkle_branch', merkle_branch_type),
])
-share1_type = bitcoin_data.ComposedType([
+share1b_type = bitcoin_data.ComposedType([
('header', bitcoin_data.block_header_type),
- ('gentx_info', gentx_info_type),
+ ('share_info', share_info_type),
+ ('other_txs', bitcoin_data.ListType(bitcoin_data.tx_type)),
])
+shares_type = CompressedList(bitcoin_data.VarStrType())
+
def calculate_merkle_branch(txs, index):
- hash_list = [(bitcoin_data.tx_hash(tx), i == index, []) for i, tx in enumerate(txs)]
+ hash_list = [(bitcoin_data.tx_type.hash256(tx), i == index, []) for i, tx in enumerate(txs)]
while len(hash_list) > 1:
hash_list = [
(
- bitcoin_data.doublesha(bitcoin_data.merkle_record_type.pack(dict(left=left, right=right))),
+ bitcoin_data.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)],
)
return hash_list[0][2]
def check_merkle_branch(tx, branch):
- hash_ = bitcoin_data.tx_hash(tx)
+ hash_ = bitcoin_data.tx_type.hash256(tx)
for step in branch:
if not step['side']:
- hash_ = bitcoin_data.doublesha(bitcoin_data.merkle_record_type.pack(dict(left=step['hash'], right=hash_)))
+ hash_ = bitcoin_data.merkle_record_type.hash256(dict(left=step['hash'], right=hash_))
else:
- hash_ = bitcoin_data.doublesha(bitcoin_data.merkle_record_type.pack(dict(left=hash_, right=step['hash'])))
+ hash_ = bitcoin_data.merkle_record_type.hash256(dict(left=hash_, right=step['hash']))
return hash_
-def txs_to_gentx_info(txs):
+def gentx_to_share_info(gentx):
return dict(
- share_info=dict(
- share_data=coinbase_type.unpack(txs[0]['tx_ins'][0]['script'])['share_data'],
- subsidy=sum(tx_out['value'] for tx_out in txs[0]['tx_outs']),
- new_script=txs[0]['tx_outs'][-1]['script'],
- ),
- merkle_branch=calculate_merkle_branch(txs, 0),
+ 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_and_shares(share_info, chain, net):
+def share_info_to_gentx(share_info, chain, net):
return generate_transaction(
- previous_share2=chain.share2s[share_info['share_data']['previous_p2pool_share_hash']],
+ previous_share2=chain.share2s[share_info['share_data']['previous_share_hash']],
nonce=share_info['share_data']['nonce'],
new_script=share_info['new_script'],
subsidy=share_info['subsidy'],
net=net,
)
-def gentx_info_to_gentx_shares_and_merkle_root(gentx_info, chain, net):
- gentx, shares = share_info_to_gentx_and_shares(gentx_info['share_info'], chain, net)
- return gentx, shares, check_merkle_branch(gentx, gentx_info['merkle_branch'])
-
class Share(object):
- def __init__(self, header, txs=None, gentx_info=None):
- if txs is not None:
- if bitcoin_data.merkle_hash(txs) != header['merkle_root']:
- raise ValueError("txs don't match header")
+ def __init__(self, header, 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')
+ self.header = header
+ self.share_info = share_info
+ self.merkle_branch = merkle_branch
+ self.other_txs = other_txs
- if gentx_info is None:
- if txs is None:
- raise ValueError('need either txs or gentx_info')
-
- gentx_info = txs_to_gentx_info(txs)
+ self.share_data = self.share_info['share_data']
+ self.new_script = self.share_info['new_script']
+ self.subsidy = self.share_info['subsidy']
- coinbase = gentx_info['share_info']['coinbase']
+ self.previous_share_hash = self.share_data['previous_share_hash']
+ self.previous_shares_hash = self.share_data['previous_shares_hash']
+ self.target2 = self.share_data['target2']
- self.header = header
- self.txs = txs
- self.gentx_info = gentx_info
- self.hash = bitcoin_data.block_hash(header)
- self.previous_share_hash = coinbase['previous_p2pool_share_hash'] if coinbase['previous_p2pool_share_hash'] != 2**256 - 1 else None
- self.chain_id_data = chain_id_type.pack(dict(last_p2pool_block_hash=coinbase['last_p2pool_block_hash'], bits=header['bits']))
+ self.hash = bitcoin_data.block_header_type.hash256(header)
+
+ @classmethod
+ def from_block(cls, block):
+ return cls(block['header'], gentx_to_share_info(block['txs'][0]), other_txs=block['txs'][1:])
+
+ @classmethod
+ def from_share1a(cls, share1a):
+ return cls(**share1a)
+
+ @classmethod
+ def from_share1b(cls, share1b):
+ return cls(**share1b)
def as_block(self):
if self.txs is None:
def flag_shared(self):
self.shared = True
-def generate_transaction(last_p2pool_block_hash, previous_share2, new_script, subsidy, nonce, net):
- shares = (previous_share2.shares if previous_share2 is not None else [net.SCRIPT]*net.SPREAD)[1:-1] + [new_script, new_script]
+def generate_transaction(tracker, previous_share_hash, new_script, subsidy, nonce, block_target, net):
+ previous_share2 = tracker.shares[previous_share_hash] if previous_share_hash is not None else None
+ #previous_share2 = chain.shares
+ #previous_shares
+ #shares =
+ #shares = (previous_share2.shares if previous_share2 is not None else [net.SCRIPT]*net.SPREAD)[1:-1] + [new_script, new_script]
+
+ chain = list(itertools.islice(tracker.get_chain(previous_share_hash), net.CHAIN_LENGTH))
+ if len(chain) < 100:
+ target2 = bitcoin_data.FloatingIntegerType().truncate_to(2**256//2**32 - 1)
+ else:
+ attempts_per_second = sum(bitcoin_data.target_to_average_attempts(share.target) for share in itertools.islice(chain, 0, max(0, len(chain) - 1)))//(chain[0].timestamp - chain[-1].timestamp)
+ pre_target = 2**256*net.SHARE_PERIOD//attempts_per_second
+ pre_target2 = math.clip(pre_target, (previous_share2.target*9//10, previous_share2.target*11//10))
+ pre_target3 = math.clip(pre_target2, (0, 2**256//2**32 - 1))
+ target2 = bitcoin_data.FloatingIntegerType().truncate_to(pre_target3)
+
+
+ attempts_to_block = bitcoin_data.target_to_average_attempts(block_target)
+ total_weight = 0
+
+ class fake_share(object):
+ script = new_script
+ share = dict(target=target2)
dest_weights = {}
- for script in shares:
- dest_weights[script] = dest_weights.get(script, 0) + 1
- total_weight = sum(dest_weights.itervalues())
+ for share in itertools.chain([fake_share], itertools.islice(tracker.get_chain(previous_share_hash), net.CHAIN_LENGTH)):
+ weight = bitcoin_data.target_to_average_attempts(share.share['target'])
+ weight = max(weight, attempts_to_block - total_weight)
+
+ dest_weights[share.script] = dest_weights.get(share.script, 0) + weight
+ total_weight += weight
+
+ if total_weight == attempts_to_block:
+ break
- amounts = dict((script, subsidy*weight*63//(64*total_weight)) for (script, weight) in dest_weights.iteritems())
- amounts[net.SCRIPT] = amounts.get(net.SCRIPT, 0) + subsidy//64 # prevent fake previous p2pool blocks
+ amounts = dict((script, subsidy*(199*weight)//(200*total_weight)) for (script, weight) in dest_weights.iteritems())
+ amounts[net.SCRIPT] = amounts.get(net.SCRIPT, 0) + subsidy*1//200 # prevent fake previous p2pool blocks
amounts[net.SCRIPT] = amounts.get(net.SCRIPT, 0) + subsidy - sum(amounts.itervalues()) # collect any extra
dests = sorted(amounts.iterkeys(), key=lambda script: (script == new_script, script))
- assert dests[-1] == new_script
+ assert dests[-1] == new_script, dests
- pre_target = sum(bitcoin_data.target_to_average_attempts(share(x ago).target) for x in xrange(1000))/(share(1000 ago).timestamp - share(1 ago).timestamp)
- bits2 = bitcoin_data.compress_target_to_bits(pre_target)
+ previous_shares = [] # XXX
return dict(
version=1,
tx_ins=[dict(
- previous_output=dict(index=4294967295, hash=0),
- sequence=4294967295,
+ previous_output=None,
+ sequence=None,
script=coinbase_type.pack(dict(
identifier=net.IDENTIFIER,
share_data=dict(
- last_p2pool_block_hash=last_p2pool_block_hash,
- previous_p2pool_share_hash=previous_share2.share.hash if previous_share2 is not None else 2**256 - 1,
+ previous_share_hash=previous_share_hash,
+ previous_shares_hash=shares_type.hash256(previous_shares),
nonce=nonce,
- bits2=bits2,
+ target2=target2,
),
)),
)],
tx_outs=[dict(value=amounts[script], script=script) for script in dests if amounts[script]],
lock_time=0,
- ), shares
+ )
class Tracker(object):
def __init__(self):
self.shares = {} # hash -> share
- self.reverse_shares = {} # previous_hash -> share_hash
+ self.reverse_shares = {} # previous_share_hash -> share_hash
self.heads = {} # hash -> (height, tail hash)
self.heads = set()
def add_share(self, share):
if share.hash in self.shares:
- return # XXX
+ return # XXX raise exception?
self.shares[share.hash] = share
- self.reverse_shares.setdefault(share.previous_hash, set()).add(share.hash)
+ self.reverse_shares.setdefault(share.previous_share_hash, set()).add(share.hash)
if self.reverse_shares.get(share.hash, set()):
pass # not a head
else:
self.heads.add(share.hash)
- if share.previous_hash in self.heads:
- self.heads.remove(share.previous_hash)
+ if share.previous_share_hash in self.heads:
+ self.heads.remove(share.previous_share_hash)
def get_chain(self, start):
share_hash_to_get = start
while share_hash_to_get in self.shares:
share = self.shares[share_hash_to_get]
yield share
- share_hash_to_get = share.previous_hash
+ share_hash_to_get = share.previous_share_hash
- def best(self):
+ def get_best_share_hash(self):
+ if not self.heads:
+ return None
return max(self.heads, key=self.score_chain)
def score_chain(self, start):
length = len(self.get_chain(start))
score = 0
- for share in itertools.islice(self.get_chain(start), 1000):
+ for share in itertools.islice(self.get_chain(start), self.net.CHAIN_LENGTH):
score += a
return (min(length, 1000), score)
+class OkayTracker(Tracker):
+ def __init__(self):
+ Tracker.__init__(self)
+ self.okay_cache = set()
+ def is_okay(self, start):
+ '''
+ Returns:
+ {'result': 'okay', verified_height: ...} # if share has an okay parent or if share has CHAIN_LENGTH children and CHAIN_LENTH parents that it verified with
+ {'result': 'needs_parent', 'parent_hash': ...} # if share doesn't have CHAIN_LENGTH parents
+ {'result': 'needs_share_shares', 'share_hash': ...} # if share has CHAIN_LENGTH children and needs its shares to
+ {'result': 'not_okay'} # if the share has a not okay parent or if the share has an okay parent and failed validation
+ '''
+
+ length = len
+ to_end_rev = []
+ for share in itertools.islice(self.get_chain(start), self.net.CHAIN_LENGTH):
+ if share in self.okay_cache:
+ return validate(share, to_end_rev[::-1])
+ to_end_rev.append(share)
+ # picking up last share from for loop, ew
+ self.okay_cache.add(share)
+ return validate(share, to_end_rev[::-1])
+class Chain(object):
+ def __init__(self):
+ pass
+
+def get_chain_descriptor(tracker, start):
+ for item in tracker.get_chain(self.net.CHAIN_LENGTH):
+ a
+ pass
+
if __name__ == '__main__':
class FakeShare(object):
- def __init__(self, hash, previous_hash):
+ def __init__(self, hash, previous_share_hash):
self.hash = hash
- self.previous_hash = previous_hash
+ self.previous_share_hash = previous_share_hash
t = Tracker()
t.add_share(FakeShare(3, 4))
print t.heads
-# TARGET_MULTIPLIER needs to be less than the current difficulty to prevent miner clients from missing shares
-
class Mainnet(bitcoin_data.Mainnet):
- TARGET_MULTIPLIER = SPREAD = 600
+ SHARE_PERIOD = 5 # seconds
+ CHAIN_LENGTH = 1000 # shares
+ SPREAD = 10 # blocks
ROOT_BLOCK = 0x6c9cb0589a44808d9a9361266a4ffb9fea2e2cf4d70bb2118b5
SCRIPT = '4104ffd03de44a6e11b9917f3a29f9443283d9871c9d743ef30d5eddcd37094b64d1b3d8090496b53256786bf5c82932ec23c3b74d9f05a6f95a8b5529352656664bac'.decode('hex')
IDENTIFIER = 0x7452839666e1f8f8
P2P_PORT = 9333
class Testnet(bitcoin_data.Testnet):
- TARGET_MULTIPLIER = SPREAD = 30
+ SHARE_PERIOD = 5 # seconds
+ CHAIN_LENGTH = 1000 # shares
+ SPREAD = 10 # blocks
ROOT_BLOCK = 0xd5070cd4f2987ad2191af71393731a2b143f094f7b84c9e6aa9a6a
SCRIPT = '410403ad3dee8ab3d8a9ce5dd2abfbe7364ccd9413df1d279bf1a207849310465b0956e5904b1155ecd17574778f9949589ebfd4fb33ce837c241474a225cf08d85dac'.decode('hex')
IDENTIFIER = 0x1ae3479e4eb6700a