mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2024-09-11 18:58:59 +03:00
CLI send transaction and get transction (#849)
* CLI send transaction and get transction * get_transaction in help * Add fingerprint to suggestion * Remove bad file checked in * Add serialized program to unhashable types * Print size of mempool and min fee, optimize excessive name computation * Fix log * lint Co-authored-by: Gene Hoffman <30377676+hoffmang9@users.noreply.github.com>
This commit is contained in:
parent
d9396d4308
commit
e55844f847
@ -1,13 +1,29 @@
|
||||
import time
|
||||
from typing import Tuple, Optional, Callable
|
||||
|
||||
import aiohttp
|
||||
import asyncio
|
||||
|
||||
from src.rpc.wallet_rpc_client import WalletRpcClient
|
||||
from src.util.byte_types import hexstr_to_bytes
|
||||
from src.util.config import load_config
|
||||
from src.util.default_root import DEFAULT_ROOT_PATH
|
||||
from src.wallet.util.wallet_types import WalletType
|
||||
from src.cmds.units import units
|
||||
|
||||
|
||||
command_list = ["send", "show", "get_transaction"]
|
||||
|
||||
|
||||
def help_message():
|
||||
print("usage: chia wallet command")
|
||||
print(f"command can be any of {command_list}")
|
||||
print("")
|
||||
print("chia wallet send -f [optional fingerprint] -i [optional wallet_id] -a [amount] -f [fee] -t [target address]")
|
||||
print("chia wallet show -f [optional fingerprint] -i [optional wallet_id]")
|
||||
print("chia wallet get_transaction -f [optional fingerprint] -i [optional wallet_id] -tx [transaction id]")
|
||||
|
||||
|
||||
def make_parser(parser):
|
||||
parser.add_argument(
|
||||
"-wp",
|
||||
@ -18,103 +34,209 @@ def make_parser(parser):
|
||||
type=int,
|
||||
default=9256,
|
||||
)
|
||||
parser.set_defaults(function=show)
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
"--fingerprint",
|
||||
help="Set the fingerprint to specify which wallet to use.",
|
||||
type=int,
|
||||
)
|
||||
parser.add_argument("-i", "--id", help="Id of the wallet to use.", type=int, default=1)
|
||||
parser.add_argument(
|
||||
"-a",
|
||||
"--amount",
|
||||
help="How much chia to send, in TXCH/XCH",
|
||||
type=int,
|
||||
)
|
||||
parser.add_argument("-m", "--fee", help="Set the fees for the transaction.", type=int, default=0)
|
||||
parser.add_argument(
|
||||
"-t",
|
||||
"--address",
|
||||
help="Address to send the TXCH/XCH",
|
||||
type=str,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-tx",
|
||||
"--tx_id",
|
||||
help="transaction id to search for",
|
||||
type=str,
|
||||
)
|
||||
parser.add_argument(
|
||||
"command",
|
||||
help=f"Command can be any one of {command_list}",
|
||||
type=str,
|
||||
nargs="?",
|
||||
)
|
||||
parser.set_defaults(function=handler)
|
||||
parser.print_help = lambda self=parser: help_message()
|
||||
|
||||
|
||||
async def print_balances(wallet_client):
|
||||
async def get_transaction(args, wallet_client, fingerprint: int):
|
||||
if args.id is None:
|
||||
print("Please specify a wallet id with -i")
|
||||
return
|
||||
else:
|
||||
wallet_id = args.id
|
||||
if args.tx_id is None:
|
||||
print("Please specify a transaction id -tx")
|
||||
return
|
||||
else:
|
||||
transaction_id = hexstr_to_bytes(args.tx_id)
|
||||
tx = await wallet_client.get_transaction(wallet_id, transaction_id=transaction_id)
|
||||
print(tx)
|
||||
|
||||
|
||||
async def send(args, wallet_client, fingerprint: int):
|
||||
if args.id is None:
|
||||
print("Please specify a wallet id with -i")
|
||||
return
|
||||
else:
|
||||
wallet_id = args.id
|
||||
if args.amount is None:
|
||||
print("Please specify an amount with -a")
|
||||
return
|
||||
else:
|
||||
amount = args.amount
|
||||
if args.amount is None:
|
||||
print("Please specify the transaction fees with -m")
|
||||
return
|
||||
else:
|
||||
fee = args.fee
|
||||
if args.address is None:
|
||||
print("Please specify a target address with -t")
|
||||
return
|
||||
else:
|
||||
address = args.address
|
||||
|
||||
print("Submitting transaction...")
|
||||
res = await wallet_client.send_transaction(wallet_id, amount, address, fee)
|
||||
tx_id = res.name
|
||||
start = time.time()
|
||||
while time.time() - start < 10:
|
||||
await asyncio.sleep(0.1)
|
||||
tx = await wallet_client.get_transaction(wallet_id, tx_id)
|
||||
if len(tx.sent_to) > 0:
|
||||
print(f"Transaction submitted to nodes: {tx.sent_to}")
|
||||
print(f"Do chia wallet get_transaction -f {fingerprint} -tx 0x{tx_id} to get status")
|
||||
return
|
||||
|
||||
print("Transaction not yet submitted to nodes.")
|
||||
print(f"Do chia wallet get_transaction -f {fingerprint} -tx 0x{tx_id} to get status")
|
||||
|
||||
|
||||
async def print_balances(args, wallet_client, fingerprint: int):
|
||||
summaries_response = await wallet_client.get_wallets()
|
||||
print("Balances")
|
||||
print(f"Balances, fingerprint: {fingerprint}")
|
||||
for summary in summaries_response:
|
||||
wallet_id = summary["id"]
|
||||
balances = await wallet_client.get_wallet_balance(wallet_id)
|
||||
typ = WalletType(int(summary["type"])).name
|
||||
if typ != "STANDARD_WALLET":
|
||||
print(f"Wallet ID {wallet_id} type {typ} {summary['name']}")
|
||||
print(f" -Confirmed: {balances['confirmed_wallet_balance']/units['colouredcoin']}")
|
||||
print(
|
||||
f" -Confirmed: balances['confirmed_wallet_balance']"
|
||||
f"{balances['confirmed_wallet_balance']/units['colouredcoin']}"
|
||||
)
|
||||
print(f" -Unconfirmed: {balances['unconfirmed_wallet_balance']/units['colouredcoin']}")
|
||||
print(f" -Spendable: {balances['spendable_balance']/units['colouredcoin']}")
|
||||
print(f" -Frozen: {balances['frozen_balance']/units['colouredcoin']}")
|
||||
print(f" -Pending change: {balances['pending_change']/units['colouredcoin']}")
|
||||
else:
|
||||
print(f"Wallet ID {wallet_id} type {typ}")
|
||||
print(f" -Confirmed: {balances['confirmed_wallet_balance']/units['chia']} TXCH")
|
||||
print(f" -Unconfirmed: {balances['unconfirmed_wallet_balance']/units['chia']} TXCH")
|
||||
print(f" -Spendable: {balances['spendable_balance']/units['chia']} TXCH")
|
||||
print(f" -Frozen: {balances['frozen_balance']/units['chia']} TXCH")
|
||||
print(f" -Pending change: {balances['pending_change']/units['chia']} TXCH")
|
||||
print(
|
||||
f" -Confirmed: {balances['confirmed_wallet_balance']} mojo "
|
||||
f"({balances['confirmed_wallet_balance']/units['chia']} TXCH)"
|
||||
)
|
||||
print(
|
||||
f" -Unconfirmed: {balances['unconfirmed_wallet_balance']} mojo "
|
||||
f"({balances['unconfirmed_wallet_balance']/units['chia']} TXCH)"
|
||||
)
|
||||
print(
|
||||
f" -Spendable: {balances['spendable_balance']} mojo "
|
||||
f"({balances['spendable_balance']/units['chia']} TXCH)"
|
||||
)
|
||||
print(
|
||||
f" -Pending change: {balances['pending_change']} mojo "
|
||||
f"({balances['pending_change']/units['chia']} TXCH)"
|
||||
)
|
||||
|
||||
|
||||
async def wallet_loop(wallet_client):
|
||||
fingerprint = None
|
||||
while True:
|
||||
# if fingerprint is None:
|
||||
fingerprints = await wallet_client.get_public_keys()
|
||||
if len(fingerprints) == 0:
|
||||
print("No keys loaded. Run 'chia keys generate' or import a key.")
|
||||
return
|
||||
if len(fingerprints) == 1:
|
||||
fingerprint = fingerprints[0]
|
||||
log_in_response = await wallet_client.log_in(fingerprint)
|
||||
else:
|
||||
print("Choose wallet key:")
|
||||
for i, fp in enumerate(fingerprints):
|
||||
print(f"{i+1}) {fp}")
|
||||
val = None
|
||||
while val is None:
|
||||
val = input("Enter a number to pick or q to quit: ")
|
||||
if val == "q":
|
||||
return
|
||||
if not val.isdigit():
|
||||
async def get_wallet(wallet_client, fingerprint=None) -> Optional[Tuple[WalletRpcClient, int]]:
|
||||
fingerprints = await wallet_client.get_public_keys()
|
||||
if len(fingerprints) == 0:
|
||||
print("No keys loaded. Run 'chia keys generate' or import a key.")
|
||||
return None
|
||||
if fingerprint is not None:
|
||||
if fingerprint not in fingerprints:
|
||||
print(f"Fingerprint {fingerprint} does not exist")
|
||||
return None
|
||||
if len(fingerprints) == 1:
|
||||
fingerprint = fingerprints[0]
|
||||
if fingerprint is not None:
|
||||
log_in_response = await wallet_client.log_in(fingerprint)
|
||||
else:
|
||||
print("Choose wallet key:")
|
||||
for i, fp in enumerate(fingerprints):
|
||||
print(f"{i+1}) {fp}")
|
||||
val = None
|
||||
while val is None:
|
||||
val = input("Enter a number to pick or q to quit: ")
|
||||
if val == "q":
|
||||
return None
|
||||
if not val.isdigit():
|
||||
val = None
|
||||
else:
|
||||
index = int(val) - 1
|
||||
if index >= len(fingerprints):
|
||||
print("Invalid value")
|
||||
val = None
|
||||
continue
|
||||
else:
|
||||
index = int(val) - 1
|
||||
if index >= len(fingerprints):
|
||||
print("Invalid value")
|
||||
val = None
|
||||
continue
|
||||
else:
|
||||
fingerprint = fingerprints[index]
|
||||
log_in_response = await wallet_client.log_in(fingerprint)
|
||||
if log_in_response["success"] is False:
|
||||
if log_in_response["error"] == "not_initialized":
|
||||
use_cloud = True
|
||||
if "backup_path" in log_in_response:
|
||||
path = log_in_response["backup_path"]
|
||||
print(f"Backup file from backup.chia.net downloaded and written to: {path}")
|
||||
val = input("Do you want to use this file to restore from backup? (Y/N) ")
|
||||
if val.lower() == "y":
|
||||
log_in_response = await wallet_client.log_in_and_restore(fingerprint, path)
|
||||
else:
|
||||
use_cloud = False
|
||||
fingerprint = fingerprints[index]
|
||||
log_in_response = await wallet_client.log_in(fingerprint)
|
||||
if log_in_response["success"] is False:
|
||||
if log_in_response["error"] == "not_initialized":
|
||||
use_cloud = True
|
||||
if "backup_path" in log_in_response:
|
||||
path = log_in_response["backup_path"]
|
||||
print(f"Backup file from backup.chia.net downloaded and written to: {path}")
|
||||
val = input("Do you want to use this file to restore from backup? (Y/N) ")
|
||||
if val.lower() == "y":
|
||||
log_in_response = await wallet_client.log_in_and_restore(fingerprint, path)
|
||||
else:
|
||||
use_cloud = False
|
||||
|
||||
if "backup_path" not in log_in_response or use_cloud is False:
|
||||
if use_cloud is True:
|
||||
val = input(
|
||||
"No online backup file found, \n Press S to skip restore from backup"
|
||||
" \n Press F to use your own backup file: "
|
||||
)
|
||||
else:
|
||||
val = input(
|
||||
"Cloud backup declined, \n Press S to skip restore from backup"
|
||||
" \n Press F to use your own backup file: "
|
||||
)
|
||||
if "backup_path" not in log_in_response or use_cloud is False:
|
||||
if use_cloud is True:
|
||||
val = input(
|
||||
"No online backup file found, \n Press S to skip restore from backup"
|
||||
" \n Press F to use your own backup file: "
|
||||
)
|
||||
else:
|
||||
val = input(
|
||||
"Cloud backup declined, \n Press S to skip restore from backup"
|
||||
" \n Press F to use your own backup file: "
|
||||
)
|
||||
|
||||
if val.lower() == "s":
|
||||
log_in_response = await wallet_client.log_in_and_skip(fingerprint)
|
||||
elif val.lower() == "f":
|
||||
val = input("Please provide the full path to your backup file: ")
|
||||
log_in_response = await wallet_client.log_in_and_restore(fingerprint, val)
|
||||
if val.lower() == "s":
|
||||
log_in_response = await wallet_client.log_in_and_skip(fingerprint)
|
||||
elif val.lower() == "f":
|
||||
val = input("Please provide the full path to your backup file: ")
|
||||
log_in_response = await wallet_client.log_in_and_restore(fingerprint, val)
|
||||
|
||||
if "success" not in log_in_response or log_in_response["success"] is False:
|
||||
if "error" in log_in_response:
|
||||
error = log_in_response["error"]
|
||||
print(f"Error: {log_in_response[error]}")
|
||||
return
|
||||
await print_balances(wallet_client)
|
||||
break
|
||||
if "success" not in log_in_response or log_in_response["success"] is False:
|
||||
if "error" in log_in_response:
|
||||
error = log_in_response["error"]
|
||||
print(f"Error: {log_in_response[error]}")
|
||||
return None
|
||||
return wallet_client, fingerprint
|
||||
|
||||
|
||||
async def show_async(args, parser):
|
||||
async def execute_with_wallet(args, parser, function: Callable):
|
||||
if args.fingerprint is None:
|
||||
fingerprint = None
|
||||
else:
|
||||
fingerprint = args.fingerprint
|
||||
|
||||
try:
|
||||
config = load_config(DEFAULT_ROOT_PATH, "config.yaml")
|
||||
self_hostname = config["self_hostname"]
|
||||
@ -123,7 +245,13 @@ async def show_async(args, parser):
|
||||
else:
|
||||
wallet_rpc_port = args.wallet_rpc_port
|
||||
wallet_client = await WalletRpcClient.create(self_hostname, wallet_rpc_port, DEFAULT_ROOT_PATH, config)
|
||||
await wallet_loop(wallet_client)
|
||||
wallet_client_f = await get_wallet(wallet_client, fingerprint=fingerprint)
|
||||
if wallet_client_f is None:
|
||||
wallet_client.close()
|
||||
await wallet_client.await_closed()
|
||||
return
|
||||
wallet_client, fingerprint = wallet_client_f
|
||||
await function(args, wallet_client, fingerprint)
|
||||
|
||||
except Exception as e:
|
||||
if isinstance(e, aiohttp.client_exceptions.ClientConnectorError):
|
||||
@ -135,5 +263,18 @@ async def show_async(args, parser):
|
||||
await wallet_client.await_closed()
|
||||
|
||||
|
||||
def show(args, parser):
|
||||
return asyncio.run(show_async(args, parser))
|
||||
def handler(args, parser):
|
||||
if args.command is None or len(args.command) < 1:
|
||||
help_message()
|
||||
parser.exit(1)
|
||||
command = args.command
|
||||
if command not in command_list:
|
||||
help_message()
|
||||
parser.exit(1)
|
||||
|
||||
if command == "get_transaction":
|
||||
return asyncio.run(execute_with_wallet(args, parser, get_transaction))
|
||||
if command == "send":
|
||||
return asyncio.run(execute_with_wallet(args, parser, send))
|
||||
elif command == "show":
|
||||
return asyncio.run(execute_with_wallet(args, parser, print_balances))
|
||||
|
@ -143,7 +143,6 @@ class FullNodeAPI:
|
||||
transaction = full_node_protocol.RespondTransaction(spend_bundle)
|
||||
|
||||
msg = Message("respond_transaction", transaction)
|
||||
self.log.debug(f"sending transaction (tx_id: {spend_bundle.name()}) to peer")
|
||||
return msg
|
||||
|
||||
@peer_required
|
||||
@ -167,26 +166,35 @@ class FullNodeAPI:
|
||||
return None
|
||||
|
||||
# Ignore if we have already added this transaction
|
||||
if self.full_node.mempool_manager.get_spendbundle(tx.transaction.name()) is not None:
|
||||
return None
|
||||
spend_name = tx.transaction.name()
|
||||
if self.full_node.mempool_manager.get_spendbundle(spend_name) is not None:
|
||||
return None
|
||||
|
||||
# This is important because we might not have added the spend bundle, but we are about to add it, so it
|
||||
# prevents double processing
|
||||
if self.full_node.mempool_manager.seen(spend_name):
|
||||
return None
|
||||
# Adds it to seen so we don't double process
|
||||
self.full_node.mempool_manager.add_and_maybe_pop_seen(spend_name)
|
||||
|
||||
# Do the expensive pre-validation outside the lock
|
||||
cost_result = await self.full_node.mempool_manager.pre_validate_spendbundle(tx.transaction)
|
||||
|
||||
async with self.full_node.blockchain.lock:
|
||||
if self.full_node.mempool_manager.get_spendbundle(tx.transaction.name()) is not None:
|
||||
# Check for unnecessary addition
|
||||
if self.full_node.mempool_manager.get_spendbundle(spend_name) is not None:
|
||||
return None
|
||||
# Performs DB operations within the lock
|
||||
cost, status, error = await self.full_node.mempool_manager.add_spendbundle(
|
||||
tx.transaction, cost_result, spend_name
|
||||
)
|
||||
if status == MempoolInclusionStatus.SUCCESS:
|
||||
self.log.debug(f"Added transaction to mempool: {tx.transaction.name()}")
|
||||
fees = tx.transaction.fees()
|
||||
self.log.debug(f"Added transaction to mempool: {spend_name} cost: {cost} fees {fees}")
|
||||
assert fees >= 0
|
||||
assert cost is not None
|
||||
new_tx = full_node_protocol.NewTransaction(
|
||||
tx.transaction.name(),
|
||||
spend_name,
|
||||
cost,
|
||||
uint64(tx.transaction.fees()),
|
||||
)
|
||||
@ -194,9 +202,7 @@ class FullNodeAPI:
|
||||
await self.server.send_to_all_except([message], NodeType.FULL_NODE, peer.peer_node_id)
|
||||
else:
|
||||
self.full_node.mempool_manager.remove_seen(spend_name)
|
||||
self.log.warning(
|
||||
f"Was not able to add transaction with id {tx.transaction.name()}, {status} error: {error}"
|
||||
)
|
||||
self.log.warning(f"Was not able to add transaction with id {spend_name}, {status} error: {error}")
|
||||
return None
|
||||
|
||||
@api_request
|
||||
@ -1002,7 +1008,7 @@ class FullNodeAPI:
|
||||
if self.full_node.mempool_manager.seen(spend_name):
|
||||
return None
|
||||
self.full_node.mempool_manager.add_and_maybe_pop_seen(spend_name)
|
||||
self.log.debug(f"Processing transaction: {request.transaction.name()}")
|
||||
self.log.debug(f"Processing transaction: {spend_name}")
|
||||
# Ignore if syncing
|
||||
if self.full_node.sync_store.get_sync_mode():
|
||||
status = MempoolInclusionStatus.FAILED
|
||||
@ -1014,14 +1020,14 @@ class FullNodeAPI:
|
||||
request.transaction, cost_result, spend_name
|
||||
)
|
||||
if status == MempoolInclusionStatus.SUCCESS:
|
||||
self.log.debug(f"Added transaction to mempool: {request.transaction.name()}")
|
||||
self.log.debug(f"Added transaction to mempool: {spend_name}")
|
||||
# Only broadcast successful transactions, not pending ones. Otherwise it's a DOS
|
||||
# vector.
|
||||
fees = request.transaction.fees()
|
||||
assert fees >= 0
|
||||
assert cost is not None
|
||||
new_tx = full_node_protocol.NewTransaction(
|
||||
request.transaction.name(),
|
||||
spend_name,
|
||||
cost,
|
||||
uint64(request.transaction.fees()),
|
||||
)
|
||||
@ -1029,22 +1035,19 @@ class FullNodeAPI:
|
||||
await self.full_node.server.send_to_all([msg], NodeType.FULL_NODE)
|
||||
else:
|
||||
self.log.warning(
|
||||
f"Wasn't able to add transaction with id {request.transaction.name()}, "
|
||||
f"status {status} error: {error}"
|
||||
f"Wasn't able to add transaction with id {spend_name}, " f"status {status} error: {error}"
|
||||
)
|
||||
|
||||
error_name = error.name if error is not None else None
|
||||
if status == MempoolInclusionStatus.SUCCESS:
|
||||
response = wallet_protocol.TransactionAck(request.transaction.name(), status, error_name)
|
||||
response = wallet_protocol.TransactionAck(spend_name, status, error_name)
|
||||
else:
|
||||
self.full_node.mempool_manager.remove_seen(spend_name)
|
||||
# If if failed/pending, but it previously succeeded (in mempool), this is idempotence, return SUCCESS
|
||||
if self.full_node.mempool_manager.get_spendbundle(request.transaction.name()) is not None:
|
||||
response = wallet_protocol.TransactionAck(
|
||||
request.transaction.name(), MempoolInclusionStatus.SUCCESS, None
|
||||
)
|
||||
if self.full_node.mempool_manager.get_spendbundle(spend_name) is not None:
|
||||
response = wallet_protocol.TransactionAck(spend_name, MempoolInclusionStatus.SUCCESS, None)
|
||||
else:
|
||||
response = wallet_protocol.TransactionAck(request.transaction.name(), status, error_name)
|
||||
response = wallet_protocol.TransactionAck(spend_name, status, error_name)
|
||||
msg = Message("transaction_ack", response)
|
||||
return msg
|
||||
|
||||
|
@ -408,6 +408,9 @@ class MempoolManager:
|
||||
self.potential_txs = {}
|
||||
for tx, cached_result, cached_name in potential_txs_copy.values():
|
||||
await self.add_spendbundle(tx, cached_result, cached_name)
|
||||
log.debug(
|
||||
f"Size of mempool: {len(self.mempool.spends)}, minimum fee to get in: {self.mempool.get_min_fee_rate()}"
|
||||
)
|
||||
|
||||
async def get_items_not_in_filter(self, mempool_filter: PyBIP158) -> List[MempoolItem]:
|
||||
items: List[MempoolItem] = []
|
||||
|
@ -8,7 +8,7 @@ import pprint
|
||||
from enum import Enum
|
||||
from typing import Any, BinaryIO, List, Type, get_type_hints, Dict, Tuple
|
||||
from src.util.byte_types import hexstr_to_bytes
|
||||
from src.types.program import Program
|
||||
from src.types.program import Program, SerializedProgram
|
||||
from src.util.hash import std_hash
|
||||
|
||||
from blspy import PrivateKey, G1Element, G2Element
|
||||
@ -48,6 +48,7 @@ unhashable_types = [
|
||||
G1Element,
|
||||
G2Element,
|
||||
Program,
|
||||
SerializedProgram,
|
||||
]
|
||||
# JSON does not support big ints, so these types must be serialized differently in JSON
|
||||
big_ints = [uint64, int64, uint128, int512]
|
||||
|
Loading…
Reference in New Issue
Block a user