2021-01-01 02:29:41 +03:00
|
|
|
import time
|
|
|
|
|
2020-12-11 19:40:02 +03:00
|
|
|
from src.consensus.sub_block_record import SubBlockRecord
|
2020-05-20 10:41:10 +03:00
|
|
|
from src.full_node.full_node import FullNode
|
|
|
|
from typing import Callable, List, Optional, Dict
|
|
|
|
|
2020-12-11 19:40:02 +03:00
|
|
|
from src.types.full_block import FullBlock
|
|
|
|
from src.types.sized_bytes import bytes32
|
|
|
|
from src.types.unfinished_header_block import UnfinishedHeaderBlock
|
|
|
|
from src.util.byte_types import hexstr_to_bytes
|
|
|
|
from src.util.ints import uint64, uint32, uint128
|
|
|
|
from src.util.ws_message import create_payload
|
2020-12-12 12:30:04 +03:00
|
|
|
from src.consensus.pos_quality import UI_ACTUAL_SPACE_CONSTANT_FACTOR
|
2020-12-11 19:40:02 +03:00
|
|
|
|
2020-05-20 10:41:10 +03:00
|
|
|
|
2020-06-17 02:46:51 +03:00
|
|
|
class FullNodeRpcApi:
|
2020-11-11 07:14:06 +03:00
|
|
|
def __init__(self, api: FullNode):
|
2020-11-05 20:54:24 +03:00
|
|
|
self.service = api
|
2020-11-11 07:14:06 +03:00
|
|
|
self.full_node = api
|
2020-06-17 02:46:51 +03:00
|
|
|
self.service_name = "chia_full_node"
|
Ms.transaction fixes (#227)
* dbus alternative for linux
* key migration
* reset mnemonics
* Fix tests, and start working on key selection
* Implement UI for changing keys
* Removing keys and mnemonic button
* Start making farmer and harvester RPCs
* routing, daemon, react
* Fix rpc shutdown
* Harvester and farmer websocket, and basic UI
* Plot display and deletion
* use proper config:
* don't crash on nondefined
* local test false
* launch daemon on start
* farmer clean
* get blocks
* header table
* Start with farmer callback, fix bug in getting latest blocks
* State changes from full node, harvester, farmer, and full node ui improvements
* split balances in react
* pending change balance
* plotter
* dev config
* gitignore fail..
* maintain connection / retry
* Fix tests
* Remove electron-ui, and style fixes
* Better farmer and full node control
* Remove electron ui references
* Uncomment out starting the dameon
* Add to changelog
* Remove timelord tab, and change full node style
* Clean up new wallet login
* Refactor RPCs
* Fix flake8 import issues in full_node_rpc_server.py
* add balance split to coloured coin wallet
* spendable balance fix
* Import private key from UI fix
* Various fixes to transaction sending, and UI tweaks
* Fix merge issues
Co-authored-by: Yostra <straya@chia.net>
Co-authored-by: Lipa Long <lipa@chia.net>
Co-authored-by: Gene Hoffman <hoffmang@hoffmang.com>
2020-05-20 12:50:39 +03:00
|
|
|
self.cached_blockchain_state: Optional[Dict] = None
|
2020-05-20 10:41:10 +03:00
|
|
|
|
2020-06-17 02:46:51 +03:00
|
|
|
def get_routes(self) -> Dict[str, Callable]:
|
|
|
|
return {
|
2020-12-11 19:40:02 +03:00
|
|
|
"/get_blockchain_state": self.get_blockchain_state,
|
|
|
|
"/get_sub_block": self.get_sub_block,
|
|
|
|
"/get_sub_block_record_by_sub_height": self.get_sub_block_record_by_sub_height,
|
|
|
|
"/get_sub_block_record": self.get_sub_block_record,
|
2021-01-14 01:13:09 +03:00
|
|
|
"/get_sub_block_records": self.get_sub_block_records,
|
2020-12-11 19:40:02 +03:00
|
|
|
"/get_unfinished_sub_block_headers": self.get_unfinished_sub_block_headers,
|
|
|
|
"/get_network_space": self.get_network_space,
|
|
|
|
"/get_unspent_coins": self.get_unspent_coins,
|
|
|
|
"/get_additions_and_removals": self.get_additions_and_removals,
|
2020-12-17 07:21:48 +03:00
|
|
|
"/get_blocks": self.get_blocks,
|
2020-06-17 02:46:51 +03:00
|
|
|
}
|
|
|
|
|
2020-09-15 14:59:42 +03:00
|
|
|
async def _state_changed(self, change: str) -> List[Dict]:
|
2020-12-11 19:40:02 +03:00
|
|
|
payloads = []
|
2021-01-01 02:29:41 +03:00
|
|
|
if change == "new_peak":
|
2020-12-11 19:40:02 +03:00
|
|
|
data = await self.get_blockchain_state({})
|
|
|
|
assert data is not None
|
|
|
|
payloads.append(
|
|
|
|
create_payload(
|
|
|
|
"get_blockchain_state",
|
|
|
|
data,
|
|
|
|
self.service_name,
|
|
|
|
"wallet_ui",
|
|
|
|
string=False,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
return payloads
|
2020-06-17 02:46:51 +03:00
|
|
|
return []
|
2020-05-20 10:41:10 +03:00
|
|
|
|
2020-12-11 19:40:02 +03:00
|
|
|
async def get_blockchain_state(self, _request: Dict):
|
|
|
|
"""
|
|
|
|
Returns a summary of the node's view of the blockchain.
|
|
|
|
"""
|
2020-12-17 23:28:17 +03:00
|
|
|
full_peak: Optional[FullBlock] = await self.service.blockchain.get_block_peak()
|
2020-12-17 05:37:14 +03:00
|
|
|
|
|
|
|
if full_peak is not None and full_peak.height > 0:
|
|
|
|
if full_peak.header_hash in self.service.blockchain.sub_blocks:
|
|
|
|
sub_block: SubBlockRecord = self.service.blockchain.sub_blocks[full_peak.header_hash]
|
|
|
|
sub_slot_iters = sub_block.sub_slot_iters
|
|
|
|
else:
|
|
|
|
sub_slot_iters = self.service.constants.SUB_SLOT_ITERS_STARTING
|
|
|
|
difficulty = uint64(
|
|
|
|
full_peak.weight - self.service.blockchain.sub_blocks[full_peak.prev_header_hash].weight
|
|
|
|
)
|
2020-12-11 19:40:02 +03:00
|
|
|
else:
|
|
|
|
difficulty = self.service.constants.DIFFICULTY_STARTING
|
|
|
|
sub_slot_iters = self.service.constants.SUB_SLOT_ITERS_STARTING
|
|
|
|
|
|
|
|
sync_mode: bool = self.service.sync_store.get_sync_mode()
|
|
|
|
|
2021-01-01 02:29:41 +03:00
|
|
|
if sync_mode:
|
2020-12-11 19:40:02 +03:00
|
|
|
max_pp = 0
|
2020-12-23 04:26:57 +03:00
|
|
|
for _, potential_peak_tuple in self.service.sync_store.potential_peaks.items():
|
|
|
|
peak_h, peak_w = potential_peak_tuple
|
|
|
|
if peak_h > max_pp:
|
|
|
|
max_pp = peak_h
|
2020-12-11 19:40:02 +03:00
|
|
|
sync_tip_height = max_pp
|
2021-01-01 02:29:41 +03:00
|
|
|
sync_tip_sub_height = max_pp
|
|
|
|
if full_peak is not None:
|
|
|
|
sync_progress_sub_height = full_peak.sub_block_height
|
|
|
|
sync_progress_height = full_peak.height
|
|
|
|
else:
|
|
|
|
sync_progress_sub_height = 0
|
|
|
|
sync_progress_height = 0
|
2020-12-11 19:40:02 +03:00
|
|
|
else:
|
|
|
|
sync_tip_height = 0
|
2021-01-01 02:29:41 +03:00
|
|
|
sync_tip_sub_height = 0
|
|
|
|
sync_progress_sub_height = 0
|
2020-12-11 19:40:02 +03:00
|
|
|
sync_progress_height = uint32(0)
|
|
|
|
|
2020-12-17 05:37:14 +03:00
|
|
|
if full_peak is not None and full_peak.height > 1:
|
|
|
|
newer_block_hex = full_peak.header_hash.hex()
|
2020-12-11 19:40:02 +03:00
|
|
|
older_block_hex = self.service.blockchain.sub_height_to_hash[
|
2020-12-17 05:37:14 +03:00
|
|
|
uint32(max(1, full_peak.sub_block_height - 1000))
|
2020-12-11 19:40:02 +03:00
|
|
|
].hex()
|
|
|
|
space = await self.get_network_space(
|
2021-01-06 07:13:39 +03:00
|
|
|
{"newer_block_header_hash": newer_block_hex, "older_block_header_hash": older_block_hex}
|
2020-12-11 19:40:02 +03:00
|
|
|
)
|
|
|
|
else:
|
|
|
|
space = {"space": uint128(0)}
|
2021-01-01 02:29:41 +03:00
|
|
|
|
|
|
|
now = time.time()
|
2021-01-02 06:13:08 +03:00
|
|
|
if (
|
|
|
|
full_peak is None
|
|
|
|
or full_peak.foliage_block is None
|
|
|
|
or full_peak.foliage_block.timestamp < now - 60 * 10
|
|
|
|
or sync_mode
|
|
|
|
):
|
2021-01-01 02:29:41 +03:00
|
|
|
synced = False
|
|
|
|
else:
|
|
|
|
synced = True
|
|
|
|
|
2020-12-11 19:40:02 +03:00
|
|
|
assert space is not None
|
|
|
|
response: Dict = {
|
|
|
|
"blockchain_state": {
|
2020-12-17 05:37:14 +03:00
|
|
|
"peak": full_peak,
|
2020-12-11 19:40:02 +03:00
|
|
|
"sync": {
|
|
|
|
"sync_mode": sync_mode,
|
2021-01-01 02:29:41 +03:00
|
|
|
"synced": synced,
|
2020-12-11 19:40:02 +03:00
|
|
|
"sync_tip_height": sync_tip_height,
|
2021-01-01 02:29:41 +03:00
|
|
|
"sync_tip_sub_height": sync_tip_sub_height,
|
2020-12-11 19:40:02 +03:00
|
|
|
"sync_progress_height": sync_progress_height,
|
2021-01-01 02:29:41 +03:00
|
|
|
"sync_progress_sub_height": sync_progress_sub_height,
|
2020-12-11 19:40:02 +03:00
|
|
|
},
|
|
|
|
"difficulty": difficulty,
|
|
|
|
"sub_slot_iters": sub_slot_iters,
|
|
|
|
"space": space["space"],
|
|
|
|
},
|
|
|
|
}
|
|
|
|
self.cached_blockchain_state = dict(response["blockchain_state"])
|
|
|
|
return response
|
|
|
|
|
|
|
|
async def get_sub_block(self, request: Dict) -> Optional[Dict]:
|
|
|
|
if "header_hash" not in request:
|
|
|
|
raise ValueError("No header_hash in request")
|
|
|
|
header_hash = hexstr_to_bytes(request["header_hash"])
|
|
|
|
|
|
|
|
block: Optional[FullBlock] = await self.service.block_store.get_full_block(header_hash)
|
|
|
|
if block is None:
|
|
|
|
raise ValueError(f"Block {header_hash.hex()} not found")
|
|
|
|
|
|
|
|
return {"sub_block": block}
|
|
|
|
|
2020-12-17 07:21:48 +03:00
|
|
|
async def get_blocks(self, request: Dict) -> Optional[Dict]:
|
|
|
|
if "start" not in request:
|
|
|
|
raise ValueError("No start in request")
|
|
|
|
if "end" not in request:
|
|
|
|
raise ValueError("No end in request")
|
2020-12-26 04:51:00 +03:00
|
|
|
exclude_hh = False
|
|
|
|
if "exclude_header_hash" in request:
|
|
|
|
exclude_hh = request["exclude_header_hash"]
|
|
|
|
|
2020-12-17 07:21:48 +03:00
|
|
|
start = int(request["start"])
|
|
|
|
end = int(request["end"])
|
|
|
|
block_range = []
|
|
|
|
for a in range(start, end):
|
|
|
|
block_range.append(uint32(a))
|
|
|
|
blocks: List[FullBlock] = await self.service.block_store.get_full_blocks_at_height(block_range)
|
2020-12-21 23:43:57 +03:00
|
|
|
json_blocks = []
|
|
|
|
for block in blocks:
|
|
|
|
json = block.to_json_dict()
|
2020-12-26 04:51:00 +03:00
|
|
|
if not exclude_hh:
|
|
|
|
json["header_hash"] = block.header_hash.hex()
|
2020-12-21 23:43:57 +03:00
|
|
|
json_blocks.append(json)
|
|
|
|
return {"blocks": json_blocks}
|
2020-12-17 07:21:48 +03:00
|
|
|
|
2021-01-14 01:13:09 +03:00
|
|
|
async def get_sub_block_records(self, request: Dict) -> Optional[Dict]:
|
|
|
|
if "start" not in request:
|
|
|
|
raise ValueError("No start in request")
|
|
|
|
if "end" not in request:
|
|
|
|
raise ValueError("No end in request")
|
|
|
|
|
|
|
|
start = int(request["start"])
|
|
|
|
end = int(request["end"])
|
|
|
|
records = []
|
|
|
|
for a in range(start, end):
|
|
|
|
header_hash: Optional[bytes32] = self.service.blockchain.sub_height_to_hash.get(uint32(a), None)
|
|
|
|
if header_hash is None:
|
|
|
|
continue
|
|
|
|
record: Optional[SubBlockRecord] = self.service.blockchain.sub_blocks.get(header_hash, None)
|
2021-01-14 03:18:42 +03:00
|
|
|
full = await self.service.blockchain.block_store.get_full_block(header_hash)
|
2021-01-14 01:13:09 +03:00
|
|
|
if record is None:
|
|
|
|
# Fetch from DB
|
|
|
|
record = await self.service.blockchain.block_store.get_sub_block_record(header_hash)
|
2021-01-14 03:18:42 +03:00
|
|
|
if record is None or full is None:
|
2021-01-14 01:13:09 +03:00
|
|
|
raise ValueError(f"Sub block {header_hash.hex()} does not exist")
|
2021-01-14 03:18:42 +03:00
|
|
|
|
|
|
|
json = record.to_json_dict()
|
|
|
|
if full.transactions_info is not None:
|
|
|
|
json["reward_claims_incorporated"] = full.transactions_info.reward_claims_incorporated
|
|
|
|
else:
|
|
|
|
json["reward_claims_incorporated"] = []
|
|
|
|
records.append(json)
|
2021-01-14 01:13:09 +03:00
|
|
|
return {"sub_block_records": records}
|
|
|
|
|
2020-12-11 19:40:02 +03:00
|
|
|
async def get_sub_block_record_by_sub_height(self, request: Dict) -> Optional[Dict]:
|
|
|
|
if "sub_height" not in request:
|
|
|
|
raise ValueError("No sub_height in request")
|
|
|
|
sub_block_height = request["sub_height"]
|
|
|
|
header_height = uint32(int(sub_block_height))
|
|
|
|
header_hash: Optional[bytes32] = self.service.blockchain.sub_height_to_hash.get(header_height, None)
|
|
|
|
if header_hash is None:
|
|
|
|
raise ValueError(f"Sub block height {sub_block_height} not found in chain")
|
|
|
|
record: Optional[SubBlockRecord] = self.service.blockchain.sub_blocks.get(header_hash, None)
|
|
|
|
if record is None:
|
|
|
|
# Fetch from DB
|
|
|
|
record = await self.service.blockchain.block_store.get_sub_block_record(header_hash)
|
|
|
|
if record is None:
|
|
|
|
raise ValueError(f"Sub block {header_hash} does not exist")
|
|
|
|
return {"sub_block_record": record}
|
|
|
|
|
|
|
|
async def get_sub_block_record(self, request: Dict):
|
|
|
|
if "header_hash" not in request:
|
|
|
|
raise ValueError("header_hash not in request")
|
|
|
|
header_hash_str = request["header_hash"]
|
|
|
|
header_hash = hexstr_to_bytes(header_hash_str)
|
|
|
|
record: Optional[SubBlockRecord] = self.service.blockchain.sub_blocks.get(header_hash, None)
|
|
|
|
if record is None:
|
|
|
|
# Fetch from DB
|
|
|
|
record = await self.service.blockchain.block_store.get_sub_block_record(header_hash)
|
|
|
|
if record is None:
|
|
|
|
raise ValueError(f"Sub block {header_hash.hex()} does not exist")
|
|
|
|
return {"sub_block_record": record}
|
|
|
|
|
|
|
|
async def get_unfinished_sub_block_headers(self, request: Dict) -> Optional[Dict]:
|
2020-12-21 23:31:05 +03:00
|
|
|
|
2020-12-23 04:26:57 +03:00
|
|
|
peak: Optional[SubBlockRecord] = self.service.blockchain.get_peak()
|
2020-12-21 23:31:05 +03:00
|
|
|
if peak is None:
|
|
|
|
return {"headers": []}
|
|
|
|
|
2020-12-11 19:40:02 +03:00
|
|
|
response_headers: List[UnfinishedHeaderBlock] = []
|
|
|
|
for ub_sub_height, block in (self.service.full_node_store.get_unfinished_blocks()).values():
|
2020-12-26 04:51:00 +03:00
|
|
|
if ub_sub_height == peak.sub_block_height:
|
2020-12-11 19:40:02 +03:00
|
|
|
unfinished_header_block = UnfinishedHeaderBlock(
|
|
|
|
block.finished_sub_slots,
|
|
|
|
block.reward_chain_sub_block,
|
|
|
|
block.challenge_chain_sp_proof,
|
|
|
|
block.reward_chain_sp_proof,
|
|
|
|
block.foliage_sub_block,
|
|
|
|
block.foliage_block,
|
|
|
|
b"",
|
|
|
|
)
|
|
|
|
response_headers.append(unfinished_header_block)
|
|
|
|
return {"headers": response_headers}
|
|
|
|
|
|
|
|
async def get_network_space(self, request: Dict) -> Optional[Dict]:
|
|
|
|
"""
|
|
|
|
Retrieves an estimate of total space validating the chain
|
|
|
|
between two block header hashes.
|
|
|
|
"""
|
|
|
|
if "newer_block_header_hash" not in request or "older_block_header_hash" not in request:
|
|
|
|
raise ValueError("Invalid request. newer_block_header_hash and older_block_header_hash required")
|
2020-12-12 10:57:39 +03:00
|
|
|
newer_block_hex = request["newer_block_header_hash"]
|
|
|
|
older_block_hex = request["older_block_header_hash"]
|
|
|
|
|
|
|
|
if newer_block_hex == older_block_hex:
|
|
|
|
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_sub_block_record(newer_block_bytes)
|
|
|
|
if newer_block is None:
|
|
|
|
raise ValueError("Newer block not found")
|
|
|
|
older_block = await self.service.block_store.get_sub_block_record(older_block_bytes)
|
|
|
|
if older_block is None:
|
|
|
|
raise ValueError("Newer block not found")
|
|
|
|
delta_weight = newer_block.weight - older_block.weight
|
|
|
|
|
|
|
|
delta_iters = newer_block.total_iters - older_block.total_iters
|
|
|
|
weight_div_iters = delta_weight / delta_iters
|
2020-12-12 12:30:04 +03:00
|
|
|
additional_difficulty_constant = 2 ** 25
|
2020-12-12 10:57:39 +03:00
|
|
|
eligible_plots_filter_multiplier = 2 ** self.service.constants.NUMBER_ZERO_BITS_PLOT_FILTER
|
2020-12-12 12:30:04 +03:00
|
|
|
network_space_bytes_estimate = (
|
|
|
|
UI_ACTUAL_SPACE_CONSTANT_FACTOR
|
|
|
|
* weight_div_iters
|
|
|
|
* additional_difficulty_constant
|
|
|
|
* eligible_plots_filter_multiplier
|
|
|
|
)
|
2020-12-11 19:40:02 +03:00
|
|
|
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:
|
|
|
|
raise ValueError("Puzzle hash not in request")
|
|
|
|
puzzle_hash = hexstr_to_bytes(request["puzzle_hash"])
|
|
|
|
|
|
|
|
coin_records = await self.service.blockchain.coin_store.get_coin_records_by_puzzle_hash(puzzle_hash)
|
|
|
|
|
|
|
|
return {"coin_records": coin_records}
|
|
|
|
|
|
|
|
async def get_additions_and_removals(self, request: Dict) -> Optional[Dict]:
|
|
|
|
if "header_hash" not in request:
|
|
|
|
raise ValueError("No header_hash in request")
|
|
|
|
header_hash = hexstr_to_bytes(request["header_hash"])
|
|
|
|
|
|
|
|
block: Optional[FullBlock] = await self.service.block_store.get_full_block(header_hash)
|
|
|
|
if block is None:
|
|
|
|
raise ValueError(f"Block {header_hash.hex()} not found")
|
|
|
|
reward_additions = block.get_included_reward_coins()
|
2020-12-11 16:29:51 +03:00
|
|
|
|
2020-12-11 19:40:02 +03:00
|
|
|
# TODO: optimize
|
|
|
|
tx_removals, tx_additions = await block.tx_removals_and_additions()
|
|
|
|
removal_records = []
|
|
|
|
addition_records = []
|
|
|
|
for tx_removal in tx_removals:
|
|
|
|
removal_records.append(await self.service.coin_store.get_coin_record(tx_removal))
|
|
|
|
for tx_addition in tx_additions + list(reward_additions):
|
|
|
|
addition_records.append(await self.service.coin_store.get_coin_record(tx_addition.name()))
|
|
|
|
return {"additions": addition_records, "removals": removal_records}
|