def read_txhash2log(txhash, cur): d = cur.execute( """ SELECT `type`,`user`,`coin_id`,`amount`,`time` FROM `log` WHERE `hash`=? """, (txhash, )).fetchall() if len(d) == 0: return None movement = Accounting() _type = _time = None for _type, user, coin_id, amount, _time in d: movement.add_coins(user, coin_id, amount) return MoveLog(txhash, _type, movement, _time, False)
def calc_return_balance(start_tx, c_address, redeem_address): """ Calc redeem balance """ balance = Accounting() for address, coin_id, amount in start_tx.outputs: if address == c_address: balance[redeem_address][coin_id] += amount return balance
def calc_tx_movement(tx, c_address, redeem_address, emulate_gas): """ Calc tx inner movement """ account = Accounting() for txhash, txindex in tx.inputs: input_tx = tx_builder.get_tx(txhash=txhash) address, coin_id, amount = input_tx.outputs[txindex] account[address][coin_id] -= amount account[redeem_address][0] += (tx.gas_amount+emulate_gas) * tx.gas_price account[c_address][0] -= emulate_gas * tx.gas_price for address, coin_id, amount in tx.outputs: account[address][coin_id] += amount return account
def read_txhash2movelog(txhash, cur): """read MoveLog by txhash""" d = cur.execute( """ SELECT `type`,`user`,`coin_id`,`amount`,`time` FROM `log` WHERE `hash`=? """, (txhash, )).fetchall() if len(d) == 0: return None movement = Accounting() ntype = ntime = None for ntype, user, coin_id, amount, ntime in d: movement[user][coin_id] += amount return MoveLog(txhash, ntype, movement, ntime)
def move_balance(self, _from, _to, coins, outer_cur=None): assert isinstance(coins, Balance), 'coins is Balance.' with closing(create_db(V.DB_ACCOUNT_PATH)) as db: cur = outer_cur or db.cursor() try: # DataBaseに即書き込む(Memoryに入れない) movements = Accounting() movements[_from] -= coins movements[_to] += coins txhash = insert_log(movements, cur) if outer_cur is None: db.commit() self.db_balance += movements return txhash except Exception: logging.error("Failed move_balance,", exc_info=True) db.rollback()
def init(self, f_delete=False): assert f_delete is False, 'Unsafe function!' with closing(create_db(V.DB_ACCOUNT_PATH)) as db: cur = db.cursor() memory_sum = Accounting() for move_log in read_log_iter(cur): # logに記録されてもBlockに取り込まれていないならTXは存在せず if builder.db.read_tx(move_log.txhash): memory_sum += move_log.movement else: logging.debug("It's unknown log {}".format(move_log)) if f_delete: delete_log(move_log.txhash, cur) if f_delete: logging.warning("Delete user's old unconfirmed tx.") db.commit() self.db_balance += memory_sum
def affect_new_tx(self, tx, outer_cur=None): with closing(create_db(V.DB_ACCOUNT_PATH)) as db: cur = outer_cur or db.cursor() movement = Accounting() # send_from_applyで登録済み if tx.hash in self.memory_movement: return # memory_movementに追加 for txhash, txindex in tx.inputs: input_tx = tx_builder.get_tx(txhash) address, coin_id, amount = input_tx.outputs[txindex] user = read_address2user(address, cur) if user is not None: movement.add_coins(user, coin_id, -1 * amount) for address, coin_id, amount in tx.outputs: user = read_address2user(address, cur) if user is not None: movement.add_coins(user, coin_id, amount) # check if len(movement) == 0: return # 無関係である move_log = MoveLog(tx.hash, tx.type, movement, tx.time, True, tx) self.memory_movement[tx.hash] = move_log logging.debug("Affect account new tx. {}".format(tx))
def __init__(self): self.db_balance = Accounting() # {txhash: (_type, movement, _time),..} self.memory_movement = dict()
class UserAccount: def __init__(self): self.db_balance = Accounting() # {txhash: (_type, movement, _time),..} self.memory_movement = dict() def init(self, f_delete=False): assert f_delete is False, 'Unsafe function!' with closing(create_db(V.DB_ACCOUNT_PATH)) as db: cur = db.cursor() memory_sum = Accounting() for move_log in read_log_iter(cur): # logに記録されてもBlockに取り込まれていないならTXは存在せず if builder.db.read_tx(move_log.txhash): memory_sum += move_log.movement else: logging.debug("It's unknown log {}".format(move_log)) if f_delete: delete_log(move_log.txhash, cur) if f_delete: logging.warning("Delete user's old unconfirmed tx.") db.commit() self.db_balance += memory_sum def get_balance(self, confirm=6): assert confirm < builder.cashe_limit - builder.batch_size, 'Too few cashe size.' assert builder.best_block, 'Not DataBase init.' # DataBase balance = self.db_balance.copy() with closing(create_db(V.DB_ACCOUNT_PATH)) as db: cur = db.cursor() # Memory limit_height = builder.best_block.height - confirm for block in builder.best_chain: for tx in block.txs: move_log = read_txhash2log(tx.hash, cur) if move_log is None: if tx.hash in self.memory_movement: move_log = self.memory_movement[tx.hash] if move_log: for user, coins in move_log.movement.items(): for coin_id, amount in coins: if limit_height < block.height: if amount < 0: balance.add_coins(user, coin_id, amount) else: balance.add_coins(user, coin_id, amount) # Unconfirmed for tx in list(tx_builder.unconfirmed.values()): move_log = read_txhash2log(tx.hash, cur) if move_log is None: if tx.hash in self.memory_movement: move_log = self.memory_movement[tx.hash] if move_log: for user, coins in move_log.movement.items(): for coin_id, amount in coins: if amount < 0: balance.add_coins(user, coin_id, amount) return balance def move_balance(self, _from, _to, coins, outer_cur=None): assert isinstance(coins, Balance), 'coins is Balance.' with closing(create_db(V.DB_ACCOUNT_PATH)) as db: cur = outer_cur or db.cursor() try: # DataBaseに即書き込む(Memoryに入れない) movements = Accounting() movements[_from] -= coins movements[_to] += coins txhash = insert_log(movements, cur) if outer_cur is None: db.commit() self.db_balance += movements return txhash except Exception: logging.error("Failed move_balance,", exc_info=True) db.rollback() def get_movement_iter(self, start=0, f_dict=False): count = 0 with closing(create_db(V.DB_ACCOUNT_PATH)) as db: cur = db.cursor() # Unconfirmed for tx in sorted(tx_builder.unconfirmed.values(), key=lambda x: x.time, reverse=True): move_log = read_txhash2log(tx.hash, cur) if move_log is None: if tx.hash in self.memory_movement: move_log = self.memory_movement[tx.hash] else: if tx.hash in self.memory_movement: move_log.pointer = self.memory_movement[tx.hash].pointer if move_log: if count >= start: if f_dict: yield move_log.get_dict_data(cur) else: yield move_log.get_tuple_data() count += 1 # Memory for block in reversed(builder.best_chain): for tx in block.txs: move_log = read_txhash2log(tx.hash, cur) if move_log is None: if tx.hash in self.memory_movement: move_log = self.memory_movement[tx.hash] else: if tx.hash in self.memory_movement: move_log.pointer = self.memory_movement[tx.hash].pointer if move_log: if count >= start: if f_dict: yield move_log.get_dict_data(cur) else: yield move_log.get_tuple_data() count += 1 # DataBase for move_log in read_log_iter(cur, start - count): # TRANSFERなど はDBとMemoryの両方に存在する if move_log.txhash in self.memory_movement: continue elif f_dict: yield move_log.get_dict_data(cur) else: yield move_log.get_tuple_data() def new_batch_apply(self, batched_blocks): with closing(create_db(V.DB_ACCOUNT_PATH)) as db: cur = db.cursor() for block in batched_blocks: for tx in block.txs: move_log = read_txhash2log(tx.hash, cur) if move_log: # User操作の記録 self.db_balance += move_log.movement if tx.hash in self.memory_movement: del self.memory_movement[tx.hash] # logging.debug("Already recoded log {}".format(tx)) elif tx.hash in self.memory_movement: # db_balanceに追加 _type, movement, _time = self.memory_movement[tx.hash].get_tuple_data() self.db_balance += movement # memory_movementから削除 del self.memory_movement[tx.hash] # insert_log insert_log(movement, cur, _type, _time, tx.hash) db.commit() def affect_new_tx(self, tx, outer_cur=None): with closing(create_db(V.DB_ACCOUNT_PATH)) as db: cur = outer_cur or db.cursor() movement = Accounting() # send_from_applyで登録済み if tx.hash in self.memory_movement: return # memory_movementに追加 for txhash, txindex in tx.inputs: input_tx = tx_builder.get_tx(txhash) address, coin_id, amount = input_tx.outputs[txindex] user = read_address2user(address, cur) if user is not None: movement.add_coins(user, coin_id, -1 * amount) for address, coin_id, amount in tx.outputs: user = read_address2user(address, cur) if user is not None: movement.add_coins(user, coin_id, amount) # check if len(movement) == 0: return # 無関係である move_log = MoveLog(tx.hash, tx.type, movement, tx.time, True, tx) self.memory_movement[tx.hash] = move_log logging.debug("Affect account new tx. {}".format(tx))
def send_many(sender, send_pairs, cur, fee_coin_id=0, gas_price=None, msg_type=C.MSG_NONE, msg_body=b'', subtract_fee_from_amount=False, retention=10800): assert isinstance(sender, int), 'Sender is user id' assert 0 < len(send_pairs), 'Empty send_pairs' # send_pairs check movements = Accounting() send_coins = Balance() outputs = list() coins = Balance() for address, coin_id, amount in send_pairs: assert isinstance(address, str) assert isinstance(coin_id, int) and isinstance(amount, int), 'CoinID, amount is int' coins[coin_id] += amount outputs.append((address, coin_id, amount)) user = read_address2userid(address=address, cur=cur) if user is not None: movements[user][coin_id] += amount # send to myself movements[sender] -= coins # movements[C.ANT_OUTSIDE] += coins # tx now = int(time() - V.BLOCK_GENESIS_TIME) tx = TX.from_dict( 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=tx, cur=cur, fee_coin_id=fee_coin_id) # subtract fee from amount if subtract_fee_from_amount: if fee_coin_id != 0: raise BlockChainError('subtract_fee option require fee_coin_id=0') subtract_fee = subtract_fee_from_user_balance(tx) # fee returns to sender's balance movements[sender][0] += subtract_fee send_coins[0] -= subtract_fee fee_coins = Balance(coin_id=fee_coin_id, amount=tx.gas_price * tx.gas_amount) # check enough balance account have for address, coin_id, amount in send_pairs: send_coins[coin_id] += amount check_enough_amount(sender=sender, send_coins=send_coins, fee_coins=fee_coins, cur=cur) # 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_movelog(movements, cur, tx.type, tx.time, tx.hash) return tx
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 = Balance(0, amount) minting_coins = Balance(mint_id, amount) else: send_coins = Balance(0, 0) minting_coins = Balance(0, 0) tx.update_time(retention) additional_gas = C.MINTCOIN_GAS + C.SIGNATURE_GAS # for mint_coin user signature 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 = Balance(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 = Accounting() 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 = Balance(coin_id=fee_coin_id, amount=tx.gas_price * tx.gas_amount) # check amount check_enough_amount(sender=sender, send_coins=Balance(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 = Accounting() minting_coins = Balance(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 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 = Accounting() outputs = list() coins = Balance() 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 = Balance(coin_id=fee_coin_id, amount=tx.gas_price * tx.gas_amount) if f_balance_check: # 残高が十分にあるかチェック send_coins = Balance() 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