#!/usr/bin/env python3 """ The wallet allows a user to query account balance, send money, and get status information about a miner. """ __all__ = [] import argparse import requests import sys import json from binascii import hexlify import logging logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)-8s %(message)s") from src.blockchain import Blockchain from src.block import Block from src.transaction import Transaction, TransactionTarget, TransactionInput from src.crypto import Signing def send_transaction(sess, url, transaction): resp = sess.put(url + 'new-transaction', data=json.dumps(transaction.to_json_compatible()), headers={"Content-Type": "application/json"}) resp.raise_for_status() def network_info(sess, url): resp = sess.get(url + 'network-info') resp.raise_for_status() return resp.json() def get_transactions(sess, url, pubkey): resp = sess.post(url + 'transactions', data=pubkey.as_bytes(), headers={"Content-Type": "application/json"}) resp.raise_for_status() return [Transaction.from_json_compatible(t) for t in resp.json()] def show_balance(sess, url, pubkeys): resp = sess.post(url + "show-balance", data=json.dumps([pk.to_json_compatible() for pk in pubkeys]), headers={"Content-Type": "application/json"}) resp.raise_for_status() return zip(pubkeys, resp.json()) def build_transaction(sess, url, source_keys, targets, change_key, transaction_fee): resp = sess.post(url + "build-transaction", data=json.dumps({ "sender-pubkeys": [k.to_json_compatible() for k in source_keys], "amount": sum(t.amount for t in targets) + transaction_fee, }), headers={"Content-Type": "application/json"}) resp.raise_for_status() resp = resp.json() remaining = resp['remaining_amount'] if remaining < 0: print("You do not have sufficient funds for this transaction. ({} missing)".format(-remaining), file=sys.stderr) sys.exit(2) elif remaining > 0: targets = targets + [TransactionTarget(change_key, remaining)] inputs = [TransactionInput.from_json_compatible(i) for i in resp['inputs']] trans = Transaction(inputs, targets) trans.sign([source_keys[idx] for idx in resp['key_indices']]) send_transaction(sess, url, trans) def parse_targets(): start = True def parse(val): nonlocal start if start: val = Signing.from_file(val) else: val = int(val) start = not start return val return parse def private_signing(path): val = Signing.from_file(path) if not val.has_private: raise ValueError("The specified key is not a private key.") return val def main(): parser = argparse.ArgumentParser(description="Wallet.") parser.add_argument("--miner-port", default=40203, type=int, help="The RPC port of the miner to connect to.") subparsers = parser.add_subparsers(dest="command") balance = subparsers.add_parser("show-balance", help="Shows the current balance of the public key " "stored in the specified file.") balance.add_argument("key", nargs="+", type=Signing.from_file) trans = subparsers.add_parser("show-transactions", help="Shows all transactions involving the public key " "stored in the specified file.") trans.add_argument("key", nargs="+", type=Signing.from_file) subparsers.add_parser("show-network", help="Prints networking information about the miner.") transfer = subparsers.add_parser("transfer", help="Transfer money.") transfer.add_argument("--private-key", type=private_signing, default=[], action="append", required=True, help="The private key(s) whose coins should be used for the transfer.") transfer.add_argument("--change-key", type=Signing.from_file, required=True, help="The private key where any remaining coins are sent to.") transfer.add_argument("--transaction-fee", type=int, default=0, help="The transaction fee you want to pay to the miner.") transfer.add_argument("target", nargs='*', metavar=("TARGET_KEY AMOUNT"), type=parse_targets(), help="The private key(s) whose coins should be used for the transfer.") args = parser.parse_args() url = "http://localhost:{}/".format(args.miner_port) s = requests.session() if args.command == 'show-transactions': for key in args.key: for trans in get_transactions(s, url, key): print(trans.to_json_compatible()) elif args.command == 'show-balance': total = 0 for pubkey, balance in show_balance(s, url, args.key): print("{}: {}".format(hexlify(pubkey.as_bytes()), balance)) total += balance print() print("total: {}".format(total)) elif args.command == 'show-network': for [k, v] in network_info(s, url): print("{}\t{}".format(k, v)) elif args.command == 'transfer': if len(args.target) % 2: print("Missing amount to transfer for last target key.\n", file=sys.stderr) parser.parse_args(["--help"]) targets = [TransactionTarget(k, a) for k, a in zip(args.target[::2], args.target[1::2])] build_transaction(s, url, args.private_key, targets, args.change_key, args.transaction_fee) else: print("You need to specify what to do.\n", file=sys.stderr) parser.parse_args(["--help"]) if __name__ == '__main__': main()