From 7e230a0edc41bc0e7a08006b8302d56de61543da Mon Sep 17 00:00:00 2001 From: micimize Date: Sun, 13 Aug 2017 13:43:00 -1000 Subject: [PATCH 01/11] submodules --- pacli/__main__.py | 643 +--------------------------------------------- pacli/card.py | 230 +++++++++++++++++ pacli/config.py | 33 ++- pacli/deck.py | 273 ++++++++++++++++++++ pacli/vote.py | 143 +++++++++++ 5 files changed, 685 insertions(+), 637 deletions(-) create mode 100644 pacli/card.py create mode 100644 pacli/deck.py create mode 100644 pacli/vote.py diff --git a/pacli/__main__.py b/pacli/__main__.py index b9fd929..933f67d 100644 --- a/pacli/__main__.py +++ b/pacli/__main__.py @@ -1,40 +1,14 @@ -from datetime import datetime -from terminaltables import AsciiTable -from binascii import hexlify -from appdirs import user_config_dir -from pacli.config import write_default_config, read_conf -from pacli.export import export_to_csv +from pacli.config import write_default_config, conf_dir, conf_file, Settings import os, argparse import pypeerassets as pa -from pypeerassets.pautils import amount_to_exponent, exponent_to_amount -import json -import logging - +from pypeerassets.pautils import exponent_to_amount from pacli.keystore import GpgKeystore, as_local_key_provider -conf_dir = user_config_dir("pacli") -conf_file = os.path.join(conf_dir, "pacli.conf") -logfile = os.path.join(conf_dir, "pacli.log") -keyfile = os.path.join(conf_dir, "pacli.gpg") - -class Settings: - pass - - -def load_conf(): - '''load user configuration''' - - settings = read_conf(conf_file) - - for key in settings: - setattr(Settings, key, settings[key]) - - logging.basicConfig(filename=logfile, level=logging.getLevelName(Settings.loglevel)) - logging.basicConfig(level=logging.getLevelName(Settings.loglevel), - format="%(asctime)s %(levelname)s %(message)s") - - logging.debug("logging initialized") +from pacli.deck import * +from pacli.card import * +from pacli.vote import * +keyfile = os.path.join(conf_dir, "pacli.gpg") def first_run(): '''if first run, setup local configuration directory.''' @@ -93,471 +67,6 @@ def change(utxo): if Settings.change == "standard": return provider.getnewaddress() -def tstamp_to_iso(tstamp): - '''make iso timestamp from unix timestamp''' - - return datetime.fromtimestamp(tstamp).isoformat() - - -def find_deck(provider, key: str) -> list: - '''find deck by ''' - - decks = list(pa.find_all_valid_decks(provider, deck_version=Settings.deck_version, prod=Settings.production)) - for i in decks: - i.short_id = i.asset_id[:20] - - return [d for d in decks if key in d.__dict__.values()] - - -class ListDecks: - - @classmethod - def __init__(cls, decks): - cls.decks = list(decks) - - ## Deck table header - deck_table = [ - ## add subscribed column - ("asset ID", "asset name", "issuer", "mode") - ] - - table = AsciiTable(deck_table, title="Decks") - - @classmethod - def dtl(cls, deck): - '''deck-to-list deck to table-printable list''' - - l = [] - l.append(deck["asset_id"][:20]) - l.append(deck["name"]) - l.append(deck["issuer"]) - l.append(deck["issue_mode"]) - - return l - - @classmethod - def pack_decks_for_printing(cls): - - assert cls.decks, {"error": "No decks found!"} - - for i in cls.decks: - cls.deck_table.append( - cls.dtl(i.__dict__) - ) - -class ListCards: - - @classmethod - def __init__(cls, provider, cards): - cls.provider = provider - cls.cards = list(cards) - - ## Deck table header - cls.card_table = [ - ## add subscribed column - ("txid", "sender", "receiver", "amount", "type", "confirms") - ] - - cls.table = AsciiTable(cls.card_table, title="Card transfers of this deck:") - - @classmethod - def dtl(cls, card, subscribed=False): - '''cards-to-list cards to table-printable list''' - - l = [] - l.append(card["txid"][:20]) - l.append(card["sender"]) - l.append(card["receiver"][0]) - l.append(exponent_to_amount(card["amount"][0], - card["number_of_decimals"])) - l.append(card["type"]) - if card["blockhash"] != 0: - l.append(cls.provider.getrawtransaction(card["txid"], 1)["confirmations"]) - else: - l.append(0) - - return l - - @classmethod - def pack_cards_for_printing(cls): - - #assert len(cls.cards) > 0, {"error": "No cards found!"} - - for i in cls.cards: - cls.card_table.append( - cls.dtl(i.__dict__) - ) - - -class DeckInfo: - - @classmethod - def __init__(cls, deck): - assert isinstance(deck, pa.Deck) - cls.deck = deck - - ## Deck table header - cls.deck_table = [ - ## add subscribed column - ("asset name", "issuer", "issue mode", "decimals", "issue time") - ] - - cls.table = AsciiTable(cls.deck_table, title="Deck id: " + cls.deck.asset_id + " ") - - @staticmethod - def dtl(deck, subscribed=False): - '''deck-to-list deck to table-printable list''' - - l = [] - l.append(deck["name"]) - l.append(deck["issuer"]) - l.append(deck["issue_mode"]) - l.append(deck["number_of_decimals"]) - l.append(tstamp_to_iso(deck["issue_time"])) - - return l - - @classmethod - def pack_decks_for_printing(cls): - - cls.deck_table.append(cls.dtl(cls.deck.__dict__)) - - -class DeckBalances: - '''Show balances of address tied with this deck.''' - - @classmethod - def __init__(cls, deck, balances): - assert isinstance(deck, pa.Deck) - cls.balances = dict(balances) - cls.deck = deck - - ## Deck table header - cls.deck_table = [ - ## add subscribed column - ("address", "balance") - ] - - cls.table = AsciiTable(cls.deck_table, title="Deck id: " + cls.deck.asset_id + " ") - - @classmethod - def dtl(cls, addr, balance): - '''deck-to-list deck to table-printable list''' - - l = [] - l.append(addr) - l.append(exponent_to_amount(balance, - cls.deck.number_of_decimals)) - - return l - - @classmethod - def pack_for_printing(cls): - - assert len(cls.balances) > 0, {"error": "No balances found!"} - - for k in cls.balances: - cls.deck_table.append( - cls.dtl(k, cls.balances[k]) - ) - - -def deck_list(provider): - '''list command''' - - decks = pa.find_all_valid_decks(provider=provider, deck_version=Settings.deck_version, - prod=Settings.production) - d = ListDecks(decks) - d.pack_decks_for_printing() - print(d.table.table) - - -def deck_subscribe(provider, deck_id): - '''subscribe command, load deck p2th into local node, pass ''' - - try: - deck = find_deck(provider, deck_id)[0] - except IndexError: - print({"error": "Deck not found!"}) - return - pa.load_deck_p2th_into_local_node(provider, deck) - - -def deck_search(provider, key): - '''search commands, query decks by ''' - - decks = find_deck(provider, key) - d = ListDecks(provider, decks) - d.pack_decks_for_printing() - print(d.table.table) - - -def deck_info(provider, deck_id): - '''info commands, show full deck details''' - - try: - deck = find_deck(provider, deck_id)[0] - except IndexError: - print("\n", {"error": "Deck not found!"}) - return - info = DeckInfo(deck) - info.pack_decks_for_printing() - print(info.table.table) - - -def deck_balances(provider, deck_id): - '''show deck balances''' - - try: - deck = find_deck(provider, deck_id)[0] - except IndexError: - print("\n", {"error": "Deck not found!"}) - return - balances = get_state(provider, deck).balances - b = DeckBalances(deck, balances) - b.pack_for_printing() - print(b.table.table) - - -def deck_checksum(provider, deck_id): - '''info commands, show full deck details''' - - try: - deck = find_deck(provider, deck_id)[0] - except IndexError: - print("\n", {"error": "Deck not found!"}) - return - deck_state = get_state(provider, deck) - if deck_state.checksum: - print("\n", "Deck checksum is correct.") - else: - print("\n", "Deck checksum is incorrect.") - - -def new_deck(provider, deck, broadcast): - ''' - Spawn a new PeerAssets deck. - - pacli deck --new '{"name": "test", "number_of_decimals": 1, "issue_mode": "ONCE"}' - - Will return deck span txid. - ''' - - deck = json.loads(deck) - deck["network"] = Settings.network - deck["production"] = Settings.production - #utxo = provider.select_inputs(0.02) # we need 0.02 PPC - utxo = default_account_utxo(provider, 0.02) - if utxo: - change_address = change(utxo) - else: - return - raw_deck = pa.deck_spawn(pa.Deck(**deck), - inputs=utxo, - change_address=change_address - ) - raw_deck_spawn = hexlify(raw_deck).decode() - signed = provider.signrawtransaction(raw_deck_spawn) - - if broadcast: - txid = provider.sendrawtransaction(signed["hex"]) - print("\n", txid, "\n") - - deck["asset_id"] = txid - d = pa.Deck(**deck) - pa.load_deck_p2th_into_local_node(provider, d) # subscribe to deck - else: - print("\nraw transaction:\n", signed["hex"], "\n") - - -def list_cards(provider, args): - ''' - List cards of this .abs - - pacli card -list - ''' - - try: - deck = find_deck(provider, args)[0] - except IndexError: - print("\n", {"error": "Deck not found!"}) - return - if isinstance(provider, pa.RpcNode): - if not provider.getaddressesbyaccount(deck.name): - print("\n", {"error": "You must subscribe to deck to be able to list transactions."}) - return - - all_cards = pa.find_card_transfers(provider, deck) - cards = pa.validate_card_issue_modes(deck, all_cards) - c = ListCards(provider, cards) - c.pack_cards_for_printing() - print(c.table.table) - - -def export_cards(provider, args): - ''' - export cards to csv - - pacli card -export filename - ''' - - try: - deck = find_deck(provider, args[0])[0] - except IndexError: - print("\n", {"error": "Deck not found!"}) - return - if not provider.getaddressesbyaccount(deck.name): - print("\n", {"error": "You must subscribe to deck to be able to list transactions."}) - return - - all_cards = pa.find_card_transfers(provider, deck) - cards = pa.validate_card_issue_modes(deck, all_cards) - - export_to_csv(cards, args[1]) - - -def card_issue(provider, args, broadcast): - ''' - Issue new cards of this deck. - - pacli card -issue '{"deck": "deck_id", - "receivers": [list of receiver addresses], - "amounts": [list of amounts] - } - ''' - - issue = json.loads(args) - try: - deck = find_deck(provider, issue["deck"])[0] - except IndexError: - print("\n", {"error": "Deck not found."}) - return - - if not provider.gettransaction(deck.asset_id)["confirmations"] > 0: - print("\n", "You are trying to issue cards on a deck which has not been confirmed yet.") - - if provider.validateaddress(deck.issuer)["ismine"]: - try: - utxo = provider.select_inputs(0.02, deck.issuer) - except ValueError: - print("\n", {"error": "Please send funds to the deck issuing address: {0}".format(deck.issuer)}) - return - else: - print("\n", {"error": "You are not the owner of this deck."}) - return - - issue["amount"] = [amount_to_exponent(float(i), deck.number_of_decimals) for i in issue["amount"]] - change_address = change(utxo) - ct = pa.CardTransfer(deck=deck, receiver=issue["receiver"], - amount=issue["amount"]) - raw_ct = hexlify(pa.card_issue(deck, ct, utxo, - change_address - ) - ).decode() - - signed = provider.signrawtransaction(raw_ct) - - if broadcast: - txid = provider.sendrawtransaction(signed["hex"]) # send the tx - print("\n", txid, "\n") - else: - print("\nraw transaction:\n", signed["hex"], "\n") - - -def card_burn(provider, args, broadcast): - ''' - Burn cards of this deck. - - pacli card -burn '{"deck": "deck_id", "amount": ["amount"]}' - ''' - - args = json.loads(args) - try: - deck = find_deck(provider, args["deck"])[0] - except IndexError: - print("\n", {"error": "Deck not found!"}) - return - if not provider.getaddressesbyaccount(deck.name): - print("\n", {"error": "You are not even subscribed to this deck, how can you burn cards?"}) - return - try: - my_balance = get_my_balance(provider, deck.asset_id) - except ValueError: - print("\n", {"error": "You have no cards on this deck."}) - return - - args["amount"] = [amount_to_exponent(float(i), deck.number_of_decimals) for i in args["amount"]] - assert sum(args["amount"]) <= sum(my_balance.values()), {"error": "You don't have enough cards on this deck."} - - utxo = provider.select_inputs(0.02) - change_address = change(utxo) - cb = pa.CardTransfer(deck=deck, receiver=[deck.issuer], amount=args["amount"]) - raw_cb = hexlify(pa.card_burn(deck, cb, utxo, - change_address - ) - ).decode() - - signed = provider.signrawtransaction(raw_cb) - - if broadcast: - txid = provider.sendrawtransaction(signed["hex"]) # send the tx - print("\n", txid, "\n") - else: - print("\nraw transaction:\n", signed["hex"], "\n") - - -def card_transfer(provider, args, broadcast): - ''' - Transfer cards to - - pacli card -transfer '{"deck": "deck_id", - "receivers": [list of receiver addresses], "amounts": [list of amounts] - } - ''' - - args = json.loads(args) - try: - deck = find_deck(provider, args["deck"])[0] - except IndexError: - print({"error": "Deck not found!"}) - return - if not provider.getaddressesbyaccount(deck.name): - print("\n", {"error": "You are not even subscribed to this deck, how can you transfer cards?"}) - try: - my_balance = get_my_balance(provider, deck.asset_id) - except ValueError: - print("\n", {"error": "You have no cards on this deck."}) - return - - args["amount"] = [amount_to_exponent(float(i), deck.number_of_decimals) for i in args["amount"]] - assert sum(args["amount"]) <= sum(my_balance.values()), {"error": "You don't have enough cards on this deck."} - - utxo = provider.select_inputs(0.02) - change_address = change(utxo) - ct = pa.CardTransfer(deck=deck, receiver=args["receiver"], amount=args["amount"]) - raw_ct = hexlify(pa.card_transfer(deck, ct, utxo, - change_address - ) - ).decode() - - signed = provider.signrawtransaction(raw_ct) - - if broadcast: - txid = provider.sendrawtransaction(signed["hex"]) # send the tx - print("\n", txid, "\n") - else: - print("\nraw transaction:\n", signed["hex"], "\n") - - -def get_state(provider, deck): - '''return balances of this deck''' - - cards = pa.find_card_transfers(provider, deck) - if cards: - return pa.DeckState(cards) - else: - raise ValueError("No cards on this deck.") - def get_my_balance(provider, deck_id): '''get balances on the deck owned by me''' @@ -634,143 +143,6 @@ def new_address(provider): provider.importprivkey(key.wif, "PACLI") return key.address -## Voting # - -def new_vote(provider, args, broadcast): - ''' - Initialize new vote on the - - pacli vote --new '{"choices": ["y", "n"], "count_mode": "SIMPLE", - "description": "test", "start_block": int, "end_block": int}' - ''' - - try: - deck = find_deck(provider, args[0])[0] - except IndexError: - print("\n", {"error": "Deck not found!"}) - return - - args = json.loads(args[1]) - inputs = provider.select_inputs(0.02) - change_address = change(inputs) - vote = pa.Vote(version=1, deck=deck, **args) - - raw_vote_init = hexlify(pa.vote_init(vote, inputs, change_address)).decode() - signed = provider.signrawtransaction(raw_vote_init)["hex"] - - if broadcast: - txid = provider.sendrawtransaction(signed) # send the tx - print("\n", txid, "\n") - else: - print("\nraw transaction:\n", signed, "\n") - - -def vote_cast(provider, args, broadcast): - ''' - cast a vote - args = deck, vote_id, choice - ''' - - try: - deck = find_deck(provider, args[0])[0] - except IndexError: - print("\n", {"error": "Deck not found!"}) - return - - for i in pa.find_vote_inits(provider, deck): - if i.vote_id == args[1]: - vote = i - - if not vote: - return {"error": "Vote not found."} - if isinstance(args[2], int): - choice = args[2] - else: - choice = list(vote.choices).index(args[2]) - inputs = provider.select_inputs(0.02) - change_address = change(inputs) - cast = pa.vote_cast(vote, choice, inputs, change_address) - - raw_vote_cast = hexlify(cast).decode() - signed = provider.signrawtransaction(raw_vote_cast)["hex"] - - if broadcast: - txid = provider.sendrawtransaction(signed) # send the tx - print("\n", txid, "\n") - else: - print("\nraw transaction:\n", signed, "\n") - - -class ListVotes: - - @classmethod - def __init__(cls, provider, votes): - cls.provider = provider - cls.votes = list(votes) - - ## Vote table header - cls.vote_table = [ - ## add subscribed column - ("vote_id", "sender", "description", "start_block", "end_block") - ] - - cls.table = AsciiTable(cls.vote_table, title="Votes on this deck:") - - @classmethod - def dtl(cls, vote, subscribed=False): - '''votes-to-list: votes to table-printable list''' - - l = [] - l.append(vote["vote_id"]) - l.append(vote["sender"]) - l.append(vote["description"]) - l.append(vote["start_block"]) - l.append(vote["end_block"]) - - return l - - @classmethod - def pack_for_printing(cls): - - for i in cls.votes: - cls.vote_table.append( - cls.dtl(i.__dict__) - ) - - -def list_votes(provider, args): - '''list all votes on the ''' - - try: - deck = find_deck(provider, args)[0] - except IndexError: - print("\n", {"error": "Deck not found!"}) - return - vote_inits = list(pa.find_vote_inits(provider, deck)) - - c = ListVotes(provider, vote_inits) - c.pack_for_printing() - print(c.table.table) - - -def vote_info(provider, args): - '''show detail information about on the ''' - - try: - deck = find_deck(provider, args[0])[0] - except IndexError: - print("\n", {"error": "Deck not found!"}) - return - - for i in pa.find_vote_inits(provider, deck): - if i.vote_id == args[1]: - vote = i - - vote.deck = vote.deck.asset_id - print("\n", vote.__dict__) - return - - def cli(): '''CLI arguments parser''' @@ -843,7 +215,6 @@ def configured_provider(Settings): def main(): first_run() - load_conf() provider = configured_provider(Settings) @@ -860,7 +231,7 @@ def main(): if args.command == "deck": if args.list: - deck_list(provider) + deck_list(provider, Settings) if args.subscribe: deck_subscribe(provider, args.subscribe) if args.search: diff --git a/pacli/card.py b/pacli/card.py new file mode 100644 index 0000000..e380a00 --- /dev/null +++ b/pacli/card.py @@ -0,0 +1,230 @@ +from pacli.export import export_to_csv +from terminaltables import AsciiTable +from binascii import hexlify +import pypeerassets as pa +from pypeerassets.pautils import amount_to_exponent, exponent_to_amount +from pacli.deck import find_deck +import json + +class ListCards: + + @classmethod + def __init__(cls, provider, cards): + cls.provider = provider + cls.cards = list(cards) + + ## Deck table header + cls.card_table = [ + ## add subscribed column + ("txid", "sender", "receiver", "amount", "type", "confirms") + ] + + cls.table = AsciiTable(cls.card_table, title="Card transfers of this deck:") + + @classmethod + def dtl(cls, card, subscribed=False): + '''cards-to-list cards to table-printable list''' + + l = [] + l.append(card["txid"][:20]) + l.append(card["sender"]) + l.append(card["receiver"][0]) + l.append(exponent_to_amount(card["amount"][0], + card["number_of_decimals"])) + l.append(card["type"]) + if card["blockhash"] != 0: + l.append(cls.provider.getrawtransaction(card["txid"], 1)["confirmations"]) + else: + l.append(0) + + return l + + @classmethod + def pack_cards_for_printing(cls): + + #assert len(cls.cards) > 0, {"error": "No cards found!"} + + for i in cls.cards: + cls.card_table.append( + cls.dtl(i.__dict__) + ) + + +def list_cards(provider, args): + ''' + List cards of this .abs + + pacli card -list + ''' + try: + deck = find_deck(provider, args)[0] + except IndexError: + print("\n", {"error": "Deck not found!"}) + return + if isinstance(provider, pa.RpcNode): + if not provider.getaddressesbyaccount(deck.name): + print("\n", {"error": "You must subscribe to deck to be able to list transactions."}) + return + + all_cards = pa.find_card_transfers(provider, deck) + cards = pa.validate_card_issue_modes(deck, all_cards) + c = ListCards(provider, cards) + c.pack_cards_for_printing() + print(c.table.table) + + +def export_cards(provider, args): + ''' + export cards to csv + + pacli card -export filename + ''' + + try: + deck = find_deck(provider, args[0])[0] + except IndexError: + print("\n", {"error": "Deck not found!"}) + return + if not provider.getaddressesbyaccount(deck.name): + print("\n", {"error": "You must subscribe to deck to be able to list transactions."}) + return + + all_cards = pa.find_card_transfers(provider, deck) + cards = pa.validate_card_issue_modes(deck, all_cards) + + export_to_csv(cards, args[1]) + + +def card_issue(provider, args, broadcast): + ''' + Issue new cards of this deck. + + pacli card -issue '{"deck": "deck_id", + "receivers": [list of receiver addresses], + "amounts": [list of amounts] + } + ''' + + issue = json.loads(args) + try: + deck = find_deck(provider, issue["deck"])[0] + except IndexError: + print("\n", {"error": "Deck not found."}) + return + + if not provider.gettransaction(deck.asset_id)["confirmations"] > 0: + print("\n", "You are trying to issue cards on a deck which has not been confirmed yet.") + + if provider.validateaddress(deck.issuer)["ismine"]: + try: + utxo = provider.select_inputs(0.02, deck.issuer) + except ValueError: + print("\n", {"error": "Please send funds to the deck issuing address: {0}".format(deck.issuer)}) + return + else: + print("\n", {"error": "You are not the owner of this deck."}) + return + + issue["amount"] = [amount_to_exponent(float(i), deck.number_of_decimals) for i in issue["amount"]] + change_address = change(utxo) + ct = pa.CardTransfer(deck=deck, receiver=issue["receiver"], + amount=issue["amount"]) + raw_ct = hexlify(pa.card_issue(deck, ct, utxo, + change_address + ) + ).decode() + + signed = provider.signrawtransaction(raw_ct) + + if broadcast: + txid = provider.sendrawtransaction(signed["hex"]) # send the tx + print("\n", txid, "\n") + else: + print("\nraw transaction:\n", signed["hex"], "\n") + + +def card_burn(provider, args, broadcast): + ''' + Burn cards of this deck. + + pacli card -burn '{"deck": "deck_id", "amount": ["amount"]}' + ''' + + args = json.loads(args) + try: + deck = find_deck(provider, args["deck"])[0] + except IndexError: + print("\n", {"error": "Deck not found!"}) + return + if not provider.getaddressesbyaccount(deck.name): + print("\n", {"error": "You are not even subscribed to this deck, how can you burn cards?"}) + return + try: + my_balance = get_my_balance(provider, deck.asset_id) + except ValueError: + print("\n", {"error": "You have no cards on this deck."}) + return + + args["amount"] = [amount_to_exponent(float(i), deck.number_of_decimals) for i in args["amount"]] + assert sum(args["amount"]) <= sum(my_balance.values()), {"error": "You don't have enough cards on this deck."} + + utxo = provider.select_inputs(0.02) + change_address = change(utxo) + cb = pa.CardTransfer(deck=deck, receiver=[deck.issuer], amount=args["amount"]) + raw_cb = hexlify(pa.card_burn(deck, cb, utxo, + change_address + ) + ).decode() + + signed = provider.signrawtransaction(raw_cb) + + if broadcast: + txid = provider.sendrawtransaction(signed["hex"]) # send the tx + print("\n", txid, "\n") + else: + print("\nraw transaction:\n", signed["hex"], "\n") + + +def card_transfer(provider, args, broadcast): + ''' + Transfer cards to + + pacli card -transfer '{"deck": "deck_id", + "receivers": [list of receiver addresses], "amounts": [list of amounts] + } + ''' + + args = json.loads(args) + try: + deck = find_deck(provider, args["deck"])[0] + except IndexError: + print({"error": "Deck not found!"}) + return + if not provider.getaddressesbyaccount(deck.name): + print("\n", {"error": "You are not even subscribed to this deck, how can you transfer cards?"}) + try: + my_balance = get_my_balance(provider, deck.asset_id) + except ValueError: + print("\n", {"error": "You have no cards on this deck."}) + return + + args["amount"] = [amount_to_exponent(float(i), deck.number_of_decimals) for i in args["amount"]] + assert sum(args["amount"]) <= sum(my_balance.values()), {"error": "You don't have enough cards on this deck."} + + utxo = provider.select_inputs(0.02) + change_address = change(utxo) + ct = pa.CardTransfer(deck=deck, receiver=args["receiver"], amount=args["amount"]) + raw_ct = hexlify(pa.card_transfer(deck, ct, utxo, + change_address + ) + ).decode() + + signed = provider.signrawtransaction(raw_ct) + + if broadcast: + txid = provider.sendrawtransaction(signed["hex"]) # send the tx + print("\n", txid, "\n") + else: + print("\nraw transaction:\n", signed["hex"], "\n") + + diff --git a/pacli/config.py b/pacli/config.py index 2dac2fd..d2f64fc 100644 --- a/pacli/config.py +++ b/pacli/config.py @@ -1,5 +1,7 @@ +from appdirs import user_config_dir +import logging import configparser -import sys +import os, sys from .default_conf import default_conf def write_default_config(conf_file=None): @@ -39,3 +41,32 @@ def read_conf(conf_file): settings["testnet"] = True return settings + + +conf_dir = user_config_dir("pacli") +conf_file = os.path.join(conf_dir, "pacli.conf") +logfile = os.path.join(conf_dir, "pacli.log") + + +def load_conf(): + '''load user configuration''' + + class Settings: + pass + + settings = read_conf(conf_file) + + for key in settings: + setattr(Settings, key, settings[key]) + + logging.basicConfig(filename=logfile, level=logging.getLevelName(Settings.loglevel)) + logging.basicConfig(level=logging.getLevelName(Settings.loglevel), + format="%(asctime)s %(levelname)s %(message)s") + + logging.debug("logging initialized") + + return Settings + + +Settings = load_conf() + diff --git a/pacli/deck.py b/pacli/deck.py new file mode 100644 index 0000000..57039f0 --- /dev/null +++ b/pacli/deck.py @@ -0,0 +1,273 @@ +from terminaltables import AsciiTable +from datetime import datetime +from binascii import hexlify +#from pypeerassets import find_card_transfers, find_all_valid_decks, DeckState, Deck, load_deck_p2th_into_local_node +import pypeerassets as pa +import json +from pacli.config import Settings + + +def default_account_utxo(provider, amount): + '''set default address to be used with pacli''' + + if "PACLI" not in provider.listaccounts().keys(): + addr = provider.getaddressesbyaccount("PACLI") + print("\n", "Please fund this address: {addr}".format(addr=addr)) + return + + for i in provider.getaddressesbyaccount("PACLI"): + try: + return provider.select_inputs(amount, i) + except ValueError: + pass + + print("\n", "Please fund one of the following addresses: {addrs}".format( + addrs=provider.getaddressesbyaccount("PACLI"))) + return + + + +def get_state(provider, deck): + '''return balances of this deck''' + + cards = pa.find_card_transfers(provider, deck) + if cards: + return pa.DeckState(cards) + else: + raise ValueError("No cards on this deck.") + + +def tstamp_to_iso(tstamp): + '''make iso timestamp from unix timestamp''' + + return datetime.fromtimestamp(tstamp).isoformat() + + +class DeckInfo: + + @classmethod + def __init__(cls, deck): + assert isinstance(deck, pa.Deck) + cls.deck = deck + + ## Deck table header + cls.deck_table = [ + ## add subscribed column + ("asset name", "issuer", "issue mode", "decimals", "issue time") + ] + + cls.table = AsciiTable(cls.deck_table, title="Deck id: " + cls.deck.asset_id + " ") + + @staticmethod + def dtl(deck, subscribed=False): + '''deck-to-list deck to table-printable list''' + + l = [] + l.append(deck["name"]) + l.append(deck["issuer"]) + l.append(deck["issue_mode"]) + l.append(deck["number_of_decimals"]) + l.append(tstamp_to_iso(deck["issue_time"])) + + return l + + @classmethod + def pack_decks_for_printing(cls): + + cls.deck_table.append(cls.dtl(cls.deck.__dict__)) + + +class DeckBalances: + '''Show balances of address tied with this deck.''' + + @classmethod + def __init__(cls, deck, balances): + assert isinstance(deck, pa.Deck) + cls.balances = dict(balances) + cls.deck = deck + + ## Deck table header + cls.deck_table = [ + ## add subscribed column + ("address", "balance") + ] + + cls.table = AsciiTable(cls.deck_table, title="Deck id: " + cls.deck.asset_id + " ") + + @classmethod + def dtl(cls, addr, balance): + '''deck-to-list deck to table-printable list''' + + l = [] + l.append(addr) + l.append(exponent_to_amount(balance, + cls.deck.number_of_decimals)) + + return l + + @classmethod + def pack_for_printing(cls): + + assert len(cls.balances) > 0, {"error": "No balances found!"} + + for k in cls.balances: + cls.deck_table.append( + cls.dtl(k, cls.balances[k]) + ) + + +class ListDecks: + + @classmethod + def __init__(cls, decks): + cls.decks = list(decks) + + ## Deck table header + deck_table = [ + ## add subscribed column + ("asset ID", "asset name", "issuer", "mode") + ] + + table = AsciiTable(deck_table, title="Decks") + + @classmethod + def dtl(cls, deck): + '''deck-to-list deck to table-printable list''' + + l = [] + l.append(deck["asset_id"][:20]) + l.append(deck["name"]) + l.append(deck["issuer"]) + l.append(deck["issue_mode"]) + + return l + + @classmethod + def pack_decks_for_printing(cls): + + assert cls.decks, {"error": "No decks found!"} + + for i in cls.decks: + cls.deck_table.append( + cls.dtl(i.__dict__) + ) + + +def find_deck(provider, key: str) -> list: + '''find deck by ''' + + decks = list(pa.find_all_valid_decks(provider, deck_version=Settings.deck_version, prod=Settings.production)) + for i in decks: + i.short_id = i.asset_id[:20] + + return [d for d in decks if key in d.__dict__.values()] + + +def deck_list(provider): + '''list command''' + + decks = pa.find_all_valid_decks(provider=provider, deck_version=Settings.deck_version, + prod=Settings.production) + d = ListDecks(decks) + d.pack_decks_for_printing() + print(d.table.table) + + +def deck_subscribe(provider, deck_id): + '''subscribe command, load deck p2th into local node, pass ''' + + try: + deck = find_deck(provider, deck_id)[0] + except IndexError: + print({"error": "Deck not found!"}) + return + pa.load_deck_p2th_into_local_node(provider, deck) + + +def deck_search(provider, key): + '''search commands, query decks by ''' + + decks = find_deck(provider, key) + d = ListDecks(provider, decks) + d.pack_decks_for_printing() + print(d.table.table) + + +def deck_info(provider, deck_id): + '''info commands, show full deck details''' + + try: + deck = find_deck(provider, deck_id)[0] + except IndexError: + print("\n", {"error": "Deck not found!"}) + return + info = DeckInfo(deck) + info.pack_decks_for_printing() + print(info.table.table) + + +def deck_balances(provider, deck_id): + '''show deck balances''' + + try: + deck = find_deck(provider, deck_id)[0] + except IndexError: + print("\n", {"error": "Deck not found!"}) + return + balances = get_state(provider, deck).balances + b = DeckBalances(deck, balances) + b.pack_for_printing() + print(b.table.table) + + +def deck_checksum(provider, deck_id): + '''info commands, show full deck details''' + + try: + deck = find_deck(provider, deck_id)[0] + except IndexError: + print("\n", {"error": "Deck not found!"}) + return + deck_state = get_state(provider, deck) + if deck_state.checksum: + print("\n", "Deck checksum is correct.") + else: + print("\n", "Deck checksum is incorrect.") + + +def new_deck(provider, deck, broadcast): + ''' + Spawn a new PeerAssets deck. + + pacli deck --new '{"name": "test", "number_of_decimals": 1, "issue_mode": "ONCE"}' + + Will return deck span txid. + ''' + + deck = json.loads(deck) + deck["network"] = Settings.network + deck["production"] = Settings.production + #utxo = provider.select_inputs(0.02) # we need 0.02 PPC + utxo = default_account_utxo(provider, 0.02) + if utxo: + change_address = change(utxo) + else: + return + raw_deck = pa.deck_spawn(pa.Deck(**deck), + inputs=utxo, + change_address=change_address + ) + raw_deck_spawn = hexlify(raw_deck).decode() + signed = provider.signrawtransaction(raw_deck_spawn) + + if broadcast: + txid = provider.sendrawtransaction(signed["hex"]) + print("\n", txid, "\n") + + deck["asset_id"] = txid + d = pa.Deck(**deck) + pa.load_deck_p2th_into_local_node(provider, d) # subscribe to deck + else: + print("\nraw transaction:\n", signed["hex"], "\n") + + diff --git a/pacli/vote.py b/pacli/vote.py new file mode 100644 index 0000000..e22250a --- /dev/null +++ b/pacli/vote.py @@ -0,0 +1,143 @@ +from terminaltables import AsciiTable +from binascii import hexlify +import pypeerassets as pa +from pacli.deck import find_deck +import json + +## Voting # + +def new_vote(provider, args, broadcast): + ''' + Initialize new vote on the + + pacli vote --new '{"choices": ["y", "n"], "count_mode": "SIMPLE", + "description": "test", "start_block": int, "end_block": int}' + ''' + + try: + deck = find_deck(provider, args[0])[0] + except IndexError: + print("\n", {"error": "Deck not found!"}) + return + + args = json.loads(args[1]) + inputs = provider.select_inputs(0.02) + change_address = change(inputs) + vote = pa.Vote(version=1, deck=deck, **args) + + raw_vote_init = hexlify(pa.vote_init(vote, inputs, change_address)).decode() + signed = provider.signrawtransaction(raw_vote_init)["hex"] + + if broadcast: + txid = provider.sendrawtransaction(signed) # send the tx + print("\n", txid, "\n") + else: + print("\nraw transaction:\n", signed, "\n") + + +def vote_cast(provider, args, broadcast): + ''' + cast a vote + args = deck, vote_id, choice + ''' + + try: + deck = find_deck(provider, args[0])[0] + except IndexError: + print("\n", {"error": "Deck not found!"}) + return + + for i in pa.find_vote_inits(provider, deck): + if i.vote_id == args[1]: + vote = i + + if not vote: + return {"error": "Vote not found."} + if isinstance(args[2], int): + choice = args[2] + else: + choice = list(vote.choices).index(args[2]) + inputs = provider.select_inputs(0.02) + change_address = change(inputs) + cast = pa.vote_cast(vote, choice, inputs, change_address) + + raw_vote_cast = hexlify(cast).decode() + signed = provider.signrawtransaction(raw_vote_cast)["hex"] + + if broadcast: + txid = provider.sendrawtransaction(signed) # send the tx + print("\n", txid, "\n") + else: + print("\nraw transaction:\n", signed, "\n") + + +class ListVotes: + + @classmethod + def __init__(cls, provider, votes): + cls.provider = provider + cls.votes = list(votes) + + ## Vote table header + cls.vote_table = [ + ## add subscribed column + ("vote_id", "sender", "description", "start_block", "end_block") + ] + + cls.table = AsciiTable(cls.vote_table, title="Votes on this deck:") + + @classmethod + def dtl(cls, vote, subscribed=False): + '''votes-to-list: votes to table-printable list''' + + l = [] + l.append(vote["vote_id"]) + l.append(vote["sender"]) + l.append(vote["description"]) + l.append(vote["start_block"]) + l.append(vote["end_block"]) + + return l + + @classmethod + def pack_for_printing(cls): + + for i in cls.votes: + cls.vote_table.append( + cls.dtl(i.__dict__) + ) + + +def list_votes(provider, args): + '''list all votes on the ''' + + try: + deck = find_deck(provider, args)[0] + except IndexError: + print("\n", {"error": "Deck not found!"}) + return + vote_inits = list(pa.find_vote_inits(provider, deck)) + + c = ListVotes(provider, vote_inits) + c.pack_for_printing() + print(c.table.table) + + +def vote_info(provider, args): + '''show detail information about on the ''' + + try: + deck = find_deck(provider, args[0])[0] + except IndexError: + print("\n", {"error": "Deck not found!"}) + return + + for i in pa.find_vote_inits(provider, deck): + if i.vote_id == args[1]: + vote = i + + vote.deck = vote.deck.asset_id + print("\n", vote.__dict__) + return + + From 10f75ab3e7bfc3689513f0ec79081ac16edce3b8 Mon Sep 17 00:00:00 2001 From: micimize Date: Sun, 13 Aug 2017 15:53:54 -1000 Subject: [PATCH 02/11] global provider, click deck --- pacli/__main__.py | 106 +++++------------- pacli/card.py | 29 ++--- pacli/config.py | 13 +++ pacli/deck.py | 272 ++++++++++++++++++---------------------------- pacli/keystore.py | 19 +++- pacli/provider.py | 47 ++++++++ pacli/vote.py | 21 ++-- 7 files changed, 236 insertions(+), 271 deletions(-) create mode 100644 pacli/provider.py diff --git a/pacli/__main__.py b/pacli/__main__.py index 933f67d..8d05657 100644 --- a/pacli/__main__.py +++ b/pacli/__main__.py @@ -1,59 +1,13 @@ -from pacli.config import write_default_config, conf_dir, conf_file, Settings +from pacli.config import Settings import os, argparse import pypeerassets as pa from pypeerassets.pautils import exponent_to_amount -from pacli.keystore import GpgKeystore, as_local_key_provider +from pacli.provider import provider from pacli.deck import * from pacli.card import * from pacli.vote import * -keyfile = os.path.join(conf_dir, "pacli.gpg") - -def first_run(): - '''if first run, setup local configuration directory.''' - - if not os.path.exists(conf_dir): - os.mkdir(conf_dir) - if not os.path.exists(conf_file): - write_default_config(conf_file) - if not os.path.exists(keyfile): - open(keyfile, 'a').close() - -def set_up(provider): - '''setup''' - - # if provider is local node, check if PA P2TH is loaded in local node - # this handles indexing of transaction - if Settings.provider == "rpcnode": - if Settings.production: - if not provider.listtransactions("PAPROD"): - pa.pautils.load_p2th_privkeys_into_local_node(provider) - if not Settings.production: - if not provider.listtransactions("PATEST"): - pa.pautils.load_p2th_privkeys_into_local_node(provider, prod=False) - #elif Settings.provider != 'holy': - # pa.pautils.load_p2th_privkeys_into_local_node(provider, keyfile) - -def default_account_utxo(provider, amount): - '''set default address to be used with pacli''' - - if "PACLI" not in provider.listaccounts().keys(): - addr = provider.getaddressesbyaccount("PACLI") - print("\n", "Please fund this address: {addr}".format(addr=addr)) - return - - for i in provider.getaddressesbyaccount("PACLI"): - try: - return provider.select_inputs(amount, i) - except ValueError: - pass - - print("\n", "Please fund one of the following addresses: {addrs}".format( - addrs=provider.getaddressesbyaccount("PACLI"))) - return - - def change(utxo): '''decide what will be change address * default - pay back to largest utxo @@ -68,30 +22,28 @@ def change(utxo): return provider.getnewaddress() -def get_my_balance(provider, deck_id): +def get_my_balance(deck_id): '''get balances on the deck owned by me''' try: - deck = find_deck(provider, deck_id)[0] + deck = find_deck(deck_id)[0] except IndexError: print("\n", {"error": "Deck not found!"}) my_addresses = provider.getaddressesbyaccount() - deck_balances = get_state(provider, deck).balances + deck_balances = get_state(deck).balances matches = list(set(my_addresses).intersection(deck_balances)) return {i: deck_balances[i] for i in matches if i in deck_balances.keys()} -def address_balance(provider, deck_id, address): +def address_balance(deck_id, address): '''show deck balances''' try: - deck = find_deck(provider, deck_id)[0] - except IndexError: - print("\n", {"error": "Deck not found!"}) - return - balances = get_state(provider, deck).balances + deck = find_deck(deck_id)[0] + except IndexError: print("\n", {"error": "Deck not found!"}) return + balances = get_state(deck).balances try: b = exponent_to_amount(balances[address], deck.number_of_decimals) except: @@ -124,7 +76,7 @@ def status(provider): }) for deck in report["subscribed_decks"]: try: - my_balances = get_my_balance(provider, deck["deck_id"]) + my_balances = get_my_balance(deck["deck_id"]) deck["balance"] = exponent_to_amount(sum(my_balances.values()), deck["number_of_decimals"]) deck["address_handle"] = list(my_balances.keys())[0] # show address which handles this deck, first one only though @@ -204,7 +156,7 @@ def configured_provider(Settings): if Settings.keystore.lower() == "gnupg": Provider = as_local_key_provider(Provider) - kwargs['keystore'] = keystore = GpgKeystore(Settings, keyfile) + kwargs['keystore'] = keystore = GpgKeystore(Settings) provider = Provider(**kwargs) set_up(provider) @@ -214,8 +166,6 @@ def configured_provider(Settings): def main(): - first_run() - provider = configured_provider(Settings) args = cli() @@ -227,45 +177,45 @@ def main(): print("\n", new_address(provider)) if args.addressbalance: - address_balance(provider, args.addressbalance[0], args.addressbalance[1]) + address_balance(args.addressbalance[0], args.addressbalance[1]) if args.command == "deck": if args.list: - deck_list(provider, Settings) + deck_list(Settings) if args.subscribe: - deck_subscribe(provider, args.subscribe) + deck_subscribe(args.subscribe) if args.search: - deck_search(provider, args.search) + deck_search(args.search) if args.info: - deck_info(provider, args.info) + deck_info(args.info) if args.new: - new_deck(provider, args.new, args.broadcast) + new_deck(args.new, args.broadcast) if args.checksum: - deck_checksum(provider, args.checksum) + deck_checksum(args.checksum) if args.balances: - deck_balances(provider, args.balances) + deck_balances(args.balances) if args.command == "card": if args.issue: - card_issue(provider, args.issue, args.broadcast) + card_issue(args.issue, args.broadcast) if args.burn: - card_burn(provider, args.burn, args.broadcast) + card_burn(args.burn, args.broadcast) if args.transfer: - card_transfer(provider, args.transfer, args.broadcast) + card_transfer(args.transfer, args.broadcast) if args.list: - list_cards(provider, args.list) + list_cards(args.list) if args.export: - export_cards(provider, args.export) + export_cards(args.export) if args.command == "vote": if args.new: - new_vote(provider, args.new, args.broadcast) + new_vote(args.new, args.broadcast) if args.list: - list_votes(provider, args.list) + list_votes(args.list) if args.cast: - vote_cast(provider, args.cast) + vote_cast(args.cast) if args.info: - vote_info(provider, args.info) + vote_info(args.info) if (hasattr(provider, 'keystore')): # could possibly make this direct behavior of dumprivkeys diff --git a/pacli/card.py b/pacli/card.py index e380a00..00c560f 100644 --- a/pacli/card.py +++ b/pacli/card.py @@ -5,11 +5,12 @@ from pypeerassets.pautils import amount_to_exponent, exponent_to_amount from pacli.deck import find_deck import json +from pacli.provider import provider class ListCards: @classmethod - def __init__(cls, provider, cards): + def __init__(cls, cards): cls.provider = provider cls.cards = list(cards) @@ -50,14 +51,14 @@ def pack_cards_for_printing(cls): ) -def list_cards(provider, args): +def list_cards(deck_key): ''' List cards of this .abs pacli card -list ''' try: - deck = find_deck(provider, args)[0] + deck = find_deck(deck_key)[0] except IndexError: print("\n", {"error": "Deck not found!"}) return @@ -68,12 +69,12 @@ def list_cards(provider, args): all_cards = pa.find_card_transfers(provider, deck) cards = pa.validate_card_issue_modes(deck, all_cards) - c = ListCards(provider, cards) + c = ListCards(cards) c.pack_cards_for_printing() print(c.table.table) -def export_cards(provider, args): +def export_cards(args): ''' export cards to csv @@ -81,7 +82,7 @@ def export_cards(provider, args): ''' try: - deck = find_deck(provider, args[0])[0] + deck = find_deck(args[0])[0] except IndexError: print("\n", {"error": "Deck not found!"}) return @@ -95,7 +96,7 @@ def export_cards(provider, args): export_to_csv(cards, args[1]) -def card_issue(provider, args, broadcast): +def card_issue(args, broadcast): ''' Issue new cards of this deck. @@ -107,7 +108,7 @@ def card_issue(provider, args, broadcast): issue = json.loads(args) try: - deck = find_deck(provider, issue["deck"])[0] + deck = find_deck(issue["deck"])[0] except IndexError: print("\n", {"error": "Deck not found."}) return @@ -143,7 +144,7 @@ def card_issue(provider, args, broadcast): print("\nraw transaction:\n", signed["hex"], "\n") -def card_burn(provider, args, broadcast): +def card_burn(args, broadcast): ''' Burn cards of this deck. @@ -152,7 +153,7 @@ def card_burn(provider, args, broadcast): args = json.loads(args) try: - deck = find_deck(provider, args["deck"])[0] + deck = find_deck(args["deck"])[0] except IndexError: print("\n", {"error": "Deck not found!"}) return @@ -160,7 +161,7 @@ def card_burn(provider, args, broadcast): print("\n", {"error": "You are not even subscribed to this deck, how can you burn cards?"}) return try: - my_balance = get_my_balance(provider, deck.asset_id) + my_balance = get_my_balance(deck.asset_id) except ValueError: print("\n", {"error": "You have no cards on this deck."}) return @@ -185,7 +186,7 @@ def card_burn(provider, args, broadcast): print("\nraw transaction:\n", signed["hex"], "\n") -def card_transfer(provider, args, broadcast): +def card_transfer(args, broadcast): ''' Transfer cards to @@ -196,14 +197,14 @@ def card_transfer(provider, args, broadcast): args = json.loads(args) try: - deck = find_deck(provider, args["deck"])[0] + deck = find_deck(args["deck"])[0] except IndexError: print({"error": "Deck not found!"}) return if not provider.getaddressesbyaccount(deck.name): print("\n", {"error": "You are not even subscribed to this deck, how can you transfer cards?"}) try: - my_balance = get_my_balance(provider, deck.asset_id) + my_balance = get_my_balance(deck.asset_id) except ValueError: print("\n", {"error": "You have no cards on this deck."}) return diff --git a/pacli/config.py b/pacli/config.py index d2f64fc..bdc13db 100644 --- a/pacli/config.py +++ b/pacli/config.py @@ -46,11 +46,22 @@ def read_conf(conf_file): conf_dir = user_config_dir("pacli") conf_file = os.path.join(conf_dir, "pacli.conf") logfile = os.path.join(conf_dir, "pacli.log") +keyfile = os.path.join(conf_dir, "pacli.gpg") + + +def init_config(): + '''if first run, setup local configuration directory.''' + if not os.path.exists(conf_dir): + os.mkdir(conf_dir) + if not os.path.exists(conf_file): + write_default_config(conf_file) def load_conf(): '''load user configuration''' + init_config() + class Settings: pass @@ -59,6 +70,8 @@ class Settings: for key in settings: setattr(Settings, key, settings[key]) + setattr(Settings, 'keyfile', keyfile) + logging.basicConfig(filename=logfile, level=logging.getLevelName(Settings.loglevel)) logging.basicConfig(level=logging.getLevelName(Settings.loglevel), format="%(asctime)s %(levelname)s %(message)s") diff --git a/pacli/deck.py b/pacli/deck.py index 57039f0..5359560 100644 --- a/pacli/deck.py +++ b/pacli/deck.py @@ -1,3 +1,5 @@ +import click +from click_default_group import DefaultGroup from terminaltables import AsciiTable from datetime import datetime from binascii import hexlify @@ -5,9 +7,10 @@ import pypeerassets as pa import json from pacli.config import Settings +from pacli.provider import provider -def default_account_utxo(provider, amount): +def default_account_utxo(amount): '''set default address to be used with pacli''' if "PACLI" not in provider.listaccounts().keys(): @@ -27,7 +30,7 @@ def default_account_utxo(provider, amount): -def get_state(provider, deck): +def get_state(deck): '''return balances of this deck''' cards = pa.find_card_transfers(provider, deck) @@ -43,118 +46,59 @@ def tstamp_to_iso(tstamp): return datetime.fromtimestamp(tstamp).isoformat() -class DeckInfo: +def print_table(title, heading, data): + data.insert(0, heading) + table = AsciiTable(data, title=title) + print(table.table) - @classmethod - def __init__(cls, deck): - assert isinstance(deck, pa.Deck) - cls.deck = deck - ## Deck table header - cls.deck_table = [ - ## add subscribed column - ("asset name", "issuer", "issue mode", "decimals", "issue time") - ] +def deck_title(deck): + return "Deck id: " + deck.asset_id + " " - cls.table = AsciiTable(cls.deck_table, title="Deck id: " + cls.deck.asset_id + " ") - @staticmethod - def dtl(deck, subscribed=False): - '''deck-to-list deck to table-printable list''' +def print_deck_info(deck): + assert isinstance(deck, pa.Deck) + ## TODO add subscribed column + heading = ("asset name", "issuer", "issue mode", "decimals", "issue time") + data = [[ + deck[attr] for attr in + ["name", "issuer", "issue_mode", "number_of_decimals", "issue_time"] + ]] + print_table(title=deck_title(deck), heading, data) - l = [] - l.append(deck["name"]) - l.append(deck["issuer"]) - l.append(deck["issue_mode"]) - l.append(deck["number_of_decimals"]) - l.append(tstamp_to_iso(deck["issue_time"])) - return l - - @classmethod - def pack_decks_for_printing(cls): - - cls.deck_table.append(cls.dtl(cls.deck.__dict__)) - - -class DeckBalances: +def print_deck_balances(deck, balances={}): '''Show balances of address tied with this deck.''' + assert isinstance(deck, pa.Deck) + precision = deck.number_of_decimals + ## TODO add subscribed column + heading = ("address", "balance") + data = [[address, exponent_to_amount(balance, precision)] + for address, balance in balances] + print_table(title=deck_title(deck), heading, data) - @classmethod - def __init__(cls, deck, balances): - assert isinstance(deck, pa.Deck) - cls.balances = dict(balances) - cls.deck = deck - - ## Deck table header - cls.deck_table = [ - ## add subscribed column - ("address", "balance") - ] - - cls.table = AsciiTable(cls.deck_table, title="Deck id: " + cls.deck.asset_id + " ") - - @classmethod - def dtl(cls, addr, balance): - '''deck-to-list deck to table-printable list''' - - l = [] - l.append(addr) - l.append(exponent_to_amount(balance, - cls.deck.number_of_decimals)) - - return l - - @classmethod - def pack_for_printing(cls): - - assert len(cls.balances) > 0, {"error": "No balances found!"} - for k in cls.balances: - cls.deck_table.append( - cls.dtl(k, cls.balances[k]) - ) +def deck_summary_line_item(deck): + deck = deck.__dict__ + return [ + deck["asset_id"][:20], + deck["name"], + deck["issuer"], + deck["issue_mode"] ] +def print_deck_list(decks): + '''Show summary of every deck''' -class ListDecks: + decks = list(decks) - @classmethod - def __init__(cls, decks): - cls.decks = list(decks) + ## TODO add subscribed column + heading = ("asset ID", "asset name", "issuer", "mode") + data = map(deck_summary_line_item, list(decks)) + print_table(title="Decks", heading, data) - ## Deck table header - deck_table = [ - ## add subscribed column - ("asset ID", "asset name", "issuer", "mode") - ] - table = AsciiTable(deck_table, title="Decks") - - @classmethod - def dtl(cls, deck): - '''deck-to-list deck to table-printable list''' - - l = [] - l.append(deck["asset_id"][:20]) - l.append(deck["name"]) - l.append(deck["issuer"]) - l.append(deck["issue_mode"]) - - return l - - @classmethod - def pack_decks_for_printing(cls): - - assert cls.decks, {"error": "No decks found!"} - - for i in cls.decks: - cls.deck_table.append( - cls.dtl(i.__dict__) - ) - - -def find_deck(provider, key: str) -> list: - '''find deck by ''' +def search_decks(key: str) -> list: + '''search decks by ''' decks = list(pa.find_all_valid_decks(provider, deck_version=Settings.deck_version, prod=Settings.production)) for i in decks: @@ -163,7 +107,7 @@ def find_deck(provider, key: str) -> list: return [d for d in decks if key in d.__dict__.values()] -def deck_list(provider): +def list(): '''list command''' decks = pa.find_all_valid_decks(provider=provider, deck_version=Settings.deck_version, @@ -173,69 +117,67 @@ def deck_list(provider): print(d.table.table) -def deck_subscribe(provider, deck_id): - '''subscribe command, load deck p2th into local node, pass ''' - - try: - deck = find_deck(provider, deck_id)[0] - except IndexError: - print({"error": "Deck not found!"}) - return - pa.load_deck_p2th_into_local_node(provider, deck) +class SingleDeck: - -def deck_search(provider, key): - '''search commands, query decks by ''' - - decks = find_deck(provider, key) - d = ListDecks(provider, decks) - d.pack_decks_for_printing() - print(d.table.table) - - -def deck_info(provider, deck_id): - '''info commands, show full deck details''' - - try: - deck = find_deck(provider, deck_id)[0] - except IndexError: - print("\n", {"error": "Deck not found!"}) - return - info = DeckInfo(deck) - info.pack_decks_for_printing() - print(info.table.table) - - -def deck_balances(provider, deck_id): - '''show deck balances''' - - try: - deck = find_deck(provider, deck_id)[0] - except IndexError: - print("\n", {"error": "Deck not found!"}) - return - balances = get_state(provider, deck).balances - b = DeckBalances(deck, balances) - b.pack_for_printing() - print(b.table.table) - - -def deck_checksum(provider, deck_id): - '''info commands, show full deck details''' - - try: - deck = find_deck(provider, deck_id)[0] - except IndexError: - print("\n", {"error": "Deck not found!"}) - return - deck_state = get_state(provider, deck) - if deck_state.checksum: - print("\n", "Deck checksum is correct.") - else: - print("\n", "Deck checksum is incorrect.") - - -def new_deck(provider, deck, broadcast): + def __init__(self, deck_id): + self.deck = find_deck(deck_id) + try: + self.deck = find_deck(key)[0] + except IndexError: + raise Exception({"error": "Deck not found!"}) + + def info(self): + '''info commands, show full deck details''' + print_deck_info(deck) + + def balances(self): + '''show deck balances''' + print_deck_balances(self.deck, get_state(self.deck).balances) + + def subscribe(self): + '''subscribe command, load deck p2th into local node>''' + pa.load_deck_p2th_into_local_node(provider, self.deck) + + def checksum(self): + ''' verify checksum ''' + if get_state(self.deck).checksum: + print("\n", "Deck checksum is correct.") + else: + print("\n", "Deck checksum is incorrect.") + + @classmethod() + def options(self, func): + for option in ['info', 'balances', 'subscribe', 'checksum']: + func = click.option('--' + option, is_flag=True)(func) + return func + + + +@click.group(cls=DefaultGroup, default='find', default_if_no_args=True) +def deck(): + pass + + +@deck.command() +@click.argument('deck_id') +@SingleDeck.options +def find(deck_id, **options): + deck = SingleDeck(deck_id) + for option in options or ['info']: + deck[option]() + + +@deck.command() +@click.argument('deck_id') +def search(deck_id): + '''search commands, query decks by ''' + print_deck_list(search_decks(deck_id)) + + +@deck.command() +@click.argument('deck') +@click.option('--broadcast/--no-broadcast', default=False) +def new_deck(deck, broadcast): ''' Spawn a new PeerAssets deck. @@ -248,7 +190,7 @@ def new_deck(provider, deck, broadcast): deck["network"] = Settings.network deck["production"] = Settings.production #utxo = provider.select_inputs(0.02) # we need 0.02 PPC - utxo = default_account_utxo(provider, 0.02) + utxo = default_account_utxo(0.02) if utxo: change_address = change(utxo) else: @@ -266,7 +208,7 @@ def new_deck(provider, deck, broadcast): deck["asset_id"] = txid d = pa.Deck(**deck) - pa.load_deck_p2th_into_local_node(provider, d) # subscribe to deck + pa.load_deck_p2th_into_local_node(d) # subscribe to deck else: print("\nraw transaction:\n", signed["hex"], "\n") diff --git a/pacli/keystore.py b/pacli/keystore.py index 8515906..1f1c479 100644 --- a/pacli/keystore.py +++ b/pacli/keystore.py @@ -1,4 +1,4 @@ -import sys, pickle +import sys, os, pickle from binascii import hexlify, unhexlify import gnupg, getpass from pypeerassets.kutil import Kutil @@ -13,6 +13,9 @@ class GpgKeystore: def __init__(self, Settings, keyfile): assert Settings.keystore == "gnupg" + if not os.path.exists(keyfile): + open(keyfile, 'a').close() + self._key = Settings.gnupgkey self._keyfile = keyfile @@ -23,6 +26,12 @@ def __init__(self, Settings, keyfile): secring='secring.gpg') self.gpg = gnupg.GPG(**self._init_settings) + def unpickle(self, decrypted: bytes) -> dict: + return pickle.loads(unhexlify(str(decrypted).encode())) + + def pickle(self, data: dict) -> str: + return hexlify(pickle.dumps(data)).decode() + def read(self) -> dict: password = getpass.getpass("Input gpg key password:") contents = open(self._keyfile, 'rb').read() @@ -33,10 +42,10 @@ def read(self) -> dict: decrypted = self.gpg.decrypt(contents, passphrase=password) assert decrypted.ok, decrypted.status - return pickle.loads(unhexlify(str(decrypted).encode())) + return self.unpickle(decrypted) def write(self, data: dict) -> str: - encrypted = self.gpg.encrypt(hexlify(pickle.dumps(data)).decode(), self._key) + encrypted = self.gpg.encrypt(self.pickle(data), self._key) assert encrypted.ok, encrypted.status keyfile = open(self._keyfile, "w") keyfile.write(str(encrypted)) @@ -64,7 +73,9 @@ def __init__(self, keystore: GpgKeystore, **kwargs): self.__init__hack__ = Provider.__init__ self.__init__hack__(**kwargs) self.keystore = keystore - self.privkeys = keystore.read() + + def load_privkeys(self): + self.privkeys = self.keystore.read() def importprivkey(self, privkey: str, label: str) -> int: """import with