chia-blockchain/chia/full_node/bundle_tools.py

133 lines
5.6 KiB
Python

import re
from typing import Optional, Tuple, List, Union
from clvm import SExp
from clvm_tools import binutils
from chia.full_node.generator import create_compressed_generator
from chia.types.blockchain_format.program import SerializedProgram, Program
from chia.types.coin_solution import CoinSolution
from chia.types.generator_types import BlockGenerator, CompressorArg
from chia.types.spend_bundle import SpendBundle
from chia.util.byte_types import hexstr_to_bytes
from chia.util.ints import uint32, uint64
def spend_bundle_to_serialized_coin_solution_entry_list(bundle: SpendBundle) -> bytes:
r = b""
for coin_solution in bundle.coin_solutions:
r += b"\xff"
r += b"\xff" + SExp.to(coin_solution.coin.parent_coin_info).as_bin()
r += b"\xff" + bytes(coin_solution.puzzle_reveal)
r += b"\xff" + SExp.to(coin_solution.coin.amount).as_bin()
r += b"\xff" + bytes(coin_solution.solution)
r += b"\x80"
r += b"\x80"
return r
def simple_solution_generator(bundle: SpendBundle) -> BlockGenerator:
"""
Simply quotes the solutions we know.
"""
cse_list = spend_bundle_to_serialized_coin_solution_entry_list(bundle)
block_program = b"\xff"
block_program += SExp.to(binutils.assemble("#q")).as_bin()
block_program += b"\xff" + cse_list + b"\x80"
return BlockGenerator(SerializedProgram.from_bytes(block_program), [])
STANDARD_TRANSACTION_PUZZLE_PREFIX = r"""ff02ffff01ff02ffff01ff02ffff03ff0bffff01ff02ffff03ffff09ff05ffff1dff0bffff1effff0bff0bffff02ff06ffff04ff02ffff04ff17ff8080808080808080ffff01ff02ff17ff2f80ffff01ff088080ff0180ffff01ff04ffff04ff04ffff04ff05ffff04ffff02ff06ffff04ff02ffff04ff17ff80808080ff80808080ffff02ff17ff2f808080ff0180ffff04ffff01ff32ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff06ffff04ff02ffff04ff09ff80808080ffff02ff06ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ffff04ffff01""" # noqa
STANDARD_TRANSACTION_PUZZLE_PATTERN = re.compile(STANDARD_TRANSACTION_PUZZLE_PREFIX + r"(b0[a-f0-9]{96})ff018080")
# match_standard_transaction_anywhere
def match_standard_transaction_at_any_index(generator_body: bytes) -> Optional[Tuple[int, int]]:
"""Return (start, end) of match, or None if pattern could not be found"""
# We intentionally match the entire puzzle, not just the prefix that we will use,
# in case we later want to convert the template generator into a tree of CLVM
# Objects before operating on it
m = STANDARD_TRANSACTION_PUZZLE_PATTERN.search(generator_body.hex())
if m:
assert m.start() % 2 == 0 and m.end() % 2 == 0
start = m.start() // 2
end = (m.end() - 98 - len("ff018080")) // 2
assert generator_body[start:end] == bytes.fromhex(STANDARD_TRANSACTION_PUZZLE_PREFIX)
return start, end
else:
return None
def match_standard_transaction_exactly_and_return_pubkey(puzzle: SerializedProgram) -> Optional[bytes]:
m = STANDARD_TRANSACTION_PUZZLE_PATTERN.fullmatch(bytes(puzzle).hex())
return None if m is None else hexstr_to_bytes(m.group(1))
def compress_cse_puzzle(puzzle: SerializedProgram) -> Optional[bytes]:
return match_standard_transaction_exactly_and_return_pubkey(puzzle)
def compress_coin_solution(coin_solution: CoinSolution):
compressed_puzzle = compress_cse_puzzle(coin_solution.puzzle_reveal)
return [
[coin_solution.coin.parent_coin_info, coin_solution.coin.amount],
[compressed_puzzle, Program.from_bytes(bytes(coin_solution.solution))],
]
def puzzle_suitable_for_compression(puzzle: SerializedProgram) -> bool:
return True if match_standard_transaction_exactly_and_return_pubkey(puzzle) else False
def bundle_suitable_for_compression(bundle: SpendBundle):
ok = []
for coin_solution in bundle.coin_solutions:
ok.append(puzzle_suitable_for_compression(coin_solution.puzzle_reveal))
return all(ok)
def compressed_coin_solution_entry_list(bundle: SpendBundle) -> List:
compressed_cse_list: List[List[Union[List[uint64], List[Union[bytes, None, Program]]]]] = []
for coin_solution in bundle.coin_solutions:
compressed_cse_list.append(compress_coin_solution(coin_solution))
return compressed_cse_list
def compressed_spend_bundle_solution(original_generator_params: CompressorArg, bundle: SpendBundle) -> BlockGenerator:
compressed_cse_list = compressed_coin_solution_entry_list(bundle)
return create_compressed_generator(original_generator_params, compressed_cse_list)
def best_solution_generator_from_template(previous_generator: CompressorArg, bundle: SpendBundle) -> BlockGenerator:
"""
Creates a compressed block generator, taking in a block that passes the checks below
"""
if bundle_suitable_for_compression(bundle):
return compressed_spend_bundle_solution(previous_generator, bundle)
else:
return simple_solution_generator(bundle)
def detect_potential_template_generator(block_height: uint32, program: SerializedProgram) -> Optional[CompressorArg]:
"""
If this returns a GeneratorArg, that means that the input, `program`, has a standard transaction
that is not compressed that we can use as a template for future blocks.
If it returns None, this block cannot be used.
In this implementation, we store the offsets needed by the compressor in the GeneratorArg
This block will serve as a template for the compression of other newly farmed blocks.
"""
m = match_standard_transaction_at_any_index(bytes(program))
if m is None:
return None
start, end = m
if start and end and end > start >= 0:
return CompressorArg(block_height, program, start, end)
else:
return None