Merge commit '99448387ada5dd492d06261ab823c59c2a55b231' into catchup/long_lived_datalayer_merkle_blob_from_main_99448387ada5dd492d06261ab823c59c2a55b231

This commit is contained in:
Kyle Altendorf
2025-03-24 17:44:35 -04:00
24 changed files with 475 additions and 306 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ from _pytest.capture import CaptureFixture
import chia._tests
from chia._tests.util.misc import Marks, datacases
from chia.cmds.gh import Per, TestCMD, get_gh_token
from chia.cmds.dev.gh import Per, TestCMD, get_gh_token
test_root = Path(chia._tests.__file__).parent
@@ -802,7 +802,7 @@ class UnspentLineageInfoCase:
),
],
parent_with_diff_amount=True,
expected_success=True,
expected_success=False,
),
UnspentLineageInfoCase(
id="Unspent with parent that has same puzzlehash and amount but is also unspent",
@@ -884,13 +884,11 @@ async def test_get_unspent_lineage_info_for_puzzle_hash(case: UnspentLineageInfo
if case.expected_success:
assert result == UnspentLineageInfo(
coin_id=bytes32(TEST_COIN_ID),
coin_amount=TEST_AMOUNT,
parent_id=(
bytes32(TEST_PARENT_ID_DIFFERENT_AMOUNT)
if case.parent_with_diff_amount
else bytes32(TEST_PARENT_ID)
),
parent_amount=TEST_PARENT_DIFFERENT_AMOUNT if case.parent_with_diff_amount else TEST_AMOUNT,
parent_parent_id=bytes32(TEST_PARENT_PARENT_ID),
)
else:
+12 -17
View File
@@ -2957,9 +2957,8 @@ async def test_skip_error_items() -> None:
called += 1
raise RuntimeError("failed to find fast forward coin")
result = await mempool.create_block_generator(local_get_unspent_lineage_info, DEFAULT_CONSTANTS, uint32(10))
assert result is not None
generator, _, _, _ = result
generator = await mempool.create_block_generator(local_get_unspent_lineage_info, DEFAULT_CONSTANTS, uint32(10))
assert generator is not None
assert called == 3
assert generator.program == SerializedProgram.from_bytes(bytes.fromhex("ff01ff8080"))
@@ -3247,21 +3246,21 @@ async def test_create_block_generator() -> None:
mempool.add_to_pool(mi)
invariant_check_mempool(mempool)
block = await mempool.create_block_generator(get_unspent_lineage_info_for_puzzle_hash, test_constants, uint32(0))
assert block is not None
generator, signature, additions, _ = block
generator = await mempool.create_block_generator(
get_unspent_lineage_info_for_puzzle_hash, test_constants, uint32(0)
)
assert generator is not None
assert set(additions) == expected_additions
assert len(additions) == len(expected_additions)
assert signature == expected_signature
assert set(generator.additions) == expected_additions
assert len(generator.additions) == len(expected_additions)
assert generator.signature == expected_signature
err, conds = run_block_generator2(
bytes(generator.program),
generator.generator_refs,
test_constants.MAX_BLOCK_COST_CLVM,
0,
signature,
generator.signature,
None,
test_constants,
)
@@ -3278,7 +3277,7 @@ async def test_create_block_generator() -> None:
assert Coin(spend.coin_id, add2[0], uint64(add2[1])) in expected_additions
num_additions += 1
assert num_additions == len(additions)
assert num_additions == len(generator.additions)
invariant_check_mempool(mempool)
@@ -3356,11 +3355,7 @@ async def test_lineage_cache(seeded_random: random.Random) -> None:
called = 0
info1 = UnspentLineageInfo(
bytes32.random(seeded_random),
uint64.from_bytes(seeded_random.randbytes(8)),
bytes32.random(seeded_random),
uint64.from_bytes(seeded_random.randbytes(8)),
bytes32.random(seeded_random),
bytes32.random(seeded_random), bytes32.random(seeded_random), bytes32.random(seeded_random)
)
async def callback1(ph: bytes32) -> Optional[UnspentLineageInfo]:
@@ -2,6 +2,7 @@ from __future__ import annotations
import dataclasses
import logging
import random
from collections.abc import Awaitable, Collection, Sequence
from typing import Any, Callable, ClassVar, Optional, Union
@@ -9,14 +10,17 @@ import pytest
from chia_rs import (
ELIGIBLE_FOR_DEDUP,
ELIGIBLE_FOR_FF,
MEMPOOL_MODE,
AugSchemeMPL,
ConsensusConstants,
G2Element,
get_conditions_from_spendbundle,
run_block_generator2,
)
from chia_rs.sized_bytes import bytes32
from chia_rs.sized_ints import uint8, uint32, uint64
from chiabip158 import PyBIP158
from clvm.casts import int_to_bytes
from chia._tests.conftest import ConsensusMode
from chia._tests.util.misc import invariant_check_mempool
@@ -41,6 +45,7 @@ from chia.protocols.full_node_protocol import RequestBlock, RespondBlock
from chia.protocols.protocol_message_types import ProtocolMessageTypes
from chia.simulator.full_node_simulator import FullNodeSimulator
from chia.simulator.simulator_protocol import FarmNewBlockProtocol
from chia.simulator.wallet_tools import WalletTool
from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.program import INFINITE_COST, Program
from chia.types.blockchain_format.serialized_program import SerializedProgram
@@ -48,6 +53,7 @@ from chia.types.clvm_cost import CLVMCost
from chia.types.coin_record import CoinRecord
from chia.types.coin_spend import CoinSpend, make_spend
from chia.types.condition_opcodes import ConditionOpcode
from chia.types.condition_with_args import ConditionWithArgs
from chia.types.eligible_coin_spends import (
DedupCoinSpend,
EligibilityAndAdditions,
@@ -63,6 +69,10 @@ from chia.types.spend_bundle import SpendBundle
from chia.types.spend_bundle_conditions import SpendBundleConditions, SpendConditions
from chia.util.errors import Err, ValidationError
from chia.wallet.conditions import AssertCoinAnnouncement
from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import (
DEFAULT_HIDDEN_PUZZLE_HASH,
calculate_synthetic_secret_key,
)
from chia.wallet.util.tx_config import DEFAULT_TX_CONFIG
from chia.wallet.wallet import Wallet
from chia.wallet.wallet_coin_record import WalletCoinRecord
@@ -2202,9 +2212,7 @@ class TestCoins:
self.coin_records[c.name()] = CoinRecord(c, uint32(0), uint32(0), False, TEST_TIMESTAMP)
self.lineage_info = {}
for ph, c in lineage.items():
self.lineage_info[ph] = UnspentLineageInfo(
c.name(), c.amount, c.parent_coin_info, uint64(1337), bytes32([42] * 32)
)
self.lineage_info[ph] = UnspentLineageInfo(c.name(), c.parent_coin_info, bytes32([42] * 32))
def spend_coin(self, coin_id: bytes32, height: uint32 = uint32(10)) -> None:
self.coin_records[coin_id] = dataclasses.replace(self.coin_records[coin_id], spent_block_index=height)
@@ -2215,9 +2223,7 @@ class TestCoins:
else:
assert coin.puzzle_hash == puzzle_hash
prev = self.lineage_info[puzzle_hash]
self.lineage_info[puzzle_hash] = UnspentLineageInfo(
coin.name(), coin.amount, coin.parent_coin_info, prev.coin_amount, prev.coin_id
)
self.lineage_info[puzzle_hash] = UnspentLineageInfo(coin.name(), coin.parent_coin_info, prev.coin_id)
async def get_coin_records(self, coin_ids: Collection[bytes32]) -> list[CoinRecord]:
ret = []
@@ -2501,3 +2507,159 @@ async def test_advancing_ff(use_optimization: bool) -> None:
spend = item.bundle_coin_spends[spend_a.coin.name()]
assert spend.eligible_for_fast_forward
assert spend.latest_singleton_coin == spend_c.coin.name()
@pytest.fixture(name="test_wallet")
def test_wallet_fixture() -> WalletTool:
return WalletTool(DEFAULT_CONSTANTS)
@pytest.fixture(name="transactions_1000")
def transactions_1000_fixture(test_wallet: WalletTool, seeded_random: random.Random) -> list[SpendBundle]:
op = ConditionOpcode
bundles: list[SpendBundle] = []
test_conditions = [
op.AGG_SIG_PARENT,
op.AGG_SIG_PUZZLE,
op.AGG_SIG_AMOUNT,
op.AGG_SIG_PUZZLE_AMOUNT,
op.AGG_SIG_PARENT_AMOUNT,
op.AGG_SIG_PARENT_PUZZLE,
op.AGG_SIG_UNSAFE,
op.AGG_SIG_ME,
op.CREATE_COIN,
op.CREATE_COIN_ANNOUNCEMENT,
op.CREATE_PUZZLE_ANNOUNCEMENT,
op.ASSERT_MY_COIN_ID,
op.ASSERT_MY_PARENT_ID,
op.ASSERT_MY_PUZZLEHASH,
op.ASSERT_MY_AMOUNT,
]
print("generating 1000 coins and spend bundles")
for i in range(1000):
# generate a coin with a dummy parent coin ID
puzzle = test_wallet.get_new_puzzle()
coin = Coin(bytes32(i.to_bytes(32, byteorder="big")), puzzle.get_tree_hash(), uint64(100 * i))
conditions: dict[ConditionOpcode, list[ConditionWithArgs]] = {
ConditionOpcode.CREATE_COIN: [
ConditionWithArgs(
ConditionOpcode.CREATE_COIN, [test_wallet.get_new_puzzle().get_tree_hash(), int_to_bytes(25 * i)]
)
]
}
# generate a somewhat arbitrarty set of conditions for the spend, just
# to have some diversity and make the block interesting
num_conditions = seeded_random.randint(0, 10)
for c in seeded_random.sample(test_conditions, num_conditions):
if c in set(
[
op.AGG_SIG_PARENT,
op.AGG_SIG_PUZZLE,
op.AGG_SIG_AMOUNT,
op.AGG_SIG_PUZZLE_AMOUNT,
op.AGG_SIG_PARENT_AMOUNT,
op.AGG_SIG_PARENT_PUZZLE,
op.AGG_SIG_UNSAFE,
op.AGG_SIG_ME,
]
):
secret_key = test_wallet.get_private_key_for_puzzle_hash(coin.puzzle_hash)
synthetic_secret_key = calculate_synthetic_secret_key(secret_key, DEFAULT_HIDDEN_PUZZLE_HASH)
cond = ConditionWithArgs(c, [bytes(synthetic_secret_key.get_g1()), b"foobar"])
elif c == op.CREATE_COIN:
cond = ConditionWithArgs(c, [test_wallet.get_new_puzzle().get_tree_hash(), int_to_bytes(i)])
elif c in set([op.CREATE_COIN_ANNOUNCEMENT, op.CREATE_PUZZLE_ANNOUNCEMENT]):
cond = ConditionWithArgs(c, [b"foobar"])
elif c == op.ASSERT_MY_COIN_ID:
cond = ConditionWithArgs(c, [coin.name()])
elif c == op.ASSERT_MY_PARENT_ID:
cond = ConditionWithArgs(c, [coin.parent_coin_info])
elif c == op.ASSERT_MY_PUZZLEHASH:
cond = ConditionWithArgs(c, [coin.puzzle_hash])
elif c == op.ASSERT_MY_AMOUNT:
cond = ConditionWithArgs(c, [int_to_bytes(coin.amount)])
conditions.setdefault(c, []).append(cond)
# generate a spend of that coin
bundle = test_wallet.generate_signed_transaction(
uint64(50 * i), test_wallet.get_new_puzzle().get_tree_hash(), coin, conditions
)
bundles.append(bundle)
return bundles
# if we try to fill the mempool with more than 550, all spends won't
# necessarily fit in the block, which the test assumes
@pytest.mark.anyio
@pytest.mark.parametrize("mempool_size", [1, 2, 50, 100, 300, 400, 550])
@pytest.mark.parametrize("seed", [0, 1, 2, 3, 4, 5, 6])
async def test_create_block_generator(mempool_size: int, seed: int, transactions_1000: list[SpendBundle]) -> None:
bundles = transactions_1000
all_coins = [s.coin for b in bundles for s in b.coin_spends]
coins = TestCoins(all_coins, {})
rng = random.Random(seed)
# run the test multiple times, generating different combinations of mempools
mempool_manager = await setup_mempool(coins)
included_bundles = rng.sample(bundles, mempool_size)
expected_additions: set[Coin] = set()
expected_removals: set[Coin] = set()
expected_signature = G2Element()
for sb in included_bundles:
pre_validation = await mempool_manager.pre_validate_spendbundle(sb)
bundle_add_info = await mempool_manager.add_spend_bundle(
sb, pre_validation, sb.name(), first_added_height=uint32(1)
)
expected_additions.update(sb.additions())
expected_removals.update(sb.removals())
expected_signature += sb.aggregated_signature
assert bundle_add_info.status == MempoolInclusionStatus.SUCCESS
item = mempool_manager.get_mempool_item(sb.name())
assert item is not None
all_items = mempool_manager.mempool.all_items()
assert len(list(all_items)) == len(included_bundles)
invariant_check_mempool(mempool_manager.mempool)
assert mempool_manager.peak is not None
new_block_gen = await mempool_manager.create_block_generator(mempool_manager.peak.header_hash)
assert new_block_gen is not None
# now, make sure the generator we got is valid
assert len(expected_additions) == len(new_block_gen.additions)
assert expected_additions == set(new_block_gen.additions)
assert len(expected_removals) == len(new_block_gen.removals)
assert expected_removals == set(new_block_gen.removals)
assert expected_signature == new_block_gen.signature
err, conds = run_block_generator2(
bytes(new_block_gen.program),
new_block_gen.generator_refs,
DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM,
MEMPOOL_MODE,
new_block_gen.signature,
None,
DEFAULT_CONSTANTS,
)
assert err is None
assert conds is not None
assert len(conds.spends) == len(expected_removals)
assert conds.cost < DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM
num_additions = 0
for spend in conds.spends:
assert Coin(spend.parent_id, spend.puzzle_hash, uint64(spend.coin_amount)) in expected_removals
for add2 in spend.create_coin:
assert Coin(spend.coin_id, add2[0], uint64(add2[1])) in expected_additions
num_additions += 1
assert num_additions == len(new_block_gen.additions)
@@ -110,11 +110,7 @@ async def test_process_fast_forward_spends_latest_unspent() -> None:
test_amount = uint64(3)
test_coin = Coin(TEST_COIN_ID, IDENTITY_PUZZLE_HASH, test_amount)
test_unspent_lineage_info = UnspentLineageInfo(
coin_id=test_coin.name(),
coin_amount=test_coin.amount,
parent_id=test_coin.parent_coin_info,
parent_amount=test_coin.amount,
parent_parent_id=TEST_COIN_ID,
coin_id=test_coin.name(), parent_id=test_coin.parent_coin_info, parent_parent_id=TEST_COIN_ID
)
async def get_unspent_lineage_info_for_puzzle_hash(puzzle_hash: bytes32) -> Optional[UnspentLineageInfo]:
@@ -141,11 +137,7 @@ async def test_process_fast_forward_spends_latest_unspent() -> None:
child_coin = item.bundle_coin_spends[test_coin.name()].additions[0]
expected_fast_forward_spends = {
IDENTITY_PUZZLE_HASH: UnspentLineageInfo(
coin_id=child_coin.name(),
coin_amount=child_coin.amount,
parent_id=test_coin.name(),
parent_amount=test_coin.amount,
parent_parent_id=test_coin.parent_coin_info,
coin_id=child_coin.name(), parent_id=test_coin.name(), parent_parent_id=test_coin.parent_coin_info
)
}
# We have set the next version from our additions to chain ff spends
@@ -202,9 +194,7 @@ def test_perform_the_fast_forward() -> None:
test_spend_data = BundleCoinSpend(test_coin_spend, False, True, [test_child_coin])
test_unspent_lineage_info = UnspentLineageInfo(
coin_id=latest_unspent_coin.name(),
coin_amount=latest_unspent_coin.amount,
parent_id=latest_unspent_coin.parent_coin_info,
parent_amount=test_child_coin.amount,
parent_parent_id=test_child_coin.parent_coin_info,
)
# Start from a fresh state of fast forward spends
@@ -227,9 +217,7 @@ def test_perform_the_fast_forward() -> None:
# (previously latest unspent)
expected_unspent_lineage_info = UnspentLineageInfo(
coin_id=expected_child_coin.name(),
coin_amount=expected_child_coin.amount,
parent_id=latest_unspent_coin.name(),
parent_amount=latest_unspent_coin.amount,
parent_parent_id=latest_unspent_coin.parent_coin_info,
)
assert fast_forward_spends == {test_ph: expected_unspent_lineage_info}
@@ -301,7 +289,7 @@ def make_singleton_coin_spend(
async def prepare_singleton_eve(
sim: SpendSim, sim_client: SimClient, is_eligible_for_ff: bool, start_amount: uint64, singleton_amount: uint64
sim: SpendSim, sim_client: SimClient, is_eligible_for_ff: bool, singleton_amount: uint64
) -> tuple[Program, CoinSpend, Program]:
# Generate starting info
key_lookup = KeyTool()
@@ -319,11 +307,11 @@ async def prepare_singleton_eve(
starting_coin = records[0].coin
# Launching
conditions, launcher_coin_spend = singleton_top_layer.launch_conditions_and_coinsol(
coin=starting_coin, inner_puzzle=inner_puzzle, comment=[], amount=start_amount
coin=starting_coin, inner_puzzle=inner_puzzle, comment=[], amount=singleton_amount
)
# Keep a remaining coin with an even amount
conditions.append(
Program.to([ConditionOpcode.CREATE_COIN, IDENTITY_PUZZLE_HASH, starting_coin.amount - start_amount - 1])
Program.to([ConditionOpcode.CREATE_COIN, IDENTITY_PUZZLE_HASH, starting_coin.amount - singleton_amount - 1])
)
# Create a solution for standard transaction
delegated_puzzle = p2_conditions.puzzle_for_conditions(conditions)
@@ -351,10 +339,10 @@ async def prepare_singleton_eve(
async def prepare_and_test_singleton(
sim: SpendSim, sim_client: SimClient, is_eligible_for_ff: bool, start_amount: uint64, singleton_amount: uint64
sim: SpendSim, sim_client: SimClient, is_eligible_for_ff: bool, singleton_amount: uint64
) -> tuple[Coin, CoinSpend, Program, Coin]:
inner_puzzle, eve_coin_spend, eve_signing_puzzle = await prepare_singleton_eve(
sim, sim_client, is_eligible_for_ff, start_amount, singleton_amount
sim, sim_client, is_eligible_for_ff, singleton_amount
)
# At this point we don't have any unspent singleton
singleton_puzzle_hash = eve_coin_spend.coin.puzzle_hash
@@ -374,11 +362,7 @@ async def prepare_and_test_singleton(
singleton_puzzle_hash
)
assert unspent_lineage_info == UnspentLineageInfo(
coin_id=singleton.name(),
coin_amount=singleton.amount,
parent_id=eve_coin.name(),
parent_amount=eve_coin.amount,
parent_parent_id=eve_coin.parent_coin_info,
coin_id=singleton.name(), parent_id=eve_coin.name(), parent_parent_id=eve_coin.parent_coin_info
)
return singleton, eve_coin_spend, inner_puzzle, remaining_coin
@@ -393,7 +377,7 @@ async def test_singleton_fast_forward_solo() -> None:
SINGLETON_AMOUNT = uint64(1337)
async with sim_and_client() as (sim, sim_client):
singleton, eve_coin_spend, inner_puzzle, _ = await prepare_and_test_singleton(
sim, sim_client, True, SINGLETON_AMOUNT, SINGLETON_AMOUNT
sim, sim_client, True, SINGLETON_AMOUNT
)
singleton_puzzle_hash = eve_coin_spend.coin.puzzle_hash
inner_puzzle_hash = inner_puzzle.get_tree_hash()
@@ -410,9 +394,7 @@ async def test_singleton_fast_forward_solo() -> None:
assert singleton_child.amount == SINGLETON_AMOUNT
assert unspent_lineage_info == UnspentLineageInfo(
coin_id=singleton_child.name(),
coin_amount=singleton_child.amount,
parent_id=eve_coin_spend.coin.name(),
parent_amount=singleton.amount,
parent_parent_id=eve_coin_spend.coin.parent_coin_info,
)
@@ -434,12 +416,10 @@ async def test_singleton_fast_forward_different_block(is_eligible_for_ff: bool)
get properly fast forwarded to the latest unspent (when it's eligible) or
get correctly rejected as a double spend (when it's not eligible)
"""
START_AMOUNT = uint64(1337)
# We're decrementing the next iteration's amount for testing purposes
SINGLETON_AMOUNT = uint64(1335)
SINGLETON_AMOUNT = uint64(1337)
async with sim_and_client() as (sim, sim_client):
singleton, eve_coin_spend, inner_puzzle, remaining_coin = await prepare_and_test_singleton(
sim, sim_client, is_eligible_for_ff, START_AMOUNT, SINGLETON_AMOUNT
sim, sim_client, is_eligible_for_ff, SINGLETON_AMOUNT
)
# Let's spend this first version, to create a bigger singleton child
singleton_puzzle_hash = eve_coin_spend.coin.puzzle_hash
@@ -475,11 +455,7 @@ async def test_singleton_fast_forward_different_block(is_eligible_for_ff: bool)
singleton_child, [remaining_coin] = await get_singleton_and_remaining_coins(sim)
assert singleton_child.amount == SINGLETON_AMOUNT
assert unspent_lineage_info == UnspentLineageInfo(
coin_id=singleton_child.name(),
coin_amount=singleton_child.amount,
parent_id=singleton.name(),
parent_amount=singleton.amount,
parent_parent_id=eve_coin_spend.coin.name(),
coin_id=singleton_child.name(), parent_id=singleton.name(), parent_parent_id=eve_coin_spend.coin.name()
)
# Now let's spend the first version again (despite being already spent by now)
remaining_spend_solution = SerializedProgram.from_program(
@@ -506,11 +482,7 @@ async def test_singleton_fast_forward_different_block(is_eligible_for_ff: bool)
)
singleton_grandchild, [remaining_coin] = await get_singleton_and_remaining_coins(sim)
assert unspent_lineage_info == UnspentLineageInfo(
coin_id=singleton_grandchild.name(),
coin_amount=singleton_grandchild.amount,
parent_id=singleton_child.name(),
parent_amount=singleton_child.amount,
parent_parent_id=singleton.name(),
coin_id=singleton_grandchild.name(), parent_id=singleton_child.name(), parent_parent_id=singleton.name()
)
else:
# As this singleton is not eligible for fast forward, attempting to
@@ -526,12 +498,10 @@ async def test_singleton_fast_forward_same_block() -> None:
singleton version, all in the same block, to make sure they get properly
fast forwarded and chained down to a latest unspent version
"""
START_AMOUNT = uint64(1337)
# We're decrementing the next iteration's amount for testing purposes
SINGLETON_AMOUNT = uint64(1335)
SINGLETON_AMOUNT = uint64(1337)
async with sim_and_client() as (sim, sim_client):
singleton, eve_coin_spend, inner_puzzle, remaining_coin = await prepare_and_test_singleton(
sim, sim_client, True, START_AMOUNT, SINGLETON_AMOUNT
sim, sim_client, True, SINGLETON_AMOUNT
)
# Let's spend this first version, to create a bigger singleton child
singleton_puzzle_hash = eve_coin_spend.coin.puzzle_hash
@@ -558,11 +528,7 @@ async def test_singleton_fast_forward_same_block() -> None:
singleton_child, [remaining_coin] = await get_singleton_and_remaining_coins(sim)
assert singleton_child.amount == SINGLETON_AMOUNT
assert unspent_lineage_info == UnspentLineageInfo(
coin_id=singleton_child.name(),
coin_amount=singleton_child.amount,
parent_id=singleton.name(),
parent_amount=singleton.amount,
parent_parent_id=eve_coin_spend.coin.name(),
coin_id=singleton_child.name(), parent_id=singleton.name(), parent_parent_id=eve_coin_spend.coin.name()
)
# Now let's send 3 arbitrary spends of the already spent singleton in
# one block. They should all properly fast forward
@@ -596,14 +562,8 @@ async def test_singleton_fast_forward_same_block() -> None:
assert unspent_lineage_info is not None
# The unspent coin ID should reflect the latest version
assert unspent_lineage_info.coin_id == latest_singleton.name()
# The latest version should have the last random amount
assert latest_singleton.amount == SINGLETON_AMOUNT
# The unspent coin amount should reflect the latest version
assert unspent_lineage_info.coin_amount == latest_singleton.amount
# The unspent parent ID should reflect the latest version's parent
assert unspent_lineage_info.parent_id == latest_singleton.parent_coin_info
# The one before it should have the second last random amount
assert unspent_lineage_info.parent_amount == SINGLETON_AMOUNT
@pytest.mark.anyio
@@ -615,7 +575,7 @@ async def test_mempool_items_immutability_on_ff() -> None:
SINGLETON_AMOUNT = uint64(1337)
async with sim_and_client() as (sim, sim_client):
singleton, eve_coin_spend, inner_puzzle, remaining_coin = await prepare_and_test_singleton(
sim, sim_client, True, SINGLETON_AMOUNT, SINGLETON_AMOUNT
sim, sim_client, True, SINGLETON_AMOUNT
)
singleton_name = singleton.name()
singleton_puzzle_hash = eve_coin_spend.coin.puzzle_hash
@@ -649,11 +609,7 @@ async def test_mempool_items_immutability_on_ff() -> None:
singleton_child_name = singleton_child.name()
assert singleton_child.amount == SINGLETON_AMOUNT
assert unspent_lineage_info == UnspentLineageInfo(
coin_id=singleton_child_name,
coin_amount=singleton_child.amount,
parent_id=singleton_name,
parent_amount=singleton.amount,
parent_parent_id=eve_coin_spend.coin.name(),
coin_id=singleton_child_name, parent_id=singleton_name, parent_parent_id=eve_coin_spend.coin.name()
)
# Now let's spend the first version again (despite being already spent
# by now) to exercise its fast forward.
@@ -694,11 +650,11 @@ async def test_double_spend_ff_spend_no_latest_unspent() -> None:
This test covers the scenario where we receive a spend bundle with a
singleton fast forward spend that has currently no unspent coin.
"""
test_amount = uint64(1337)
singleton_amount = uint64(1337)
async with sim_and_client() as (sim, sim_client):
# Prepare a singleton spend
singleton, eve_coin_spend, inner_puzzle, _ = await prepare_and_test_singleton(
sim, sim_client, True, start_amount=test_amount, singleton_amount=test_amount
sim, sim_client, True, singleton_amount=singleton_amount
)
singleton_name = singleton.name()
singleton_puzzle_hash = eve_coin_spend.coin.puzzle_hash
@@ -708,7 +664,7 @@ async def test_double_spend_ff_spend_no_latest_unspent() -> None:
sig = AugSchemeMPL.sign(sk, b"foobar", g1)
inner_conditions: list[list[Any]] = [
[ConditionOpcode.AGG_SIG_UNSAFE, bytes(g1), b"foobar"],
[ConditionOpcode.CREATE_COIN, inner_puzzle_hash, test_amount],
[ConditionOpcode.CREATE_COIN, inner_puzzle_hash, singleton_amount],
]
singleton_coin_spend, _ = make_singleton_coin_spend(eve_coin_spend, singleton, inner_puzzle, inner_conditions)
# Get its current latest unspent info
@@ -717,9 +673,7 @@ async def test_double_spend_ff_spend_no_latest_unspent() -> None:
)
assert unspent_lineage_info == UnspentLineageInfo(
coin_id=singleton_name,
coin_amount=test_amount,
parent_id=eve_coin_spend.coin.name(),
parent_amount=eve_coin_spend.coin.amount,
parent_parent_id=eve_coin_spend.coin.parent_coin_info,
)
# Let's remove this latest unspent coin from the coin store
+69 -18
View File
@@ -27,41 +27,72 @@ from chia.types.unfinished_block import UnfinishedBlock
class TestNewPeak:
@pytest.mark.anyio
async def test_timelord_new_peak_basic(
self, bt: BlockTools, timelord: tuple[TimelordAPI, ChiaServer], default_1000_blocks: list[FullBlock]
self,
timelord: tuple[TimelordAPI, ChiaServer],
default_1000_blocks: list[FullBlock],
one_node: tuple[list[FullNodeService], list[FullNodeSimulator], BlockTools],
) -> None:
[full_node_service], _, bt = one_node
full_node = full_node_service._node
async with create_blockchain(bt.constants, 2) as (b1, _):
async with create_blockchain(bt.constants, 2) as (b2, _):
timelord_api, _ = timelord
for block in default_1000_blocks:
await _validate_and_add_block(b1, block)
await _validate_and_add_block(b2, block)
await full_node.add_block(block)
peak = timelord_peak_from_block(b1, default_1000_blocks[-1])
assert peak is not None
assert timelord_api.timelord.new_peak is None
await timelord_api.new_peak_timelord(peak)
assert timelord_api.timelord.new_peak is not None
assert timelord_api.timelord.new_peak.reward_chain_block.height == peak.reward_chain_block.height
await time_out_assert(60, tl_new_peak_is_none, True, timelord_api)
assert timelord_api.timelord.last_state.peak is not None
assert (
timelord_api.timelord.last_state.peak.reward_chain_block.get_hash()
== peak.reward_chain_block.get_hash()
)
blocks = bt.get_consecutive_blocks(1, default_1000_blocks)
await _validate_and_add_block(b1, blocks[-1])
await _validate_and_add_block(b2, blocks[-1])
await timelord_api.new_peak_timelord(timelord_peak_from_block(b1, blocks[-1]))
assert timelord_api.timelord.new_peak.reward_chain_block.height == blocks[-1].height
await full_node.add_block(blocks[-1])
await time_out_assert(60, tl_new_peak_is_none, True, timelord_api)
assert timelord_api.timelord.last_state.peak is not None
assert (
timelord_api.timelord.last_state.peak.reward_chain_block.get_hash()
== blocks[-1].reward_chain_block.get_hash()
)
fn_peak = full_node.blockchain.get_peak()
assert fn_peak is not None and fn_peak.header_hash == blocks[-1].header_hash
blocks_1 = bt.get_consecutive_blocks(2, blocks)
await _validate_and_add_block(b1, blocks_1[-2])
await _validate_and_add_block(b1, blocks_1[-1])
await timelord_api.new_peak_timelord(timelord_peak_from_block(b1, blocks_1[-2]))
await full_node.add_block(blocks_1[-2])
await timelord_api.new_peak_timelord(timelord_peak_from_block(b1, blocks_1[-1]))
assert timelord_api.timelord.new_peak.reward_chain_block.height == blocks_1[-1].height
await full_node.add_block(blocks_1[-1])
await time_out_assert(60, tl_new_peak_is_none, True, timelord_api)
assert timelord_api.timelord.last_state.peak is not None
assert (
timelord_api.timelord.last_state.peak.reward_chain_block.get_hash()
== blocks_1[-1].reward_chain_block.get_hash()
)
fn_peak = full_node.blockchain.get_peak()
assert fn_peak is not None and fn_peak.header_hash == blocks_1[-1].header_hash
# new unknown peak, weight less then curr peak
blocks_2 = bt.get_consecutive_blocks(1, blocks)
await _validate_and_add_block(b2, blocks_2[-1])
await timelord_api.new_peak_timelord(timelord_peak_from_block(b2, blocks_2[-1]))
await full_node.add_block(blocks_2[-1])
await time_out_assert(60, tl_new_peak_is_none, True, timelord_api)
assert timelord_api.timelord.last_state.last_weight == blocks_1[-1].weight
assert timelord_api.timelord.last_state.total_iters == blocks_1[-1].reward_chain_block.total_iters
fn_peak = full_node.blockchain.get_peak()
assert fn_peak is not None and fn_peak.header_hash == blocks_1[-1].header_hash
@pytest.mark.anyio
async def test_timelord_new_peak_unfinished_not_orphaned(
@@ -106,7 +137,7 @@ class TestNewPeak:
assert timelord_unf_block.reward_chain_block.total_iters <= new_peak.reward_chain_block.total_iters
await timelord_api.new_peak_timelord(new_peak)
await time_out_assert(60, peak_new_peak_is_none, True, timelord_api)
await time_out_assert(60, tl_new_peak_is_none, True, timelord_api)
assert (
timelord_api.timelord.last_state.peak.reward_chain_block.get_hash()
@@ -135,8 +166,11 @@ class TestNewPeak:
assert timelord_api.timelord.new_peak is None
await timelord_api.new_peak_timelord(peak)
assert timelord_api.timelord.new_peak is not None
await time_out_assert(60, tl_new_peak_is_none, True, timelord_api)
assert timelord_api.timelord.last_state.peak is not None
assert (
timelord_api.timelord.new_peak.reward_chain_block.get_hash() == peak.reward_chain_block.get_hash()
timelord_api.timelord.last_state.peak.reward_chain_block.get_hash()
== peak.reward_chain_block.get_hash()
)
# make two new blocks on tip, block_2 has higher total iterations
@@ -177,7 +211,7 @@ class TestNewPeak:
# add block_2 peak and make sure we skip it and prefer to finish block_1
assert timelord_unf_block.reward_chain_block.total_iters <= new_peak.reward_chain_block.total_iters
await timelord_api.new_peak_timelord(new_peak)
await time_out_assert(60, peak_new_peak_is_none, True, timelord_api)
await time_out_assert(60, tl_new_peak_is_none, True, timelord_api)
# check that peak did not change
assert (
@@ -238,9 +272,11 @@ class TestNewPeak:
assert peak is not None
assert timelord_api.timelord.new_peak is None
await timelord_api.new_peak_timelord(peak)
assert timelord_api.timelord.new_peak is not None
await time_out_assert(60, tl_new_peak_is_none, True, timelord_api)
assert timelord_api.timelord.last_state.peak is not None
assert (
timelord_api.timelord.new_peak.reward_chain_block.get_hash() == peak.reward_chain_block.get_hash()
timelord_api.timelord.last_state.peak.reward_chain_block.get_hash()
== peak.reward_chain_block.get_hash()
)
# make two new blocks on tip
@@ -277,7 +313,7 @@ class TestNewPeak:
assert timelord_unf_block.reward_chain_block.total_iters <= new_peak.reward_chain_block.total_iters
await timelord_api.new_peak_timelord(new_peak)
await time_out_assert(60, peak_new_peak_is_none, True, timelord_api)
await time_out_assert(60, tl_new_peak_is_none, True, timelord_api)
assert (
timelord_api.timelord.last_state.peak.reward_chain_block.get_hash()
@@ -286,22 +322,30 @@ class TestNewPeak:
@pytest.mark.anyio
async def test_timelord_new_peak_unfinished_eos(
self, bt: BlockTools, timelord: tuple[TimelordAPI, ChiaServer], default_1000_blocks: list[FullBlock]
self,
one_node: tuple[list[FullNodeService], list[FullNodeSimulator], BlockTools],
timelord: tuple[TimelordAPI, ChiaServer],
default_1000_blocks: list[FullBlock],
) -> None:
[full_node_service], _, bt = one_node
full_node = full_node_service._node
async with create_blockchain(bt.constants, 2) as (b1, _):
async with create_blockchain(bt.constants, 2) as (b2, _):
timelord_api, _ = timelord
for block in default_1000_blocks:
await _validate_and_add_block(b1, block)
await _validate_and_add_block(b2, block)
await full_node.add_block(block)
peak = timelord_peak_from_block(b1, default_1000_blocks[-1])
assert peak is not None
assert timelord_api.timelord.new_peak is None
await timelord_api.new_peak_timelord(peak)
assert timelord_api.timelord.new_peak is not None
await time_out_assert(60, tl_new_peak_is_none, True, timelord_api)
assert timelord_api.timelord.last_state.peak is not None
assert (
timelord_api.timelord.new_peak.reward_chain_block.get_hash() == peak.reward_chain_block.get_hash()
timelord_api.timelord.last_state.peak.reward_chain_block.get_hash()
== peak.reward_chain_block.get_hash()
)
# make two new blocks on tip, block_2 is in a new slot
@@ -315,6 +359,9 @@ class TestNewPeak:
await _validate_and_add_block(b1, block_1)
await _validate_and_add_block(b2, block_2)
await full_node.add_block(block_2)
fn_peak = full_node.blockchain.get_peak()
assert fn_peak is not None and fn_peak.header_hash == block_2.header_hash
block_record = b2.block_record(block_2.header_hash)
sub_slot_iters, difficulty = get_next_sub_slot_iters_and_difficulty(
@@ -340,7 +387,8 @@ class TestNewPeak:
new_peak = timelord_peak_from_block(b1, block_1)
assert timelord_unf_block.reward_chain_block.total_iters >= new_peak.reward_chain_block.total_iters
await timelord_api.new_peak_timelord(new_peak)
await time_out_assert(60, peak_new_peak_is_none, True, timelord_api)
await full_node.add_block(block_1)
await time_out_assert(60, tl_new_peak_is_none, True, timelord_api)
# make sure we switch to lower iteration peak
assert (
@@ -348,6 +396,9 @@ class TestNewPeak:
== new_peak.reward_chain_block.get_hash()
)
fn_peak = full_node.blockchain.get_peak()
assert fn_peak is not None and fn_peak.header_hash == block_1.header_hash
@pytest.mark.anyio
async def test_timelord_new_peak_node_sync(
self,
@@ -373,7 +424,7 @@ class TestNewPeak:
assert (
timelord_api.timelord.new_peak.reward_chain_block.get_hash() == peak.reward_chain_block.get_hash()
)
await time_out_assert(60, peak_new_peak_is_none, True, timelord_api)
await time_out_assert(60, tl_new_peak_is_none, True, timelord_api)
# make two new blocks on tip, block_2 has higher total iterations
block_1 = bt.get_consecutive_blocks(1, default_1000_blocks)[-1]
block_2 = bt.get_consecutive_blocks(
@@ -394,7 +445,7 @@ class TestNewPeak:
assert timelord_api.timelord.new_peak is not None
assert peak.header_hash == block_2.header_hash
assert peak_tl.reward_chain_block.get_hash() == peak.reward_chain_block.get_hash()
await time_out_assert(60, peak_new_peak_is_none, True, timelord_api)
await time_out_assert(60, tl_new_peak_is_none, True, timelord_api)
await full_node.add_block(block_1)
await _validate_and_add_block(b1, block_1)
@@ -491,5 +542,5 @@ def timelord_peak_from_block(
)
def peak_new_peak_is_none(timelord: TimelordAPI) -> bool:
def tl_new_peak_is_none(timelord: TimelordAPI) -> bool:
return timelord.timelord.new_peak is None
+6 -6
View File
@@ -12,7 +12,7 @@ from chia.cmds.completion import completion
from chia.cmds.configure import configure_cmd
from chia.cmds.data import data_cmd
from chia.cmds.db import db_cmd
from chia.cmds.dev import dev_cmd
from chia.cmds.dev.main import dev_cmd
from chia.cmds.farm import farm_cmd
from chia.cmds.init import init_cmd
from chia.cmds.keys import keys_cmd
@@ -58,19 +58,19 @@ CONTEXT_SETTINGS = {
def cli(
ctx: click.Context,
root_path: str,
keys_root_path: Optional[str] = None,
keys_root_path: str,
passphrase_file: Optional[TextIOWrapper] = None,
) -> None:
from pathlib import Path
context = ChiaCliContext.set_default(ctx=ctx)
context.root_path = Path(root_path)
context.keys_root_path = Path(keys_root_path)
# keys_root_path and passphrase_file will be None if the passphrase options have been
set_keys_root_path(Path(keys_root_path))
# passphrase_file will be None if the passphrase options have been
# scrubbed from the CLI options
if keys_root_path is not None:
set_keys_root_path(Path(keys_root_path))
if passphrase_file is not None:
import sys
+2 -1
View File
@@ -25,7 +25,7 @@ from chia_rs.sized_bytes import bytes32
from typing_extensions import dataclass_transform
from chia.util.byte_types import hexstr_to_bytes
from chia.util.default_root import DEFAULT_ROOT_PATH
from chia.util.default_root import DEFAULT_KEYS_ROOT_PATH, DEFAULT_ROOT_PATH
from chia.util.streamable import is_type_SpecificOptional
SyncCmd = Callable[..., None]
@@ -68,6 +68,7 @@ class ChiaCliContext:
context_dict_key: ClassVar[str] = "_chia_cli_context"
root_path: pathlib.Path = DEFAULT_ROOT_PATH
keys_root_path: pathlib.Path = DEFAULT_KEYS_ROOT_PATH
expected_prefix: Optional[str] = None
rpc_port: Optional[int] = None
keys_fingerprint: Optional[int] = None
View File
+4 -4
View File
@@ -2,10 +2,10 @@ from __future__ import annotations
import click
from chia.cmds.gh import gh_group
from chia.cmds.installers import installers_group
from chia.cmds.mempool import mempool_cmd
from chia.cmds.sim import sim_cmd
from chia.cmds.dev.gh import gh_group
from chia.cmds.dev.installers import installers_group
from chia.cmds.dev.mempool import mempool_cmd
from chia.cmds.dev.sim import sim_cmd
@click.group("dev", help="Developer commands and tools")
@@ -6,7 +6,7 @@ from typing import Optional
import click
from chia.cmds.cmd_classes import ChiaCliContext
from chia.cmds.mempool_funcs import create_block_async, export_mempool_async, import_mempool_async
from chia.cmds.dev.mempool_funcs import create_block_async, export_mempool_async, import_mempool_async
@click.group("mempool", help="Debug the mempool")
+28 -39
View File
@@ -38,7 +38,7 @@ from chia.types.blockchain_format.proof_of_space import ProofOfSpace
from chia.types.blockchain_format.vdf import VDFInfo, VDFProof
from chia.types.end_of_slot_bundle import EndOfSubSlotBundle
from chia.types.full_block import FullBlock
from chia.types.generator_types import BlockGenerator
from chia.types.generator_types import BlockGenerator, NewBlockGenerator
from chia.types.unfinished_block import UnfinishedBlock
from chia.util.hash import std_hash
from chia.util.prev_transaction_block import get_prev_transaction_block
@@ -79,10 +79,7 @@ def compute_block_fee(additions: Sequence[Coin], removals: Sequence[Coin]) -> ui
def create_foliage(
constants: ConsensusConstants,
reward_block_unfinished: RewardChainBlockUnfinished,
block_generator: Optional[BlockGenerator],
aggregate_sig: G2Element,
additions: list[Coin],
removals: list[Coin],
new_block_gen: Optional[NewBlockGenerator],
prev_block: Optional[BlockRecord],
blocks: BlockRecordsProtocol,
total_iters_sp: uint128,
@@ -103,8 +100,7 @@ def create_foliage(
Args:
constants: consensus constants being used for this chain
reward_block_unfinished: the reward block to look at, potentially at the signage point
block_generator: transactions to add to the foliage block, if created
aggregate_sig: aggregate of all transactions (or infinity element)
new_block_gen: transactions to add to the foliage block, if created, including aggregate signature
prev_block: the previous block at the signage point
blocks: dict from header hash to blocks, of all ancestor blocks
total_iters_sp: total iters at the signage point
@@ -165,15 +161,16 @@ def create_foliage(
foliage_transaction_block_hash: Optional[bytes32]
if is_transaction_block:
cost = uint64(0)
cost: uint64
spend_bundle_fees: uint64
# Calculate the cost of transactions
if block_generator is not None:
cost = compute_cost(block_generator, constants, height)
spend_bundle_fees = compute_fees(additions, removals)
if new_block_gen is not None:
cost = compute_cost(new_block_gen, constants, height)
spend_bundle_fees = compute_fees(new_block_gen.additions, new_block_gen.removals)
else:
spend_bundle_fees = uint64(0)
cost = uint64(0)
reward_claims_incorporated = []
if height > 0:
@@ -215,14 +212,19 @@ def create_foliage(
)
reward_claims_incorporated += [pool_coin, farmer_coin]
curr = blocks.block_record(curr.prev_hash)
additions.extend(reward_claims_incorporated.copy())
for coin in additions:
for coin in reward_claims_incorporated:
tx_additions.append(coin)
byte_array_tx.append(bytearray(coin.puzzle_hash))
for coin in removals:
cname = coin.name()
tx_removals.append(cname)
byte_array_tx.append(bytearray(cname))
if new_block_gen is not None:
for coin in new_block_gen.additions:
tx_additions.append(coin)
byte_array_tx.append(bytearray(coin.puzzle_hash))
for coin in new_block_gen.removals:
cname = coin.name()
tx_removals.append(cname)
byte_array_tx.append(bytearray(cname))
bip158: PyBIP158 = PyBIP158(byte_array_tx)
encoded = bytes(bip158.GetEncoded())
@@ -247,8 +249,8 @@ def create_foliage(
removals_root = bytes32(compute_merkle_set_root(tx_removals))
generator_hash = bytes32.zeros
if block_generator is not None:
generator_hash = std_hash(block_generator.program)
if new_block_gen is not None:
generator_hash = std_hash(new_block_gen.program)
generator_refs_hash = bytes32([1] * 32)
filter_hash: bytes32 = std_hash(encoded)
@@ -256,7 +258,7 @@ def create_foliage(
transactions_info: Optional[TransactionsInfo] = TransactionsInfo(
generator_hash,
generator_refs_hash,
aggregate_sig,
new_block_gen.signature if new_block_gen else G2Element(),
spend_bundle_fees,
cost,
reward_claims_incorporated,
@@ -318,10 +320,7 @@ def create_unfinished_block(
timestamp: uint64,
blocks: BlockRecordsProtocol,
seed: bytes = b"",
block_generator: Optional[BlockGenerator] = None,
aggregate_sig: G2Element = G2Element(),
additions: Optional[list[Coin]] = None,
removals: Optional[list[Coin]] = None,
new_block_gen: Optional[NewBlockGenerator] = None,
prev_block: Optional[BlockRecord] = None,
finished_sub_slots_input: Optional[list[EndOfSubSlotBundle]] = None,
compute_cost: Callable[[BlockGenerator, ConsensusConstants, uint32], uint64] = compute_block_cost,
@@ -347,10 +346,7 @@ def create_unfinished_block(
signage_point: signage point information (VDFs)
timestamp: timestamp to add to the foliage block, if created
seed: seed to randomize chain
block_generator: transactions to add to the foliage block, if created
aggregate_sig: aggregate of all transactions (or infinity element)
additions: Coins added in spend_bundle
removals: Coins removed in spend_bundle
new_block_gen: transactions to add to the foliage block, if created, including aggregate signature
prev_block: previous block (already in chain) from the signage point
blocks: dictionary from header hash to SBR of all included SBR
finished_sub_slots_input: finished_sub_slots at the signage point
@@ -409,17 +405,10 @@ def create_unfinished_block(
signage_point.rc_vdf,
rc_sp_signature,
)
if additions is None:
additions = []
if removals is None:
removals = []
(foliage, foliage_transaction_block, transactions_info) = create_foliage(
constants,
rc_block,
block_generator,
aggregate_sig,
additions,
removals,
new_block_gen,
prev_block,
blocks,
total_iters_sp,
@@ -440,8 +429,8 @@ def create_unfinished_block(
foliage,
foliage_transaction_block,
transactions_info,
block_generator.program if block_generator else None,
[], # generator_refs
new_block_gen.program if new_block_gen else None,
new_block_gen.block_refs if new_block_gen else [],
)
+4 -10
View File
@@ -11,7 +11,6 @@ import typing_extensions
from aiosqlite import Cursor
from chia_rs.sized_bytes import bytes32
from chia_rs.sized_ints import uint32, uint64
from clvm.casts import int_from_bytes
from chia.protocols.wallet_protocol import CoinState
from chia.types.blockchain_format.coin import Coin
@@ -613,27 +612,22 @@ class CoinStore:
async with self.db_wrapper.reader_no_transaction() as conn:
async with conn.execute(
"SELECT unspent.coin_name, "
"unspent.amount, "
"unspent.coin_parent, "
"parent.amount, "
"parent.coin_parent "
"FROM coin_record AS unspent INDEXED BY coin_puzzle_hash "
"LEFT JOIN coin_record AS parent ON unspent.coin_parent = parent.coin_name "
"WHERE unspent.spent_index = 0 "
"AND parent.spent_index > 0 "
"AND unspent.puzzle_hash = ? "
"AND parent.puzzle_hash = unspent.puzzle_hash",
"AND parent.puzzle_hash = unspent.puzzle_hash "
"AND parent.amount = unspent.amount",
(puzzle_hash,),
) as cursor:
rows = list(await cursor.fetchall())
if len(rows) != 1:
log.debug("Expected 1 unspent with puzzle hash %s, but found %s", puzzle_hash.hex(), len(rows))
return None
coin_id, coin_amount, parent_id, parent_amount, parent_parent_id = rows[0]
coin_id, parent_id, parent_parent_id = rows[0]
return UnspentLineageInfo(
coin_id=bytes32(coin_id),
coin_amount=uint64(int_from_bytes(coin_amount)),
parent_id=bytes32(parent_id),
parent_amount=uint64(int_from_bytes(parent_amount)),
parent_parent_id=bytes32(parent_parent_id),
coin_id=bytes32(coin_id), parent_id=bytes32(parent_id), parent_parent_id=bytes32(parent_parent_id)
)
+9 -15
View File
@@ -62,7 +62,7 @@ from chia.types.blockchain_format.sub_epoch_summary import SubEpochSummary
from chia.types.coin_record import CoinRecord
from chia.types.end_of_slot_bundle import EndOfSubSlotBundle
from chia.types.full_block import FullBlock
from chia.types.generator_types import BlockGenerator
from chia.types.generator_types import BlockGenerator, NewBlockGenerator
from chia.types.mempool_inclusion_status import MempoolInclusionStatus
from chia.types.peer_info import PeerInfo
from chia.types.spend_bundle import SpendBundle
@@ -835,10 +835,7 @@ class FullNodeAPI:
# 3. In a future sub-slot that we already know of
# Grab best transactions from Mempool for given tip target
aggregate_signature: G2Element = G2Element()
block_generator: Optional[BlockGenerator] = None
additions: Optional[list[Coin]] = []
removals: Optional[list[Coin]] = []
new_block_gen: Optional[NewBlockGenerator]
async with self.full_node.blockchain.priority_mutex.acquire(priority=BlockchainMutexPriority.high):
peak: Optional[BlockRecord] = self.full_node.blockchain.get_peak()
@@ -863,12 +860,15 @@ class FullNodeAPI:
while not curr_l_tb.is_transaction_block:
curr_l_tb = self.full_node.blockchain.block_record(curr_l_tb.prev_hash)
try:
block = await self.full_node.mempool_manager.create_block_generator(curr_l_tb.header_hash)
if block is not None:
block_generator, aggregate_signature, additions, removals = block
new_block_gen = await self.full_node.mempool_manager.create_block_generator(
curr_l_tb.header_hash
)
except Exception as e:
self.log.error(f"Traceback: {traceback.format_exc()}")
self.full_node.log.error(f"Error making spend bundle {e} peak: {peak}")
new_block_gen = None
else:
new_block_gen = None
def get_plot_sig(to_sign: bytes32, _extra: G1Element) -> G2Element:
if to_sign == request.challenge_chain_sp:
@@ -1005,10 +1005,7 @@ class FullNodeAPI:
timestamp,
self.full_node.blockchain,
b"",
block_generator,
aggregate_signature,
additions,
removals,
new_block_gen,
prev_b,
finished_sub_slots,
)
@@ -1065,9 +1062,6 @@ class FullNodeAPI:
self.full_node.blockchain,
b"",
None,
G2Element(),
None,
None,
prev_b,
finished_sub_slots,
)
+6 -4
View File
@@ -20,7 +20,7 @@ from chia.types.blockchain_format.serialized_program import SerializedProgram
from chia.types.clvm_cost import CLVMCost
from chia.types.coin_spend import CoinSpend
from chia.types.eligible_coin_spends import EligibleCoinSpends, SkipDedup, UnspentLineageInfo
from chia.types.generator_types import BlockGenerator
from chia.types.generator_types import NewBlockGenerator
from chia.types.internal_mempool_item import InternalMempoolItem
from chia.types.mempool_item import MempoolItem
from chia.types.spend_bundle import SpendBundle
@@ -492,7 +492,7 @@ class Mempool:
get_unspent_lineage_info_for_puzzle_hash: Callable[[bytes32], Awaitable[Optional[UnspentLineageInfo]]],
constants: ConsensusConstants,
height: uint32,
) -> Optional[tuple[BlockGenerator, G2Element, list[Coin], list[Coin]]]:
) -> Optional[NewBlockGenerator]:
"""
height is needed in case we fast-forward a transaction and we need to
re-run its puzzle.
@@ -520,8 +520,10 @@ class Mempool:
logging.INFO if duration < 1 else logging.WARNING,
f"serializing block generator took {duration:0.2f} seconds",
)
return (
BlockGenerator(SerializedProgram.from_bytes(block_program), []),
return NewBlockGenerator(
SerializedProgram.from_bytes(block_program),
[],
[],
spend_bundle.aggregated_signature,
additions,
removals,
+2 -3
View File
@@ -13,7 +13,6 @@ from chia_rs import (
ELIGIBLE_FOR_FF,
BLSCache,
ConsensusConstants,
G2Element,
supports_fast_forward,
validate_clvm_and_signature,
)
@@ -34,7 +33,7 @@ from chia.types.clvm_cost import CLVMCost
from chia.types.coin_record import CoinRecord
from chia.types.eligible_coin_spends import EligibilityAndAdditions, UnspentLineageInfo
from chia.types.fee_rate import FeeRate
from chia.types.generator_types import BlockGenerator
from chia.types.generator_types import NewBlockGenerator
from chia.types.mempool_inclusion_status import MempoolInclusionStatus
from chia.types.mempool_item import BundleCoinSpend, MempoolItem
from chia.types.spend_bundle import SpendBundle
@@ -244,7 +243,7 @@ class MempoolManager:
async def create_block_generator(
self,
last_tb_header_hash: bytes32,
) -> Optional[tuple[BlockGenerator, G2Element, list[Coin], list[Coin]]]:
) -> Optional[NewBlockGenerator]:
"""
Returns a block generator program, the aggregate signature and all additions and removals, for a new block
"""
+20 -12
View File
@@ -4,7 +4,6 @@ import time
from datetime import datetime, timezone
from typing import TYPE_CHECKING, Any, ClassVar, Optional, cast
from chia_rs import G2Element
from chia_rs.sized_bytes import bytes32
from chia_rs.sized_ints import uint32, uint64, uint128
@@ -25,7 +24,7 @@ from chia.types.blockchain_format.proof_of_space import calculate_prefix_bits
from chia.types.coin_record import CoinRecord
from chia.types.coin_spend import CoinSpend
from chia.types.full_block import FullBlock
from chia.types.generator_types import BlockGenerator
from chia.types.generator_types import BlockGenerator, NewBlockGenerator
from chia.types.mempool_inclusion_status import MempoolInclusionStatus
from chia.types.spend_bundle import SpendBundle
from chia.types.spend_bundle_conditions import SpendBundleConditions
@@ -840,8 +839,7 @@ class FullNodeRpcApi:
return {"mempool_items": [item.to_json_dict() for item in items]}
async def create_block_generator(self, _: dict[str, Any]) -> EndpointResult:
mempool_generator = BlockGenerator()
signature = G2Element()
gen = NewBlockGenerator()
# Grab best transactions from Mempool for given tip target
async with self.service.blockchain.priority_mutex.acquire(priority=BlockchainMutexPriority.low):
@@ -849,9 +847,11 @@ class FullNodeRpcApi:
if peak is None:
return {
"generator": mempool_generator.program,
"refs": mempool_generator.generator_refs,
"sig": signature,
"generator": gen.program,
"refs": gen.block_refs,
"additions": gen.additions,
"removals": gen.removals,
"sig": gen.signature,
}
# Finds the last transaction block before this one
@@ -863,14 +863,22 @@ class FullNodeRpcApi:
start_time = time.monotonic()
try:
result = await self.service.mempool_manager.create_block_generator(curr_l_tb.header_hash)
assert result is not None
mempool_generator, signature, _additions, _removals = result
maybe_gen = await self.service.mempool_manager.create_block_generator(curr_l_tb.header_hash)
if maybe_gen is None:
self.service.log.error(f"failed to create block generator, peak: {peak}")
else:
gen = maybe_gen
except Exception:
self.service.log.exception(f"Error making spend bundle peak: {peak}")
self.service.log.exception(f"Error creating block generator, peak: {peak}")
self.service.log.info(f"Simulated block constructed in {time.monotonic() - start_time:0.2f} seconds")
return {"generator": mempool_generator.program, "refs": mempool_generator.generator_refs, "sig": signature}
return {
"generator": gen.program,
"refs": gen.block_refs,
"additions": gen.additions,
"removals": gen.removals,
"sig": gen.signature,
}
def _get_spendbundle_type_cost(self, name: str) -> uint64:
"""
+84 -83
View File
@@ -97,7 +97,7 @@ from chia.types.blockchain_format.vdf import VDFInfo, VDFProof
from chia.types.condition_opcodes import ConditionOpcode
from chia.types.end_of_slot_bundle import EndOfSubSlotBundle
from chia.types.full_block import FullBlock
from chia.types.generator_types import BlockGenerator
from chia.types.generator_types import BlockGenerator, NewBlockGenerator
from chia.types.spend_bundle import SpendBundle
from chia.types.unfinished_block import UnfinishedBlock
from chia.util.bech32m import encode_puzzle_hash
@@ -596,6 +596,8 @@ class BlockTools:
skip_overflow: bool = False,
min_signage_point: int = -1,
) -> list[FullBlock]:
# make a copy to not have different invocations affect each other
block_refs = block_refs[:]
assert num_blocks > 0
if block_list_input is not None:
block_list = block_list_input.copy()
@@ -611,6 +613,9 @@ class BlockTools:
tx_block_heights.append(b.height)
constants = self.constants
# this indicates whether the passed in transaction_data has been
# included in a transaction block yet
transaction_data_included = False
if time_per_block is None:
time_per_block = float(constants.SUB_SLOT_TIME_TARGET) / float(constants.SLOT_BLOCKS_TARGET)
@@ -767,20 +772,9 @@ class BlockTools:
continue
assert latest_block.header_hash in blocks
additions = None
removals = None
if transaction_data_included:
transaction_data = None
block_refs = []
if transaction_data is not None:
additions = compute_additions_unchecked(transaction_data)
removals = transaction_data.removals()
elif include_transactions:
assert wallet is not None
assert rng is not None
transaction_data, additions = make_spend_bundle(available_coins, wallet, rng)
removals = transaction_data.removals()
transaction_data_included = False
assert last_timestamp is not None
if proof_of_space.pool_contract_puzzle_hash is not None:
@@ -795,32 +789,48 @@ class BlockTools:
else:
pool_target = PoolTarget(self.pool_ph, uint32(0))
block_generator: Optional[BlockGenerator]
if dummy_block_references and len(tx_block_heights) > 4:
block_refs.extend(
[
tx_block_heights[1],
tx_block_heights[len(tx_block_heights) // 2],
tx_block_heights[-2],
]
)
new_gen: Optional[NewBlockGenerator]
if transaction_data is not None:
# this means the caller passed in transaction_data
# to be included in the block.
additions = compute_additions_unchecked(transaction_data)
removals = transaction_data.removals()
if start_height >= constants.HARD_FORK_HEIGHT:
block_generator = simple_solution_generator_backrefs(transaction_data)
block_refs = []
program = simple_solution_generator_backrefs(transaction_data).program
else:
block_generator = simple_solution_generator(transaction_data)
aggregate_signature = transaction_data.aggregated_signature
program = simple_solution_generator(transaction_data).program
block_refs = []
new_gen = NewBlockGenerator(
program, [], block_refs, transaction_data.aggregated_signature, additions, removals
)
elif include_transactions:
# if the caller did not pass in specific
# transactions, this parameter means we just want
# some transactions
assert wallet is not None
assert rng is not None
transaction_data, additions = make_spend_bundle(available_coins, wallet, rng)
removals = transaction_data.removals()
program = simple_solution_generator(transaction_data).program
new_gen = NewBlockGenerator(
program, [], block_refs, transaction_data.aggregated_signature, additions, removals
)
transaction_data_included = False
elif dummy_block_references:
program = SerializedProgram.from_bytes(solution_generator([]))
new_gen = NewBlockGenerator(program, [], block_refs, G2Element(), [], [])
else:
block_generator = None
aggregate_signature = G2Element()
new_gen = None
if dummy_block_references:
if block_generator is None:
program = SerializedProgram.from_bytes(solution_generator([]))
block_generator = BlockGenerator(program, [])
if len(tx_block_heights) > 4:
block_refs.extend(
[
tx_block_heights[1],
tx_block_heights[len(tx_block_heights) // 2],
tx_block_heights[-2],
]
)
(
full_block,
block_record,
@@ -838,10 +848,7 @@ class BlockTools:
last_timestamp,
start_height,
time_per_block,
block_generator,
aggregate_signature,
additions,
removals,
new_gen,
height_to_hash,
difficulty,
required_iters,
@@ -854,7 +861,6 @@ class BlockTools:
seed,
normalized_to_identity_cc_ip=normalized_to_identity_cc_ip,
current_time=current_time,
block_refs=block_refs,
)
if block_record.is_transaction_block:
transaction_data_included = True
@@ -1033,19 +1039,8 @@ class BlockTools:
latest_block_eos = latest_block
overflow_cc_challenge = finished_sub_slots_at_ip[-1].challenge_chain.get_hash()
overflow_rc_challenge = finished_sub_slots_at_ip[-1].reward_chain.get_hash()
additions = None
removals = None
if transaction_data_included:
transaction_data = None
if transaction_data is not None:
additions = compute_additions_unchecked(transaction_data)
removals = transaction_data.removals()
elif include_transactions:
assert wallet is not None
assert rng is not None
transaction_data, additions = make_spend_bundle(available_coins, wallet, rng)
removals = transaction_data.removals()
transaction_data_included = False
sub_slots_finished += 1
self.log.info(
f"Sub slot finished. blocks included: {blocks_added_this_sub_slot} blocks_per_slot: "
@@ -1113,30 +1108,47 @@ class BlockTools:
pool_target = PoolTarget(pool_reward_puzzle_hash, uint32(0))
else:
pool_target = PoolTarget(self.pool_ph, uint32(0))
if dummy_block_references and len(tx_block_heights) > 4:
block_refs.extend(
[
tx_block_heights[1],
tx_block_heights[len(tx_block_heights) // 2],
tx_block_heights[-2],
]
)
if transaction_data is not None:
# this means the caller passed in transaction_data
# to be included in the block.
additions = compute_additions_unchecked(transaction_data)
removals = transaction_data.removals()
if start_height >= constants.HARD_FORK_HEIGHT:
block_generator = simple_solution_generator_backrefs(transaction_data)
block_refs = []
program = simple_solution_generator_backrefs(transaction_data).program
else:
block_generator = simple_solution_generator(transaction_data)
aggregate_signature = transaction_data.aggregated_signature
program = simple_solution_generator(transaction_data).program
block_refs = []
new_gen = NewBlockGenerator(
program, [], block_refs, transaction_data.aggregated_signature, additions, removals
)
elif include_transactions:
# if the caller did not pass in specific
# transactions, this parameter means we just want
# some transactions
assert wallet is not None
assert rng is not None
transaction_data, additions = make_spend_bundle(available_coins, wallet, rng)
removals = transaction_data.removals()
program = simple_solution_generator(transaction_data).program
new_gen = NewBlockGenerator(
program, [], block_refs, transaction_data.aggregated_signature, additions, removals
)
transaction_data_included = False
elif dummy_block_references:
program = SerializedProgram.from_bytes(solution_generator([]))
new_gen = NewBlockGenerator(program, [], block_refs, G2Element(), [], [])
else:
block_generator = None
aggregate_signature = G2Element()
if dummy_block_references:
if block_generator is None:
program = SerializedProgram.from_bytes(solution_generator([]))
block_generator = BlockGenerator(program, [])
if len(tx_block_heights) > 4:
block_refs.extend(
[
tx_block_heights[1],
tx_block_heights[len(tx_block_heights) // 2],
tx_block_heights[-2],
]
)
new_gen = None
(
full_block,
@@ -1155,10 +1167,7 @@ class BlockTools:
last_timestamp,
start_height,
time_per_block,
block_generator,
aggregate_signature,
additions,
removals,
new_gen,
height_to_hash,
difficulty,
required_iters,
@@ -1173,7 +1182,6 @@ class BlockTools:
overflow_rc_challenge=overflow_rc_challenge,
normalized_to_identity_cc_ip=normalized_to_identity_cc_ip,
current_time=current_time,
block_refs=block_refs,
)
if block_record.is_transaction_block:
@@ -1788,10 +1796,7 @@ def get_full_block_and_block_record(
last_timestamp: float,
start_height: uint32,
time_per_block: float,
block_generator: Optional[BlockGenerator],
aggregate_signature: G2Element,
additions: Optional[list[Coin]],
removals: Optional[list[Coin]],
new_gen: Optional[NewBlockGenerator],
height_to_hash: dict[uint32, bytes32],
difficulty: uint64,
required_iters: uint64,
@@ -1803,7 +1808,6 @@ def get_full_block_and_block_record(
prev_block: BlockRecord,
seed: bytes = b"",
*,
block_refs: list[uint32] = [],
overflow_cc_challenge: Optional[bytes32] = None,
overflow_rc_challenge: Optional[bytes32] = None,
normalized_to_identity_cc_ip: bool = False,
@@ -1838,10 +1842,7 @@ def get_full_block_and_block_record(
uint64(timestamp),
BlockCache(blocks),
seed,
block_generator,
aggregate_signature,
additions,
removals,
new_gen,
prev_block,
finished_sub_slots,
compute_cost=compute_cost_test,
+11 -16
View File
@@ -47,9 +47,7 @@ class DedupCoinSpend:
@dataclasses.dataclass(frozen=True)
class UnspentLineageInfo:
coin_id: bytes32
coin_amount: uint64
parent_id: bytes32
parent_amount: uint64
parent_parent_id: bytes32
@@ -70,16 +68,19 @@ def set_next_singleton_version(
next iteration
"""
singleton_child = next(
(addition for addition in singleton_additions if addition.puzzle_hash == current_singleton.puzzle_hash), None
(
addition
for addition in singleton_additions
if addition.puzzle_hash == current_singleton.puzzle_hash and addition.amount == current_singleton.amount
),
None,
)
if singleton_child is None:
raise ValueError("Could not find fast forward child singleton.")
# Keep track of this in order to chain the next ff
fast_forward_spends[current_singleton.puzzle_hash] = UnspentLineageInfo(
coin_id=singleton_child.name(),
coin_amount=singleton_child.amount,
parent_id=singleton_child.parent_coin_info,
parent_amount=current_singleton.amount,
parent_parent_id=current_singleton.parent_coin_info,
)
@@ -107,14 +108,10 @@ def perform_the_fast_forward(
ValueError if none of the additions are considered to be the singleton's
next iteration
"""
new_coin = Coin(
unspent_lineage_info.parent_id, spend_data.coin_spend.coin.puzzle_hash, unspent_lineage_info.coin_amount
)
new_parent = Coin(
unspent_lineage_info.parent_parent_id,
spend_data.coin_spend.coin.puzzle_hash,
unspent_lineage_info.parent_amount,
)
singleton_ph = spend_data.coin_spend.coin.puzzle_hash
singleton_amount = spend_data.coin_spend.coin.amount
new_coin = Coin(unspent_lineage_info.parent_id, singleton_ph, singleton_amount)
new_parent = Coin(unspent_lineage_info.parent_parent_id, singleton_ph, singleton_amount)
# These hold because puzzle hash is not expected to change
assert new_coin.name() == unspent_lineage_info.coin_id
assert new_parent.name() == unspent_lineage_info.parent_id
@@ -126,7 +123,7 @@ def perform_the_fast_forward(
for addition in spend_data.additions:
patched_addition = Coin(unspent_lineage_info.coin_id, addition.puzzle_hash, addition.amount)
patched_additions.append(patched_addition)
if addition.puzzle_hash == spend_data.coin_spend.coin.puzzle_hash:
if addition.puzzle_hash == singleton_ph and addition.amount == singleton_amount:
# We found the next version of this singleton
singleton_child = patched_addition
if singleton_child is None:
@@ -135,9 +132,7 @@ def perform_the_fast_forward(
# Keep track of this in order to chain the next ff
fast_forward_spends[spend_data.coin_spend.coin.puzzle_hash] = UnspentLineageInfo(
coin_id=singleton_child.name(),
coin_amount=singleton_child.amount,
parent_id=singleton_child.parent_coin_info,
parent_amount=unspent_lineage_info.coin_amount,
parent_parent_id=unspent_lineage_info.parent_id,
)
return new_coin_spend, patched_additions
+26
View File
@@ -2,12 +2,38 @@ from __future__ import annotations
from dataclasses import dataclass, field
from chia_rs import Coin, G2Element
from chia_rs.sized_ints import uint32
from chia.types.blockchain_format.serialized_program import SerializedProgram
from chia.util.streamable import Streamable, streamable
# This holds what we need to pre validate a block generator
@streamable
@dataclass(frozen=True)
class BlockGenerator(Streamable):
program: SerializedProgram = field(default_factory=SerializedProgram.default)
# to run the block generator, we need the actual bytes of the previous
# generators it may reference. These are parameters passed in to the block
# generator
generator_refs: list[bytes] = field(default_factory=list)
# When we create a new block, this object holds the block generator and
# additional information we need to create the UnfinishedBlock from it.
# When creating a block, we still need to be able to run it, to compute its cost
# and validate it. Therefore, this is a superset of the BlockGenerator class.
@dataclass(frozen=True)
class NewBlockGenerator(BlockGenerator):
# when creating a block, we include the block heights of the generators we
# reference. Tese are block heights and generator_refs contain the
# corresponding bytes of the generator programs
block_refs: list[uint32] = field(default_factory=list)
# the aggregate signature of all AGG_SIG_* conditions returned by the block
# generator.
signature: G2Element = G2Element()
# all CREATE_COIN outputs created by the block generator
additions: list[Coin] = field(default_factory=list)
# all coins being spent by the block generator
removals: list[Coin] = field(default_factory=list)