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

* New RPC block_spends - Get spends for block using transaction generator (#10446)

* get spends for block using transaction generator

* type annotations and use existing function

* return None on exception

* parse to CoinSpend

* specify return type of get_block_spends in rpc client

* see what can be asserted

* test fix

* flags not necessary as we don't validate

* simplifying as cost is not required as we are not validating

* improve test to cover transaction generator ref_list

* simplify test

* remove unused import

* slight change and cleanup

* lint cleanup

* clean up

* wait until transaction is in mempool

* fix lint

Co-authored-by: Jack Nelson <jack@jacknelson.xyz>

* fix lint

Correct type and run lint checker

* switch to custom clvm

* add new puzzle

* curry and make better

* forgot to set linter to tools dir

* undo run_block changes

* Revert "curry and make better"

The tests do not like it

* correct tests

Co-authored-by: Jack Nelson <jack@jacknelson.xyz>
This commit is contained in:
Freddie Coleman 2022-07-25 15:17:09 +01:00 committed by GitHub
parent ebe8857083
commit 72c8ba4a29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 89 additions and 0 deletions

View File

@ -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
@ -18,6 +22,7 @@ from chia.util.byte_types import hexstr_to_bytes
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]):
@ -41,6 +46,7 @@ class FullNodeRpcApi:
"/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,
@ -399,6 +405,32 @@ class FullNodeRpcApi:
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")

View File

@ -200,6 +200,16 @@ class FullNodeRpcClient(RpcClient):
# 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:
return None
async def push_tx(self, spend_bundle: SpendBundle):
return await self.fetch("push_tx", {"spend_bundle": spend_bundle.to_json_dict()})

View File

@ -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)))
)

View File

@ -0,0 +1 @@
ff02ffff01ff05ffff02ff05ffff04ff02ffff04ff13ff8080808080ffff04ffff01ff02ffff01ff05ffff02ff3effff04ff02ffff04ff05ff8080808080ffff04ffff01ffffff81ff7fff81df81bfffffff02ffff03ffff09ff0bffff01818080ffff01ff04ff80ffff04ff05ff808080ffff01ff02ffff03ffff0aff0bff1880ffff01ff02ff1affff04ff02ffff04ffff02ffff03ffff0aff0bff1c80ffff01ff02ffff03ffff0aff0bff1480ffff01ff0880ffff01ff04ffff0effff18ffff011fff0b80ffff0cff05ff80ffff01018080ffff04ffff0cff05ffff010180ff80808080ff0180ffff01ff04ffff18ffff013fff0b80ffff04ff05ff80808080ff0180ff80808080ffff01ff04ff0bffff04ff05ff80808080ff018080ff0180ff04ffff0cff15ff80ff0980ffff04ffff0cff15ff0980ff808080ffff04ffff04ff05ff1380ffff04ff2bff808080ffff02ff16ffff04ff02ffff04ff09ffff04ffff02ff3effff04ff02ffff04ff15ff80808080ff8080808080ff02ffff03ffff09ffff0cff05ff80ffff010180ff1080ffff01ff02ff2effff04ff02ffff04ffff02ff3effff04ff02ffff04ffff0cff05ffff010180ff80808080ff80808080ffff01ff02ff12ffff04ff02ffff04ffff0cff05ffff010180ffff04ffff0cff05ff80ffff010180ff808080808080ff0180ff018080ff018080

View File

@ -0,0 +1 @@
f890a7866079009517ae0b652d530268d2531bbac99670aaba461d604bc0ff0c

View File

@ -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__)

View File

@ -46,6 +46,7 @@ wallet_program_files = set(
"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",
]
)

View File

@ -187,6 +187,31 @@ class TestRpc:
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):