def load_boot_file(): normal_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'boot.dat') extra_path = os.path.join(V.DB_HOME_DIR, 'boot.dat') if os.path.exists(normal_path): with open(normal_path, mode='br') as fp: data = bjson.loads( b64decode(fp.read().replace(b'\n', b'').replace(b'\r', b''))) elif os.path.exists(extra_path): with open(extra_path, mode='br') as fp: data = bjson.loads( b64decode(fp.read().replace(b'\n', b'').replace(b'\r', b''))) else: raise FileNotFoundError('Cannot find boot.dat "{}" or "{}" ?'.format( normal_path, extra_path)) genesis_block = Block(binary=data['block']) genesis_block.flag = C.BLOCK_GENESIS genesis_block.height = 0 for b_tx in data['txs']: tx = TX(binary=b_tx) tx.height = 0 genesis_block.txs.append(tx) connections = data.get('connections', list()) network_ver = data['network_ver'] return genesis_block, network_ver, connections
def put_to_block_stack(r, before_waiter): block_tmp = dict() batch_txs = list() for block_b, block_height, block_flag, txs in r: block = Block(binary=block_b) block.height = block_height block.flag = block_flag for tx_b, tx_signature in txs: tx = TX(binary=tx_b) tx.height = None tx.signature = tx_signature tx_from_database = tx_builder.get_tx(txhash=tx.hash) if tx_from_database: block.txs.append(tx_from_database) else: block.txs.append(tx) block_tmp[block_height] = block batch_txs.extend(block.txs) # check if len(block_tmp) == 0: return None batch_sign_cashe(batch_txs) before_waiter.wait() with write_protect_lock: block_stack.update(block_tmp) return batch_workhash(tuple(block_tmp.values()))
def check_block(block: Block): # 挿入前にBlockの正当性チェック if block.f_orphan: raise BlockChainError('Block is orphan.') elif len(block.txs) == 0: raise BlockChainError('Block don\'t have any txs.') elif block.getsize() > C.SIZE_BLOCK_LIMIT: raise BlockChainError('Block size is too large [{}b>{}b]'.format(block.getsize(), C.SIZE_BLOCK_LIMIT)) bits = get_bits_by_hash(previous_hash=block.previous_hash, consensus=block.flag)[0] if block.bits != bits: raise BlockChainError('Block bits differ from calc. [{}!={}]'.format(block.bits, bits)) logging.debug("Checked block {}.".format(block))
async def getwork(*args, **kwargs): # https://en.bitcoin.it/wiki/Getwork if len(args) == 0: now = int(time() - V.BLOCK_GENESIS_TIME) for block in getwork_cashe.values(): if block.previous_hash != builder.best_block.hash: continue if now - block.time < 10: mining_block = block break else: mining_block = await get_mining_block(**kwargs) getwork_cashe[mining_block.merkleroot] = mining_block mining_block.bits2target() # Pre-processed SHA-2 input chunks data = mining_block.b # 80 bytes data += unhexlify(b'800000000000000000000000000000000000000000000000' b'000000000000000000000000000000000000000000000280' ) # 48+80=128bytes new_data = b'' for i in range(0, 128, 4): new_data += data[i:i + 4][::-1] if extra_target: return { "data": hexlify(new_data).decode(), "target": hexlify(extra_target.to_bytes(32, 'big')).decode() } else: return { "data": hexlify(new_data).decode(), "target": hexlify(mining_block.target_hash).decode() } else: data = unhexlify(args[0].encode()) new_data = b'' for i in range(0, 128, 4): new_data += data[i:i + 4][::-1] block = Block(binary=new_data[:80]) if block.previous_hash != builder.best_block.hash: return 'PreviousHash don\'t match.' if block.merkleroot in getwork_cashe: block.txs.extend(getwork_cashe[block.merkleroot].txs) result = await submitblock(block, **kwargs) if result is None: return True elif extra_target and block.pow_check(extra_target=extra_target): return True else: logging.debug("GetWorkReject by \"{}\"".format(result)) return result else: logging.debug("GetWorkReject by \"Not found merkleroot.\"") return 'Not found merkleroot.'
async def submitblock(block_hex_or_obj, **kwargs): if isinstance(block_hex_or_obj, str): block_bin = unhexlify(block_hex_or_obj.encode()) # Block mined_block = Block(binary=block_bin[:80]) if mined_block.previous_hash != builder.best_block.hash: return 'PreviousHash don\'t match.' previous_block = builder.get_block(mined_block.previous_hash) mined_block.height = previous_block.height + 1 mined_block.flag = int(kwargs['password']) # tx length storage_flag = int.from_bytes(block_bin[80:81], 'little') if storage_flag < 0xfd: tx_len = storage_flag pos = 81 elif storage_flag == 0xfd: tx_len = int.from_bytes(block_bin[81:83], 'little') pos = 83 elif storage_flag == 0xfe: tx_len = int.from_bytes(block_bin[81:85], 'little') pos = 85 else: # == 0xff tx_len = int.from_bytes(block_bin[81:89], 'little') pos = 89 if F_HEAVY_DEBUG: logging.debug("RpcSubmit block: pos={}, tx_len={}".format( pos, tx_len)) # correct txs while len(block_bin) > pos: tx = TX() tx.b = block_bin tx.deserialize(first_pos=pos, f_raise=False) if tx.version != __chain_version__: return 'tx_ver do not match [{}!={}]'.format( tx.version, __chain_version__) pos += len(tx.b) mined_block.txs.append( tx_builder.get_tx(txhash=tx.hash, default=tx)) # check if tx_len != len(mined_block.txs): return 'Do not match txlen [{}!={}]'.format( tx_len, len(mined_block.txs)) if pos != len(block_bin): return 'Do not match pos [{}!={}]'.format(pos, len(block_bin)) elif isinstance(block_hex_or_obj, Block): mined_block = block_hex_or_obj previous_block = builder.get_block(mined_block.previous_hash) mined_block.height = previous_block.height + 1 mined_block.flag = int(kwargs['password']) else: return 'Unknown input? -> {}'.format(block_hex_or_obj) mined_block.update_pow() if mined_block.pow_check(): confirmed_generating_block(mined_block) return None # accepted else: return 'not satisfied work.'
def make_block_by_node(blockhash): """ create Block by outside node """ r = ask_node(cmd=DirectCmd.BLOCK_BY_HASH, data={'blockhash': blockhash}) if isinstance(r, str): raise BlockChainError('make_block_by_node() failed, by "{}"'.format(hexlify(blockhash).decode(), r)) block = Block(binary=r['block']) block.flag = r['flag'] before_block = builder.get_block(blockhash=block.previous_hash) if before_block is None: raise BlockChainError('Not found BeforeBeforeBlock {}'.format(hexlify(block.previous_hash).decode())) block.height = before_block.height + 1 for tx in r['txs']: _tx = TX(binary=tx['tx']) _tx.height = block.height _tx.signature = tx['sign'] block.txs.append(_tx) return block
def fill_newblock_info(data): new_block = Block(binary=data['block']) logging.debug("Fill newblock={}".format(hexlify(new_block.hash).decode())) proof = TX(binary=data['proof']) new_block.txs.append(proof) new_block.flag = data['block_flag'] proof.signature = data['sign'] # Check the block is correct info if not new_block.pow_check(): raise BlockChainError('Proof of work is not satisfied.') my_block = builder.get_block(new_block.hash) if my_block: raise BlockChainError('Already inserted block {}'.format(my_block)) before_block = builder.get_block(new_block.previous_hash) if before_block is None: logging.debug("Cannot find beforeBlock {}, try to ask outside node." .format(hexlify(new_block.previous_hash).decode())) # not found beforeBlock, need to check other node have the the block new_block.inner_score *= 0.70 # unknown previousBlock, score down before_block = make_block_by_node(blockhash=new_block.previous_hash) if not new_insert_block(before_block, time_check=True): # require time_check, it was generated only a few seconds ago # print([block for block in builder.chain.values()]) raise BlockChainError('Failed insert beforeBlock {}'.format(before_block)) new_height = before_block.height + 1 proof.height = new_height new_block.height = new_height # Append general txs for txhash in data['txs'][1:]: tx = tx_builder.get_tx(txhash) if tx is None: new_block.inner_score *= 0.75 # unknown tx, score down logging.debug("Unknown tx, try to download.") r = ask_node(cmd=DirectCmd.TX_BY_HASH, data={'txhash': txhash}, f_continue_asking=True) if isinstance(r, str): raise BlockChainError('Failed unknown tx download "{}"'.format(r)) tx = TX(binary=r['tx']) tx.signature = r['sign'] check_tx(tx, include_block=None) tx_builder.put_unconfirmed(tx) logging.debug("Success unknown tx download {}".format(tx)) tx.height = new_height new_block.txs.append(tx) return new_block
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 read_block(self, blockhash): if self.is_batch_thread() and blockhash in self.batch['_block']: b = self.batch['_block'][blockhash] elif is_plyvel: b = self._block.get(blockhash, default=None) else: b = self._block.Get(blockhash, default=None) if b is None: return None b = bytes(b) height, _time, work, b_block, flag, tx_len = struct_block.unpack_from( b) idx = struct_block.size assert len(b) == idx + tx_len, 'Not correct size. [{}={}]'.format( len(b), idx + tx_len) block = Block(binary=b_block) block.height = height block.work_hash = work block.flag = flag # block.txs = [self.read_tx(b[idx+32*i:idx+32*i+32]) for i in range(tx_len//32)] block.txs = [ tx_builder.get_tx(b[idx + 32 * i:idx + 32 * i + 32]) for i in range(tx_len // 32) ] return block
async def getwork(*args, **kwargs): """ https://en.bitcoin.it/wiki/Getwork Result: 1. "data" (hex string, required) block data 2. "target" (hex string, required) little endian hash target """ if len(args) == 0: now = int(time() - V.BLOCK_GENESIS_TIME) for block in getwork_cashe.values(): if block.previous_hash != chain_builder.best_block.hash: continue if now - block.time < 10: mining_block = block break else: mining_block = await get_mining_block(int(kwargs['password'])) getwork_cashe[mining_block.merkleroot] = mining_block mining_block.bits2target() # Pre-processed SHA-2 input chunks data = mining_block.b # 80 bytes data += a2b_hex('800000000000000000000000000000000000000000000000' '000000000000000000000000000000000000000000000280') # 48+80=128bytes new_data = b'' for i in range(0, 128, 4): new_data += data[i:i + 4][::-1] if extra_target: return {"data": new_data.hex(), "target": extra_target.to_bytes(32, 'big').hex()} else: return {"data": new_data.hex(), "target": mining_block.target_hash.hex()} else: data = a2b_hex(args[0]) new_data = b'' for i in range(0, 128, 4): new_data += data[i:i + 4][::-1] block = Block.from_binary(binary=new_data[:80]) if block.previous_hash != chain_builder.best_block.hash: return 'PreviousHash don\'t match' if block.merkleroot in getwork_cashe: block.txs.extend(getwork_cashe[block.merkleroot].txs) result = await submitblock(block, **kwargs) if result is None: return True elif extra_target and block.pow_check(extra_target=extra_target): return True else: log.debug("GetWorkReject by \"{}\"".format(result)) return result else: log.debug("GetWorkReject by \"Not found merkleroot.\"") return 'Not found merkleroot'
def fill_newblock_info(data): new_block = Block(binary=data['block']) proof = TX(binary=data['proof']) new_block.txs.append(proof) new_block.flag = data['block_flag'] proof.signature = data['sign'] # Check the block is correct info if not new_block.pow_check(): raise BlockChainError('Proof of work is not satisfied.') if builder.get_block(new_block.hash): raise BlockChainError('Already inserted block.') before_block = builder.get_block(new_block.previous_hash) if before_block is None: raise BlockChainError('Not found beforeBlock {}.'.format( hexlify(new_block.previous_hash).decode())) new_height = before_block.height + 1 proof.height = new_height new_block.height = new_height # Append general txs for txhash in data['txs'][1:]: tx = tx_builder.get_tx(txhash) if tx is None: new_block.inner_score *= 0.75 # unknown tx, score down logging.debug("Unknown tx, try to download.") r = ask_node(cmd=DirectCmd.TX_BY_HASH, data={'txhash': txhash}, f_continue_asking=True) if isinstance(r, str): raise BlockChainError( 'Failed unknown tx download "{}"'.format(r)) tx = TX(binary=r['tx']) tx.signature = r['sign'] check_tx(tx, include_block=None) tx_builder.put_unconfirmed(tx) logging.debug("Success unknown tx download {}".format(tx)) tx.height = new_height new_block.txs.append(tx) return new_block
def check_unconfirmed_order(best_block, ordered_unconfirmed_txs): if len(ordered_unconfirmed_txs) == 0: return None s = time() dummy_proof_tx = TX() dummy_proof_tx.type = C.TX_POW_REWARD, dummy_block = Block() dummy_block.height = best_block.height + 1 dummy_block.previous_hash = best_block.hash dummy_block.txs.append(dummy_proof_tx) # dummy for proof tx dummy_block.txs.extend(ordered_unconfirmed_txs) tx = None try: for tx in ordered_unconfirmed_txs: if tx.type == C.TX_GENESIS: pass elif tx.type == C.TX_POS_REWARD: pass elif tx.type == C.TX_POW_REWARD: pass elif tx.type == C.TX_TRANSFER: pass elif tx.type == C.TX_MINT_COIN: check_tx_mint_coin(tx=tx, include_block=dummy_block) elif tx.type == C.TX_VALIDATOR_EDIT: check_tx_validator_edit(tx=tx, include_block=dummy_block) elif tx.type == C.TX_CONCLUDE_CONTRACT: check_tx_contract_conclude(tx=tx, include_block=dummy_block) else: raise BlockChainError('Unknown tx type "{}"'.format(tx.type)) else: log.debug('Finish unconfirmed order check {}mSec'.format( int((time() - s) * 1000))) return None except Exception as e: log.warning(e, exc_info=True) # return errored tx return tx
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_mining_block(consensus): global mining_address # create proof_tx mining_address = mining_address or V.MINING_ADDRESS or new_key() 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( tx={ 'type': C.TX_POW_REWARD, 'inputs': list(), 'outputs': [(mining_address, 0, reward + fees)], 'gas_price': 0, 'gas_amount': 0, 'message_type': C.MSG_PLAIN if V.MINING_MESSAGE else C.MSG_NONE, 'message': V.MINING_MESSAGE if V.MINING_MESSAGE else b'' }) proof_tx.update_time() # create mining block bits, target = get_bits_by_hash(previous_hash=previous_block.hash, consensus=consensus) mining_block = Block( 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) mining_block.txs.extend(unconfirmed_txs) mining_block.update_merkleroot() mining_block.update_time(proof_tx.time) return mining_block
def fill_newblock_info(data): new_block: Block = Block.from_binary(binary=data['binary']) log.debug("fill newblock height={} newblock={}".format( data.get('height'), new_block.hash.hex())) proof: TX = data['proof'] new_block.txs.append(proof) new_block.flag = data['block_flag'] my_block = chain_builder.get_block(new_block.hash) if my_block: raise BlockChainError('Already inserted block {}'.format(my_block)) before_block = chain_builder.get_block(new_block.previous_hash) if before_block is None: log.debug("Cannot find beforeBlock, try to ask outside node") # not found beforeBlock, need to check other node have the the block new_block.inner_score *= 0.70 # unknown previousBlock, score down before_block = make_block_by_node(blockhash=new_block.previous_hash, depth=0) new_height = before_block.height + 1 proof.height = new_height new_block.height = new_height # work check # TODO: correct position? if not new_block.pow_check(): raise BlockChainError('Proof of work is not satisfied') # Append general txs for txhash in data['txs'][1:]: tx = tx_builder.get_tx(txhash) if tx is None: new_block.inner_score *= 0.75 # unknown tx, score down log.debug("Unknown tx, try to download") r = ask_node(cmd=DirectCmd.TX_BY_HASH, data={'txhash': txhash}, f_continue_asking=True) if isinstance(r, str): raise BlockChainError( 'Failed unknown tx download "{}"'.format(r)) tx: TX = r tx.height = None check_tx(tx, include_block=None) tx_builder.put_unconfirmed(tx) log.debug("Success unknown tx download {}".format(tx)) tx.height = new_height new_block.txs.append(tx) return new_block
def load_boot_file(url=None): normal_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'boot.json') extra_path = os.path.join(V.DB_HOME_DIR, 'boot.json') if url: data = requests.get(url=url).json() elif os.path.exists(normal_path): with open(normal_path, mode='r') as fp: data = json.load(fp) elif os.path.exists(extra_path): with open(extra_path, mode='r') as fp: data = json.load(fp) else: raise FileNotFoundError('Cannot find boot.json "{}" or "{}" ?'.format( normal_path, extra_path)) # load from exist boot.json genesis_block = Block.from_binary(binary=a2b_hex(data['genesis_binary'])) assert genesis_block.hash == a2b_hex(data['genesis_hash']) genesis_block.flag = C.BLOCK_GENESIS genesis_block.height = 0 for tx_dct in data['txs']: tx = TX.from_binary(binary=a2b_hex(tx_dct['binary'])) assert tx.hash == a2b_hex(tx_dct['hash']) tx.height = 0 genesis_block.txs.append(tx) connections = data['connections'] network_ver = data['network_ver'] if isinstance(data['params'], dict): # new type boot.json params = data['params'] params['consensus'] = { int(k): v for k, v in params['consensus'].items() } elif isinstance(data['params'], str): # old type boot.json params = original_mpk.unpackb(a2b_hex(data['params']), raw=True, encoding='utf8') else: raise Exception('Unknown type params') return genesis_block, params, network_ver, connections
def object_hook(dct): if isinstance(dct, dict) and '_bc4py_class_' in dct: if dct['_bc4py_class_'] == 'Block': block = Block.from_binary(binary=dct['binary']) block.height = dct['height'] block.flag = dct['flag'] block.txs.extend(object_hook(tx) for tx in dct['txs']) for tx in block.txs: tx.height = block.height return block elif dct['_bc4py_class_'] == 'TX': tx = TX.from_binary(binary=dct['binary']) tx.height = dct['height'] tx.signature.extend(tuple(sig) for sig in dct['signature']) tx.R = dct['R'] return tx else: raise Exception('Not found class name "{}"'.format( dct['_bc4py_class_'])) else: return dct
def init(self, genesis_block: Block, batch_size=None): assert self.db, 'Why database connection failed?' if batch_size is None: batch_size = self.cashe_limit # GenesisBlockか確認 t = time.time() try: if genesis_block.hash != self.db.read_block_hash(0): raise BlockBuilderError("Don't match genesis hash [{}!={}]".format( hexlify(genesis_block.hash).decode(), hexlify(self.db.read_block_hash(0).decode()))) elif genesis_block != self.db.read_block(genesis_block.hash): raise BlockBuilderError("Don't match genesis binary [{}!={}]".format( hexlify(genesis_block.b).decode(), hexlify(self.db.read_block(genesis_block.hash).b).decode())) except Exception: # GenesisBlockしか無いのでDummyBlockを入れる処理 self.root_block = Block() self.root_block.hash = b'\xff' * 32 self.chain[genesis_block.hash] = genesis_block self.best_chain = [genesis_block] self.best_block = genesis_block logging.info("Set dummy block. GenesisBlock={}".format(genesis_block)) user_account.init() return # 0HeightよりBlockを取得して確認 before_block = genesis_block batch_blocks = list() for height, blockhash in self.db.read_block_hash_iter(start_height=1): block = self.db.read_block(blockhash) if block.previous_hash != before_block.hash: raise BlockBuilderError("PreviousHash != BlockHash [{}!={}]" .format(block, before_block)) elif block.height != height: raise BlockBuilderError("BlockHeight != DBHeight [{}!={}]" .format(block.height, height)) elif height != before_block.height+1: raise BlockBuilderError("DBHeight != BeforeHeight+1 [{}!={}+1]" .format(height, before_block.height)) for tx in block.txs: if tx.height != height: raise BlockBuilderError("TXHeight != BlockHeight [{}!{}]" .format(tx.height, height)) # inputs for txhash, txindex in tx.inputs: input_tx = self.db.read_tx(txhash) address, coin_id, amount = input_tx.outputs[txindex] _coin_id, _amount, f_used = self.db.read_address_idx(address, txhash, txindex) usedindex = self.db.read_usedindex(txhash) if coin_id != _coin_id or amount != _amount: raise BlockBuilderError("Inputs, coin_id != _coin_id or amount != _amount [{}!{}] [{}!={}]" .format(coin_id, _coin_id, amount, _amount)) elif txindex not in usedindex: raise BlockBuilderError("Already used but unused. [{} not in {}]".format(txindex, usedindex)) elif not f_used: raise BlockBuilderError("Already used but unused flag. [{}:{}]".format(input_tx, txindex)) # outputs for index, (address, coin_id, amount) in enumerate(tx.outputs): _coin_id, _amount, f_used = self.db.read_address_idx(address, tx.hash, index) if coin_id != _coin_id or amount != _amount: raise BlockBuilderError("Outputs, coin_id != _coin_id or amount != _amount [{}!{}] [{}!={}]" .format(coin_id, _coin_id, amount, _amount)) # Block確認終了 before_block = block batch_blocks.append(block) if len(batch_blocks) >= batch_size: user_account.new_batch_apply(batch_blocks) batch_blocks.clear() logging.debug("UserAccount batched at {} height.".format(block.height)) # import from starter.dat self.root_block = before_block memorized_blocks, self.best_block = self.load_starter(before_block) # Memory化されたChainを直接復元 for block in memorized_blocks: batch_blocks.append(block) self.chain[block.hash] = block for tx in block.txs: if tx.hash not in tx_builder.chained_tx: tx_builder.chained_tx[tx.hash] = tx if tx.hash in tx_builder.unconfirmed: del tx_builder.unconfirmed[tx.hash] self.best_chain = list(reversed(memorized_blocks)) # UserAccount update user_account.new_batch_apply(batch_blocks) user_account.init() logging.info("Init finished, last block is {} {}Sec" .format(before_block, round(time.time()-t, 3)))
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")
async def submitblock(*args, **kwargs): """ Attempts to submit new block to network. See https://en.bitcoin.it/wiki/BIP_0022 for full specification. Arguments 1. "hexdata" (string, required) the hex-encoded block data to submit 2. "dummy" (optional) dummy value, for compatibility with BIP22. This value is ignored. Result: null if success string if failed """ if len(args) == 0: raise ValueError('no argument found') block_hex_or_obj = args[0] if isinstance(block_hex_or_obj, str): block_bin = a2b_hex(block_hex_or_obj) # Block mined_block = Block.from_binary(binary=block_bin[:80]) if mined_block.previous_hash != chain_builder.best_block.hash: return 'PreviousHash don\'t match' previous_block = chain_builder.get_block(mined_block.previous_hash) mined_block.height = previous_block.height + 1 mined_block.flag = int(kwargs['password']) # tx length storage_flag = int.from_bytes(block_bin[80:81], 'little') if storage_flag < 0xfd: tx_len = storage_flag pos = 81 elif storage_flag == 0xfd: tx_len = int.from_bytes(block_bin[81:83], 'little') pos = 83 elif storage_flag == 0xfe: tx_len = int.from_bytes(block_bin[81:85], 'little') pos = 85 else: # == 0xff tx_len = int.from_bytes(block_bin[81:89], 'little') pos = 89 log.debug("RpcSubmit block: pos={}, tx_len={}".format(pos, tx_len)) # correct txs while len(block_bin) > pos: tx = TX() tx.b = block_bin tx.deserialize(first_pos=pos, f_raise=False) if tx.version != __chain_version__: return 'tx_ver do not match [{}!={}]'.format(tx.version, __chain_version__) pos += len(tx.b) mined_block.txs.append(tx_builder.get_tx(txhash=tx.hash, default=tx)) # check format if tx_len != len(mined_block.txs): return 'Do not match txlen [{}!={}]'.format(tx_len, len(mined_block.txs)) if pos != len(block_bin): return 'Do not match pos [{}!={}]'.format(pos, len(block_bin)) elif isinstance(block_hex_or_obj, Block): mined_block = block_hex_or_obj previous_block = chain_builder.get_block(mined_block.previous_hash) mined_block.height = previous_block.height + 1 mined_block.flag = int(kwargs['password']) else: return 'Unknown input? -> {}'.format(block_hex_or_obj) mined_block.update_pow() if mined_block.pow_check(): confirmed_generating_block(mined_block) return None # accepted else: return 'not satisfied work'
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) self.event_close.set() while self.event_close.is_set(): # 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: logging.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_POS) reward = GompertzCurve.calc_block_reward(previous_block.height + 1) staking_block = Block( block={ '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_POS staking_block.bits2target() staking_block.txs.append(None) # Dummy proof tx 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: logging.debug("Reset by \"nothing params found\"") sleep(1) break elif previous_block.hash != staking_block.previous_hash: logging.debug("Reset by \"Don't match previous_hash\"") sleep(1) break elif not proof_tx.pos_check( previous_hash=previous_block.hash, pos_target_hash=staking_block.target_hash): continue else: # Staked yay!! proof_tx.height = staking_block.height proof_tx.signature = [ message2signature(proof_tx.b, proof_tx.outputs[0][0]) ] staking_block.txs[0] = proof_tx # Fit block size while staking_block.getsize() > C.SIZE_BLOCK_LIMIT: tx = staking_block.txs.pop() if tx.type == C.TX_FINISH_CONTRACT: staking_block.txs.pop() staking_block.update_time(proof_tx.time) staking_block.update_merkleroot() 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: logging.info("Staking... margin={}% limit={}".format( round(remain * 100, 1), staking_limit)) self.hashrate = (calculate_nam, time()) sleep(max(0.0, remain)) logging.info("Close signal")
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 create_genesis_block(all_supply, block_span, prefix=b'\x98', contract_prefix=b'\x12', digit_number=8, minimum_price=100, consensus=None, premine=None): """ Height0のGenesisBlockを作成する :param all_supply: PoW/POS合わせた全採掘量、プリマインを除く :param block_span: Blockの採掘間隔(Sec) :param prefix: 一般アドレスの頭文字、b'\x98'=N :param contract_prefix: コントラクトの頭文字、b'\x98'=C :param digit_number: コインの分解能 :param minimum_price: 最小gas_price :param consensus: 採掘アルゴ {consensus: ratio(0~100), ..} :param premine: プリマイン [(address, coin_id, amount), ...] """ # default: Yescript9割, Stake1割の分配 consensus = consensus or {C.BLOCK_YES_POW: 90, C.BLOCK_POS: 10} 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_POS, C.BLOCK_YES_POW, C.BLOCK_X11_POW, C.BLOCK_HMQ_POW, C.BLOCK_LTC_POW, C.BLOCK_X16_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)) # params assert isinstance(minimum_price, int), 'minimum_price is INT' genesis_time = int(time.time()) # premine premine_txs = list() for index, chunk in enumerate(chunked(premine or list(), 256)): tx = TX( tx={ 'version': __chain_version__, 'type': C.TX_TRANSFER, 'time': 0, 'deadline': 10800, 'inputs': list(), 'outputs': chunk, 'gas_price': 0, 'gas_amount': 0, 'message_type': C.MSG_PLAIN, 'message': 'Premine {}'.format(index).encode() }) tx.height = 0 premine_txs.append(tx) # validator V.BLOCK_GENESIS_TIME = int(time.time()) with closing(create_db(V.DB_ACCOUNT_PATH)) as db: ck = create_new_user_keypair(C.ANT_CONTRACT, db.cursor()) db.commit() c_address = convert_address(ck, contract_prefix) c_bin = contract2binary(Contract) c_cs = { ck.encode(): b'\x00\x00\x00\x00', b'\x00' + b'\x00\x00\x00\x00': b'\x01' } # TODO: 初期値どうする? validator_tx = TX( tx={ 'version': __chain_version__, 'type': C.TX_CREATE_CONTRACT, 'time': 0, 'deadline': 10800, 'inputs': list(), 'outputs': list(), 'gas_price': 0, 'gas_amount': 0, 'message_type': C.MSG_BYTE, 'message': bjson.dumps((c_address, c_bin, c_cs), compress=False) }) validator_tx.height = 0 params = { 'prefix': prefix, # CompressedKey prefix 'contract_prefix': contract_prefix, # ContractKey prefix 'validator_address': c_address, 'genesis_time': genesis_time, # GenesisBlockの採掘時間 'all_supply': all_supply, # 全採掘量 'block_span': block_span, # ブロックの採掘間隔 'digit_number': digit_number, # 小数点以下の桁数 'minimum_price': minimum_price, 'contract_minimum_amount': pow(10, digit_number), 'consensus': consensus } # Block承認のアルゴリズム # BLockChainの設定TX setting_tx = TX( tx={ 'version': __chain_version__, 'type': C.TX_GENESIS, 'time': 0, 'deadline': 10800, 'inputs': list(), 'outputs': list(), 'gas_price': 0, 'gas_amount': 0, 'message_type': C.MSG_BYTE, 'message': bjson.dumps(params, compress=False) }) setting_tx.height = 0 # height0のBlock生成 genesis_block = Block( 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(setting_tx) genesis_block.txs.append(validator_tx) genesis_block.txs.extend(premine_txs) genesis_block.bits2target() genesis_block.target2diff() genesis_block.update_merkleroot() genesis_block.serialize() return genesis_block