def check_new_tx(tx: TX): if tx.height is not None: raise CheckWatchError('is not unconfirmed? {}'.format(tx)) elif tx.message_type != C.MSG_BYTE: return elif tx.type == C.TX_CONCLUDE_CONTRACT: # 十分な署名が集まったら消す c_address, start_hash, c_storage = bjson.loads(tx.message) v = get_validator_object(c_address=c_address, stop_txhash=tx.hash) related_list = check_related_address(v.validators) if related_list: data = (time(), tx, related_list, c_address, start_hash, c_storage) watching_tx[tx.hash] = data NewInfo.put((C_Conclude, False, data)) elif tx.type == C.TX_VALIDATOR_EDIT: # 十分な署名が集まったら消す c_address, new_address, flag, sig_diff = bjson.loads(tx.message) v = get_validator_object(c_address=c_address, stop_txhash=tx.hash) related_list = check_related_address(v.validators) if related_list: data = (time(), tx, related_list, c_address, new_address, flag, sig_diff) watching_tx[tx.hash] = data NewInfo.put((C_Validator, False, data)) else: pass
def read_contract_iter(self, c_address, start_idx=None): f_batch = self.is_batch_thread() batch_copy = self.batch['_contract'].copy() if self.batch else dict() b_c_address = c_address.encode() # caution: iterator/RangeIter's result include start and stop, need to add 1. start = b_c_address + ((start_idx+1).to_bytes(8, ITER_ORDER) if start_idx else b'\x00'*8) stop = b_c_address + b'\xff'*8 if is_plyvel: contract_iter = self._contract.iterator(start=start, stop=stop) else: contract_iter = self._contract.RangeIter(key_from=start, key_to=stop) for k, v in contract_iter: k, v = bytes(k), bytes(v) # KEY: [c_address 40s]-[index uint8] # VALUE: [start_hash 32s]-[finish_hash 32s]-[bjson(c_method, c_args, c_storage)] # c_address, index, start_hash, finish_hash, message if f_batch and k in batch_copy and start <= k <= stop: v = batch_copy[k] del batch_copy[k] dummy, index = struct_construct_key.unpack(k) start_hash, finish_hash, raw_message = v[0:32], v[32:64], v[64:] message = bjson.loads(raw_message) yield index, start_hash, finish_hash, message if f_batch: for k, v in sorted(batch_copy.items(), key=lambda x: x[0]): if k.startswith(b_c_address) and start <= k <= stop: dummy, index = struct_construct_key.unpack(k) start_hash, finish_hash, raw_message = v[0:32], v[32:64], v[64:] message = bjson.loads(raw_message) yield index, start_hash, finish_hash, message
def read_coins_iter(self, coin_id): f_batch = self.is_batch_thread() batch_copy = self.batch['_coins'].copy() if self.batch else dict() b_coin_id = coin_id.to_bytes(4, ITER_ORDER) start = b_coin_id + b'\x00'*4 stop = b_coin_id + b'\xff'*4 if is_plyvel: coins_iter = self._coins.iterator(start=start, stop=stop) else: coins_iter = self._coins.RangeIter(key_from=start, key_to=stop) for k, v in coins_iter: k, v = bytes(k), bytes(v) # coin_id, index, txhash if f_batch and k in batch_copy and start <= k <= stop: v = batch_copy[k] del batch_copy[k] dummy, index = struct_coins.unpack(k) txhash, (params, setting) = v[:32], bjson.loads(v[32:]) yield index, txhash, params, setting if f_batch: for k, v in sorted(batch_copy.items(), key=lambda x: x[0]): if k.startswith(b_coin_id) and start <= k <= stop: dummy, index = struct_coins.unpack(k) txhash, (params, setting) = v[:32], bjson.loads(v[32:]) yield index, txhash, params, setting
def read_contract_iter(self, c_address): f_batch = self.is_batch_thread() batch_copy = self.batch['_contract'].copy() if self.batch else dict() b_c_address = c_address.encode() start = b_c_address + b'\x00'*4 stop = b_c_address + b'\xff'*4 if is_plyvel: contract_iter = self._contract.iterator(start=start, stop=stop) else: contract_iter = self._contract.RangeIter(key_from=start, key_to=stop) for k, v in contract_iter: k, v = bytes(k), bytes(v) # KEY: [c_address 40s]-[index uint4] # VALUE: [start_hash 32s]-[finish_hash 32s]-[len uint4]-[bjson(c_method, c_args, c_storage)] # c_address, index, start_hash, finish_hash, message if f_batch and k in batch_copy: v = batch_copy[k] del batch_copy[k] dummy, index = struct_construct_key.unpack(k) start_hash, finish_hash, raw_message = v[0:32], v[32:64], v[64:] message = bjson.loads(raw_message) yield index, start_hash, finish_hash, message if f_batch: for k, v in sorted(batch_copy.items(), key=lambda x: x[0]): if k.startswith(b_c_address): dummy, index = struct_construct_key.unpack(k) start_hash, finish_hash, raw_message = v[0:32], v[32:64], v[64:] message = bjson.loads(raw_message) yield index, start_hash, finish_hash, message
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 get_contract_history_iter(c_address, best_block=None, best_chain=None): # DataBaseより last_index = 0 for dummy, index, start_hash, finish_hash in builder.db.read_contract_iter(c_address): yield index, start_hash, finish_hash, None, False last_index += 1 # Memoryより best_chain = best_chain or _get_best_chain_all(best_block) for block in reversed(best_chain): for tx in block.txs: if tx.type == C.TX_CREATE_CONTRACT: c_address2, c_bin, c_cs = bjson.loads(tx.message) if c_address == c_address2: yield 0, tx.hash, b'\x00'*32, tx.height, True last_index += 1 elif tx.type == C.TX_START_CONTRACT: c_address2, c_method, c_args, c_redeem = bjson.loads(tx.message) if c_address != c_address2: continue for finish_tx in block.txs: if finish_tx.type != C.TX_FINISH_CONTRACT: continue dummy0, start_hash, dummy1 = bjson.loads(finish_tx.message) if start_hash == tx.hash: yield last_index, start_hash, finish_tx.hash, tx.height, True last_index += 1 break # Unconfirmedより if best_block is None: validator_cks, required_num = get_validator_info() for tx in sorted(tx_builder.unconfirmed.values(), key=lambda x: x.time): if tx.type == C.TX_CREATE_CONTRACT: yield 0, tx.hash, b'\x00'*32, None, True last_index += 1 if tx.type == C.TX_START_CONTRACT: c_address2, c_method, c_args, c_redeem = bjson.loads(tx.message) if c_address != c_address2: continue for finish_tx in tx_builder.unconfirmed.values(): if len(finish_tx.signature) < required_num: continue elif finish_tx.type != C.TX_FINISH_CONTRACT: continue dummy0, start_hash, dummy1 = bjson.loads(finish_tx.message) if start_hash == tx.hash: yield last_index, start_hash, finish_tx.hash, None, True last_index += 1 break
def processing(): self.threadid = get_ident() while not self.f_stop: user = msg_body = None try: user, msg_body = processing_que.get(timeout=1) item = bjson.loads(msg_body) if item['type'] == T_REQUEST: if item['cmd'] == ClientCmd.BROADCAST: # broadcastはCheckを含む為に別スレッド broadcast_que.put((user, item)) else: self.type_request(user=user, item=item) elif item['type'] == T_RESPONSE: self.type_response(user=user, item=item) elif item['type'] == T_ACK: self.type_ack(user=user, item=item) else: logging.debug("Unknown type {}".format(item['type'])) except bjson.BJsonBaseError: self.p2p.remove_connection(user) logging.debug("BJsonBaseError", exc_info=Debug.P_EXCEPTION) except queue.Empty: pass except Exception as e: logging.debug("Processing error, ({}, {}, {})".format( user.name, msg_body, e), exc_info=Debug.P_EXCEPTION) self.f_finish = True self.f_running = False logging.info("Close processing.")
def set_blockchain_params(genesis_block): assert 'spawn' in multiprocessing.get_all_start_methods(), 'Not found spawn method.' setting_tx = genesis_block.txs[0] params = bjson.loads(setting_tx.message) V.BLOCK_GENESIS_HASH = genesis_block.hash V.BLOCK_PREFIX = params.get('prefix') V.BLOCK_CONTRACT_PREFIX = params.get('contract_prefix') V.BLOCK_GENESIS_TIME = params.get('genesis_time') V.BLOCK_ALL_SUPPLY = params.get('all_supply') V.BLOCK_TIME_SPAN = params.get('block_span') V.BLOCK_REWARD = params.get('block_reward') V.CONTRACT_VALIDATOR_ADDRESS = params.get('validator_address') V.COIN_DIGIT = params.get('digit_number') V.COIN_MINIMUM_PRICE = params.get('minimum_price') V.CONTRACT_MINIMUM_AMOUNT = params.get('contract_minimum_amount') consensus = params.get('consensus') if isinstance(consensus, dict): V.BLOCK_CONSENSUS = consensus V.BLOCK_BASE_CONSENSUS = min(consensus.keys()) else: # TODO: remove after test pow_ratio = params.get('pow_ratio') V.BLOCK_CONSENSUS = {C.BLOCK_YES_POW: pow_ratio, C.BLOCK_POS: 100 - pow_ratio} V.BLOCK_BASE_CONSENSUS = C.BLOCK_YES_POW GompertzCurve.setup_params()
def processing(): que = self.p2p.core_que.create() while not self.f_stop: user = msg_body = None try: user, msg_body = que.get(timeout=1) item = bjson.loads(msg_body) if item['type'] == T_REQUEST: self.type_request(user=user, item=item) elif item['type'] == T_RESPONSE: self.type_response(user=user, item=item) elif item['type'] == T_ACK: self.type_ack(user=user, item=item) else: logging.debug("Unknown type {}".format(item['type'])) except bjson.BJsonBaseError: self.p2p.remove_connection(user) logging.debug("BJsonBaseError", exc_info=Debug.P_EXCEPTION) except queue.Empty: pass except Exception as e: logging.debug("Processing error, ({}, {}, {})" .format(user.name, msg_body, e), exc_info=Debug.P_EXCEPTION) self.f_finish = True self.f_running = False logging.info("Close process.")
def check_new_block(block: Block): for tx in block.txs: if tx.height is None: raise CheckWatchError('is unconfirmed? {}'.format(tx)) elif tx.message_type != C.MSG_BYTE: continue elif tx.type == C.TX_TRANSFER: # ConcludeTXを作成するべきフォーマットのTXを見つける c_address, c_method, redeem_address, c_args = bjson.loads( tx.message) v = get_validator_object(c_address=c_address) related_list = check_related_address(v.validators) if related_list: data = (time(), tx, related_list, c_address, c_method, redeem_address, c_args) watching_tx[tx.hash] = data NewInfo.put((C_RequestConclude, False, data)) elif tx.type == C.TX_CONCLUDE_CONTRACT: if tx.hash in watching_tx: # try to delete c_transfer_tx and conclude_tx _time, _tx, _related_list, _c_address, start_hash, c_storage = watching_tx[ tx.hash] if start_hash in watching_tx: del watching_tx[start_hash] del watching_tx[tx.hash] data = (time(), tx) NewInfo.put((C_FinishConclude, False, data)) elif tx.type == C.TX_VALIDATOR_EDIT: if tx.hash in watching_tx: del watching_tx[tx.hash] data = (time(), tx) NewInfo.put((C_FinishValidator, False, data)) else: pass
def test(): import time import json with open("sample.json") as f: t = json.load(f) t.append("namuyan" * 1024) s = time.time() pr_dump = cProfile.Profile() pr_dump.enable() bj = dumps(t) pr_dump.disable() print("encode", time.time() - s, "Sec") stats_dump = pstats.Stats(pr_dump) stats_dump.sort_stats('ncalls') stats_dump.print_stats() pr_load = cProfile.Profile() pr_load.enable() decoded = loads(bj) pr_load.disable() print("decode", time.time() - s, "Sec") stats_load = pstats.Stats(pr_load) stats_load.sort_stats('ncalls') stats_load.print_stats() print(len(bj) // 1000, "kB, match=", t == decoded)
def check_tx_create_contract(tx: TX, include_block: Block): if len(tx.inputs) == 0 or len(tx.outputs) == 0: raise BlockChainError('No inputs or outputs.') elif tx.message_type != C.MSG_BYTE: raise BlockChainError('create contract tx is bytes msg.') elif V.BLOCK_CONTRACT_PREFIX is None: raise BlockChainError('Not set contract prefix ?') elif V.BLOCK_CONTRACT_PREFIX == V.BLOCK_PREFIX: raise BlockChainError('normal prefix same with contract prefix.') # GAS量チェック estimate_gas = tx.getsize() + C.CONTRACT_CREATE_FEE if estimate_gas > tx.gas_amount: raise BlockChainError('Insufficient gas [{}>{}]'.format( estimate_gas, tx.gas_amount)) # Contractをデコードできるか c_address, c_bin, c_cs = bjson.loads(tx.message) binary2contract(c_bin) # ContractStorageの初期値チェック if c_cs: for k, v in c_cs.items(): if not isinstance(k, bytes) or not isinstance(v, bytes): raise BlockChainError('cs format is wrong. {}'.format(c_cs)) if not is_address(c_address, V.BLOCK_CONTRACT_PREFIX): raise BlockChainError('Is not contract address. {}'.format(c_address)) # 既に登録されていないかチェック cs = get_contract_storage(c_address, include_block) if cs.version != 0: raise BlockChainError('Already created contract. {}'.format(tx))
def _loop(v_address): while P.F_NOW_BOOTING: time.sleep(2) for tx in tx_builder.unconfirmed.values(): if tx.type == C.TX_START_CONTRACT: P.VALIDATOR_OBJ.put_unvalidated(tx) # check validated start tx for tx in tx_builder.unconfirmed.values(): if tx.type == C.TX_FINISH_CONTRACT: c_result, start_hash, cs_diff = bjson.loads(tx.message) start_tx = P.VALIDATOR_OBJ[start_hash] if start_tx: sign_pair = message2signature(tx.b, v_address) if sign_pair in tx.signature: P.VALIDATOR_OBJ.put_validated(start_tx) logging.info("Enabled validator mode [{}].".format(v_address)) while True: for start_tx in P.VALIDATOR_OBJ.get_unvalidated(): finish_tx, estimate_gas = finish_contract_tx(start_tx) P.VALIDATOR_OBJ.put_validated(start_tx) finish_tx.signature.append(message2signature(finish_tx.b, v_address)) print(estimate_gas, finish_tx) logging.info("Validated! {}".format(finish_tx)) time.sleep(1) time.sleep(1)
async def conclude_contract(request): start = time() post = await web_base.content_type_json_check(request) try: start_hash = unhexlify(post['start_hash'].encode()) start_tx = tx_builder.get_tx(txhash=start_hash) if start_tx is None: return web_base.error_res('Not found start_tx {}'.format( post['start_hash'])) c_address, c_method, redeem_address, c_args = bjson.loads( start_tx.message) send_pairs = post.get('send_pairs', None) c_storage = post.get('storage', None) tx = create_conclude_tx(c_address=c_address, start_tx=start_tx, redeem_address=redeem_address, send_pairs=send_pairs, c_storage=c_storage) if not send_newtx(new_tx=tx): raise Exception('Failed to send new tx.') return web_base.json_res({ 'hash': hexlify(tx.hash).decode(), 'gas_amount': tx.gas_amount, 'gas_price': tx.gas_price, 'fee': tx.gas_amount * tx.gas_price, 'time': round(time() - start, 3) }) except Exception: return web_base.error_res()
async def get_contract_history(request): try: c_address = request.query['c_address'] data = list() # database for index, start_hash, finish_hash, (c_method, c_args, c_storage) in\ builder.db.read_contract_iter(c_address=c_address): data.append({ 'index': index, 'height': index // 0xffffffff, 'start_hash': hexlify(start_hash).decode(), 'finish_hash': hexlify(finish_hash).decode(), 'c_method': c_method, 'c_args': [decode(a) for a in c_args], 'c_storage': {decode(k): decode(v) for k, v in c_storage.items()} if c_storage else None }) # memory for block in reversed(builder.best_chain): for tx in block.txs: if tx.type != C.TX_CONCLUDE_CONTRACT: continue _c_address, start_hash, c_storage = bjson.loads(tx.message) if _c_address != c_address: continue start_tx = tx_builder.get_tx(txhash=start_hash) dummy, c_method, redeem_address, c_args = bjson.loads( start_tx.message) index = start_tx2index(start_tx=start_tx) data.append({ 'index': index, 'height': tx.height, 'start_hash': hexlify(start_hash).decode(), 'finish_hash': hexlify(tx.hash).decode(), 'c_method': c_method, 'c_args': [decode(a) for a in c_args], 'c_storage': {decode(k): decode(v) for k, v in c_storage.items()} if c_storage else None, # 'redeem_address': redeem_address, }) return web_base.json_res(data) except Exception as e: logging.error(e) return web_base.error_res()
def finish_contract_tx(start_tx, f_limit=True): assert start_tx.height is None, 'StartTX height is None.' assert P.VALIDATOR_OBJ, 'You are not a validator.' c_address, c_data, c_args, c_redeem = bjson.loads(start_tx.message) if f_limit: gas_limit = start_tx.gas_amount - start_tx.getsize() else: gas_limit = None status, result, estimate_gas, line = try_emulate(start_tx, gas_limit) if status: # 成功時 outputs, cs_result = result if cs_result is None: message = bjson.dumps((True, start_tx.hash, None), compress=False) else: cs = get_contract_storage(c_address) cs_diff = cs.diff_dev(new_key_value=cs_result.key_value) message = bjson.dumps((True, start_tx.hash, cs_diff), compress=False) try: check_output_format(outputs) except BlockChainError as e: logging.debug("Contract failed `emulate success` {}".format(e)) return failed_finish_tx(start_tx), estimate_gas else: # 失敗時 outputs = None logging.debug("Contract failed `emulate failed` {}".format(result)) return failed_finish_tx(start_tx), 0 # finish tx 作成 finish_tx = TX( tx={ 'version': __chain_version__, 'type': C.TX_FINISH_CONTRACT, 'time': start_tx.time, 'deadline': start_tx.deadline, 'inputs': list(), 'outputs': outputs or list(), 'gas_price': start_tx.gas_price, 'gas_amount': 1, 'message_type': C.MSG_BYTE, 'message': message }) to_user_redeem = (c_redeem, 0, 0) finish_tx.outputs.append(to_user_redeem) # gas_amountを返す # fill input/output # TODO: c_redeemを用いてFeeを還元 redeem_gas = start_tx.gas_amount - start_tx.getsize() - estimate_gas if not fill_inputs_outputs(finish_tx, c_address, start_tx.hash, redeem_gas): return failed_finish_tx(start_tx), estimate_gas redeem_idx = finish_tx.outputs.index(to_user_redeem) redeem_amount = -1 * finish_tx.gas_amount * finish_tx.gas_price finish_tx.outputs[redeem_idx] = (c_redeem, 0, redeem_amount) replace_redeem_dummy_address(finish_tx, c_address) finish_tx.serialize() return finish_tx, estimate_gas
def check_tx_mint_coin(tx, include_block): if not (0 < len(tx.inputs) and 0 < len(tx.outputs)): raise BlockChainError('Input and output is more than 1.') elif tx.message_type != C.MSG_BYTE: raise BlockChainError('TX_MINT_COIN message is bytes.') elif include_block and 0 == include_block.txs.index(tx): raise BlockChainError('tx index is not proof tx.') elif tx.gas_amount < tx.size + len( tx.signature) * C.SIGNATURE_GAS + C.MINTCOIN_GAS: raise BlockChainError('Insufficient gas amount [{}<{}+{}+{}]'.format( tx.gas_amount, tx.size, len(tx.signature) * C.SIGNATURE_GAS, C.MINTCOIN_GAS)) # check new mintcoin format try: mint_id, params, setting = bjson.loads(tx.message) except Exception as e: raise BlockChainError('BjsonDecodeError: {}'.format(e)) m_before = get_mintcoin_object(coin_id=mint_id, best_block=include_block, stop_txhash=tx.hash) result = check_mintcoin_new_format(m_before=m_before, new_params=params, new_setting=setting) if isinstance(result, str): raise BlockChainError('Failed check mintcoin block={}: {}'.format( include_block, result)) # signature check require_cks, coins = input_output_digest(tx=tx) owner_address = m_before.address if owner_address: require_cks.add(owner_address) signed_cks = get_signed_cks(tx) if signed_cks != require_cks: raise BlockChainError( 'Signature check failed. signed={} require={} lack={}'.format( signed_cks, require_cks, require_cks - signed_cks)) # amount check if 0 < len(set(coins.keys()) - {0, mint_id}): raise BlockChainError('Unexpected coin_id included. {}'.format( set(coins.keys()) - {0, mint_id})) if mint_id in coins: # increase/decrease mintcoin amount if not m_before.setting['additional_issue']: raise BlockChainError( 'additional_issue is False but change amount.') if coins[0] + coins[mint_id] < 0: raise BlockChainError('Too many output amount. {}'.format(coins)) if coins[mint_id] < 0: pass # increase if coins[mint_id] > 0: pass # decrease else: # only change mintcoin status if params is None and setting is None: raise BlockChainError('No update found.') if sum(coins.values()) < 0: raise BlockChainError('Too many output amount. {}'.format(coins))
def check_tx_start_contract(start_tx: TX, include_block: Block): # 共通チェック c_address, c_data, c_args, c_redeem = bjson.loads(start_tx.message) if not is_address(c_address, V.BLOCK_CONTRACT_PREFIX): raise BlockChainError('Is not contract address. {}'.format(c_address)) elif not (c_args is None or isinstance(c_args, list) or isinstance(c_args, tuple)): raise BlockChainError('c_args is {}'.format(type(c_args))) elif not is_address(c_redeem, V.BLOCK_PREFIX): raise BlockChainError('Is not redeem address. {}'.format(c_redeem)) elif start_tx.gas_price < V.COIN_MINIMUM_PRICE: raise BlockChainError('GasPrice is too low. [{}<{}]'.format( start_tx.gas_price, V.COIN_MINIMUM_PRICE)) elif start_tx.gas_amount < V.CONTRACT_MINIMUM_AMOUNT: raise BlockChainError('GasAmount is too low. [{}<{}]'.format( start_tx.gas_amount, V.CONTRACT_MINIMUM_AMOUNT)) # Block内チェック if include_block: # 同一のStartTXを示すFinishTXが存在しないかチェック count = 0 for finish_tx in include_block.txs: if finish_tx.type != C.TX_FINISH_CONTRACT: continue c_status, c_start_hash, c_diff = bjson.loads(finish_tx.message) if c_start_hash != start_tx.hash: continue count += 1 if count == 0: raise BlockChainError( 'Not found FinishTX on block. {}'.format(start_tx)) if count > 1: raise BlockChainError( 'Find some FinishTX on block. {}'.format(count)) else: c_address, c_method, c_args, c_redeem = bjson.loads(start_tx.message) get_contract_binary(c_address) if P.VALIDATOR_OBJ and im_a_validator(include_block): P.VALIDATOR_OBJ.put_unvalidated(start_tx) logging.debug("Add validation que {}".format(start_tx))
def create_signed_tx_as_validator(tx: TX): assert tx.type in (C.TX_VALIDATOR_EDIT, C.TX_CONCLUDE_CONTRACT) assert tx.hash in tx_builder.unconfirmed copied_tx = deepcopy(tx) # sign as another validator c_address, *dummy = bjson.loads(copied_tx.message) # validator object stop_txhash = copied_tx.hash if copied_tx.type == C.TX_VALIDATOR_EDIT else None v = get_validator_object(c_address=c_address, stop_txhash=stop_txhash) if setup_contract_signature(copied_tx, v.validators) == 0: raise BlockChainError('Cannot sign, you are not validator.') return copied_tx
def deserialize(self): d = bjson.loads(self.binary) self.version = d['version'] self.coin_id = d['coin_id'] self.name = d.get('name') self.unit = d.get('unit') self.digit = d.get('digit') self.amount = d['amount'] self.additional_issue = d.get('additional_issue') self.owner = d['owner'] self.image = d.get('image') self.sign = d['sign'] self.message = d.get('message')
def get_contract_binary(c_address, best_block=None, best_chain=None): assert V.CONTRACT_VALIDATOR_ADDRESS, 'Not found validator address.' if c_address == V.CONTRACT_VALIDATOR_ADDRESS: return contract2binary(Contract) # DataBaseより for dummy, index, start_hash, finish_hash in builder.db.read_contract_iter(c_address): start_tx = tx_builder.get_tx(start_hash) dummy, c_bin, c_cs = bjson.loads(start_tx.message) return c_bin # Memoryより best_chain = best_chain or _get_best_chain_all(best_block) for block in reversed(best_chain): for tx in block.txs: if tx.type == C.TX_CREATE_CONTRACT: dummy, c_bin, c_cs = bjson.loads(tx.message) return c_bin # Unconfirmedより if best_block is None: for tx in sorted(tx_builder.unconfirmed.values(), key=lambda x: x.time): if tx.type == C.TX_CREATE_CONTRACT: dummy, c_bin, c_cs = bjson.loads(tx.message) return c_bin return None
def get_contract_storage(c_address, best_block=None, best_chain=None): # DataBaseより cs = ContractStorage() for dummy, index, start_hash, finish_hash in builder.db.read_contract_iter(c_address): if index == 0: start_tx = tx_builder.get_tx(start_hash) dummy, c_bin, c_cs = bjson.loads(start_tx.message) cs.key_value = c_cs or dict() else: finish_tx = tx_builder.get_tx(finish_hash) c_status, dummy, c_diff = bjson.loads(finish_tx.message) cs.marge(c_diff) # Memoryより best_chain = best_chain or _get_best_chain_all(best_block) for block in reversed(best_chain): for tx in block.txs: if tx.type == C.TX_CREATE_CONTRACT: check_address, c_bin, c_cs = bjson.loads(tx.message) if c_address == check_address: cs.key_value = c_cs or dict() elif tx.type == C.TX_START_CONTRACT: pass elif tx.type == C.TX_FINISH_CONTRACT: c_status, start_hash, c_diff = bjson.loads(tx.message) start_tx = tx_builder.get_tx(start_hash) check_address, c_bin, c_cs = bjson.loads(start_tx.message) if c_address == check_address: cs.marge(c_diff) # Unconfirmedより if best_block is None: for tx in sorted(tx_builder.unconfirmed.values(), key=lambda x: x.time): if tx.type == C.TX_CREATE_CONTRACT: check_address, c_bin, c_cs = bjson.loads(tx.message) cs.key_value = c_cs or dict() elif tx.type == C.TX_START_CONTRACT: pass elif tx.type == C.TX_FINISH_CONTRACT: c_status, start_hash, c_diff = bjson.loads(tx.message) start_tx = tx_builder.get_tx(start_hash) check_address, c_bin, c_cs = bjson.loads(start_tx.message) if c_address == check_address: cs.marge(c_diff) return cs
def check_tx_finish_contract(finish_tx, include_block): if finish_tx.message_type != C.MSG_BYTE: raise BlockChainError('message type is bytes.') finish_status, start_hash, finish_diff = bjson.loads(finish_tx.message) # StartTXを探し出す start_tx = get_start_by_finish_tx(finish_tx, start_hash, include_block) # FinishTXとStartTXの整合性チェック if start_tx.gas_price != finish_tx.gas_price: raise BlockChainError( 'StartGasPrice differ from FinishGasPrice. [{}!={}]'.format( start_tx.gas_price, finish_tx.gas_price)) elif finish_tx.gas_amount > 0: raise BlockChainError('Not redeem amount found. [{}>0]'.format( finish_tx.gas_amount)) elif include_block: if include_block.txs.index(start_tx) >= include_block.txs.index( finish_tx): raise BlockChainError( 'start tx index is higher than finish tx. [{}>={}]'.format( include_block.txs.index(start_tx), include_block.txs.index(finish_tx)))
async def get_validator_history(request): try: c_address = request.query['c_address'] data = list() # database for index, new_address, flag, txhash, sig_diff in builder.db.read_validator_iter( c_address=c_address): data.append({ 'index': index, 'height': None, 'new_address': new_address, 'flag': flag, 'txhash': hexlify(txhash).decode(), 'sig_diff': sig_diff }) # memory index = len(data) for block in reversed(builder.best_chain): for tx in block.txs: if tx.type != C.TX_VALIDATOR_EDIT: continue _c_address, new_address, flag, sig_diff = bjson.loads( tx.message) if _c_address != c_address: continue data.append({ 'index': index, 'height': tx.height, 'new_address': new_address, 'flag': flag, 'txhash': hexlify(tx.hash).decode(), 'sig_diff': sig_diff }) index += 1 return web_base.json_res(data) except Exception as e: logging.error(e) return web_base.error_res()
def batch_apply(self, force=False): # 無チェックで挿入するから要注意 if not force and self.cashe_limit > len(self.chain): return list() # cashe許容量を上回っているので記録 self.db.batch_create() logging.debug("Start batch apply. chain={} force={}".format(len(self.chain), force)) best_chain = self.best_chain.copy() batch_count = self.batch_size batched_blocks = list() with closing(create_db(V.DB_ACCOUNT_PATH)) as db: cur = db.cursor() # DO NOT USE FOR WRITE! try: block = None while batch_count > 0 and len(best_chain) > 0: batch_count -= 1 block = best_chain.pop() # 古いものから順に batched_blocks.append(block) self.db.write_block(block) # Block assert len(block.txs) > 0, "found no tx in {}".format(block) for tx in block.txs: self.db.write_tx(tx) # TX # inputs for index, (txhash, txindex) in enumerate(tx.inputs): # DataBase内でのみのUsedIndexを取得 usedindex = self.db.read_usedindex(txhash) if txindex in usedindex: raise BlockBuilderError('Already used index? {}:{}' .format(hexlify(txhash).decode(), txindex)) usedindex.add(txindex) self.db.write_usedindex(txhash, usedindex) # UsedIndex update input_tx = tx_builder.get_tx(txhash) address, coin_id, amount = input_tx.outputs[txindex] if config['full_address_index'] or is_address(ck=address, prefix=V.BLOCK_CONTRACT_PREFIX)\ or read_address2user(address=address, cur=cur): # 必要なAddressのみ self.db.write_address_idx(address, txhash, txindex, coin_id, amount, True) # outputs for index, (address, coin_id, amount) in enumerate(tx.outputs): if config['full_address_index'] or is_address(ck=address, prefix=V.BLOCK_CONTRACT_PREFIX) \ or read_address2user(address=address, cur=cur): # 必要なAddressのみ self.db.write_address_idx(address, tx.hash, index, coin_id, amount, False) # TXの種類による追加操作 if tx.type == C.TX_GENESIS: pass elif tx.type == C.TX_TRANSFER: pass elif tx.type == C.TX_POW_REWARD: pass elif tx.type == C.TX_POS_REWARD: pass elif tx.type == C.TX_MINT_COIN: mint_id, params, setting = bjson.loads(tx.message) self.db.write_coins(coin_id=mint_id, txhash=tx.hash, params=params, setting=setting) elif tx.type == C.TX_VALIDATOR_EDIT: c_address, new_address, flag, sig_diff = bjson.loads(tx.message) self.db.write_validator(c_address=c_address, new_address=new_address, flag=flag, tx=tx, sign_diff=sig_diff) elif tx.type == C.TX_CONCLUDE_CONTRACT: c_address, start_hash, c_storage = bjson.loads(tx.message) start_tx = tx_builder.get_tx(txhash=start_hash) dummy, c_method, redeem_address, c_args = bjson.loads(start_tx.message) self.db.write_contract(c_address=c_address, start_tx=start_tx, finish_hash=tx.hash, message=(c_method, c_args, c_storage)) # block挿入終了 self.best_chain = best_chain self.root_block = block self.db.batch_commit() self.save_starter() # root_blockよりHeightの小さいBlockを消す for blockhash, block in self.chain.copy().items(): if self.root_block.height >= block.height: del self.chain[blockhash] logging.debug("Success batch {} blocks, root={}." .format(len(batched_blocks), self.root_block)) # アカウントへ反映↓ user_account.new_batch_apply(batched_blocks) return batched_blocks # [<height=n>, <height=n+1>, .., <height=n+m>] except Exception as e: self.db.batch_rollback() logging.warning("Failed batch block builder. '{}'".format(e), exc_info=True) return list()
def check_tx_validator_edit(tx: TX, include_block: Block): # common check if not (len(tx.inputs) > 0 and len(tx.inputs) > 0): raise BlockChainError('No inputs or outputs.') elif tx.message_type != C.MSG_BYTE: raise BlockChainError('validator_edit_tx is bytes msg.') elif V.BLOCK_CONTRACT_PREFIX is None: raise BlockChainError('Not set contract prefix ?') elif V.BLOCK_CONTRACT_PREFIX == V.BLOCK_PREFIX: raise BlockChainError('normal prefix same with contract prefix.') # message try: c_address, new_address, flag, sig_diff = bjson.loads(tx.message) except Exception as e: raise BlockChainError('BjsonError: {}'.format(e)) # inputs/outputs address check for txhash, txindex in tx.inputs: input_tx = tx_builder.get_tx(txhash) if input_tx is None: raise BlockChainError('Not found input tx.') address, coin_id, amount = input_tx.outputs[txindex] if address != c_address: raise BlockChainError('1 Not contract address. {}'.format(address)) for address, coin_id, amount in tx.outputs: if address != c_address: raise BlockChainError('2 Not contract address. {}'.format(address)) # check new_address v_before = get_validator_object(c_address=c_address, best_block=include_block, stop_txhash=tx.hash) if new_address: if not is_address(ck=new_address, prefix=V.BLOCK_PREFIX): raise BlockChainError('new_address is normal prefix.') elif flag == F_NOP: raise BlockChainError('input new_address, but NOP.') if v_before.index == -1: # create validator for the first time if new_address is None: raise BlockChainError('Not setup new_address.') elif flag != F_ADD: raise BlockChainError('Need to add new_address flag.') elif sig_diff != 1: raise BlockChainError('sig_diff is 1.') else: # edit already created validator next_validator_num = len(v_before.validators) # Note: Add/Remove after next_require_num = v_before.require + sig_diff if flag == F_ADD: if new_address is None: raise BlockChainError('Not setup new_address.') elif new_address in v_before.validators: raise BlockChainError('Already included new_address.') next_validator_num += 1 elif flag == F_REMOVE: if new_address is None: raise BlockChainError('Not setup new_address.') elif new_address not in v_before.validators: raise BlockChainError('Not include new_address.') elif len(v_before.validators) < 2: raise BlockChainError('validator is now only {}.'.format(len(v_before.validators))) next_validator_num -= 1 elif flag == F_NOP: if new_address is not None: raise BlockChainError('Input new_address?') else: raise BlockChainError('unknown flag {}.'.format(flag)) # sig_diff check if not (0 < next_require_num <= next_validator_num): raise BlockChainError('sig_diff check failed, 0 < {} <= {}.' .format(next_require_num, next_validator_num)) contract_required_gas_check(tx=tx, v=v_before, extra_gas=C.VALIDATOR_EDIT_GAS) contract_signature_check(extra_tx=tx, v=v_before, include_block=include_block)
def check_tx_contract_conclude(tx: TX, include_block: Block): # common check if not (len(tx.inputs) > 0 and len(tx.inputs) > 0): raise BlockChainError('No inputs or outputs.') elif tx.message_type != C.MSG_BYTE: raise BlockChainError('validator_edit_tx is bytes msg.') elif V.BLOCK_CONTRACT_PREFIX is None: raise BlockChainError('Not set contract prefix ?') elif V.BLOCK_CONTRACT_PREFIX == V.BLOCK_PREFIX: raise BlockChainError('normal prefix same with contract prefix.') try: c_address, start_hash, c_storage = bjson.loads(tx.message) except Exception as e: raise BlockChainError('BjsonError: {}'.format(e)) if not (isinstance(c_address, str) and len(c_address) == 40): raise BlockChainError('1. Not correct format. {}'.format(c_address)) if not (isinstance(start_hash, bytes) and len(start_hash) == 32): raise BlockChainError('2. Not correct format. {}'.format(start_hash)) if not (c_storage is None or isinstance(c_storage, dict)): raise BlockChainError('3. Not correct format. {}'.format(c_storage)) # check already created conclude tx check_finish_hash = get_conclude_hash_by_start_hash( c_address=c_address, start_hash=start_hash, best_block=include_block, stop_txhash=tx.hash) if check_finish_hash is not None: raise BlockChainError('Already start_hash used. {}'.format(hexlify(check_finish_hash).decode())) # inputs address check for txhash, txindex in tx.inputs: input_tx = tx_builder.get_tx(txhash) if input_tx is None: raise BlockChainError('Not found input tx.') address, coin_id, amount = input_tx.outputs[txindex] if address != c_address: raise BlockChainError('Not contract address. {}'.format(address)) # validator check v = get_validator_object(c_address=c_address, best_block=include_block, stop_txhash=tx.hash) if v.require == 0: raise BlockChainError('At least 1 validator required. {}'.format(v.require)) # check start tx start_tx = tx_builder.get_tx(txhash=start_hash) if start_tx is None: raise BlockChainError('Not found start tx. {}'.format(hexlify(start_hash).decode())) if start_tx.height is None: raise BlockChainError('Start tx is unconfirmed. {}'.format(start_tx)) if start_tx.type != C.TX_TRANSFER: raise BlockChainError('Start tx is TRANSFER, not {}.'.format(C.txtype2name.get(start_tx.type, None))) if start_tx.message_type != C.MSG_BYTE: raise BlockChainError('Start tx is MSG_BYTE, not {}.'.format(C.msg_type2name.get(start_tx.message_type, None))) if start_tx.time != tx.time or start_tx.deadline != tx.deadline: raise BlockChainError('time of conclude_tx and start_tx is same, {}!={}.'.format(start_tx.time, tx.time)) try: c_start_address, c_method, c_args = bjson.loads(start_tx.message) except Exception as e: raise BlockChainError('BjsonError: {}'.format(e)) if c_address != c_start_address: raise BlockChainError('Start tx\'s contract address is different. {}!={}'.format(c_address, c_start_address)) if not isinstance(c_method, str): raise BlockChainError('c_method is string. {}'.format(c_method)) if not (isinstance(c_args, tuple) or isinstance(c_args, list)): raise BlockChainError('4. Not correct format. {}'.format(c_args)) # contract check c_before = get_contract_object(c_address=c_address, best_block=include_block, stop_txhash=tx.hash) if c_method == M_INIT: if len(c_args) != 3: raise BlockChainError('c_args is 3 items.') if c_before.index != -1: raise BlockChainError('Already created contract. {}'.format(c_before.index)) c_bin, c_extra_imports, c_settings = c_args if not isinstance(c_bin, bytes): raise BlockChainError('5. Not correct format. {}'.format(c_args)) if not (c_extra_imports is None or isinstance(c_extra_imports, tuple) or isinstance(c_extra_imports, list)): raise BlockChainError('6. Not correct format. {}'.format(c_extra_imports)) if not (c_settings is None or isinstance(c_settings, dict)): raise BlockChainError('7. Not correct format. {}'.format(c_settings)) elif c_method == M_UPDATE: if len(c_args) != 3: raise BlockChainError('c_args is 3 items.') if c_before.index == -1: raise BlockChainError('Not created contract.') c_bin, c_extra_imports, c_settings = c_args if not (c_bin is None or isinstance(c_bin, bytes)): raise BlockChainError('8. Not correct format. {}'.format(c_args)) if not (c_extra_imports is None or isinstance(c_extra_imports, tuple)): raise BlockChainError('9. Not correct format. {}'.format(c_extra_imports)) if not (c_settings is None or isinstance(c_settings, dict)): raise BlockChainError('10. Not correct format. {}'.format(c_settings)) if not (c_bin or c_extra_imports or c_settings): raise BlockChainError('No change found. {}, {}, {}'.format(c_bin, c_extra_imports, c_settings)) else: pass # user oriented data contract_required_gas_check(tx=tx, v=v, extra_gas=0) contract_signature_check(extra_tx=tx, v=v, include_block=include_block)
def decode(b): # transfer: [c_address]-[c_method]-[redeem_address]-[c_args] # conclude: [c_address]-[start_hash]-[c_storage] return bjson.loads(b)
def try_emulate(start_tx, gas_limit=None, out=None): start_time = time.time() cxt = get_context('spawn') que = cxt.Queue() # out = out or io.StringIO() c_address, c_method, c_args, c_redeem = bjson.loads(start_tx.message) c_bin = get_contract_binary(c_address) assert c_bin, 'Not found c_bin of {}'.format(c_address) params = { 'sub_dir': V.SUB_DIR, 'genesis_block': builder.get_block(builder.get_block_hash(0)), 'c_bin': c_bin, 'c_address': c_address, 'c_method': c_method, 'args': c_args} p = cxt.Process(target=_work, args=(params, start_tx, que)) p.start() que_cmd, port = que.get(timeout=10) if que_cmd != CMD_PORT: raise TypeError('Not correct command="{}" data="{}"'.format(que_cmd, port)) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(("127.0.0.1", port)) sock.settimeout(10) # Start emulation line = fee = 0 cmd = EMU_STEP error = None logging.debug("Start contract emulate port={}".format(port)) while True: try: msg_list = sock.recv(8192).decode(errors='ignore').replace("\r", "").split("\n") if len(msg_list) <= 1: sock.close() break elif len(msg_list) < 3: pass elif gas_limit and fee > gas_limit: error = 'reached gas limit. [{}>{}]'.format(fee, gas_limit) break elif cmd in (EMU_STEP, EMU_NEXT, EMU_UNTIL, EMU_RETURN): msg_list, path, words = msg_list[:-3], msg_list[-3][2:], msg_list[-2][3:] file = os.path.split(path)[1] print(file, words, path) if file.startswith('exe.py') and file.endswith('work_field()'): cmd = EMU_STEP # Start! print("start contract {}", file=out) elif file.startswith('contract('): line += 1 fee += 1 cmd = EMU_STEP else: cmd = EMU_NEXT # Add fee for func, gas in __price__.items(): if func in words and words.startswith('def ' + func + '('): fee += gas print("{}:read [{}] {} >> {}".format(line, fee, cmd, words), file=out) else: msg = ', '.join(msg_list) print("msg [{}] >>".format(cmd), msg, file=out) # response to work field sock.send((cmd + "\n").encode()) except ConnectionResetError: break except BaseException: error = str(traceback.format_exc()) break logging.debug("Finish contract {}Sec error:{}".format(round(time.time() - start_time, 3), error)) # Close emulation try: que_cmd, result = que.get_nowait() print(result) try: que.close() except: pass try: p.terminate() except: pass if que_cmd == CMD_ERROR: return False, result, fee, line elif que_cmd == CMD_SUCCESS: return True, result, fee, line except BaseException: return False, error, fee, line
def get_tx_message_data(tx): return bjson.loads(tx.message)[1]