Skip to content

Commit

Permalink
Add Announcements (Chia-Network#881)
Browse files Browse the repository at this point in the history
* cherrypicked add announcement opcodes

* remove unused import in test_mempool

* fixed broken test

* fix black superlint errors

* simple convert of coloured coins to announcements

announcements message now type bytes

* removed assert coin consumed

* updated RL higher level puzzle

* fix RL wallet to use announcement instead of lock

* rebase add announcement to blockchain_check_conditions

* lint and re-enable tests

* remove lingering reference to coin_consumed

* fixed bug with block validation

* fix RL wallet

* -update CC to use more simple announcement message

* lint fix for announcement class

* remove breakpoint
improve announcement set checking
delete TODO

* deleted commented out code
use .extend() instead of nested for

* fix mempool tests and aesthetic changes for richard

* fix mempool and add to debug_spend_bundle

* fixed test_blockchain_transactions

* flake

* update test_mempool annouce tests to use height not subheight

Co-authored-by: Yostra <[email protected]>
  • Loading branch information
matt-o-how and Yostra authored Feb 10, 2021
1 parent 9d064fe commit b37e6e7
Show file tree
Hide file tree
Showing 30 changed files with 252 additions and 443 deletions.
9 changes: 7 additions & 2 deletions src/consensus/block_body_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@
from src.consensus.sub_block_record import SubBlockRecord
from src.types.coin import Coin
from src.types.coin_record import CoinRecord
from src.types.announcement import Announcement
from src.types.condition_opcodes import ConditionOpcode
from src.types.condition_var_pair import ConditionVarPair
from src.types.full_block import FullBlock, additions_for_npc
from src.types.full_block import FullBlock, additions_for_npc, announcements_for_npc
from src.types.name_puzzle_condition import NPC
from src.types.sized_bytes import bytes32
from src.types.unfinished_block import UnfinishedBlock
Expand Down Expand Up @@ -141,6 +142,7 @@ async def validate_block_body(
removals: List[bytes32] = []
coinbase_additions: List[Coin] = list(expected_reward_coins)
additions: List[Coin] = []
announcements: List[Announcement] = []
npc_list: List[NPC] = []
removals_puzzle_dic: Dict[bytes32, bytes32] = {}
cost: uint64 = uint64(0)
Expand Down Expand Up @@ -168,6 +170,7 @@ async def validate_block_body(
removals_puzzle_dic[npc.coin_name] = npc.puzzle_hash

additions = additions_for_npc(npc_list)
announcements = announcements_for_npc(npc_list)

# 9. Check that the correct cost is in the transactions info
if block.transactions_info.cost != cost:
Expand Down Expand Up @@ -342,10 +345,12 @@ async def validate_block_body(
pairs_pks = []
pairs_msgs = []
for npc in npc_list:
unspent = removal_coin_records[npc.coin_name]
assert height is not None
unspent = removal_coin_records[npc.coin_name]
error = blockchain_check_conditions_dict(
unspent,
removal_coin_records,
announcements,
npc.condition_dict,
prev_transaction_block_height,
block.foliage_block.timestamp,
Expand Down
35 changes: 18 additions & 17 deletions src/consensus/blockchain_check_conditions.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,14 @@
from typing import Optional, Dict, List
from typing import Optional, Dict, List, Set

from src.types.condition_var_pair import ConditionVarPair
from src.types.coin_record import CoinRecord
from src.types.sized_bytes import bytes32
from src.types.announcement import Announcement
from src.util.clvm import int_from_bytes
from src.util.condition_tools import ConditionOpcode
from src.util.errors import Err
from src.util.ints import uint64, uint32


def blockchain_assert_coin_consumed(condition: ConditionVarPair, removed: Dict[bytes32, CoinRecord]) -> Optional[Err]:
"""
Checks coin consumed conditions
Returns None if conditions are met, if not returns the reason why it failed
"""
coin_name = condition.vars[0]
if coin_name not in removed:
return Err.ASSERT_COIN_CONSUMED_FAILED
return None


def blockchain_assert_my_coin_id(condition: ConditionVarPair, unspent: CoinRecord) -> Optional[Err]:
"""
Checks if CoinID matches the id from the condition
Expand Down Expand Up @@ -91,24 +80,36 @@ def blockchain_assert_relative_time_exceeds(condition: ConditionVarPair, unspent
return None


def blockchain_assert_announcement(condition: ConditionVarPair, announcements: Set[bytes]) -> Optional[Err]:
"""
Check if an announcement is included in the list of announcements
"""
announcement_hash = condition.vars[0]
if announcement_hash not in announcements:
return Err.ASSERT_ANNOUNCE_CONSUMED_FAILED

return None


def blockchain_check_conditions_dict(
unspent: CoinRecord,
removed: Dict[bytes32, CoinRecord],
announcements: List[Announcement],
conditions_dict: Dict[ConditionOpcode, List[ConditionVarPair]],
prev_transaction_block_height: uint32,
timestamp: uint64,
) -> Optional[Err]:
"""
Check all conditions against current state.
"""
announcement_names = set([a.name() for a in announcements])
for con_list in conditions_dict.values():
cvp: ConditionVarPair
for cvp in con_list:
error = None
if cvp.opcode is ConditionOpcode.ASSERT_COIN_CONSUMED:
error = blockchain_assert_coin_consumed(cvp, removed)
elif cvp.opcode is ConditionOpcode.ASSERT_MY_COIN_ID:
if cvp.opcode is ConditionOpcode.ASSERT_MY_COIN_ID:
error = blockchain_assert_my_coin_id(cvp, unspent)
elif cvp.opcode is ConditionOpcode.ASSERT_ANNOUNCEMENT:
error = blockchain_assert_announcement(cvp, announcement_names)
elif cvp.opcode is ConditionOpcode.ASSERT_BLOCK_INDEX_EXCEEDS:
error = blockchain_assert_block_index_exceeds(cvp, prev_transaction_block_height)
elif cvp.opcode is ConditionOpcode.ASSERT_BLOCK_AGE_EXCEEDS:
Expand Down
3 changes: 2 additions & 1 deletion src/consensus/condition_costs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ class ConditionCost(Enum):
# Condition Costs
AGG_SIG = 20
CREATE_COIN = 200
ASSERT_COIN_CONSUMED = 0
ASSERT_MY_COIN_ID = 0
ASSERT_TIME_EXCEEDS = 0
ASSERT_RELATIVE_TIME_EXCEEDS = 0
ASSERT_BLOCK_INDEX_EXCEEDS = 0
ASSERT_BLOCK_AGE_EXCEEDS = 0
ASSERT_FEE = 0
CREATE_ANNOUNCEMENT = 0
ASSERT_ANNOUNCEMENT = 0
6 changes: 4 additions & 2 deletions src/consensus/cost_calculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,12 @@ def calculate_cost_of_program(
total_vbyte_cost += len(cvp_list) * ConditionCost.ASSERT_BLOCK_INDEX_EXCEEDS.value
elif condition is ConditionOpcode.ASSERT_MY_COIN_ID:
total_vbyte_cost += len(cvp_list) * ConditionCost.ASSERT_MY_COIN_ID.value
elif condition is ConditionOpcode.ASSERT_COIN_CONSUMED:
total_vbyte_cost += len(cvp_list) * ConditionCost.ASSERT_COIN_CONSUMED.value
elif condition is ConditionOpcode.ASSERT_FEE:
total_vbyte_cost += len(cvp_list) * ConditionCost.ASSERT_FEE.value
elif condition is ConditionOpcode.CREATE_ANNOUNCEMENT:
total_vbyte_cost += len(cvp_list) * ConditionCost.CREATE_ANNOUNCEMENT.value
elif condition is ConditionOpcode.ASSERT_ANNOUNCEMENT:
total_vbyte_cost += len(cvp_list) * ConditionCost.ASSERT_ANNOUNCEMENT.value
else:
# We ignore unknown conditions in order to allow for future soft forks
pass
Expand Down
23 changes: 11 additions & 12 deletions src/full_node/mempool_check_conditions.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@
from src.wallet.puzzles.generator_loader import GENERATOR_MOD, GENERATOR_FOR_SINGLE_COIN_MOD


def mempool_assert_coin_consumed(condition: ConditionVarPair, spend_bundle: SpendBundle) -> Optional[Err]:
def mempool_assert_announcement_consumed(condition: ConditionVarPair, spend_bundle: SpendBundle) -> Optional[Err]:
"""
Checks coin consumed conditions
Returns None if conditions are met, if not returns the reason why it failed
Check if an announcement is included in the list of announcements
"""
bundle_removals = spend_bundle.removal_names()
coin_name = condition.vars[0]
if coin_name not in bundle_removals:
return Err.ASSERT_COIN_CONSUMED_FAILED
announcements = spend_bundle.announcements()
announcement_hash = condition.vars[0]
if announcement_hash not in [ann.name() for ann in announcements]:
return Err.ASSERT_ANNOUNCE_CONSUMED_FAILED

return None


Expand Down Expand Up @@ -99,8 +99,7 @@ def mempool_assert_relative_time_exceeds(condition: ConditionVarPair, unspent: C

def get_name_puzzle_conditions(block_program: SerializedProgram, safe_mode: bool):
# TODO: allow generator mod to take something (future)
# TODO: check strict mode locations are set correctly
# TODO: write various tests
# TODO: write more tests
try:
if safe_mode:
cost, result = GENERATOR_MOD.run_safe_with_cost(block_program)
Expand Down Expand Up @@ -160,10 +159,10 @@ def mempool_check_conditions_dict(
cvp: ConditionVarPair
for cvp in con_list:
error = None
if cvp.opcode is ConditionOpcode.ASSERT_COIN_CONSUMED:
error = mempool_assert_coin_consumed(cvp, spend_bundle)
elif cvp.opcode is ConditionOpcode.ASSERT_MY_COIN_ID:
if cvp.opcode is ConditionOpcode.ASSERT_MY_COIN_ID:
error = mempool_assert_my_coin_id(cvp, unspent)
elif cvp.opcode is ConditionOpcode.ASSERT_ANNOUNCEMENT:
error = mempool_assert_announcement_consumed(cvp, spend_bundle)
elif cvp.opcode is ConditionOpcode.ASSERT_BLOCK_INDEX_EXCEEDS:
error = mempool_assert_block_index_exceeds(cvp, prev_transaction_block_height)
elif cvp.opcode is ConditionOpcode.ASSERT_BLOCK_AGE_EXCEEDS:
Expand Down
12 changes: 12 additions & 0 deletions src/types/announcement.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from src.types.sized_bytes import bytes32
from dataclasses import dataclass
from src.util.hash import std_hash


@dataclass(frozen=True)
class Announcement:
parent_coin_info: bytes32
message: bytes

def name(self) -> bytes32:
return std_hash(bytes(self.parent_coin_info + self.message))
6 changes: 5 additions & 1 deletion src/types/coin_solution.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

from .coin import Coin
from .program import Program
from src.util.chain_utils import additions_for_solution
from .announcement import Announcement
from src.util.chain_utils import additions_for_solution, announcements_for_solution
from src.util.streamable import Streamable, streamable


Expand All @@ -22,3 +23,6 @@ class CoinSolution(Streamable):

def additions(self) -> List[Coin]:
return additions_for_solution(self.coin.name(), self.solution)

def announcements(self) -> List[Announcement]:
return announcements_for_solution(self.coin.name(), self.solution)
3 changes: 2 additions & 1 deletion src/types/condition_opcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ class ConditionOpcode(bytes, enum.Enum):
UNKNOWN = bytes([49])
AGG_SIG = bytes([50])
CREATE_COIN = bytes([51])
ASSERT_COIN_CONSUMED = bytes([52])
ASSERT_ANNOUNCEMENT = bytes([52])
ASSERT_MY_COIN_ID = bytes([53])
ASSERT_RELATIVE_TIME_EXCEEDS = bytes([54])
ASSERT_BLOCK_INDEX_EXCEEDS = bytes([55])
ASSERT_BLOCK_AGE_EXCEEDS = bytes([56])
AGG_SIG_ME = bytes([57])
ASSERT_FEE = bytes([58])
ASSERT_TIME_EXCEEDS = bytes([59])
CREATE_ANNOUNCEMENT = bytes([60])

def __bytes__(self) -> bytes:
return bytes(self.value)
Expand Down
15 changes: 14 additions & 1 deletion src/types/full_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@
from src.types.header_block import HeaderBlock
from src.types.name_puzzle_condition import NPC
from src.types.coin import Coin
from src.types.announcement import Announcement
from src.types.sized_bytes import bytes32
from src.full_node.mempool_check_conditions import get_name_puzzle_conditions
from src.util.condition_tools import created_outputs_for_conditions_dict
from src.util.condition_tools import (
created_outputs_for_conditions_dict,
created_announcements_for_conditions_dict,
)
from src.util.streamable import Streamable, streamable
from src.types.vdf import VDFProof
from src.types.reward_chain_sub_block import RewardChainSubBlock
Expand Down Expand Up @@ -140,3 +144,12 @@ def additions_for_npc(npc_list: List[NPC]) -> List[Coin]:
additions.append(coin)

return additions


def announcements_for_npc(npc_list: List[NPC]) -> List[Announcement]:
announcements: List[Announcement] = []

for npc in npc_list:
announcements.extend(created_announcements_for_conditions_dict(npc.condition_dict, npc.coin_name))

return announcements
7 changes: 7 additions & 0 deletions src/types/spend_bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from typing import List

from src.types.coin import Coin
from src.types.announcement import Announcement
from src.types.sized_bytes import bytes32
from src.util.streamable import Streamable, streamable
from .coin_solution import CoinSolution
Expand Down Expand Up @@ -37,6 +38,12 @@ def additions(self) -> List[Coin]:
items.extend(coin_solution.additions())
return items

def announcements(self) -> List[Announcement]:
items: List[Announcement] = []
for coin_solution in self.coin_solutions:
items.extend(coin_solution.announcements())
return items

def removals(self) -> List[Coin]:
""" This should be used only by wallet"""
return [_.coin for _ in self.coin_solutions]
Expand Down
12 changes: 12 additions & 0 deletions src/util/chain_utils.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from typing import List

from src.types.coin import Coin
from src.types.announcement import Announcement
from src.util.condition_tools import (
created_outputs_for_conditions_dict,
conditions_dict_for_solution,
created_announcements_for_conditions_dict,
)


Expand All @@ -15,3 +17,13 @@ def additions_for_solution(coin_name, solution) -> List[Coin]:
if err or dic is None:
return []
return created_outputs_for_conditions_dict(dic, coin_name)


def announcements_for_solution(coin_name, solution) -> List[Announcement]:
"""
Checks the conditions created by CoinSolution and returns the list of announcements
"""
err, dic, cost = conditions_dict_for_solution(solution)
if err or dic is None:
return []
return created_announcements_for_conditions_dict(dic, coin_name)
17 changes: 17 additions & 0 deletions src/util/condition_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from src.types.condition_var_pair import ConditionVarPair
from src.types.condition_opcodes import ConditionOpcode
from src.types.coin import Coin
from src.types.announcement import Announcement
from src.types.program import Program
from src.types.sized_bytes import bytes32
from src.util.clvm import int_from_bytes
Expand Down Expand Up @@ -107,6 +108,22 @@ def created_outputs_for_conditions_dict(
return output_coins


def created_announcements_for_conditions_dict(
conditions_dict: Dict[ConditionOpcode, List[ConditionVarPair]],
input_coin_name: bytes32,
) -> List[Announcement]:
output_announcements = []
for cvp in conditions_dict.get(ConditionOpcode.CREATE_ANNOUNCEMENT, []):
# TODO: check condition very carefully
# (ensure there are the correct number and type of parameters)
# maybe write a type-checking framework for conditions
# and don't just fail with asserts
message = cvp.vars[0]
announcement = Announcement(input_coin_name, message)
output_announcements.append(announcement)
return output_announcements


def conditions_dict_for_solution(
solution,
) -> Tuple[Optional[Err], Optional[Dict[ConditionOpcode, List[ConditionVarPair]]], uint64]:
Expand Down
2 changes: 1 addition & 1 deletion src/util/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class Err(Enum):
BAD_FARMER_COIN_AMOUNT = 9
INVALID_CONDITION = 10
ASSERT_MY_COIN_ID_FAILED = 11
ASSERT_COIN_CONSUMED_FAILED = 12
ASSERT_ANNOUNCE_CONSUMED_FAILED = 12
ASSERT_BLOCK_AGE_EXCEEDS_FAILED = 13
ASSERT_BLOCK_INDEX_EXCEEDS_FAILED = 14
ASSERT_TIME_EXCEEDS_FAILED = 15
Expand Down
10 changes: 6 additions & 4 deletions src/util/wallet_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
DEFAULT_HIDDEN_PUZZLE_HASH,
)
from src.wallet.puzzles.puzzle_utils import (
make_assert_coin_consumed_condition,
make_create_announcement,
make_assert_announcement,
make_assert_my_coin_id_condition,
make_create_coin_condition,
make_assert_block_index_exceeds_condition,
Expand Down Expand Up @@ -99,10 +100,12 @@ def make_solution(self, condition_dic: Dict[ConditionOpcode, List[ConditionVarPa
for cvp in con_list:
if cvp.opcode == ConditionOpcode.CREATE_COIN:
ret.append(make_create_coin_condition(cvp.vars[0], cvp.vars[1]))
if cvp.opcode == ConditionOpcode.CREATE_ANNOUNCEMENT:
ret.append(make_create_announcement(cvp.vars[0]))
if cvp.opcode == ConditionOpcode.AGG_SIG:
ret.append(make_assert_aggsig_condition(cvp.vars[0]))
if cvp.opcode == ConditionOpcode.ASSERT_COIN_CONSUMED:
ret.append(make_assert_coin_consumed_condition(cvp.vars[0]))
if cvp.opcode == ConditionOpcode.ASSERT_ANNOUNCEMENT:
ret.append(make_assert_announcement(cvp.vars[0]))
if cvp.opcode == ConditionOpcode.ASSERT_TIME_EXCEEDS:
ret.append(make_assert_time_exceeds_condition(cvp.vars[0]))
if cvp.opcode == ConditionOpcode.ASSERT_MY_COIN_ID:
Expand All @@ -113,7 +116,6 @@ def make_solution(self, condition_dic: Dict[ConditionOpcode, List[ConditionVarPa
ret.append(make_assert_block_age_exceeds_condition(cvp.vars[0]))
if cvp.opcode == ConditionOpcode.ASSERT_FEE:
ret.append(make_assert_fee_condition(cvp.vars[0]))

return solution_for_conditions(Program.to(ret))

def generate_unsigned_transaction(
Expand Down
9 changes: 0 additions & 9 deletions src/wallet/cc_wallet/cc_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,15 +162,6 @@ def spend_bundle_for_spendable_ccs(
coin_solution = CoinSolution(input_coins[index], full_solution)
coin_solutions.append(coin_solution)

# now add solutions to consume the lock coins

for _ in range(N):
prev_index = (_ - 1) % N
prev_coin = spendable_cc_list[prev_index].coin
this_coin = spendable_cc_list[_].coin
subtotal = subtotals[_]
coin_solution = coin_solution_for_lock_coin(prev_coin, subtotal, this_coin)
coin_solutions.append(coin_solution)
if sigs is None or sigs == []:
return SpendBundle(coin_solutions, NULL_SIGNATURE)
else:
Expand Down
Loading

0 comments on commit b37e6e7

Please sign in to comment.