def test_create_hashchain(self): seed = sha256(b'test_seed') HASHCHAIN_SIZE = 100 hcb = hashchain(seed, 1, HASHCHAIN_SIZE) self.assertIsNotNone(hcb) self.assertEqual(HASHCHAIN_SIZE + 1, len(hcb.hashchain)) # FIXME: Why seed comes as an array of tuples? self.assertEqual( '127f5db0388cd82bd4af80b88e8d68409e1f70fd322f96b3f2aca55b0ade116f', bin2hstr(hcb.seed[0])) self.assertEqual( '1d3b37dedc74980941b3b65640e8d2851658feac0d38196f372ada9c2ac0b077', bin2hstr(hcb.hc_terminator)) self.assertEqual( '1d3b37dedc74980941b3b65640e8d2851658feac0d38196f372ada9c2ac0b077', bin2hstr(hcb.hashchain[-1])) self.assertEqual( '127f5db0388cd82bd4af80b88e8d68409e1f70fd322f96b3f2aca55b0ade116f', bin2hstr(hcb.hashchain[0])) self.assertNotEqual( '127f5db0388cd82bd4af80b88e8d68409e1f70fd322f96b3f2aca55b0ade116f', bin2hstr(hcb.hashchain[1])) self.assertEqual( 'ff7f4850bc6499e08e104c6967ee66e665e57d7e0e429072e646d14e1b92600a', bin2hstr(hcb.hashchain[50]))
def commit(self, chain, block, address_txn, ignore_save_wallet=False): # FIXME: This indexing approach is very inefficient blocks_left = helper.get_blocks_left(block.blockheader.blocknumber) staker = block.blockheader.stake_selector self.stake_validators_list.sv_list[staker].nonce += 1 for address in address_txn: self._save_address_state(address, address_txn[address]) for dup_tx in block.duplicate_transactions: if dup_tx.coinbase1.txto in self.stake_validators_list.sv_list: self.stake_validators_list.sv_list[dup_tx.coinbase1.txto].is_banned = True if blocks_left == 1: logger.info('EPOCH change: resetting stake_list, activating next_stake_list, updating PRF with ' 'seed+entropy updating wallet hashchains..') self.stake_validators_list.move_next_epoch() # TODO: To be fixed later #self.stake_list_put(self.stake_validators_list.to_json()) xmss = chain.wallet.address_bundle[0].xmss tmphc = hashchain(xmss.get_seed_private(), epoch=block.blockheader.epoch + 1) chain.hash_chain = tmphc.hashchain if not ignore_save_wallet: chain.wallet.save_wallet() self.state_set_blockheight(chain.height() + 1) logger.debug('%s %s tx passed verification.', bin2hstr(block.blockheader.headerhash), len(block.transactions)) return True
def __init__(self, chain): self.chain = chain self.state = self.chain.state self.blocks = dict() self.size = config.dev.reorg_limit self.pending_blocks = dict() self.epoch = max(0, self.chain.height() ) // config.dev.blocks_per_epoch # Main chain epoch self.epoch_seed = None self.hash_chain = dict() self.slave_xmss = dict() self.slave_xmsspool = None self.assign_slave_xmsspool(0) # TODO: For the moment, only the first address is used (discussed with cyyber) private_seed = self.chain.wallet.address_bundle[ 0].xmss.get_seed_private() self._wallet_private_seeds = {self.epoch: private_seed} self.hash_chain[self.epoch] = hashchain(private_seed).hashchain self.tx_buffer = dict( ) # maintain the list of tx transaction that has been confirmed in buffer if self.chain.height() > 0: self.epoch = self.chain.m_blockchain[ -1].blockheader.blocknumber // config.dev.blocks_per_epoch
def pre_pos_1(self, data=None): # triggered after genesis for block 1.. logger.info('pre_pos_1') # are we a staker in the stake list? if self.chain.mining_address not in self.chain.m_blockchain[0].stake_list: logger.info('not in stake list..no further pre_pos_x calls') return logger.info('mining address: %s in the genesis.stake_list', self.chain.mining_address) xmss = self.chain.wallet.address_bundle[0].xmss tmphc = hashchain(xmss.get_seed_private(), epoch=0) self.chain.hash_chain = tmphc.hashchain self.chain.block_chain_buffer.hash_chain[0] = tmphc.hashchain tmpbalance = self.chain.state.state_balance(self.chain.mining_address) slave_xmss = self.chain.block_chain_buffer.get_slave_xmss(0) if not slave_xmss: logger.info('Waiting for SLAVE XMSS to be done') reactor.callLater(5, self.pre_pos_1) return st = StakeTransaction().create(blocknumber=0, xmss=self.chain.wallet.address_bundle[0].xmss, slave_public_key=slave_xmss.pk(), hashchain_terminator=tmphc.hc_terminator, first_hash=tmphc.hashchain[-1][-2], balance=tmpbalance) self.chain.add_tx_to_pool(st) # send the stake tx to generate hashchain terminators for the staker addresses.. self.p2pFactory.send_st_to_peers(st) logger.info('await delayed call to build staker list from genesis') reactor.callLater(5, self.pre_pos_2, st)
def post_block_logic(self, blocknumber): """ post block logic we initiate the next POS cycle send R1, send ST, reset POS flags and remove unnecessary messages in chain.stake_reveal_one and _two.. :return: """ if self.p2pFactory.stake: next_stake_list = self.chain.block_chain_buffer.next_stake_list_get( blocknumber) epoch = blocknumber // config.dev.blocks_per_epoch epoch_blocknum = blocknumber - epoch * config.dev.blocks_per_epoch if epoch_blocknum < config.dev.stake_before_x_blocks and self.chain.mining_address not in next_stake_list: diff = max( 1, int((config.dev.stake_before_x_blocks * (1 - config.dev.st_txn_safety_margin) - epoch_blocknum))) if random.randint(1, diff) == 1: self.make_st_tx(blocknumber, None) elif epoch_blocknum >= config.dev.stake_before_x_blocks - 1 and self.chain.mining_address in next_stake_list: if not next_stake_list[self.chain.mining_address].first_hash: threshold_blocknum = self.chain.block_chain_buffer.get_threshold( blocknumber, self.chain.mining_address) max_threshold_blocknum = config.dev.blocks_per_epoch if threshold_blocknum == config.dev.low_staker_first_hash_block: max_threshold_blocknum = config.dev.high_staker_first_hash_block if threshold_blocknum - 1 <= epoch_blocknum < max_threshold_blocknum - 1: diff = max( 1, int((max_threshold_blocknum * (1 - config.dev.st_txn_safety_margin) - epoch_blocknum))) if random.randint(1, diff) == 1: xmss = self.chain.wallet.address_bundle[0].xmss tmphc = hashchain(xmss.get_seed_private(), epoch=epoch + 1) self.make_st_tx(blocknumber, tmphc.hashchain[-1][-2]) stake_list = self.chain.block_chain_buffer.stake_list_get( blocknumber) delay = config.dev.minimum_minting_delay if self.chain.mining_address in stake_list: if stake_list[self.chain.mining_address].is_banned: logger.warning('You have been banned.') else: self.create_next_block(blocknumber) delay = None last_blocknum = self.chain.block_chain_buffer.height() self.restart_post_block_logic(last_blocknum + 1, delay) return
def update_hash_chain(self, blocknumber): epoch = int((blocknumber + 1) // config.dev.blocks_per_epoch) logger.info('Created new hash chain') prev_private_seed = self._wallet_private_seeds[epoch - 1] self._wallet_private_seeds[epoch] = prev_private_seed self.hash_chain[epoch] = hashchain(prev_private_seed, epoch=epoch).hashchain
def pre_pos_1(self, data=None): # triggered after genesis for block 1.. logger.info('pre_pos_1') # are we a staker in the stake list? genesis_block = self.buffered_chain.get_block(0) found = False for genesisBalance in genesis_block.genesis_balance: if genesisBalance.address.encode( ) == self.buffered_chain.staking_address: logger.info('Found in Genesis Address %s %s', genesisBalance.address.encode(), genesisBalance.balance) found = True break if not found: return logger.info('mining address: %s in the genesis.stake_list', self.buffered_chain.staking_address) xmss = self.buffered_chain.wallet.address_bundle[0].xmss tmphc = hashchain(xmss.get_seed_private(), epoch=0) self.buffered_chain.hash_chain[0] = tmphc.hashchain slave_xmss = self.buffered_chain.get_slave_xmss(0) if not slave_xmss: logger.info('Waiting for SLAVE XMSS to be done') reactor.callLater(5, self.pre_pos_1) return signing_xmss = self.buffered_chain.wallet.address_bundle[0].xmss st = StakeTransaction.create(activation_blocknumber=1, xmss=signing_xmss, slavePK=slave_xmss.pk(), hashchain_terminator=tmphc.hc_terminator) st.sign(signing_xmss) self.buffered_chain.tx_pool.add_tx_to_pool(st) # send the stake tx to generate hashchain terminators for the staker addresses.. self.p2p_factory.broadcast_st(st) vote = Vote.create( addr_from=self.buffered_chain.wallet.address_bundle[0].address, blocknumber=0, headerhash=genesis_block.headerhash, xmss=slave_xmss) vote.sign(slave_xmss) self.buffered_chain.add_vote(vote) # send the stake votes for genesis block self.p2p_factory.broadcast_vote(vote) logger.info('await delayed call to build staker list from genesis') reactor.callLater(5, self.pre_pos_2, st)
def pre_pos_2(self, data=None): logger.info('pre_pos_2') if self.chain.height() >= 1: return # assign hash terminators to addresses and generate a temporary stake list ordered by st.hash.. tmp_list = [] for tx in self.chain.transaction_pool: if tx.subtype == qrl.core.Transaction_subtypes.TX_SUBTYPE_STAKE: if tx.txfrom in self.chain.m_blockchain[0].stake_list and tx.first_hash: tmp_list.append([tx.txfrom, tx.hash, 0, tx.first_hash, GenesisBlock().get_info()[tx.txfrom], tx.slave_public_key]) self.chain.state.stake_validators_list.add_sv(tx.txfrom, tx.slave_public_key, tx.hash, tx.first_hash, GenesisBlock().get_info()[tx.txfrom]) self.chain.block_chain_buffer.epoch_seed = self.chain.state.calc_seed(tmp_list) self.chain.stake_list = sorted(tmp_list, key=lambda staker: self.chain.score(stake_address=staker[0], reveal_one=bin2hstr(sha256(reduce(lambda set1, set2: set1 + set2, staker[1]))), balance=staker[4], seed=self.chain.block_chain_buffer.epoch_seed)) self.chain.block_chain_buffer.epoch_seed = format(self.chain.block_chain_buffer.epoch_seed, 'x') logger.info('genesis stakers ready = %s / %s', len(self.chain.stake_list), config.dev.minimum_required_stakers) logger.info('node address: %s', self.chain.mining_address) if len(self.chain.stake_list) < config.dev.minimum_required_stakers: # stake pool still not full..reloop.. self.p2pFactory.send_st_to_peers(data) logger.info('waiting for stakers.. retry in 5s') reactor.callID = reactor.callLater(5, self.pre_pos_2, data) return if self.chain.mining_address == self.chain.stake_list[0][0]: logger.info('designated to create block 1: building block..') tmphc = hashchain(self.chain.wallet.address_bundle[0].xmss.get_seed_private()) # create the genesis block 2 here.. reveal_hash, vote_hash = self.chain.select_hashchain(self.chain.m_blockchain[-1].blockheader.headerhash, self.chain.mining_address, tmphc.hashchain, blocknumber=1) b = self.chain.m_create_block(reveal_hash[-2], vote_hash[-2]) self.pre_block_logic(b) else: logger.info('await block creation by stake validator: %s', self.chain.stake_list[0][0]) self.last_bk_time = time.time() self.restart_unsynced_logic() return
def test_hashchain_verify(self): seed = sha256(b'test_seed') HASHCHAIN_SIZE = 100 hcb = hashchain(seed, 1, HASHCHAIN_SIZE) self.assertIsNotNone(hcb) self.assertEqual(HASHCHAIN_SIZE + 1, len(hcb.hashchain)) for i, value in enumerate(hcb.hashchain): tmp = sha256_n(value, HASHCHAIN_SIZE - i) logger.info("{:-4} {} {}".format(i, bin2hstr(value), bin2hstr(tmp))) self.assertEqual(hcb.hc_terminator, tmp)
def test_hashchain_verify(self): seed = sha256(b'test_seed') HASHCHAIN_SIZE = 100 hcb = hashchain(seed, 1, HASHCHAIN_SIZE) self.assertIsNotNone(hcb) self.assertEqual(HASHCHAIN_SIZE + 1, len(hcb.hashchain)) for i, value in enumerate(hcb.hashchain): tmp = sha256_n(value, HASHCHAIN_SIZE - i) print("{:-4} {} {}".format(i, bin2hstr(value), bin2hstr(tmp))) self.assertEqual(hcb.hc_terminator, tmp)
def add_block_mainchain(self, chain, block, validate=True): # TODO : minimum block validation in unsynced _state blocknum = block.blockheader.blocknumber epoch = int(blocknum // config.dev.blocks_per_epoch) prev_headerhash = block.blockheader.prev_blockheaderhash # FIXME: Chain should be checking this. Avoid complex references if blocknum <= chain.height(): return if blocknum - 1 == chain.height(): if prev_headerhash != chain.m_blockchain[-1].blockheader.headerhash: logger.info('prev_headerhash of block doesnt match with headerhash of m_blockchain') return elif blocknum - 1 > 0: if blocknum - 1 not in self.blocks or prev_headerhash != self.blocks[blocknum - 1][0].block.blockheader.headerhash: logger.info('No block found in buffer that matches with the prev_headerhash of received block') return if validate: if not chain.m_add_block(block): logger.info("Failed to add block by m_add_block, re-requesting the block #%s", blocknum) return else: if self.state.state_add_block(chain, block, ignore_save_wallet=True) is True: chain.m_blockchain.append(block) block_left = config.dev.blocks_per_epoch - ( block.blockheader.blocknumber - (block.blockheader.epoch * config.dev.blocks_per_epoch)) self.add_txns_buffer() if block_left == 1: # As state_add_block would have already moved the next stake list to stake_list self.epoch_seed = bin2hstr(hex(self.state.stake_validators_list.calc_seed())) private_seed = chain.wallet.address_bundle[0].xmss.get_seed_private() self._wallet_private_seeds[epoch + 1] = private_seed self.hash_chain[epoch + 1] = hashchain(private_seed, epoch=epoch + 1).hashchain if epoch in self._wallet_private_seeds: del self._wallet_private_seeds[epoch] if epoch in self.slave_xmss: del self.slave_xmss[epoch] else: self.epoch_seed = bin2hstr(sha256(block.blockheader.reveal_hash + hstr2bin(self.epoch_seed))) chain.update_last_tx(block) chain.update_tx_metadata(block) self.epoch = epoch return True
def test_create_hashchain(self): seed = sha256(b'test_seed') HASHCHAIN_SIZE = 100 hcb = hashchain(seed, 1, HASHCHAIN_SIZE) self.assertIsNotNone(hcb) self.assertEqual(HASHCHAIN_SIZE + 1, len(hcb.hashchain)) # FIXME: Why seed comes as an array of tuples? self.assertEqual('127f5db0388cd82bd4af80b88e8d68409e1f70fd322f96b3f2aca55b0ade116f', bin2hstr(hcb.seed[0])) self.assertEqual('1d3b37dedc74980941b3b65640e8d2851658feac0d38196f372ada9c2ac0b077', bin2hstr(hcb.hc_terminator)) self.assertEqual('1d3b37dedc74980941b3b65640e8d2851658feac0d38196f372ada9c2ac0b077', bin2hstr(hcb.hashchain[-1])) self.assertEqual('127f5db0388cd82bd4af80b88e8d68409e1f70fd322f96b3f2aca55b0ade116f', bin2hstr(hcb.hashchain[0])) self.assertNotEqual('127f5db0388cd82bd4af80b88e8d68409e1f70fd322f96b3f2aca55b0ade116f', bin2hstr(hcb.hashchain[1])) self.assertEqual('ff7f4850bc6499e08e104c6967ee66e665e57d7e0e429072e646d14e1b92600a', bin2hstr(hcb.hashchain[50]))
def state_update_genesis(self, chain, block, address_txn): # Start Updating coin base txn tx = block.transactions[0] # Expecting only 1 txn of COINBASE subtype in genesis block pubhash = tx.generate_pubhash(tx.PK, tx.ots_key) if tx.nonce != 1: logger.warning('nonce incorrect, invalid tx') logger.warning('subtype: %s', tx.subtype) logger.warning('%s actual: %s expected: %s', tx.txfrom, tx.nonce, address_txn[tx.txfrom][0] + 1) return False # TODO: To be fixed later if pubhash in address_txn[tx.txfrom][2]: logger.warning('pubkey reuse detected: invalid tx %s', tx.txhash) logger.warning('subtype: %s', tx.subtype) return False address_txn[tx.txto][1] += tx.amount address_txn[tx.txfrom][2].append(pubhash) # Coinbase update end here tmp_list = [] for tx in block.transactions: if tx.subtype == TX_SUBTYPE_STAKE: # update txfrom, hash and stake_nonce against genesis for current or next stake_list tmp_list.append([tx.txfrom, tx.hash, 0, tx.first_hash, GenesisBlock().get_info()[tx.txfrom], tx.slave_public_key]) if tx.txfrom == block.blockheader.stake_selector: if tx.txfrom in chain.m_blockchain[0].stake_list: self.stake_validators_list.add_sv(tx.txfrom, tx.slave_public_key, tx.hash, tx.first_hash, tx.balance) self.stake_validators_list.sv_list[tx.txfrom].nonce += 1 else: logger.warning('designated staker not in genesis..') return False else: if tx.txfrom in chain.m_blockchain[0].stake_list: self.stake_validators_list.add_sv(tx.txfrom, tx.slave_public_key, tx.hash, tx.first_hash, tx.balance) else: self.stake_validators_list.add_next_sv(tx.txfrom, tx.slave_public_key, tx.hash, tx.first_hash, tx.balance) pubhash = tx.generate_pubhash(tx.PK, tx.ots_key) address_txn[tx.txfrom][2].append(pubhash) epoch_seed = self.stake_validators_list.calc_seed() chain.block_chain_buffer.epoch_seed = epoch_seed self.put_epoch_seed(epoch_seed) chain.block_chain_buffer.epoch_seed = chain.state.calc_seed(tmp_list) chain.stake_list = sorted(tmp_list, key=lambda staker: chain.score(stake_address=staker[0], reveal_one=bin2hstr(sha256(reduce( lambda set1, set2: set1 + set2, staker[1]))), balance=staker[4], seed=chain.block_chain_buffer.epoch_seed)) chain.block_chain_buffer.epoch_seed = format(chain.block_chain_buffer.epoch_seed, 'x') if chain.stake_list[0][0] != block.blockheader.stake_selector: logger.info('stake selector wrong..') return xmss = chain.wallet.address_bundle[0].xmss tmphc = hashchain(xmss.get_seed_private(), epoch=0) chain.hash_chain = tmphc.hashchain chain.wallet.save_wallet() return True
def pre_pos_2(self, data=None): logger.info('pre_pos_2') if self.buffered_chain.height >= 1: return # assign hash terminators to addresses and generate a temporary stake list ordered by st.hash.. tmp_list = [] seed_list = [] genesis_block = self.buffered_chain.get_block(0) total_genesis_stake_amount = 0 for tx in self.buffered_chain.tx_pool.transaction_pool: tx.pbdata.nonce = 1 if tx.subtype == qrl_pb2.Transaction.STAKE: for genesisBalance in genesis_block.genesis_balance: if tx.txfrom == genesisBalance.address.encode( ) and tx.activation_blocknumber == 1: tmp_list.append([ tx.txfrom, tx.hash, 0, genesisBalance.balance, tx.slave_public_key ]) seed_list.append(tx.hash) # FIXME: This goes to stake validator list without verification, Security Risk self.buffered_chain._chain.pstate.stake_validators_tracker.add_sv( genesisBalance.balance, tx, 1) total_genesis_stake_amount += genesisBalance.balance self.buffered_chain.epoch_seed = calc_seed(seed_list) # TODO : Needed to be reviewed later self.buffered_chain.stake_list = sorted( tmp_list, key=lambda staker: score( stake_address=staker[0], reveal_one=bin2hstr( sha256( str( reduce(lambda set1, set2: set1 + set2, tuple(staker[1]))).encode())), balance=staker[3], seed=self.buffered_chain.epoch_seed)) # self.buffered_chain.epoch_seed = format(self.buffered_chain.epoch_seed, 'x') # FIXME: Why hex string? logger.info('genesis stakers ready = %s / %s', len(self.buffered_chain.stake_list), config.dev.minimum_required_stakers) logger.info('node address: %s', self.buffered_chain.staking_address) # stake pool still not full..reloop.. if len(self.buffered_chain.stake_list ) < config.dev.minimum_required_stakers: self.p2p_factory.broadcast_st(data) logger.info('waiting for stakers.. retry in 5s') reactor.callID = reactor.callLater(5, self.pre_pos_2, data) return voteMetadata = self.buffered_chain.get_consensus(0) consensus_ratio = voteMetadata.total_stake_amount / total_genesis_stake_amount if consensus_ratio < 0.51: logger.info('Consensus lower than 51%%.. retry in 5s') reactor.callID = reactor.callLater(5, self.pre_pos_2, data) return if self.buffered_chain.staking_address == self.buffered_chain.stake_list[ 0][0]: logger.info('designated to create block 1: building block..') tmphc = hashchain(self.buffered_chain.wallet.address_bundle[0]. xmss.get_seed_private()) # create the genesis block 2 here.. reveal_hash = self.buffered_chain.select_hashchain( self.buffered_chain.staking_address, tmphc.hashchain, blocknumber=1) b = self.buffered_chain.create_block( reveal_hash[-2]) # FIXME: This is incorrect, rewire self.pre_block_logic(b) # FIXME: Ignore return value? else: logger.info('await block creation by stake validator: %s', self.buffered_chain.stake_list[0][0]) self.last_bk_time = time.time() self.restart_unsynced_logic()