Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New RPC block_spends - Get spends for block using transaction generator #12062

Merged
merged 10 commits into from
Jul 25, 2022
32 changes: 32 additions & 0 deletions chia/rpc/full_node_rpc_api.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
from typing import Any, Dict, List, Optional

from clvm.casts import int_from_bytes

from chia.consensus.block_record import BlockRecord
from chia.consensus.pos_quality import UI_ACTUAL_SPACE_CONSTANT_FACTOR
from chia.full_node.full_node import FullNode
from chia.full_node.generator import setup_generator_args
from chia.full_node.mempool_check_conditions import get_puzzle_and_solution_for_coin
from chia.rpc.rpc_server import Endpoint, EndpointResult
from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.program import Program, SerializedProgram
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.coin_record import CoinRecord
Expand All @@ -18,6 +22,7 @@
from chia.util.ints import uint32, uint64, uint128
from chia.util.log_exceptions import log_exceptions
from chia.util.ws_message import WsRpcMessage, create_payload_dict
from chia.wallet.puzzles.decompress_block_spends import DECOMPRESS_BLOCK_SPENDS


def coin_record_dict_backwards_compat(coin_record: Dict[str, Any]):
Expand All @@ -41,6 +46,7 @@ def get_routes(self) -> Dict[str, Endpoint]:
"/get_block_record_by_height": self.get_block_record_by_height,
"/get_block_record": self.get_block_record,
"/get_block_records": self.get_block_records,
"/get_block_spends": self.get_block_spends,
"/get_unfinished_block_headers": self.get_unfinished_block_headers,
"/get_network_space": self.get_network_space,
"/get_additions_and_removals": self.get_additions_and_removals,
Expand Down Expand Up @@ -399,6 +405,32 @@ async def get_block_records(self, request: Dict) -> EndpointResult:
records.append(record)
return {"block_records": records}

async def get_block_spends(self, request: Dict) -> EndpointResult:
if "header_hash" not in request:
raise ValueError("No header_hash in request")
header_hash = bytes32.from_hexstr(request["header_hash"])
full_block: Optional[FullBlock] = await self.service.block_store.get_full_block(header_hash)
if full_block is None or full_block.transactions_generator is None:
raise ValueError(f"Block {header_hash.hex()} not found or invalid block generator")

spends: List[CoinSpend] = []
block_generator = await self.service.blockchain.get_block_generator(full_block)
if block_generator is None:
return {"block_spends": spends}

block_program, block_program_args = setup_generator_args(block_generator)
_, coin_spends = DECOMPRESS_BLOCK_SPENDS.run_with_cost(
self.service.constants.MAX_BLOCK_COST_CLVM, block_program, block_program_args
)

for spend in coin_spends.as_iter():
parent, puzzle, amount, solution = spend.as_iter()
puzzle_hash = puzzle.get_tree_hash()
coin = Coin(parent.atom, puzzle_hash, int_from_bytes(amount.atom))
spends.append(CoinSpend(coin, puzzle, solution))

return {"block_spends": spends}

async def get_block_record_by_height(self, request: Dict) -> EndpointResult:
if "height" not in request:
raise ValueError("No height in request")
Expand Down
10 changes: 10 additions & 0 deletions chia/rpc/full_node_rpc_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,16 @@ async def get_block_records(self, start: int, end: int) -> List:
# TODO: return block records
return response["block_records"]

async def get_block_spends(self, header_hash: bytes32) -> Optional[List[CoinSpend]]:
try:
response = await self.fetch("get_block_spends", {"header_hash": header_hash.hex()})
block_spends = []
for block_spend in response["block_spends"]:
block_spends.append(CoinSpend.from_json_dict(block_spend))
return block_spends
except Exception:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does the caller know what went wrong here? we don't log any exceptions, just catch them and return None

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its what every other api call does.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we have to catch anything. The RPC server will catch it and return the error string to the caller. In this case we don't know what failed

return None

async def push_tx(self, spend_bundle: SpendBundle):
return await self.fetch("push_tx", {"spend_bundle": spend_bundle.to_json_dict()})

Expand Down
16 changes: 16 additions & 0 deletions chia/wallet/puzzles/decompress_block_spends.clvm
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
(mod (block_program (block_ref))

(defconstant local_deserialize_mod
;; this monstrosity is the assembly output of `chialisp_deserialisation.clvm`
;; it's pasted in here because the compiler doesn't yet support nested `mod`
;; my apologies -- RK

(a (q 5 (a 62 (c 2 (c 5 ()))))
(c (q ((-1 . 127) -33 . -65) ((a (i (= 11 (q . -128)) (q 4 () (c 5 ())) (q 2 (i (>s 11 24) (q 2 26 (c 2 (c (a (i (>s 11 28) (q 2 (i (>s 11 20) (q 8) (q 4 (concat (logand (q . 31) 11) (substr 5 () (q . 1))) (c (substr 5 (q . 1)) ()))) 1) (q 4 (logand (q . 63) 11) (c 5 ()))) 1) ()))) (q 4 11 (c 5 ()))) 1)) 1) 4 (substr 21 () 9) (c (substr 21 9) ())) (c (c 5 19) (c 43 ())) (a 22 (c 2 (c 9 (c (a 62 (c 2 (c 21 ()))) ())))) 2 (i (= (substr 5 () (q . 1)) 16) (q 2 46 (c 2 (c (a 62 (c 2 (c (substr 5 (q . 1)) ()))) ()))) (q 2 18 (c 2 (c (substr 5 (q . 1)) (c (substr 5 () (q . 1)) ()))))) 1)
1))
)

; main
; select the first value, and return decompressed block spends.
(f (a block_program (list local_deserialize_mod block_ref)))
)
1 change: 1 addition & 0 deletions chia/wallet/puzzles/decompress_block_spends.clvm.hex
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ff02ffff01ff05ffff02ff05ffff04ff02ffff04ff13ff8080808080ffff04ffff01ff02ffff01ff05ffff02ff3effff04ff02ffff04ff05ff8080808080ffff04ffff01ffffff81ff7fff81df81bfffffff02ffff03ffff09ff0bffff01818080ffff01ff04ff80ffff04ff05ff808080ffff01ff02ffff03ffff0aff0bff1880ffff01ff02ff1affff04ff02ffff04ffff02ffff03ffff0aff0bff1c80ffff01ff02ffff03ffff0aff0bff1480ffff01ff0880ffff01ff04ffff0effff18ffff011fff0b80ffff0cff05ff80ffff01018080ffff04ffff0cff05ffff010180ff80808080ff0180ffff01ff04ffff18ffff013fff0b80ffff04ff05ff80808080ff0180ff80808080ffff01ff04ff0bffff04ff05ff80808080ff018080ff0180ff04ffff0cff15ff80ff0980ffff04ffff0cff15ff0980ff808080ffff04ffff04ff05ff1380ffff04ff2bff808080ffff02ff16ffff04ff02ffff04ff09ffff04ffff02ff3effff04ff02ffff04ff15ff80808080ff8080808080ff02ffff03ffff09ffff0cff05ff80ffff010180ff1080ffff01ff02ff2effff04ff02ffff04ffff02ff3effff04ff02ffff04ffff0cff05ffff010180ff80808080ff80808080ffff01ff02ff12ffff04ff02ffff04ffff0cff05ffff010180ffff04ffff0cff05ff80ffff010180ff808080808080ff0180ff018080ff018080
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
f890a7866079009517ae0b652d530268d2531bbac99670aaba461d604bc0ff0c
3 changes: 3 additions & 0 deletions chia/wallet/puzzles/decompress_block_spends.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from chia.wallet.puzzles.load_clvm import load_serialized_clvm

DECOMPRESS_BLOCK_SPENDS = load_serialized_clvm("decompress_block_spends.clvm", package_or_requirement=__name__)
1 change: 1 addition & 0 deletions tests/clvm/test_clvm_compilation.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"chia/wallet/puzzles/nft_state_layer.clvm",
"chia/wallet/puzzles/nft_ownership_layer.clvm",
"chia/wallet/puzzles/nft_ownership_transfer_program_one_way_claim_with_royalties.clvm",
"chia/wallet/puzzles/decompress_block_spends.clvm",
]
)

Expand Down
25 changes: 25 additions & 0 deletions tests/core/test_full_node_rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,31 @@ def stop_node_cb():
assert len(await client.get_coin_records_by_puzzle_hash(ph, True, 0, blocks[-1].height + 1)) == 2
assert len(await client.get_coin_records_by_puzzle_hash(ph, True, 0, 1)) == 0

coin_records = await client.get_coin_records_by_puzzle_hash(ph, False)

coin_spends = []

# Spend 3 coins using standard transaction
for i in range(3):
spend_bundle = wallet.generate_signed_transaction(
coin_records[i].coin.amount, ph_receiver, coin_records[i].coin
)
await client.push_tx(spend_bundle)
coin_spends = coin_spends + spend_bundle.coin_spends
await time_out_assert(
5, full_node_api_1.full_node.mempool_manager.get_spendbundle, spend_bundle, spend_bundle.name()
)

await full_node_api_1.farm_new_transaction_block(FarmNewBlockProtocol(ph_2))
block: FullBlock = (await full_node_api_1.get_all_full_blocks())[-1]

assert len(block.transactions_generator_ref_list) > 0 # compression has occurred

block_spends = await client.get_block_spends(block.header_hash)

assert len(block_spends) == 3
assert block_spends == coin_spends

memo = 32 * b"\f"

for i in range(2):
Expand Down