def create_mining_block(consensus): global mining_address # setup mining address for PoW with mining_address_lock: if mining_address is None: if V.MINING_ADDRESS is None: with create_db(V.DB_ACCOUNT_PATH) as db: cur = db.cursor() mining_address = generate_new_address_by_userid( C.ANT_UNKNOWN, cur) db.commit() else: mining_address = V.MINING_ADDRESS if unconfirmed_txs is None: raise FailedGenerateWarning('unconfirmed_txs is None') if previous_block is None: raise FailedGenerateWarning('previous_block is None') # create proof_tx reward = GompertzCurve.calc_block_reward(previous_block.height + 1) fees = sum(tx.gas_amount * tx.gas_price for tx in unconfirmed_txs) proof_tx = TX.from_dict( tx={ 'type': C.TX_POW_REWARD, 'inputs': list(), 'outputs': [(mining_address, 0, reward + fees)], 'gas_price': 0, 'gas_amount': 0, 'message_type': C.MSG_NONE, 'message': b'' }) proof_tx.update_time() # create mining block bits, target = get_bits_by_hash(previous_hash=previous_block.hash, consensus=consensus) mining_block = Block.from_dict( block={ 'merkleroot': b'\xff' * 32, 'time': 0, 'previous_hash': previous_block.hash, 'bits': bits, 'nonce': b'\xff\xff\xff\xff' }) proof_tx.height = previous_block.height + 1 mining_block.height = proof_tx.height mining_block.flag = consensus mining_block.bits2target() mining_block.txs.append(proof_tx) if unconfirmed_txs is None: raise FailedGenerateWarning('unconfirmed_txs is None') mining_block.txs.extend(unconfirmed_txs) mining_block.update_merkleroot() mining_block.update_time(proof_tx.time) return mining_block
def get_submit_data(job: Job, extranonce1: bytes, extranonce2: bytes, nonce: bytes, difficulty: float) \ -> (Optional[bytes], bytes, bool, bool): """check client submit data and generate node submit data""" assert len(extranonce1) == 4 and len(extranonce2) == 4 coinbase = job.coinbase1 + extranonce1 + extranonce2 + job.coinbase2 coinbase_hash = sha256d_hash(coinbase) merkleroot_list = [coinbase_hash] + [tx[0] for tx in job.unconfirmed] merkleroot = merkleroot_hash(merkleroot_list) block = Block.from_dict({ 'version': job.version, 'previous_hash': job.previous_hash, 'merkleroot': merkleroot, 'time': job.ntime, 'bits': int.from_bytes(job.bits, 'big'), 'nonce': nonce, # meta 'height': job.height, 'flag': job.algorithm, }) # check fulfill target or share update_work_hash(block) f_mined = block.pow_check() f_shared = block.pow_check(int(DEFAULT_TARGET / difficulty)) log.debug(f"block -> {block.height} {block.hash.hex()}") log.debug(f"coinbase -> {coinbase.hex()}") log.debug(f"header -> {block.b.hex()}") log.debug(f"merkleroot -> {len(merkleroot_list)} {merkleroot.hex()}") log.debug( f"workhash -> {block.work_hash.hex()} mined:{f_mined} shared:{f_shared}" ) # generate submit data when mined if f_mined: submit_data = block.b tx_len = len(job.unconfirmed) + 1 if tx_len < 0xfd: submit_data += tx_len.to_bytes(1, 'little') elif tx_len <= 0xffff: submit_data += b'\xfd' + tx_len.to_bytes(2, 'little') elif tx_len <= 0xffffffff: submit_data += b'\xfe' + tx_len.to_bytes(4, 'little') elif tx_len <= 0xffffffffffffffff: # == 0xff submit_data += b'\xff' + tx_len.to_bytes(8, 'little') else: raise Exception(f"overflowed tx length {tx_len}") submit_data += coinbase for tx in job.unconfirmed: submit_data += tx[1] else: submit_data = None return submit_data, block, f_mined, f_shared
def create_genesis_block(mining_supply, block_span, hrp='pycon', digit_number=8, minimum_price=100, consensus=None, genesis_msg="blockchain for python", premine=None): """ Height0のGenesisBlockを作成する :param mining_supply: PoW/POS合わせた全採掘量、プリマインを除く :param block_span: Blockの採掘間隔(Sec) :param hrp: human readable part :param digit_number: コインの分解能 :param minimum_price: 最小gas_price :param consensus: 採掘アルゴ {consensus: ratio(0~100), ..} :param genesis_msg: GenesisMessage :param premine: プリマイン [(address, coin_id, amount), ...] """ # default: Yescript9割, Stake1割の分配 consensus = consensus or {C.BLOCK_X16S_POW: 100} if sum(consensus.values()) != 100: raise BlockChainError('sum of consensus values is 100 [!={}]'.format(sum(consensus.values()))) elif not isinstance(sum(consensus.values()), int): raise BlockChainError('value is int only') elif not (0 < min(consensus.values()) <= 100): raise BlockChainError('out of range {}'.format(min(consensus.values()))) elif not (0 < max(consensus.values()) <= 100): raise BlockChainError('out of range {}'.format(min(consensus.values()))) all_consensus = { C.BLOCK_COIN_POS, C.BLOCK_CAP_POS, C.BLOCK_FLK_POS, C.BLOCK_YES_POW, C.BLOCK_X11_POW, C.BLOCK_HMQ_POW, C.BLOCK_LTC_POW, C.BLOCK_X16S_POW } if len(set(consensus.keys()) - all_consensus) > 0: raise BlockChainError('Not found all_consensus number {}'.format(set(consensus.keys()) - all_consensus)) elif len(set(consensus.keys()) & all_consensus) == 0: raise BlockChainError('No usable consensus found {}'.format(set(consensus.keys()) & all_consensus)) elif not (0 < len(hrp) < 5): raise BlockChainError('hrp is too long hrp={}'.format(hrp)) elif 'dummy' in hrp or '1' in hrp: raise BlockChainError('Not allowed include "dummy" and "1" str {}'.format(hrp)) # params assert isinstance(minimum_price, int), 'minimum_price is INT' genesis_time = int(time()) # BLockChainの設定TX params = { 'hrp': hrp, 'genesis_time': genesis_time, # GenesisBlockの採掘時間 'mining_supply': mining_supply, # 全採掘量 'block_span': block_span, # ブロックの採掘間隔 'digit_number': digit_number, # 小数点以下の桁数 'minimum_price': minimum_price, 'contract_minimum_amount': pow(10, digit_number), 'consensus': consensus, # Block承認のアルゴリズム } V.BLOCK_GENESIS_TIME = genesis_time # first tx first_tx = TX.from_dict( tx={ 'type': C.TX_GENESIS, 'time': 0, 'deadline': 10800, 'gas_price': 0, 'gas_amount': 0, 'message_type': C.MSG_PLAIN, 'message': genesis_msg.encode() }) first_tx.height = 0 # premine premine_txs = list() for index, chunk in enumerate(chunked(premine or list(), 255)): tx = TX.from_dict(tx={ 'type': C.TX_TRANSFER, 'time': 0, 'deadline': 10800, 'outputs': chunk, 'gas_price': 0, 'gas_amount': 0 }) tx.height = 0 premine_txs.append(tx) # height0のBlock生成 genesis_block = Block.from_dict(block={ 'merkleroot': b'\x00' * 32, 'time': 0, 'previous_hash': b'\xff' * 32, 'bits': MAX_BITS, 'nonce': b'\xff' * 4 }) # block params genesis_block.height = 0 genesis_block.flag = C.BLOCK_GENESIS # block body genesis_block.txs.append(first_tx) genesis_block.txs.extend(premine_txs) genesis_block.bits2target() genesis_block.target2diff() genesis_block.update_merkleroot() genesis_block.serialize() return genesis_block, params
def proof_of_capacity(self): dir_path: str = self.config.get('path', os.path.join(V.DB_HOME_DIR, 'plots')) while self.f_enable: # check start mining if previous_block is None or unconfirmed_txs is None: sleep(0.1) continue if not os.path.exists(dir_path): sleep(30) continue s = time() previous_hash = previous_block.hash height = previous_block.height + 1 block_time = int(s - V.BLOCK_GENESIS_TIME) bits, target = get_bits_by_hash(previous_hash=previous_hash, consensus=C.BLOCK_CAP_POS) reward = GompertzCurve.calc_block_reward(height) # start staking by capacity count = 0 for file_name in os.listdir(dir_path): m = optimize_file_name_re.match(file_name) if m is None: continue count += int(m.group(3)) - int(m.group(2)) if count < 1: log.debug("not found plot file, wait 60 sec") sleep(60) continue # let's seek files nonce, work_hash, address = multi_seek(dir=dir_path, previous_hash=previous_hash, target=target.to_bytes( 32, 'little'), time=block_time, worker=os.cpu_count()) if work_hash is None: # return failed => (None, None, err-msg) if int(s) % 300 == 0: log.debug("PoC mining info by \"{}\"".format(address)) else: # return success => (nonce, workhash, address) if previous_block is None or unconfirmed_txs is None: continue if previous_block.hash != previous_hash: continue # Staked by capacity yay!! total_fee = sum(tx.gas_price * tx.gas_amount for tx in unconfirmed_txs) staked_block = Block.from_dict( block={ 'version': 0, # always 0 'previous_hash': previous_hash, 'merkleroot': b'\x00' * 32, 'time': 0, 'bits': bits, 'nonce': nonce, 'height': height, 'flag': C.BLOCK_CAP_POS }) staked_proof_tx = TX.from_dict( tx={ 'type': C.TX_POS_REWARD, 'time': block_time, 'deadline': block_time + 10800, 'gas_price': 0, 'gas_amount': 0, 'outputs': [(address, 0, reward + total_fee)] }) staked_block.txs.append(staked_proof_tx) staked_block.txs.extend(unconfirmed_txs) while staked_block.size > C.SIZE_BLOCK_LIMIT: staked_block.txs.pop() staked_block.update_time(staked_proof_tx.time) staked_block.update_merkleroot() staked_block.work_hash = work_hash signature = sign_message_by_address(raw=staked_block.b, address=address) staked_proof_tx.signature.append(signature) confirmed_generating_block(staked_block) # finish all used_time = time() - s self.hashrate = (count, time()) sleep(max(1 - used_time, 0)) log.info("Close signal")
def proof_of_stake(self): global staking_limit limit_deque = deque(maxlen=10) while self.f_enable: # check start mining if previous_block is None or unconfirmed_txs is None or unspents_txs is None: sleep(0.1) continue if len(unspents_txs) == 0: log.info("No unspents for staking, wait 180s") sleep(180) continue start = time() # create staking block bits, target = get_bits_by_hash(previous_hash=previous_block.hash, consensus=C.BLOCK_COIN_POS) reward = GompertzCurve.calc_block_reward(previous_block.height + 1) staking_block = Block.from_dict( block={ 'version': 0, # always 0 'merkleroot': b'\xff' * 32, 'time': 0, 'previous_hash': previous_block.hash, 'bits': bits, 'nonce': b'\xff\xff\xff\xff' }) staking_block.height = previous_block.height + 1 staking_block.flag = C.BLOCK_COIN_POS staking_block.bits2target() staking_block.txs.append(None) # Dummy proof tx if unconfirmed_txs is None: raise FailedGenerateWarning('unconfirmed_txs is None') staking_block.txs.extend(unconfirmed_txs) calculate_nam = 0 for proof_tx in unspents_txs.copy(): address = proof_tx.outputs[0][0] proof_tx.outputs[0] = (address, 0, proof_tx.pos_amount + reward) proof_tx.update_time() calculate_nam += 1 # next check block if previous_block is None or unconfirmed_txs is None or unspents_txs is None: log.debug("Reset by \"nothing params found\"") sleep(1) break elif previous_block.hash != staking_block.previous_hash: log.debug("Reset by \"Don't match previous_hash\"") sleep(1) break elif not stake_coin_check( tx=proof_tx, previous_hash=previous_block.hash, target_hash=staking_block.target_hash): continue else: # Staked yay!! proof_tx.height = staking_block.height staking_block.txs[0] = proof_tx # Fit block size while staking_block.size > C.SIZE_BLOCK_LIMIT: staking_block.txs.pop() staking_block.update_time(proof_tx.time) staking_block.update_merkleroot() signature = sign_message_by_address(raw=staking_block.b, address=address) proof_tx.signature.append(signature) confirmed_generating_block(staking_block) break else: # check time used = time() - start remain = 1.0 - used max_limit = max(50, int(calculate_nam / max(0.0001, used))) limit_deque.append(int(max_limit * self.power_limit)) staking_limit = sum(limit_deque) // len(limit_deque) if int(time()) % 90 == 0: log.info("Staking... margin={}% limit={}".format( round(remain * 100, 1), staking_limit)) self.hashrate = (calculate_nam, time()) sleep(max(0.0, remain + random() - 0.5)) log.info("Close signal")