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 = UserCoins() _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 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 init(self): with closing(create_db(V.DB_ACCOUNT_PATH)) as db: cur = db.cursor() memory_sum = UserCoins() 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.warning("It's unknown log {}".format(move_log)) # delete_log(move_log.txhash, cur) self.db_balance += memory_sum
def move_balance(self, _from, _to, coins, outer_cur=None): assert isinstance(coins, CoinObject), 'coins is CoinObject.' with closing(create_db(V.DB_ACCOUNT_PATH)) as db: cur = outer_cur or db.cursor() try: # DataBaseに即書き込む(Memoryに入れない) movements = UserCoins() 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 BaseException: 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 = UserCoins() 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 = UserCoins() # 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.users) == 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 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 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 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 __init__(self): self.db_balance = UserCoins() # {txhash: (_type, movement, _time),..} self.memory_movement = dict()
class UserAccount: def __init__(self): self.db_balance = UserCoins() # {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 = UserCoins() 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, CoinObject), 'coins is CoinObject.' with closing(create_db(V.DB_ACCOUNT_PATH)) as db: cur = outer_cur or db.cursor() try: # DataBaseに即書き込む(Memoryに入れない) movements = UserCoins() 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 BaseException: 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 = UserCoins() # 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.users) == 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))