def amount_check(tx, payfee_coin_id): # Inputs input_coins = CoinObject() 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( hexlify(txhash).decode())) address, coin_id, amount = input_tx.outputs[txindex] input_coins[coin_id] += amount # Outputs output_coins = CoinObject() for address, coin_id, amount in tx.outputs: if amount <= 0: raise BlockChainError('Input amount is more than 0') output_coins[coin_id] += amount # Fee fee_coins = CoinObject(coin_id=payfee_coin_id, amount=tx.gas_price * tx.gas_amount) # Check all plus amount remain_amount = input_coins - output_coins - fee_coins if not remain_amount.is_all_plus_amount(): raise BlockChainError( 'There are minus amount coins. {}={}-{}-{}'.format( remain_amount, input_coins, output_coins, fee_coins))
def start_contract_tx(c_address, c_method, cur, c_args=None, outputs=None, sender=C.ANT_UNKNOWN, gas_price=None, additional_gas_amount=None, retention=10800): # TXを作成 now = int(time.time()) - V.BLOCK_GENESIS_TIME c_redeem = create_new_user_keypair(C.ANT_NAME_UNKNOWN, cur) message = bjson.dumps((c_address, c_method, c_args or tuple(), c_redeem), compress=False) tx = TX( tx={ 'version': __chain_version__, 'type': C.TX_START_CONTRACT, 'time': now, 'deadline': now + retention, 'inputs': list(), 'outputs': outputs or list(), 'gas_price': gas_price or V.COIN_MINIMUM_PRICE, 'gas_amount': 1, 'message_type': C.MSG_BYTE, 'message': message }) check_output_format(tx.outputs) tx.gas_amount = tx.getsize() + 96 tx.serialize() # fill unspents fee_coin_id = 0 input_address = fill_inputs_outputs( tx, cur, fee_coin_id, additional_gas_amount or V.CONTRACT_MINIMUM_AMOUNT) fee_coins = CoinObject(fee_coin_id, tx.gas_price * tx.gas_amount) movements = UserCoins() movements[sender] -= fee_coins movements[C.ANT_OUTSIDE] += fee_coins # account check send_coins = CoinObject() check_enough_amount(sender, send_coins, fee_coins) if sender in (C.ANT_OUTSIDE, C.ANT_RESERVED): raise BlockChainError('Not allowed inner account.') # replace dummy address replace_redeem_dummy_address(tx, cur) # setup signature tx.serialize() setup_signature(tx, input_address) movements[sender] -= fee_coins movements[C.ANT_OUTSIDE] += fee_coins insert_log(movements, cur, tx.type, tx.time, tx.hash) return tx
def create_contract_tx(c_bin, cur, sender=C.ANT_UNKNOWN, c_cs=None, gas_price=None, retention=10800): assert isinstance(c_bin, bytes), 'contract is bytes code.' assert isinstance(sender, int), 'Sender is id.' if c_cs: for k, v in c_cs.items(): assert isinstance(k, bytes), 'Key is bytes.' assert isinstance(v, bytes), 'Value is bytes.' # TXを作成 now = int(time.time()) - V.BLOCK_GENESIS_TIME ck = create_new_user_keypair(C.ANT_NAME_CONTRACT, cur) c_address = convert_address(ck, V.BLOCK_CONTRACT_PREFIX) message = bjson.dumps((c_address, c_bin, c_cs), compress=False) tx = TX( tx={ 'version': __chain_version__, 'type': C.TX_CREATE_CONTRACT, 'time': now, 'deadline': now + retention, 'inputs': list(), 'outputs': list(), 'gas_price': gas_price or V.COIN_MINIMUM_PRICE, 'gas_amount': 1, 'message_type': C.MSG_BYTE, 'message': message }) tx.gas_amount = tx.getsize() + C.CONTRACT_CREATE_FEE + 96 # fill unspents fee_coin_id = 0 input_address = fill_inputs_outputs(tx, cur, fee_coin_id, C.CONTRACT_CREATE_FEE) fee_coins = CoinObject(fee_coin_id, tx.gas_price * tx.gas_amount) movements = UserCoins() movements[sender] -= fee_coins movements[C.ANT_OUTSIDE] += fee_coins # account check send_coins = CoinObject() check_enough_amount(sender, send_coins, fee_coins) if sender in (C.ANT_OUTSIDE, C.ANT_RESERVED): raise BlockChainError('Not allowed inner account.') # replace dummy address replace_redeem_dummy_address(tx, cur) # setup signature tx.serialize() setup_signature(tx, input_address) movements[sender] -= fee_coins movements[C.ANT_OUTSIDE] += fee_coins insert_log(movements, cur, tx.type, tx.time, tx.hash) return c_address, tx
def input_output_digest(tx): require_cks = set() coins = CoinObject() for txhash, txindex in tx.inputs: input_tx = tx_builder.get_tx(txhash=txhash) if input_tx is None: raise BlockChainError('input tx is None. {}:{}'.format( hexlify(txhash).decode(), txindex)) address, coin_id, amount = input_tx.outputs[txindex] require_cks.add(address) coins[coin_id] += amount coins[0] -= tx.gas_amount * tx.gas_price for address, coin_id, amount in tx.outputs: coins[coin_id] -= amount return require_cks, coins
async def move_many(request): try: post = await web_base.content_type_json_check(request) ant_from = post.get('from', C.ANT_NAME_UNKNOWN) ant_to = post['to'] coins = CoinObject() for k, v in post['coins'].items(): coins[int(k)] += int(v) with closing(create_db(V.DB_ACCOUNT_PATH)) as db: cur = db.cursor() _from = read_name2user(ant_from, cur) _to = read_name2user(ant_to, cur) txhash = user_account.move_balance(_from, _to, coins, cur) db.commit() return web_base.json_res({ 'txhash': hexlify(txhash).decode(), 'from_id': _from, 'to_id': _to}) except Exception as e: return web.Response(text=str(e), status=400)
async def send_from_user(request): start = time() if P.F_NOW_BOOTING: return web.Response(text='Now booting...', status=403) post = await web_base.content_type_json_check(request) with closing(create_db(V.DB_ACCOUNT_PATH)) as db: cur = db.cursor() try: from_name = post.get('from', C.ANT_NAME_UNKNOWN) from_id = read_name2user(from_name, cur) to_address = post['address'] coin_id = int(post.get('coin_id', 0)) amount = int(post['amount']) coins = CoinObject(coin_id, amount) message = post.get('message', None) if message: msg_type = C.MSG_PLAIN msg_body = message.encode() else: msg_type = C.MSG_NONE msg_body = b'' new_tx = send_from(from_id, to_address, coins, cur, msg_type=msg_type, msg_body=msg_body) if not send_newtx(new_tx=new_tx, outer_cur=cur): raise BaseException('Failed to send new tx.') db.commit() return web_base.json_res({ 'hash': hexlify(new_tx.hash).decode(), 'gas_amount': new_tx.gas_amount, 'gas_price': new_tx.gas_price, 'fee': new_tx.gas_amount * new_tx.gas_price, 'time': round(time() - start, 3) }) except Exception as e: db.rollback() return web_base.error_res()
def change_mintcoin(mint_id, cur, amount=0, message=None, additional_issue=None, image=None, sender=C.ANT_UNKNOWN): mint_old = get_mintcoin(mint_id) assert mint_old, 'Not defined MintCoin {}'.format(mint_id) mint_new = MintCoinObject(None) mint_new.version = mint_old.version + 1 mint_new.coin_id = mint_id mint_new.amount = amount mint_new.additional_issue = additional_issue mint_address = get_address(mint_old.owner, prefix=V.BLOCK_PREFIX) uuid, sk, pk = read_address2keypair(mint_address, cur) mint_new.owner = pk mint_new.image = image mint_new.message = message # マージチェック mint_new.marge(mint_old) # Message内署名 mint_new.generate_sign(sk) mint_new.serialize() mint_new.check_param() mint_new.check_sign() logging.info("New Mintcoin skeleton created coin_id={}".format( mint_new.coin_id)) # movement movements = UserCoins() minting_coins = CoinObject(mint_id, amount) movements[sender] += minting_coins movements[C.ANT_OUTSIDE] -= minting_coins # TXを作成する base_coin_id = 0 now = int(time.time()) - V.BLOCK_GENESIS_TIME tx = TX( tx={ 'version': __chain_version__, 'type': C.TX_MINT_COIN, 'time': now, 'deadline': now + 10800, 'inputs': list(), 'outputs': [(MINTCOIN_DUMMY_ADDRESS, base_coin_id, amount)] if 0 < amount else list(), 'gas_price': V.COIN_MINIMUM_PRICE, 'gas_amount': 1, 'message_type': C.MSG_BYTE, 'message': mint_new.binary }) tx.gas_amount = tx.getsize() + 96 + C.MINTCOIN_FEE tx.serialize() fee_coin_id = 0 input_address = fill_inputs_outputs(tx, cur, fee_coin_id, C.MINTCOIN_FEE) fee_coins = CoinObject(fee_coin_id, tx.gas_price * tx.gas_amount) # check amount check_enough_amount(sender, CoinObject(base_coin_id, amount), fee_coins) # replace dummy address replace_redeem_dummy_address(tx, cur) # replace dummy mint_id if amount > 0: replace_mint_dummy_address(tx, mint_address, mint_id) # setup signature tx.serialize() setup_signature(tx, input_address) movements[sender] -= fee_coins movements[C.ANT_OUTSIDE] += fee_coins insert_log(movements, cur, tx.type, tx.time, tx.hash) return mint_new, tx
def issue_mintcoin(name, unit, amount, digit, cur, gas_price=None, message='', additional_issue=True, image=None, sender=C.ANT_UNKNOWN): mint = MintCoinObject(None) new_mint_id = get_new_coin_id() mint.version = 0 mint.coin_id = new_mint_id mint.name = name mint.unit = unit mint.digit = digit mint.supply_before = 0 mint.amount = amount mint.additional_issue = additional_issue new_mint_address = create_new_user_keypair(C.ANT_NAME_UNKNOWN, cur) uuid, sk, pk = read_address2keypair(new_mint_address, cur) mint.owner = pk mint.image = image mint.message = message # Message内署名 mint.generate_sign(sk) mint.serialize() mint.check_param() mint.check_sign() logging.info("New Mintcoin skeleton created coin_id={}".format( mint.coin_id)) # movement movements = UserCoins() minting_coins = CoinObject(new_mint_id, amount) movements[sender] += minting_coins movements[C.ANT_OUTSIDE] -= minting_coins # TXを作成する base_coin_id = 0 now = int(time.time()) - V.BLOCK_GENESIS_TIME tx = TX( tx={ 'version': __chain_version__, 'type': C.TX_MINT_COIN, 'time': now, 'deadline': now + 10800, 'inputs': list(), 'outputs': [(MINTCOIN_DUMMY_ADDRESS, base_coin_id, amount)], 'gas_price': gas_price or V.COIN_MINIMUM_PRICE, 'gas_amount': 1, 'message_type': C.MSG_BYTE, 'message': mint.binary }) tx.gas_amount = tx.getsize() + 96 + C.MINTCOIN_FEE tx.serialize() # fill unspents fee_coin_id = 0 input_address = fill_inputs_outputs(tx, cur, fee_coin_id, C.MINTCOIN_FEE) fee_coins = CoinObject(fee_coin_id, tx.gas_price * tx.gas_amount) # check amount check_enough_amount(sender, CoinObject(base_coin_id, amount), fee_coins) # replace dummy address replace_redeem_dummy_address(tx, cur) # replace dummy mint_id replace_mint_dummy_address(tx, new_mint_address, new_mint_id) # setup signature tx.serialize() setup_signature(tx, input_address) movements[sender] -= fee_coins movements[C.ANT_OUTSIDE] += fee_coins insert_log(movements, cur, tx.type, tx.time, tx.hash) return mint, tx
def fill_inputs_outputs(finish_tx, c_address, start_hash, redeem_gas, dust_percent=0.8, utxo_cashe=None): assert finish_tx.gas_price > 0, "Gas params is none zero." # outputsの合計を取得 output_coins = CoinObject() for address, coin_id, amount in finish_tx.outputs.copy(): if address == DUMMY_REDEEM_ADDRESS: # 償還Outputは再構築するので消す finish_tx.outputs.remove((address, coin_id, amount)) continue output_coins[coin_id] += amount # 一時的にfeeの概算 fee_coins = CoinObject(coin_id=0, amount=finish_tx.gas_price * finish_tx.gas_amount) # 必要なだけinputsを取得 finish_tx.inputs.clear() need_coins = output_coins + fee_coins input_coins = CoinObject() f_dust_skipped = False if utxo_cashe is None: utxo_iter = get_utxo_iter({c_address}) utxo_cashe = list() f_put_cashe = True else: utxo_iter = utxo_cashe f_put_cashe = False for dummy, height, txhash, txindex, coin_id, amount in utxo_iter: if f_put_cashe: utxo_cashe.append( (dummy, height, txhash, txindex, coin_id, amount)) if coin_id not in need_coins: continue elif need_coins[coin_id] * dust_percent > amount: f_dust_skipped = True continue need_coins[coin_id] -= amount input_coins[coin_id] += amount finish_tx.inputs.append((txhash, txindex)) if need_coins.is_all_minus_amount(): break else: if f_dust_skipped and dust_percent > 0.00001: new_dust_percent = round(dust_percent * 0.7, 6) logging.debug("Retry by lower dust percent. {}=>{}".format( dust_percent, new_dust_percent)) return fill_inputs_outputs(finish_tx, c_address, start_hash, redeem_gas, new_dust_percent) elif len(finish_tx.inputs) > 255: logging.debug( 'Too many inputs, unspent tx\'s amount is too small.') return False else: # 失敗に変更 logging.debug('Insufficient balance. inputs={} needs={}'.format( input_coins, need_coins)) return False # redeemを計算 redeem_coins = input_coins - output_coins - fee_coins for coin_id, amount in redeem_coins: finish_tx.outputs.append((DUMMY_REDEEM_ADDRESS, coin_id, amount)) # Feeをチェックし再計算するか決める finish_tx.serialize() need_gas_amount = finish_tx.getsize() - redeem_gas if finish_tx.gas_amount == need_gas_amount: return True elif need_gas_amount > 0: # FINISH_TXのみGasは負の値 # Gas使いすぎ,失敗に変更 logging.debug( 'Too match gas used. need_gas={}'.format(need_gas_amount)) return False else: # insufficient gas logging.debug("Retry calculate tx fee. [{}=>{}+{}={}]".format( finish_tx.gas_amount, finish_tx.getsize(), redeem_gas, need_gas_amount)) finish_tx.gas_amount = need_gas_amount return fill_inputs_outputs(finish_tx, c_address, start_hash, redeem_gas, dust_percent, utxo_cashe)
def send_many(sender, send_pairs, cur, fee_coin_id=0, gas_price=None, msg_type=C.MSG_NONE, msg_body=b'', f_balance_check=True, retention=10800): assert isinstance(sender, int), 'Sender is user id.' assert 0 < len(send_pairs), 'Empty send_pairs.' # send_pairs check movements = UserCoins() outputs = list() coins = CoinObject() for address, coin_id, amount in send_pairs: assert isinstance( address, str) and len(address) == 40, 'Recipient is 40 letter string.' assert isinstance(coin_id, int) and isinstance( amount, int), 'CoinID, amount is int.' coins[coin_id] += amount outputs.append((address, coin_id, amount)) movements[sender] -= coins movements[C.ANT_OUTSIDE] += coins # tx now = int(time.time() - V.BLOCK_GENESIS_TIME) tx = TX( tx={ 'version': __chain_version__, 'type': C.TX_TRANSFER, 'time': now, 'deadline': now + retention, 'inputs': list(), 'outputs': outputs, 'gas_price': gas_price or V.COIN_MINIMUM_PRICE, 'gas_amount': 1, 'message_type': msg_type, 'message': msg_body }) tx.gas_amount = tx.size + C.SIGNATURE_GAS # fill unspents input_address = fill_inputs_outputs(tx, cur, fee_coin_id, additional_gas=0) # account check fee_coins = CoinObject(coin_id=fee_coin_id, amount=tx.gas_price * tx.gas_amount) if f_balance_check: # 残高が十分にあるかチェック send_coins = CoinObject() for address, coin_id, amount in send_pairs: send_coins[coin_id] += amount check_enough_amount(sender, send_coins, fee_coins) if sender in (C.ANT_OUTSIDE, C.ANT_RESERVED): # 内部アカウントは不可 raise BlockChainError('Not allowed inner account.') # replace dummy address replace_redeem_dummy_address(tx, cur) # setup signature tx.serialize() setup_signature(tx, input_address) movements[sender] -= fee_coins movements[C.ANT_OUTSIDE] += fee_coins insert_log(movements, cur, tx.type, tx.time, tx.hash) return tx
def check_tx_mint_coin(tx, include_block): if not (0 < len(tx.inputs) and 0 < len(tx.outputs) <= 2): 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.getsize() + C.MINTCOIN_FEE: raise BlockChainError('Insufficient gas amount [{}<{}+{}]'.format( tx.gas_amount, tx.getsize(), C.MINTCOIN_FEE)) coins = CoinObject() for txhash, txindex in tx.inputs: input_tx = tx_builder.get_tx(txhash) if input_tx is None: raise BlockChainError('Not found BaseTX {} of {}'.format( hexlify(txhash).decode(), tx)) address, coin_id, amount = input_tx.outputs[txindex] coins[coin_id] += amount if coin_id != 0: raise BlockChainError( 'TX_MINT_COIN inputs are all coinID 0. {}'.format(coin_id)) for address, coin_id, amount in tx.outputs: coins[coin_id] -= amount payfee_coin_id = 0 coins[payfee_coin_id] -= tx.gas_amount * tx.gas_price if sum(coins.values()) < 0: print(tx.getinfo()) raise BlockChainError('mintcoin amount sum is {}'.format( sum(coins.values()))) mint = MintCoinObject(txhash=tx.hash, binary=tx.message) mint_id = get_mint_id_from_tx(coins) if mint_id: # 追加発行あり if set(coins.keys()) != {0, mint_id}: raise BlockChainError('Allowed two coin_ids [{}!={}]'.format( set(coins.keys()), {0, mint_id})) if mint_id != mint.coin_id: raise BlockChainError('Differ coin_id [{}!={}]'.format( mint_id, mint.coin_id)) elif coins[mint_id] * -1 != mint.amount: raise BlockChainError('Differ amount [{}!={}]'.format( coins[mint_id] * -1, mint.amount)) else: # 追加発行なし if set(coins.keys()) != {0}: raise BlockChainError('Allowed one coin_id [{}!={}]'.format( set(coins.keys()), {0})) if mint.amount != 0: raise BlockChainError('No amount [{}!=0]'.format(mint.amount)) mint_id = mint.coin_id try: # 読み込んでおかしなところがなければOK old_mint = get_mintcoin(mint_id=mint_id, best_block=include_block) if include_block is None: mint.marge(old_mint) except MintCoinError as e: raise BlockChainError('Failed check mint coin "{}"'.format(e))
def change_mintcoin(mint_id, cur, amount=None, description=None, image=None, setting=None, new_address=None, gas_price=None, sender=C.ANT_UNKNOWN, retention=10800): assert amount or description or image or setting or new_address params = dict() if description: params['description'] = description if image: params['image'] = image if new_address: params['address'] = new_address if len(params) == 0: params = None if not params and not setting and not amount: raise BlockChainError('No update found.') m_before = get_mintcoin_object(coin_id=mint_id) if m_before.version == -1: raise BlockChainError('Not init mintcoin. {}'.format(m_before)) result = check_mintcoin_new_format(m_before=m_before, new_params=params, new_setting=setting) if isinstance(result, str): raise BlockChainError('check_mintcoin_new_format(): {}'.format(result)) msg_body = bjson.dumps((mint_id, params, setting), compress=False) tx = TX( tx={ 'type': C.TX_MINT_COIN, 'gas_price': gas_price or V.COIN_MINIMUM_PRICE, 'gas_amount': 1, 'message_type': C.MSG_BYTE, 'message': msg_body }) if amount: tx.outputs.append((MINTCOIN_DUMMY_ADDRESS, 0, amount)) send_coins = CoinObject(0, amount) minting_coins = CoinObject(mint_id, amount) else: send_coins = CoinObject(0, 0) minting_coins = CoinObject(0, 0) tx.update_time(retention) additional_gas = C.MINTCOIN_GAS tx.gas_amount = tx.size + C.SIGNATURE_GAS + additional_gas tx.serialize() # fill unspents fee_coin_id = 0 input_address = fill_inputs_outputs(tx=tx, cur=cur, fee_coin_id=fee_coin_id, additional_gas=additional_gas) input_address.add(m_before.address) fee_coins = CoinObject(coin_id=fee_coin_id, amount=tx.gas_price * tx.gas_amount) # check amount check_enough_amount(sender=sender, send_coins=send_coins, fee_coins=fee_coins) # replace dummy address replace_redeem_dummy_address(tx=tx, cur=cur) # replace dummy mint_id replace_mint_dummy_address(tx=tx, mint_address=m_before.address, mint_id=mint_id, f_raise=False) # setup signature tx.serialize() setup_signature(tx=tx, input_address=input_address) # movement movements = UserCoins() movements[sender] += minting_coins movements[C.ANT_OUTSIDE] -= minting_coins movements[sender] -= fee_coins movements[C.ANT_OUTSIDE] += fee_coins insert_log(movements, cur, tx.type, tx.time, tx.hash) return tx
def issue_mintcoin(name, unit, digit, amount, cur, description=None, image=None, additional_issue=True, change_address=True, gas_price=None, sender=C.ANT_UNKNOWN, retention=10800): mint_id = get_new_coin_id() sender_name = read_user2name(user=sender, cur=cur) mint_address = create_new_user_keypair(name=sender_name, cur=cur) params = { "name": name, "unit": unit, "digit": digit, "address": mint_address, "description": description, "image": image } setting = { "additional_issue": additional_issue, "change_address": change_address } m_before = get_mintcoin_object(coin_id=mint_id) result = check_mintcoin_new_format(m_before=m_before, new_params=params, new_setting=setting) if isinstance(result, str): raise BlockChainError('check_mintcoin_new_format(): {}'.format(result)) msg_body = bjson.dumps((mint_id, params, setting), compress=False) tx = TX( tx={ 'type': C.TX_MINT_COIN, 'inputs': list(), 'outputs': [(MINTCOIN_DUMMY_ADDRESS, 0, amount)], 'gas_price': gas_price or V.COIN_MINIMUM_PRICE, 'gas_amount': 1, 'message_type': C.MSG_BYTE, 'message': msg_body }) tx.update_time(retention) additional_gas = C.MINTCOIN_GAS tx.gas_amount = tx.size + C.SIGNATURE_GAS + additional_gas tx.serialize() # fill unspents fee_coin_id = 0 input_address = fill_inputs_outputs(tx=tx, cur=cur, fee_coin_id=fee_coin_id, additional_gas=additional_gas) # input_address.add(mint_address) fee_coins = CoinObject(coin_id=fee_coin_id, amount=tx.gas_price * tx.gas_amount) # check amount check_enough_amount(sender=sender, send_coins=CoinObject(0, amount), fee_coins=fee_coins) # replace dummy address replace_redeem_dummy_address(tx=tx, cur=cur) # replace dummy mint_id replace_mint_dummy_address(tx=tx, mint_address=mint_address, mint_id=mint_id, f_raise=True) # setup signature tx.serialize() setup_signature(tx=tx, input_address=input_address) # movement movements = UserCoins() minting_coins = CoinObject(mint_id, amount) movements[sender] += minting_coins movements[C.ANT_OUTSIDE] -= minting_coins movements[sender] -= fee_coins movements[C.ANT_OUTSIDE] += fee_coins insert_log(movements, cur, tx.type, tx.time, tx.hash) return mint_id, tx
def fill_inputs_outputs(tx, cur, fee_coin_id=0, additional_fee=0, dust_percent=0.8, utxo_cashe=None): assert tx.gas_price > 0, "Gas params is none zero." # outputsの合計を取得 output_coins = CoinObject() for address, coin_id, amount in tx.outputs.copy(): if address == DUMMY_REDEEM_ADDRESS: # 償還Outputは再構築するので消す tx.outputs.remove((address, coin_id, amount)) continue output_coins[coin_id] += amount # 一時的にfeeの概算 fee_coins = CoinObject(coin_id=fee_coin_id, amount=tx.gas_price * tx.gas_amount) # 必要なだけinputsを取得 tx.inputs.clear() need_coins = output_coins + fee_coins input_coins = CoinObject() input_address = set() f_dust_skipped = False if utxo_cashe is None: utxo_iter = get_unspents_iter(cur) utxo_cashe = list() f_put_cashe = True else: utxo_iter = utxo_cashe f_put_cashe = False for address, height, txhash, txindex, coin_id, amount in utxo_iter: if f_put_cashe: utxo_cashe.append( (address, height, txhash, txindex, coin_id, amount)) if coin_id not in need_coins: continue elif need_coins[coin_id] * dust_percent > amount: f_dust_skipped = True continue need_coins[coin_id] -= amount input_coins[coin_id] += amount input_address.add(address) tx.inputs.append((txhash, txindex)) if need_coins.is_all_minus_amount(): break else: if f_dust_skipped and dust_percent > 0.00001: new_dust_percent = round(dust_percent * 0.7, 6) logging.debug("Retry by lower dust percent. {}=>{}".format( dust_percent, new_dust_percent)) return fill_inputs_outputs(tx, cur, fee_coin_id, additional_fee, new_dust_percent, utxo_cashe) elif len(tx.inputs) > 255: raise BlockChainError( 'Too many inputs, unspent tx\'s amount is too small.') else: raise BlockChainError( 'Insufficient balance. inputs={} needs={}'.format( input_coins, need_coins)) # redeemを計算 redeem_coins = input_coins - output_coins - fee_coins for coin_id, amount in redeem_coins: tx.outputs.append((DUMMY_REDEEM_ADDRESS, coin_id, amount)) # Feeをチェックし再計算するか決める tx.serialize() need_gas_amount = tx.getsize() + len(input_address) * 96 + additional_fee if 0 <= tx.gas_amount - need_gas_amount < 10000: # input/outputを混ぜる return input_address else: # insufficient gas logging.debug("Retry calculate tx fee. [{}=>{}+{}={}]".format( tx.gas_amount, tx.getsize() + len(input_address) * 96, additional_fee, need_gas_amount)) tx.gas_amount = need_gas_amount return fill_inputs_outputs(tx, cur, fee_coin_id, additional_fee, dust_percent, utxo_cashe)