| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134 |
- #!/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
- 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):
- 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),
- }), 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("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)
- else:
- print("You need to specify what to do.\n", file=sys.stderr)
- parser.parse_args(["--help"])
- if __name__ == '__main__':
- main()
|