def create_conclude_tx(c_address, start_tx, send_pairs=None, c_storage=None): assert isinstance(start_tx, TX) assert send_pairs is None or isinstance(send_pairs, list) assert c_storage is None or isinstance(c_storage, dict) message = bjson.dumps((c_address, start_tx.hash, c_storage), compress=False) v = get_validator_object(c_address=c_address) send_pairs = send_pairs or list() tx = TX( tx={ 'type': C.TX_CONCLUDE_CONTRACT, 'time': start_tx.time, 'deadline': start_tx.deadline, 'gas_price': start_tx.gas_price, 'gas_amount': 0, 'outputs': [tuple(s) for s in send_pairs], 'message_type': C.MSG_BYTE, 'message': message }) extra_gas = C.SIGNATURE_GAS * v.require tx.gas_amount = tx.size + extra_gas # fill unspents fill_contract_inputs_outputs(tx=tx, c_address=c_address, additional_gas=extra_gas) # replace dummy address replace_redeem_dummy_address(tx=tx, replace_by=c_address) tx.serialize() if v.index == -1: raise BlockChainError( 'Not init validator address. {}'.format(c_address)) if setup_contract_signature(tx, v.validators) == 0: raise BlockChainError('Cannot sign, you are not validator.') return tx
def contract_fill(c: Contract, best_block=None, best_chain=None, stop_txhash=None): # database c_iter = builder.db.read_contract_iter(c_address=c.c_address, start_idx=c.db_index) for index, start_hash, finish_hash, (c_method, c_args, c_storage) in c_iter: if start_hash == stop_txhash or finish_hash == stop_txhash: return c.update(db_index=index, start_hash=start_hash, finish_hash=finish_hash, c_method=c_method, c_args=c_args, c_storage=c_storage) # memory if best_chain: _best_chain = None elif best_block and best_block == builder.best_block: _best_chain = builder.best_chain else: dummy, _best_chain = builder.get_best_chain(best_block=best_block) for block in reversed(best_chain or _best_chain): for tx in block.txs: if tx.hash == stop_txhash: return if tx.type != C.TX_CONCLUDE_CONTRACT: continue c_address, start_hash, c_storage = decode(tx.message) if c_address != c.c_address: continue if start_hash == stop_txhash: return start_tx = tx_builder.get_tx(txhash=start_hash) dummy, c_method, redeem_address, c_args = decode(start_tx.message) index = start_tx2index(start_tx=start_tx) c.update(db_index=index, start_hash=start_hash, finish_hash=tx.hash, c_method=c_method, c_args=c_args, c_storage=c_storage) # unconfirmed (check validator condition satisfied) if best_block is None: unconfirmed = list() for conclude_tx in tuple(tx_builder.unconfirmed.values()): if conclude_tx.hash == stop_txhash: break if conclude_tx.type != C.TX_CONCLUDE_CONTRACT: continue c_address, start_hash, c_storage = decode(conclude_tx.message) if c_address != c.c_address: continue if start_hash == stop_txhash: break start_tx = tx_builder.get_tx(txhash=start_hash) if start_tx.height is None: continue sort_key = start_tx2index(start_tx=start_tx) unconfirmed.append((c_address, start_tx, conclude_tx, c_storage, sort_key)) v = get_validator_object(c_address=c.c_address, best_block=best_block, best_chain=best_chain, stop_txhash=stop_txhash) for c_address, start_tx, conclude_tx, c_storage, sort_key in sorted(unconfirmed, key=lambda x: x[4]): if len(conclude_tx.signature) < v.require: continue # ignore unsatisfied ConcludeTXs dummy, c_method, redeem_address, c_args = decode(start_tx.message) c.update(db_index=sort_key, start_hash=start_tx.hash, finish_hash=conclude_tx.hash, c_method=c_method, c_args=c_args, c_storage=c_storage)
async def validator_info(request): try: c_address = request.query['c_address'] f_confirmed = bool(request.query.get('confirmed', False)) best_block = builder.best_block if f_confirmed else None v = get_validator_object(c_address=c_address, best_block=best_block) return web_base.json_res(v.info) except Exception as e: logging.error(e) return web_base.error_res()
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 get_validator_by_contract_info(c_address, start_tx=None, start_hash=None, best_block=None, best_chain=None, stop_txhash=None): c = get_contract_object(c_address=c_address, best_block=best_block, best_chain=best_chain, stop_txhash=stop_txhash) if c.version > -1: v = get_validator_object(v_address=c.v_address, best_block=best_block, best_chain=best_chain, stop_txhash=stop_txhash) if v.version > -1: return v else: raise BlockChainError('ValidatorTX is not init. {}'.format( v.v_address)) elif start_tx or start_hash: if start_tx is None: start_tx = tx_builder.get_tx(txhash=start_hash) raw_args = start_tx.encoded_message() if len(raw_args) != 4: raise BlockChainError('Not correct args count {}'.format(raw_args)) c_address, c_method, redeem_address, c_args = raw_args if c_method != M_INIT: raise BlockChainError('StartTX method is not INIT') if len(c_args) != 4: raise BlockChainError('Not correct c_args count {}'.format(c_args)) c_bin, v_address, c_extra_imports, c_settings = c_args return get_validator_object(v_address=v_address, best_block=best_block, best_chain=best_chain, stop_txhash=stop_txhash) else: raise BlockChainError('ContractTX is not init. {}'.format(c_address))
def create_validator_edit_tx(v_address, cur, new_address=None, flag=F_NOP, sig_diff=0, gas_price=None, retention=10800): assert not (flag == F_NOP and sig_diff == 0), 'No edit info' if new_address is None and flag != F_NOP: raise BlockChainError('No cosigner edit, but flag is not NOP') # validator object v = get_validator_object(v_address=v_address) if v.version == -1: if new_address is None or flag != F_ADD or sig_diff != 1: raise BlockChainError('Not correct info') else: next_require = v.require + sig_diff next_validator_num = len(v.validators) if flag == F_ADD: next_validator_num += 1 elif flag == F_REMOVE: next_validator_num -= 1 if not (0 < next_require <= next_validator_num): raise BlockChainError('ReqError, 0 < {} <= {}'.format( next_require, next_validator_num)) # tx create message = msgpack.packb((v_address, new_address, flag, sig_diff), use_bin_type=True) tx = TX.from_dict( tx={ 'type': C.TX_VALIDATOR_EDIT, 'gas_price': gas_price or V.COIN_MINIMUM_PRICE, 'gas_amount': 0, 'message_type': C.MSG_MSGPACK, 'message': message }) tx.gas_amount = tx.size tx.update_time(retention) # fill unspents additional_gas = C.VALIDATOR_EDIT_GAS + v.require * C.SIGNATURE_GAS input_address = fill_inputs_outputs(tx=tx, cur=cur, additional_gas=additional_gas) assert len(input_address & set(v.validators)) == 0, 'Not implemented?' # replace dummy address replace_redeem_dummy_address(tx=tx, cur=cur) setup_signature(tx, input_address) if v.version > -1 and setup_contract_signature(tx, v.validators) == 0: raise BlockChainError('Cannot sign, you are not validator') return tx
def create_validator_edit_tx(c_address, new_address=None, flag=F_NOP, sig_diff=0, gas_price=None, retention=10800): assert not (flag == F_NOP and sig_diff == 0), 'No edit info.' if new_address is None and flag != F_NOP: raise BlockChainError('No cosigner edit, but flag is not NOP.') # validator object v = get_validator_object(c_address=c_address) if v.version == -1: if new_address is None or flag != F_ADD or sig_diff != 1: raise BlockChainError('Not correct info.') else: next_require = v.require + sig_diff next_validator_num = len(v.validators) if flag == F_ADD: next_validator_num += 1 elif flag == F_REMOVE: next_validator_num -= 1 if not (0 < next_require <= next_validator_num): raise BlockChainError('ReqError, 0 < {} <= {}'.format( next_require, next_validator_num)) # tx create message = bjson.dumps((c_address, new_address, flag, sig_diff), compress=False) tx = TX( tx={ 'type': C.TX_VALIDATOR_EDIT, 'gas_price': gas_price or V.COIN_MINIMUM_PRICE, 'gas_amount': 0, 'message_type': C.MSG_BYTE, 'message': message }) extra_gas = C.VALIDATOR_EDIT_GAS + C.SIGNATURE_GAS * v.require tx.gas_amount = tx.size + extra_gas tx.update_time(retention) # fill unspents fill_contract_inputs_outputs(tx=tx, c_address=c_address, additional_gas=extra_gas) # replace dummy address replace_redeem_dummy_address(tx=tx, replace_by=c_address) tx.serialize() if len(v.validators) > 0 and setup_contract_signature(tx, v.validators) == 0: raise BlockChainError('Cannot sign, you are not validator.') return tx
async def validator_info(request): try: c_address = request.query['c_address'] f_confirmed = bool(request.query.get('confirmed', False)) stop_hash = request.query.get('stophash', None) if stop_hash: stop_hash = a2b_hex(stop_hash) best_block = builder.best_block if f_confirmed else None v = get_validator_object(c_address=c_address, best_block=best_block, stop_txhash=stop_hash) return web_base.json_res(v.info) except Exception as e: logging.error(e) return web_base.error_res()
def create_signed_tx_as_validator(tx: TX): tx = deepcopy(tx) if tx.type == C.TX_VALIDATOR_EDIT: v_address, new_address, flag, sig_diff = tx.encoded_message() v = get_validator_object(v_address=v_address, stop_txhash=tx.hash) elif tx.type == C.TX_CONCLUDE_CONTRACT: c_address, start_hash, c_storage = tx.encoded_message() v = get_validator_by_contract_info(c_address=c_address, start_hash=start_hash) else: raise BlockChainError('Not found tx type {}'.format(tx)) # sign and check how many add signs if setup_contract_signature(tx, v.validators) == 0: raise BlockChainError( 'Cannot sign, you are not validator or already signed') return tx
def signature_check(tx, include_block): require_cks = set() checked_cks = set() signed_cks = set(tx.verified_list) for txhash, txindex in tx.inputs: input_tx = tx_builder.get_tx(txhash) if input_tx is None: raise BlockChainError('Not found input tx {}'.format(txhash.hex())) if len(input_tx.outputs) <= txindex: raise BlockChainError('txindex is over range {}<={}'.format( len(input_tx.outputs), txindex)) address, coin_id, amount = input_tx.outputs[txindex] if address in checked_cks: continue elif is_address(ck=address, hrp=V.BECH32_HRP, ver=C.ADDR_NORMAL_VER): require_cks.add(address) elif is_address(ck=address, hrp=V.BECH32_HRP, ver=C.ADDR_VALIDATOR_VER): v_before = get_validator_object(v_address=address, best_block=include_block, stop_txhash=tx.hash) if v_before.version == -1: raise BlockChainError('Not init validator {}'.format(address)) if len(signed_cks & v_before.validators) < v_before.require: raise BlockChainError( 'Don\'t satisfy required signature {}<{}'.format( len(signed_cks & v_before.validators), v_before.require)) require_cks.update(v_before.validators) elif is_address(ck=address, hrp=V.BECH32_HRP, ver=C.ADDR_CONTRACT_VER): raise BlockChainError( 'Not allow ContractAddress include in normal Transfer. {}'. format(address, tx)) else: raise BlockChainError('Not common address {} {}'.format( address, tx)) # success check checked_cks.add(address) if not (0 < len(require_cks) < 256): raise BlockChainError('require signature is over range num={}'.format( len(require_cks))) if require_cks != signed_cks: raise BlockChainError('Signature verification failed. [{}={}]'.format( require_cks, signed_cks))
def create_conclude_tx(c_address, start_tx, redeem_address, send_pairs=None, c_storage=None, emulate_gas=0): assert isinstance(start_tx, TX) assert send_pairs is None or isinstance(send_pairs, list) assert c_storage is None or isinstance(c_storage, dict) assert isinstance(emulate_gas, int) message = bjson.dumps((c_address, start_tx.hash, c_storage), compress=False) v = get_validator_object(c_address=c_address) send_pairs = send_pairs or list() tx = TX( tx={ 'type': C.TX_CONCLUDE_CONTRACT, 'time': start_tx.time, 'deadline': start_tx.deadline, 'gas_price': start_tx.gas_price, 'gas_amount': 0, 'outputs': [tuple(s) for s in send_pairs], 'message_type': C.MSG_BYTE, 'message': message }) extra_gas = C.SIGNATURE_GAS * v.require tx.gas_amount = tx.size + extra_gas # fill unspents fill_contract_inputs_outputs(tx=tx, c_address=c_address, additional_gas=extra_gas) # replace dummy address replace_redeem_dummy_address(tx=tx, replace_by=c_address) # fix redeem fees if send_pairs: # conclude_txで使用したGasを、ユーザーから引いてコントラクトに戻す処理 conclude_fee = (emulate_gas + tx.gas_amount) * tx.gas_price fee_coin_id = 0 f_finish_add = f_finish_sub = False for index, (address, coin_id, amount) in enumerate(tx.outputs): if coin_id != fee_coin_id: continue elif not f_finish_add and address == c_address: f_finish_add = True tx.outputs[index] = (address, coin_id, amount + conclude_fee) elif not f_finish_sub and address == redeem_address: f_finish_sub = True tx.outputs[index] = (address, coin_id, amount - conclude_fee) else: pass if not (f_finish_add and f_finish_sub): raise BlockChainError( 'Cannot move conclude fee, add={} sub={}'.format( f_finish_add, f_finish_sub)) logging.debug("Move conclude fee {}:{}".format(fee_coin_id, conclude_fee)) tx.serialize() if v.version == -1: raise BlockChainError( 'Not init validator address. {}'.format(c_address)) if setup_contract_signature(tx, v.validators) == 0: raise BlockChainError('Cannot sign, you are not validator.') return tx
def _update_unconfirmed_info(): with unconfirmed_lock: s = time() # sort unconfirmed txs unconfirmed_txs = sorted(tx_builder.unconfirmed.values(), key=lambda x: x.time) unconfirmed_txs = sorted(unconfirmed_txs, key=lambda x: x.gas_price, reverse=True) # reject tx (input tx is unconfirmed) limit_height = builder.best_block.height - C.MATURE_HEIGHT best_block, best_chain = builder.get_best_chain() used_pairs = set() for tx in unconfirmed_txs.copy(): if tx.height is not None: if tx.hash in tx_builder.unconfirmed: del tx_builder.unconfirmed[tx.hash] unconfirmed_txs.remove(tx) continue if Debug.F_STICKY_TX_REJECTION and tx.hash in sticky_failed_txhash: unconfirmed_txs.remove(tx) continue # inputs check for txhash, txindex in tx.inputs: input_tx = tx_builder.get_tx(txhash) if input_tx is None: unconfirmed_txs.remove(tx) break elif input_tx.height is None: unconfirmed_txs.remove(tx) break elif input_tx.type in (C.TX_POS_REWARD, C.TX_POW_REWARD) and \ input_tx.height > limit_height: unconfirmed_txs.remove(tx) break elif is_usedindex(txhash, txindex, tx.hash, best_block, best_chain): unconfirmed_txs.remove(tx) break # check inputs used same unconfirmed_txs input_pair = (txhash, txindex) if input_pair in used_pairs: unconfirmed_txs.remove(tx) break used_pairs.add(input_pair) # contract tx for tx in unconfirmed_txs.copy(): if tx.type == C.TX_CONCLUDE_CONTRACT: try: c_address, start_hash, c_storage = bjson.loads(tx.message) except Exception: unconfirmed_txs.remove(tx) # failed decode bjson continue start_tx = tx_builder.get_tx(txhash=start_hash) if start_tx is None or start_tx.height is None: unconfirmed_txs.remove(tx) # start tx is confirmed continue v = get_validator_object(c_address=c_address, best_block=best_block, best_chain=best_chain) signed_cks = get_signed_cks(tx) accept_cks = signed_cks & set(v.validators) if v.require > len(accept_cks): unconfirmed_txs.remove(tx) continue elif tx.type == C.TX_VALIDATOR_EDIT: try: c_address, address, flag, sig_diff = bjson.loads( tx.message) except Exception: unconfirmed_txs.remove(tx) # failed decode bjson continue v = get_validator_object(c_address=c_address, best_block=best_block, best_chain=best_chain) signed_cks = get_signed_cks(tx) accept_cks = signed_cks & set(v.validators) if v.require > len(accept_cks): unconfirmed_txs.remove(tx) continue else: pass # limit per tx's in block if Debug.F_LIMIT_INCLUDE_TX_IN_BLOCK: unconfirmed_txs = unconfirmed_txs[:Debug. F_LIMIT_INCLUDE_TX_IN_BLOCK] update_unconfirmed_txs(unconfirmed_txs) logging.debug("Update unconfirmed={}/{} {}Sec".format( len(unconfirmed_txs), len(tx_builder.unconfirmed), round(time() - s, 3)))