Refactor RPCs

This commit is contained in:
Mariano Sorgente
2020-10-01 11:07:12 -07:00
committed by Gene Hoffman
parent 5303db737b
commit 08ac82001c
16 changed files with 612 additions and 661 deletions
@@ -290,8 +290,8 @@ export const handle_message = (store, payload) => {
}
}
if (payload.data.success === false) {
if (payload.data.reason) {
store.dispatch(openDialog("Error: ", payload.data.reason));
if (payload.data.error) {
store.dispatch(openDialog("Error: ", payload.data.error));
}
}
};
+5 -3
View File
@@ -62,9 +62,11 @@ def check_keys(new_root):
break
for sk, _ in all_sks:
all_targets.append(
encode_puzzle_hash(create_puzzlehash_for_pk(
master_sk_to_wallet_sk(sk, uint32(i)).get_g1()
))
encode_puzzle_hash(
create_puzzlehash_for_pk(
master_sk_to_wallet_sk(sk, uint32(i)).get_g1()
)
)
)
if all_targets[-1] == config["farmer"].get("xch_target_address"):
stop_searching_for_farmer = True
+3 -7
View File
@@ -319,18 +319,14 @@ async def show_async(args, parser):
else:
wallet_rpc_port = args.wallet_rpc_port
wallet_client = await WalletRpcClient.create(self_hostname, wallet_rpc_port)
get_keys_response = await wallet_client.get_keys()
if (
"public_key_fingerprints" not in get_keys_response
or len(get_keys_response["public_key_fingerprints"]) == 0
):
fingerprints = await wallet_client.get_public_keys()
if len(fingerprints) == 0:
print("Error, no keys loaded")
wallet_client.close()
await wallet_client.await_closed()
client.close()
await client.await_closed()
return
fingerprints = get_keys_response["public_key_fingerprints"]
fingerprint = None
if len(fingerprints) == 1:
fingerprint = fingerprints[0][0]
@@ -403,7 +399,7 @@ async def show_async(args, parser):
error = log_in_response["error"]
print(f"Error: {log_in_response[error]}")
summaries_response = await wallet_client.get_wallet_summaries()
summaries_response = await wallet_client.get_wallets()
if "wallet_summaries" not in summaries_response:
print("Wallet summary cannot be displayed")
else:
+3 -1
View File
@@ -1824,7 +1824,9 @@ class FullNode:
tx.transaction
)
if status == MempoolInclusionStatus.SUCCESS:
self.log.info(f"Added transaction to mempool: {tx.transaction.name()}")
self.log.info(
f"Added transaction to mempool: {tx.transaction.name()}"
)
# Only broadcast successful transactions, not pending ones. Otherwise it's a DOS
# vector.
fees = tx.transaction.fees()
+2 -2
View File
@@ -26,7 +26,7 @@ class FarmerRpcApi:
response = []
seen_challenges: Set = set()
if self.service.current_weight == 0:
return {"success": True, "latest_challenges": []}
return {"latest_challenges": []}
for pospace_fin in self.service.challenges[self.service.current_weight]:
estimates = self.service.challenge_to_estimates.get(
pospace_fin.challenge_hash, []
@@ -43,4 +43,4 @@ class FarmerRpcApi:
}
)
seen_challenges.add(pospace_fin.challenge_hash)
return {"success": True, "latest_challenges": response}
return {"latest_challenges": response}
+28 -29
View File
@@ -1,8 +1,6 @@
from src.full_node.full_node import FullNode
from typing import Callable, List, Optional, Dict
from aiohttp import web
from src.types.header import Header
from src.types.full_block import FullBlock
from src.util.ints import uint32, uint64, uint128
@@ -60,7 +58,7 @@ class FullNodeRpcApi:
difficulty: uint64 = self.service.blockchain.get_next_difficulty(lca)
lca_block = await self.service.block_store.get_block(lca.header_hash)
if lca_block is None:
return None
raise ValueError("No LCA block is set")
min_iters: uint64 = self.service.blockchain.get_next_min_iters(lca_block)
ips: uint64 = uint64(
min_iters
@@ -93,7 +91,6 @@ class FullNodeRpcApi:
space = {"space": uint128(0)}
assert space is not None
response: Dict = {
"success": True,
"blockchain_state": {
"tips": tips,
"tip_hashes": tip_hashes,
@@ -114,43 +111,43 @@ class FullNodeRpcApi:
async def get_block(self, request: Dict) -> Optional[Dict]:
if "header_hash" not in request:
return None
raise ValueError("No header_hash in request")
header_hash = hexstr_to_bytes(request["header_hash"])
block: Optional[FullBlock] = await self.service.block_store.get_block(
header_hash
)
if block is None:
return None
raise ValueError(f"Block {header_hash.hex()} not found")
return {"success": True, "block": block}
return {"block": block}
async def get_header_by_height(self, request: Dict) -> Optional[Dict]:
if "height" not in request:
return None
raise ValueError("No height in request")
height = request["height"]
header_height = uint32(int(height))
header_hash: Optional[bytes32] = self.service.blockchain.height_to_hash.get(
header_height, None
)
if header_hash is None:
return None
raise ValueError(f"Height {height} not found in chain")
header: Header = self.service.blockchain.headers[header_hash]
return {"success": True, "header": header}
return {"header": header}
async def get_header(self, request: Dict):
if "header_hash" not in request:
return None
raise ValueError("header_hash not in request")
header_hash_str = request["header_hash"]
header_hash = hexstr_to_bytes(header_hash_str)
header: Optional[Header] = self.service.blockchain.headers.get(
header_hash, None
)
return {"success": True, "header": header}
return {"header": header}
async def get_unfinished_block_headers(self, request: Dict) -> Optional[Dict]:
if "height" not in request:
return None
raise ValueError("height not in request")
height = request["height"]
response_headers: List[Header] = []
for block in (
@@ -158,7 +155,7 @@ class FullNodeRpcApi:
).values():
if block.height == height:
response_headers.append(block.header)
return {"success": True, "headers": response_headers}
return {"headers": response_headers}
async def get_latest_block_headers(self, request: Dict) -> Optional[Dict]:
headers: Dict[bytes32, Header] = {}
@@ -240,7 +237,7 @@ class FullNodeRpcApi:
unfinished_with_meta.extend(finished_with_meta)
return {"success": True, "latest_blocks": unfinished_with_meta}
return {"latest_blocks": unfinished_with_meta}
async def get_total_miniters(self, newer_block, older_block) -> Optional[uint64]:
"""
@@ -251,7 +248,7 @@ class FullNodeRpcApi:
older_block.prev_header_hash
)
if older_block_parent is None:
return None
raise ValueError("Older block not found")
older_diff = older_block.weight - older_block_parent.weight
curr_mi = calculate_min_iters_from_iterations(
older_block.proof_of_space,
@@ -269,17 +266,17 @@ class FullNodeRpcApi:
uint32(int(curr_h))
)
if curr_b_header_hash is None:
return None
raise ValueError(f"Curr header hash {curr_h} not found")
curr_b_block = await self.service.block_store.get_block(
curr_b_header_hash
)
if curr_b_block is None or curr_b_block.proof_of_time is None:
return None
raise ValueError("Block invalid")
curr_parent = await self.service.block_store.get_block(
curr_b_block.prev_header_hash
)
if curr_parent is None:
return None
raise ValueError("Curr parent block invalid")
curr_diff = curr_b_block.weight - curr_parent.weight
curr_mi = calculate_min_iters_from_iterations(
curr_b_block.proof_of_space,
@@ -288,7 +285,7 @@ class FullNodeRpcApi:
self.service.constants.NUMBER_ZERO_BITS_CHALLENGE_SIG,
)
if curr_mi is None:
raise web.HTTPBadRequest()
raise ValueError("Curr_mi invalid")
total_mi = uint64(total_mi + curr_mi)
return total_mi
@@ -302,29 +299,31 @@ class FullNodeRpcApi:
"newer_block_header_hash" not in request
or "older_block_header_hash" not in request
):
return None
raise ValueError(
"Invalid request. newer_block_header_hash and older_block_header_hash required"
)
newer_block_hex = request["newer_block_header_hash"]
older_block_hex = request["older_block_header_hash"]
if newer_block_hex == older_block_hex:
return None
raise ValueError("New and old must not be the same")
newer_block_bytes = hexstr_to_bytes(newer_block_hex)
older_block_bytes = hexstr_to_bytes(older_block_hex)
newer_block = await self.service.block_store.get_block(newer_block_bytes)
if newer_block is None:
raise web.HTTPNotFound()
raise ValueError("Newer block not found")
older_block = await self.service.block_store.get_block(older_block_bytes)
if older_block is None:
raise web.HTTPNotFound()
raise ValueError("Newer block not found")
delta_weight = newer_block.header.data.weight - older_block.header.data.weight
delta_iters = (
newer_block.header.data.total_iters - older_block.header.data.total_iters
)
total_min_inters = await self.get_total_miniters(newer_block, older_block)
if total_min_inters is None:
raise web.HTTPNotFound()
raise ValueError("Min iters invalid")
delta_iters -= total_min_inters
weight_div_iters = delta_weight / delta_iters
tips_adjustment_constant = 0.65
@@ -338,14 +337,14 @@ class FullNodeRpcApi:
* tips_adjustment_constant
* eligible_plots_filter_mult
)
return {"success": True, "space": uint128(int(network_space_bytes_estimate))}
return {"space": uint128(int(network_space_bytes_estimate))}
async def get_unspent_coins(self, request: Dict) -> Optional[Dict]:
"""
Retrieves the unspent coins for a given puzzlehash.
"""
if "puzzle_hash" not in request:
return None
raise ValueError("Puzzle hash not in request")
puzzle_hash = hexstr_to_bytes(request["puzzle_hash"])
header_hash = request.get("header_hash", None)
@@ -361,7 +360,7 @@ class FullNodeRpcApi:
)
)
return {"success": True, "coin_records": coin_records}
return {"coin_records": coin_records}
async def get_heaviest_block_seen(self, request: Dict) -> Optional[Dict]:
tips: List[Header] = self.service.blockchain.get_current_tips()
@@ -373,4 +372,4 @@ class FullNodeRpcApi:
for _, pot_block in potential_tips:
if pot_block.weight > max_tip.weight:
max_tip = pot_block.header
return {"success": True, "tip": max_tip}
return {"tip": max_tip}
+8 -17
View File
@@ -1,4 +1,3 @@
import aiohttp
from typing import Dict, Optional, List
from src.types.full_block import FullBlock
from src.types.header import Header
@@ -30,10 +29,8 @@ class FullNodeRpcClient(RpcClient):
async def get_block(self, header_hash) -> Optional[FullBlock]:
try:
response = await self.fetch("get_block", {"header_hash": header_hash.hex()})
except aiohttp.client_exceptions.ClientResponseError as e:
if e.message == "Not Found":
return None
raise
except Exception:
return None
return FullBlock.from_json_dict(response["block"])
async def get_header_by_height(self, header_height) -> Optional[Header]:
@@ -41,10 +38,8 @@ class FullNodeRpcClient(RpcClient):
response = await self.fetch(
"get_header_by_height", {"height": header_height}
)
except aiohttp.client_exceptions.ClientResponseError as e:
if e.message == "Not Found":
return None
raise
except Exception:
return None
return Header.from_json_dict(response["header"])
async def get_header(self, header_hash) -> Optional[Header]:
@@ -54,10 +49,8 @@ class FullNodeRpcClient(RpcClient):
)
if response["header"] is None:
return None
except aiohttp.client_exceptions.ClientResponseError as e:
if e.message == "Not Found":
return None
raise
except Exception:
return None
return Header.from_json_dict(response["header"])
async def get_unfinished_block_headers(self, height: uint32) -> List[Header]:
@@ -75,10 +68,8 @@ class FullNodeRpcClient(RpcClient):
"older_block_header_hash": older_block_header_hash,
},
)
except aiohttp.client_exceptions.ClientResponseError as e:
if e.message == "Not Found":
return None
raise
except Exception:
return None
return network_space_bytes_estimate["space"]
async def get_unspent_coins(
+11 -9
View File
@@ -29,7 +29,6 @@ class HarvesterRpcApi:
async def get_plots(self, request: Dict) -> Dict:
plots, failed_to_open, not_found = self.service._get_plots()
return {
"success": True,
"plots": plots,
"failed_to_open_filenames": failed_to_open,
"not_found_filenames": not_found,
@@ -37,23 +36,26 @@ class HarvesterRpcApi:
async def refresh_plots(self, request: Dict) -> Dict:
await self.service._refresh_plots()
return {"success": True}
return {}
async def delete_plot(self, request: Dict) -> Dict:
filename = request["filename"]
success = self.service._delete_plot(filename)
return {"success": success}
if self.service._delete_plot(filename):
return {}
raise ValueError(f"Not able to delete file {filename}")
async def add_plot_directory(self, request: Dict) -> Dict:
dirname = request["dirname"]
success = await self.service._add_plot_directory(dirname)
return {"success": success}
if await self.service._add_plot_directory(dirname):
return {}
raise ValueError(f"Did not add plot directory {dirname}")
async def get_plot_directories(self, request: Dict) -> Dict:
plot_dirs = await self.service._get_plot_directories()
return {"success": True, "directories": plot_dirs}
return {"directories": plot_dirs}
async def remove_plot_directory(self, request: Dict) -> Dict:
dirname = request["dirname"]
success = await self.service._remove_plot_directory(dirname)
return {"success": success}
if await self.service._remove_plot_directory(dirname):
return {}
raise ValueError(f"Did not remove plot directory {dirname}")
+7 -1
View File
@@ -31,7 +31,13 @@ class RpcClient:
async def fetch(self, path, request_json):
async with self.session.post(self.url + path, json=request_json) as response:
response.raise_for_status()
return await response.json()
res_json = await response.json()
if not res_json["success"]:
if "error" in res_json:
raise Exception(res_json["error"])
else:
raise Exception()
return res_json
async def get_connections(self) -> List[Dict]:
response = await self.fetch("get_connections", {})
+33 -14
View File
@@ -59,16 +59,27 @@ class RpcServer:
def _wrap_http_handler(self, f) -> Callable:
async def inner(request) -> aiohttp.web.Response:
request_data = await request.json()
res_object = await f(request_data)
if res_object is None:
raise aiohttp.web.HTTPNotFound()
try:
res_object = await f(request_data)
if res_object is None:
res_object = {}
if "success" not in res_object:
res_object["success"] = True
except Exception as e:
tb = traceback.format_exc()
self.log.warning(f"Error while handling message: {tb}")
if len(e.args) > 0:
res_object = {"success": False, "error": f"{e.args[0]}"}
else:
res_object = {"success": False, "error": f"{e}"}
return obj_to_response(res_object)
return inner
async def get_connections(self, request: Dict) -> Dict:
if self.rpc_api.service.global_connections is None:
return {"success": False}
raise ValueError("Global connections is not set")
connections = self.rpc_api.service.global_connections.get_connections()
con_info = [
{
@@ -86,7 +97,7 @@ class RpcServer:
}
for con in connections
]
return {"success": True, "connections": con_info}
return {"connections": con_info}
async def open_connection(self, request: Dict):
host = request["host"]
@@ -98,8 +109,8 @@ class RpcServer:
if getattr(self.rpc_api.service, "server", None) is None or not (
await self.rpc_api.service.server.start_client(target_node, on_connect)
):
raise aiohttp.web.HTTPInternalServerError()
return {"success": True}
raise ValueError("Start client failed, or server is not set")
return {}
async def close_connection(self, request: Dict):
node_id = hexstr_to_bytes(request["node_id"])
@@ -111,10 +122,10 @@ class RpcServer:
if c.node_id == node_id
]
if len(connections_to_close) == 0:
raise aiohttp.web.HTTPNotFound()
raise ValueError(f"Connection with node_id {node_id.hex()} does not exist")
for connection in connections_to_close:
self.rpc_api.service.global_connections.close(connection)
return {"success": True}
return {}
async def stop_node(self, request):
"""
@@ -122,7 +133,7 @@ class RpcServer:
"""
if self.stop_cb is not None:
self.stop_cb()
return {"success": True}
return {}
async def ws_api(self, message):
"""
@@ -145,8 +156,8 @@ class RpcServer:
f = getattr(self.rpc_api, command, None)
if f is not None:
return await f(data)
else:
return {"error": f"unknown_command {command}"}
raise ValueError(f"unknown_command {command}")
async def safe_handle(self, websocket, payload):
message = None
@@ -154,14 +165,22 @@ class RpcServer:
message = json.loads(payload)
self.log.info(f"Rpc call <- {message['command']}")
response = await self.ws_api(message)
# Only respond if we return something from api call
if response is not None:
log.info(f"Rpc response -> {message['command']}")
# Set success to true automatically (unless it's already set)
if "success" not in response:
response["success"] = True
await websocket.send_str(format_response(message, response))
except Exception as e:
tb = traceback.format_exc()
self.log.error(f"Error while handling message: {tb}")
error = {"success": False, "error": f"{e}"}
self.log.warning(f"Error while handling message: {tb}")
if len(e.args) > 0:
error = {"success": False, "error": f"{e.args[0]}"}
else:
error = {"success": False, "error": f"{e}"}
if message is None:
return
await websocket.send_str(format_response(message, error))
+474 -529
View File
File diff suppressed because it is too large Load Diff
+13 -20
View File
@@ -1,5 +1,8 @@
from typing import Dict
from typing import Dict, List
from src.rpc.rpc_client import RpcClient
from src.wallet.transaction_record import TransactionRecord
from src.util.ints import uint64
from src.types.sized_bytes import bytes32
class WalletRpcClient(RpcClient):
@@ -11,14 +14,12 @@ class WalletRpcClient(RpcClient):
to the full node.
"""
async def get_wallet_summaries(self) -> Dict:
return await self.fetch("get_wallet_summaries", {})
async def get_wallets(self) -> Dict:
return (await self.fetch("get_wallets", {}))["wallets"]
async def get_wallet_balance(self, wallet_id: str) -> Dict:
return await self.fetch("get_wallet_balance", {"wallet_id": wallet_id})
<<<<<<< HEAD
=======
async def send_transaction(
self, wallet_id: str, amount: uint64, address: str, fee: uint64 = uint64(0)
) -> Dict:
@@ -38,9 +39,7 @@ class WalletRpcClient(RpcClient):
async def get_next_address(self, wallet_id: str) -> Dict:
return await self.fetch("get_next_address", {"wallet_id": wallet_id})
async def get_transaction(
self, wallet_id: str, transaction_id: bytes32
) -> Dict:
async def get_transaction(self, wallet_id: str, transaction_id: bytes32) -> Dict:
res = await self.fetch(
"get_transaction",
@@ -49,18 +48,12 @@ class WalletRpcClient(RpcClient):
res["transaction"] = TransactionRecord.from_json_dict(res["transaction"])
return res
async def get_transactions(
self, wallet_id: str,
) -> Dict:
res = await self.fetch(
"get_transactions",
{"walled_id": wallet_id},
)
parsed = [TransactionRecord.from_json_dict(tx) for tx in res["transactions"])
res[transactions] = parsed
async def get_transactions(self, wallet_id: str,) -> Dict:
res = await self.fetch("get_transactions", {"walled_id": wallet_id},)
parsed = [TransactionRecord.from_json_dict(tx) for tx in res["transactions"]]
res["transactions"] = parsed
return res
>>>>>>> f1c565a8... More test
async def log_in(self, fingerprint) -> Dict:
return await self.fetch(
"log_in",
@@ -92,5 +85,5 @@ class WalletRpcClient(RpcClient):
},
)
async def get_keys(self) -> Dict:
return await self.fetch("get_public_keys", {})
async def get_public_keys(self) -> List:
return (await self.fetch("get_public_keys", {}))["public_key_fingerprints"]
+1 -6
View File
@@ -15,10 +15,5 @@ def run_program(
pre_eval_f=None,
):
return default_run_program(
program,
args,
quote_kw,
operator_lookup,
max_cost,
pre_eval_f=pre_eval_f,
program, args, quote_kw, operator_lookup, max_cost, pre_eval_f=pre_eval_f,
)
+1 -8
View File
@@ -253,14 +253,7 @@ class CCWallet:
inner_puzzle = await self.inner_puzzle_for_cc_puzhash(coin.puzzle_hash)
lineage_proof = Program.to(
(
1,
[
coin.parent_coin_info,
inner_puzzle.get_tree_hash(),
coin.amount,
],
)
(1, [coin.parent_coin_info, inner_puzzle.get_tree_hash(), coin.amount])
)
await self.add_lineage(coin.name(), lineage_proof)
+1 -1
View File
@@ -376,7 +376,7 @@ class WalletStateManager:
"""
self.pending_tx_callback = callback
def state_changed(self, state: str, wallet_id: int = None, data_object = {}):
def state_changed(self, state: str, wallet_id: int = None, data_object={}):
"""
Calls the callback if it's present.
"""
+20 -12
View File
@@ -1,15 +1,13 @@
import asyncio
from secrets import token_bytes
import pytest
from src.protocols import full_node_protocol
from src.simulator.simulator_protocol import FarmNewBlockProtocol, ReorgProtocol
from src.simulator.simulator_protocol import FarmNewBlockProtocol
from src.types.peer_info import PeerInfo
from src.util.ints import uint16, uint32, uint64
from src.util.ints import uint16, uint32
from tests.setup_nodes import setup_simulators_and_wallets, bt
from src.consensus.block_rewards import calculate_base_fee, calculate_block_reward
from tests.time_out_assert import time_out_assert, time_out_assert_not_None
from tests.time_out_assert import time_out_assert
from src.util.chech32 import encode_puzzle_hash
from src.rpc.wallet_rpc_client import WalletRpcClient
from src.rpc.wallet_rpc_api import WalletRpcApi
@@ -95,25 +93,35 @@ class TestWalletRpc:
async def tx_in_mempool():
tx = (await client.get_transaction("1", transaction_id))["transaction"]
return tx.is_in_mempool()
await time_out_assert(5, tx_in_mempool, True)
await time_out_assert(5, wallet.get_unconfirmed_balance, initial_funds - tx_amount)
assert (await client.get_wallet_balance("1"))["wallet_balance"]["unconfirmed_wallet_balance"] == initial_funds - tx_amount
assert (await client.get_wallet_balance("1"))["wallet_balance"]["confirmed_wallet_balance"] == initial_funds
await time_out_assert(
5, wallet.get_unconfirmed_balance, initial_funds - tx_amount
)
assert (await client.get_wallet_balance("1"))["wallet_balance"][
"unconfirmed_wallet_balance"
] == initial_funds - tx_amount
assert (await client.get_wallet_balance("1"))["wallet_balance"][
"confirmed_wallet_balance"
] == initial_funds
for i in range(0, 5):
await full_node_1.farm_new_block(FarmNewBlockProtocol(ph_2))
async def eventual_balance():
return (await client.get_wallet_balance("1"))["wallet_balance"]["confirmed_wallet_balance"]
return (await client.get_wallet_balance("1"))["wallet_balance"][
"confirmed_wallet_balance"
]
await time_out_assert(5, eventual_balance, initial_funds_eventually - tx_amount)
await time_out_assert(
5, eventual_balance, initial_funds_eventually - tx_amount
)
address = (await client.get_next_address("1"))["address"]
assert len(address) > 10
txs = (await client.get_transactions("1"))["transactions"]
assert len(txs) > 1
except Exception:
# Checks that the RPC manages to stop the node