Tweak block validation, rename some files, and concentrate error codes

This commit is contained in:
Mariano Sorgente 2020-03-31 02:27:22 +09:00
parent f721c58ba5
commit 920043c1d4
No known key found for this signature in database
GPG Key ID: 0F866338C369278C
65 changed files with 682 additions and 479 deletions

View File

@ -42,13 +42,11 @@ setup(
"src.cmds",
"src.consensus",
"src.full_node",
"src.pool",
"src.protocols",
"src.rpc",
"src.server",
"src.simulator",
"src.types",
"src.types.hashable",
"src.util",
"src.wallet",
"src.wallet.puzzles",

View File

@ -3,8 +3,8 @@ from secrets import token_bytes
from blspy import PrivateKey, ExtendedPrivateKey
from src.path import mkdir, path_from_root
from src.pool import create_puzzlehash_for_pk
from src.types.hashable.BLSSignature import BLSPublicKey
from src.consensus.coinbase import create_puzzlehash_for_pk
from src.types.BLSSignature import BLSPublicKey
from src.util.config import load_config, save_config, str2bool

View File

@ -2,8 +2,8 @@ import blspy
from src.types.sized_bytes import bytes32
from src.util.ints import uint64
from src.types.hashable.coin import Coin
from src.types.hashable.BLSSignature import BLSSignature, BLSPublicKey
from src.types.coin import Coin
from src.types.BLSSignature import BLSSignature, BLSPublicKey
from src.wallet.puzzles.p2_delegated_puzzle import puzzle_for_pk

View File

@ -6,7 +6,7 @@ from blspy import PrivateKey, Util
from src.consensus.block_rewards import calculate_block_reward
from src.consensus.constants import constants as consensus_constants
from src.consensus.pot_iterations import calculate_iterations_quality
from src.pool import create_coinbase_coin_and_signature
from src.consensus.coinbase import create_coinbase_coin_and_signature
from src.protocols import farmer_protocol, harvester_protocol
from src.server.outbound_message import Delivery, Message, NodeType, OutboundMessage
from src.types.proof_of_space import ProofOfSpace

View File

@ -17,18 +17,17 @@ from src.consensus.pot_iterations import (
from src.full_node.store import FullNodeStore
from src.types.full_block import FullBlock, additions_for_npc
from src.types.hashable.coin import Coin, hash_coin_list
from src.types.hashable.coin_record import CoinRecord
from src.types.coin import Coin, hash_coin_list
from src.types.coin_record import CoinRecord
from src.types.header_block import HeaderBlock
from src.types.header import Header
from src.types.sized_bytes import bytes32
from src.full_node.coin_store import CoinStore
from src.util.ConsensusError import Err
from src.util.errors import Err, ConsensusError
from src.util.cost_calculator import calculate_cost_of_program
from src.util.merkle_set import MerkleSet
from src.util.blockchain_check_conditions import blockchain_check_conditions_dict
from src.util.condition_tools import hash_key_pairs_for_conditions_dict
from src.util.errors import InvalidGenesisBlock
from src.util.ints import uint32, uint64
from src.types.challenge import Challenge
from src.util.hash import std_hash
@ -100,9 +99,12 @@ class Blockchain:
self.genesis = FullBlock.from_bytes(self.constants["GENESIS_BLOCK"])
self.coinbase_freeze = self.constants["COINBASE_FREEZE_PERIOD"]
result, removed = await self.receive_block(self.genesis)
result, removed, error_code = await self.receive_block(self.genesis)
if result != ReceiveBlockResult.ADDED_TO_HEAD:
raise InvalidGenesisBlock()
if error_code is not None:
raise ConsensusError(error_code)
else:
raise RuntimeError(f"Invalid genesis block {self.genesis}")
headers_input: Dict[str, Header] = await self._load_headers_from_store()
@ -487,23 +489,27 @@ class Blockchain:
block: FullBlock,
pre_validated: bool = False,
pos_quality_string: bytes32 = None,
) -> Tuple[ReceiveBlockResult, Optional[Header]]:
) -> Tuple[ReceiveBlockResult, Optional[Header], Optional[Err]]:
"""
Adds a new block into the blockchain, if it's valid and connected to the current
blockchain, regardless of whether it is the child of a head, or another block.
Returns a header if block is added to head. Returns an error if the block is
invalid.
"""
genesis: bool = block.height == 0 and not self.tips
if block.header_hash in self.headers:
return ReceiveBlockResult.ALREADY_HAVE_BLOCK, None
return ReceiveBlockResult.ALREADY_HAVE_BLOCK, None, None
if block.prev_header_hash not in self.headers and not genesis:
return ReceiveBlockResult.DISCONNECTED_BLOCK, None
return ReceiveBlockResult.DISCONNECTED_BLOCK, None, None
if not await self.validate_block(
error_code: Optional[Err] = await self.validate_block(
block, genesis, pre_validated, pos_quality_string
):
return ReceiveBlockResult.INVALID_BLOCK, None
)
if error_code is not None:
return ReceiveBlockResult.INVALID_BLOCK, None, error_code
# Cache header in memory
self.headers[block.header_hash] = block.header
@ -512,9 +518,9 @@ class Blockchain:
await self.store.add_block(block)
res, header = await self._reconsider_heads(block.header, genesis)
if res:
return ReceiveBlockResult.ADDED_TO_HEAD, header
return ReceiveBlockResult.ADDED_TO_HEAD, header, None
else:
return ReceiveBlockResult.ADDED_AS_ORPHAN, None
return ReceiveBlockResult.ADDED_AS_ORPHAN, None, None
async def validate_unfinished_block(
self,
@ -522,39 +528,41 @@ class Blockchain:
prev_full_block: Optional[FullBlock],
pre_validated: bool = True,
pos_quality_string: bytes32 = None,
) -> bool:
) -> Tuple[Optional[Err], Optional[uint64]]:
"""
Block validation algorithm. Returns true if the candidate block is fully valid
(except for proof of time). The same as validate_block, but without proof of time
and challenge validation.
Block validation algorithm. Returns the number of VDF iterations that this block's
proof of time must have, if the candidate block is fully valid (except for proof of
time). The same as validate_block, but without proof of time and challenge validation.
If the block is invalid, an error code is returned.
"""
if not pre_validated:
# 1. Check the proof of space hash is valid
# 1. The hash of the proof of space must match header_data.proof_of_space_hash
if block.proof_of_space.get_hash() != block.header.data.proof_of_space_hash:
return False
return (Err.INVALID_POSPACE_HASH, None)
# 3. Check coinbase signature with pool pk
# 2. The coinbase signature must be valid, according the the pool public key
pair = block.header.data.coinbase_signature.PkMessagePair(
block.proof_of_space.pool_pubkey, block.header.data.coinbase.name(),
)
if not block.header.data.coinbase_signature.validate([pair]):
return False
return (Err.INVALID_COINBASE_SIGNATURE, None)
# 4. Check harvester signature of header data is valid based on harvester key
# 3. Check harvester signature of header data is valid based on harvester key
if not block.header.harvester_signature.verify(
[blspy.Util.hash256(block.header.data.get_hash())],
[block.proof_of_space.plot_pubkey],
):
return False
return (Err.INVALID_HARVESTER_SIGNATURE, None)
# 5. Check previous pointer(s) / flyclient
# 4. If not genesis, the previous block must exist
if prev_full_block is not None and block.prev_header_hash not in self.headers:
return False
return (Err.DOES_NOT_EXTEND, None)
# 6. Check Now+2hrs > timestamp > avg timestamp of last 11 blocks
# 5. If not genesis, the timestamp must be >= the average timestamp of last 11 blocks
# and less than 2 hours in the future (if block height < 11, average all previous blocks).
# Average is the sum, int diveded by the number of timestamps
if prev_full_block is not None:
# TODO: do something about first 11 blocks
last_timestamps: List[uint64] = []
curr = prev_full_block.header
while len(last_timestamps) < self.constants["NUMBER_OF_TIMESTAMPS"]:
@ -563,28 +571,32 @@ class Blockchain:
if not fetched:
break
curr = fetched
if (
len(last_timestamps) != self.constants["NUMBER_OF_TIMESTAMPS"]
and curr.height != 0
):
return False
prev_time: uint64 = uint64(int(sum(last_timestamps) / len(last_timestamps)))
if len(last_timestamps) != self.constants["NUMBER_OF_TIMESTAMPS"]:
# For blocks 1 to 10, average timestamps of all previous blocks
assert curr.height == 0
prev_time: uint64 = uint64(
int(sum(last_timestamps) // len(last_timestamps))
)
if block.header.data.timestamp < prev_time:
return False
return (Err.TIMESTAMP_TOO_FAR_IN_PAST, None)
if (
block.header.data.timestamp
> time.time() + self.constants["MAX_FUTURE_TIME"]
):
return False
return (Err.TIMESTAMP_TOO_FAR_IN_FUTURE, None)
# 7. Check filter hash is correct
# 6. The compact block filter must be correct, according to the body (BIP158)
if block.header.data.filter_hash != bytes32([0] * 32):
if block.transactions_filter is None:
return (Err.INVALID_TRANSACTIONS_FILTER_HASH, None)
if std_hash(block.transactions_filter) != block.header.data.filter_hash:
return False
return (Err.INVALID_TRANSACTIONS_FILTER_HASH, None)
elif block.transactions_filter is not None:
return (Err.INVALID_TRANSACTIONS_FILTER_HASH, None)
# 8. Check extension data, if any is added
# 7. Extension data must be valid, if any is present
# 9. Compute challenge of parent
# Compute challenge of parent
challenge_hash: bytes32
if prev_full_block is not None:
challenge: Optional[Challenge] = self.get_challenge(prev_full_block)
@ -592,30 +604,101 @@ class Blockchain:
challenge_hash = challenge.get_hash()
# 8. Check challenge hash of prev is the same as in pos
if challenge_hash != block.proof_of_space.challenge_hash:
return False
return (Err.INVALID_POSPACE_CHALLENGE, None)
else:
# Only check this for genesis block, which we know hash a proof of time
# 9. If genesis, the challenge hash in the proof of time must be the same as in the proof of space
assert block.proof_of_time is not None
challenge_hash = block.proof_of_time.challenge_hash
if challenge_hash != block.proof_of_space.challenge_hash:
return False
return (Err.INVALID_POSPACE_CHALLENGE, None)
# 10. Check proof of space based on challenge
# 10. The proof of space must be valid on the challenge
if pos_quality_string is None:
pos_quality_string = block.proof_of_space.verify_and_get_quality_string()
if not pos_quality_string:
return False
return (Err.INVALID_POSPACE, None)
# 11. Check block height = prev height + 1
if prev_full_block is not None:
# 11. If not genesis, the height on the previous block must be one less than on this block
if block.height != prev_full_block.height + 1:
return False
return (Err.INVALID_HEIGHT, None)
else:
# 12. If genesis, the height must be 0
if block.height != 0:
return False
return (Err.INVALID_HEIGHT, None)
return True
# 13. The coinbase reward must match the block schedule
coinbase_reward = calculate_block_reward(block.height)
if coinbase_reward != block.header.data.coinbase.amount:
return (Err.INVALID_COINBASE_AMOUNT, None)
fee_base = calculate_base_fee(block.height)
# target reward_fee = 1/8 coinbase reward + tx fees
if block.transactions_generator is not None:
# 14. Make sure transactions generator hash is valid (or all 0 if not present)
if (
std_hash(block.transactions_generator)
!= block.header.data.generator_hash
):
return (Err.INVALID_TRANSACTIONS_GENERATOR_HASH, None)
# 15. If not genesis, the transactions must be valid and fee must be valid
# Verifies that fee_base + TX fees = fee_coin.amount
err = await self._validate_transactions(block, fee_base)
if err is not None:
return (err, None)
else:
# Make sure transactions generator hash is valid (or all 0 if not present)
if block.header.data.generator_hash != bytes32(bytes([0] * 32)):
return (Err.INVALID_TRANSACTIONS_GENERATOR_HASH, None)
# 16. If genesis, the fee must be the base fee, agg_sig must be None, and merkle roots must be valid
if fee_base != block.header.data.fees_coin.amount:
return (Err.INVALID_BLOCK_FEE_AMOUNT, None)
root_error = self._validate_merkle_root(block)
if root_error:
return (root_error, None)
if block.header.data.aggregated_signature is not None:
return (Err.BAD_AGGREGATE_SIGNATURE, None)
difficulty: uint64
if prev_full_block is not None:
difficulty = self.get_next_difficulty(prev_full_block.header_hash)
min_iters = self.get_next_min_iters(prev_full_block)
else:
difficulty = uint64(self.constants["DIFFICULTY_STARTING"])
min_iters = uint64(self.constants["MIN_ITERS_STARTING"])
number_of_iters: uint64 = calculate_iterations_quality(
pos_quality_string, block.proof_of_space.size, difficulty, min_iters,
)
assert count_significant_bits(difficulty) <= self.constants["SIGNIFICANT_BITS"]
assert count_significant_bits(min_iters) <= self.constants["SIGNIFICANT_BITS"]
if prev_full_block is not None:
# 17. If not genesis, the total weight must be the parent weight + difficulty
if block.weight != prev_full_block.weight + difficulty:
return (Err.INVALID_WEIGHT, None)
# 18. If not genesis, the total iters must be parent iters + number_iters
if (
block.header.data.total_iters
!= prev_full_block.header.data.total_iters + number_of_iters
):
return (Err.INVALID_TOTAL_ITERS, None)
else:
# 19. If genesis, the total weight must be starting difficulty
if block.weight != difficulty:
return (Err.INVALID_WEIGHT, None)
# 20. If genesis, the total iters must be number iters
if block.header.data.total_iters != number_of_iters:
return (Err.INVALID_TOTAL_ITERS, None)
return (None, number_of_iters)
async def validate_block(
self,
@ -623,7 +706,7 @@ class Blockchain:
genesis: bool = False,
pre_validated: bool = False,
pos_quality_string: bytes32 = None,
) -> bool:
) -> Optional[Err]:
"""
Block validation algorithm. Returns true iff the candidate block is fully valid,
and extends something in the blockchain.
@ -635,103 +718,37 @@ class Blockchain:
else:
prev_full_block = None
# 1. Validate unfinished block (check the rest of the conditions)
if not (
await self.validate_unfinished_block(
block, prev_full_block, pre_validated, pos_quality_string
)
):
return False
difficulty: uint64
min_iters: uint64
if not genesis:
difficulty = self.get_next_difficulty(block.prev_header_hash)
assert prev_full_block is not None
min_iters = self.get_next_min_iters(prev_full_block)
else:
difficulty = uint64(self.constants["DIFFICULTY_STARTING"])
min_iters = uint64(self.constants["MIN_ITERS_STARTING"])
if count_significant_bits(difficulty) > self.constants["SIGNIFICANT_BITS"]:
return False
if count_significant_bits(min_iters) > self.constants["SIGNIFICANT_BITS"]:
return False
# 3. Check number of iterations on PoT is correct, based on prev block and PoS
if pos_quality_string is None:
pos_quality_string = block.proof_of_space.verify_and_get_quality_string()
if pos_quality_string is None:
return False
number_of_iters: uint64 = calculate_iterations_quality(
pos_quality_string, block.proof_of_space.size, difficulty, min_iters,
# 0. Validate unfinished block (check the rest of the conditions)
err, number_of_iters = await self.validate_unfinished_block(
block, prev_full_block, pre_validated, pos_quality_string
)
if err is not None:
return err
assert number_of_iters is not None
if block.proof_of_time is None:
return False
return Err.BLOCK_IS_NOT_FINISHED
# 1. The number of iterations (based on quality, pos, difficulty, ips) must be the same as in the PoT
if number_of_iters != block.proof_of_time.number_of_iterations:
return False
return Err.INVALID_NUM_ITERATIONS
# 4. Check PoT
# 2. the PoT must be valid, on a discriminant of size 1024, and the challenge_hash
if not pre_validated:
if not block.proof_of_time.is_valid(
self.constants["DISCRIMINANT_SIZE_BITS"]
):
return False
return Err.INVALID_POT
# 3. If not genesis, the challenge_hash in the proof of time must match the challenge on the previous block
if not genesis:
assert prev_full_block is not None
prev_challenge: Optional[Challenge] = self.get_challenge(prev_full_block)
assert prev_challenge is not None
# 5. and check if PoT.challenge_hash matches
if block.proof_of_time.challenge_hash != prev_challenge.get_hash():
return False
# 6a. Check total_weight = parent total_weight + difficulty
if block.weight != prev_full_block.weight + difficulty:
return False
# 7a. Check total_iters = parent total_iters + number_iters
if (
block.header.data.total_iters
!= prev_full_block.header.data.total_iters + number_of_iters
):
return False
else:
# 6b. Check total_weight = parent total_weight + difficulty
if block.weight != difficulty:
return False
# 7b. Check total_iters = parent total_iters + number_iters
if block.header.data.total_iters != number_of_iters:
return False
coinbase_reward = calculate_block_reward(block.height)
if coinbase_reward != block.header.data.coinbase.amount:
return False
fee_base = calculate_base_fee(block.height)
# 8 Validate transactions
# target reward_fee = 1/8 coinbase reward + tx fees
if block.transactions_generator:
# Validate transactions, and verify that fee_base + TX fees = fee_coin.amount
err = await self._validate_transactions(block, fee_base)
if err:
return False
else:
if fee_base != block.header.data.fees_coin.amount:
return False
root_error = self._validate_merkle_root(block)
if root_error:
return False
if block.header.data.aggregated_signature is not None:
return False
return True
return Err.INVALID_POT_CHALLENGE
return None
async def pre_validate_blocks(
self, blocks: List[FullBlock]
@ -994,12 +1011,13 @@ class Blockchain:
async def _validate_transactions(
self, block: FullBlock, fee_base: uint64
) -> Optional[Err]:
# 1. Check that transactions generator is present
if not block.transactions_generator:
return Err.UNKNOWN
# Get List of names removed, puzzles hashes for removed coins and conditions crated
error, npc_list, cost = calculate_cost_of_program(block.transactions_generator)
# 2. Check that cost <= MAX_BLOCK_COST_CLVM
if cost > self.constants["MAX_BLOCK_COST_CLVM"]:
return Err.BLOCK_COST_EXCEEDS_MAX
if error:

View File

@ -2,8 +2,8 @@ import asyncio
from typing import Dict, Optional, List
import aiosqlite
from src.types.full_block import FullBlock
from src.types.hashable.coin import Coin
from src.types.hashable.coin_record import CoinRecord
from src.types.coin import Coin
from src.types.coin_record import CoinRecord
from src.types.sized_bytes import bytes32
from src.types.header import Header
from src.util.ints import uint32

View File

@ -28,23 +28,21 @@ from src.server.outbound_message import Delivery, Message, NodeType, OutboundMes
from src.server.server import ChiaServer
from src.types.challenge import Challenge
from src.types.full_block import FullBlock
from src.types.hashable.coin import Coin, hash_coin_list
from src.types.hashable.BLSSignature import BLSSignature
from src.types.coin import Coin, hash_coin_list
from src.types.BLSSignature import BLSSignature
from src.util.cost_calculator import calculate_cost_of_program
from src.util.hash import std_hash
from src.types.hashable.spend_bundle import SpendBundle
from src.types.hashable.program import Program
from src.types.spend_bundle import SpendBundle
from src.types.program import Program
from src.types.header import Header, HeaderData
from src.types.header_block import HeaderBlock
from src.types.peer_info import PeerInfo
from src.types.proof_of_space import ProofOfSpace
from src.types.sized_bytes import bytes32
from src.full_node.coin_store import CoinStore
from src.util import errors
from src.util.api_decorators import api_request
from src.util.errors import InvalidUnfinishedBlock
from src.util.ints import uint32, uint64, uint128
from src.util.ConsensusError import Err
from src.util.errors import Err, ConsensusError
from src.types.mempool_inclusion_status import MempoolInclusionStatus
OutboundMessageGenerator = AsyncGenerator[OutboundMessage, None]
@ -418,9 +416,7 @@ class FullNode:
if not verify_weight(
tip_block.header, headers, self.blockchain.headers[fork_point_hash],
):
raise errors.InvalidWeight(
f"Weight of {tip_block.header.get_hash()} not valid."
)
raise ConsensusError(Err.INVALID_WEIGHT, [tip_block.header])
self.log.info(
f"Validated weight of headers. Downloaded {len(headers)} headers, tip height {tip_height}"
@ -536,13 +532,17 @@ class FullNode:
index += 1
async with self.store.lock:
result, header_block = await self.blockchain.receive_block(
block, validated, pos
)
(
result,
header_block,
error_code,
) = await self.blockchain.receive_block(block, validated, pos)
if (
result == ReceiveBlockResult.INVALID_BLOCK
or result == ReceiveBlockResult.DISCONNECTED_BLOCK
):
if error_code is not None:
raise ConsensusError(error_code, block.header_hash)
raise RuntimeError(f"Invalid block {block.header_hash}")
# Always immediately add the block to the database, after updating blockchain state
@ -1072,15 +1072,17 @@ class FullNode:
)
assert prev_full_block is not None
if not await self.blockchain.validate_unfinished_block(block, prev_full_block):
raise InvalidUnfinishedBlock()
error_code, iterations_needed = await self.blockchain.validate_unfinished_block(
block, prev_full_block
)
if error_code is not None:
raise ConsensusError(error_code)
assert iterations_needed is not None
challenge = self.blockchain.get_challenge(prev_full_block)
assert challenge is not None
challenge_hash = challenge.get_hash()
iterations_needed: uint64 = uint64(
block.header.data.total_iters - prev_full_block.header.data.total_iters
)
if (
self.store.get_unfinished_block((challenge_hash, iterations_needed))
@ -1535,7 +1537,7 @@ class FullNode:
async with self.store.lock:
# Tries to add the block to the blockchain
added, replaced = await self.blockchain.receive_block(
added, replaced, error_code = await self.blockchain.receive_block(
respond_block.block, val, pos
)
if added == ReceiveBlockResult.ADDED_TO_HEAD:
@ -1546,10 +1548,12 @@ class FullNode:
if added == ReceiveBlockResult.ALREADY_HAVE_BLOCK:
return
elif added == ReceiveBlockResult.INVALID_BLOCK:
self.log.warning(
f"Block {header_hash} at height {respond_block.block.height} is invalid."
self.log.error(
f"Block {header_hash} at height {respond_block.block.height} is invalid with code {error_code}."
)
return
assert error_code is not None
raise ConsensusError(error_code, header_hash)
elif added == ReceiveBlockResult.DISCONNECTED_BLOCK:
self.log.warning(f"Disconnected block {header_hash}")
tip_height = min(

View File

@ -2,7 +2,7 @@ from typing import List, Dict
from sortedcontainers import SortedDict
from src.types.hashable.coin import Coin
from src.types.coin import Coin
from src.types.mempool_item import MempoolItem
from src.types.sized_bytes import bytes32
from src.util.ints import uint32, uint64

View File

@ -7,15 +7,15 @@ from chiabip158 import PyBIP158
from src.consensus.constants import constants as consensus_constants
from src.util.bundle_tools import best_solution_program
from src.types.full_block import FullBlock
from src.types.hashable.coin import Coin
from src.types.hashable.spend_bundle import SpendBundle
from src.types.hashable.coin_record import CoinRecord
from src.types.coin import Coin
from src.types.spend_bundle import SpendBundle
from src.types.coin_record import CoinRecord
from src.types.header import Header
from src.types.mempool_item import MempoolItem
from src.full_node.mempool import Mempool
from src.types.sized_bytes import bytes32
from src.full_node.coin_store import CoinStore
from src.util.ConsensusError import Err
from src.util.errors import Err
from src.util.cost_calculator import calculate_cost_of_program
from src.util.mempool_check_conditions import mempool_check_conditions_dict
from src.util.condition_tools import hash_key_pairs_for_conditions_dict

View File

@ -3,7 +3,7 @@ import logging
import aiosqlite
from typing import Dict, List, Optional, Tuple
from src.types.hashable.program import Program
from src.types.program import Program
from src.types.full_block import FullBlock
from src.types.header import HeaderData, Header
from src.types.header_block import HeaderBlock

View File

@ -1,7 +1,7 @@
from dataclasses import dataclass
from blspy import PrependSignature
from src.types.hashable.coin import Coin
from src.types.hashable.BLSSignature import BLSSignature
from src.types.coin import Coin
from src.types.BLSSignature import BLSSignature
from src.types.proof_of_space import ProofOfSpace
from src.types.sized_bytes import bytes32
from src.util.cbor_message import cbor_message

View File

@ -2,7 +2,7 @@ from dataclasses import dataclass
from typing import List
from src.types.full_block import FullBlock
from src.types.hashable.spend_bundle import SpendBundle
from src.types.spend_bundle import SpendBundle
from src.types.header_block import HeaderBlock
from src.types.peer_info import PeerInfo
from src.types.proof_of_time import ProofOfTime

View File

@ -1,8 +1,8 @@
from dataclasses import dataclass
from typing import List, Tuple, Optional
from src.types.hashable.coin import Coin
from src.types.hashable.spend_bundle import SpendBundle
from src.types.coin import Coin
from src.types.spend_bundle import SpendBundle
from src.types.header_block import HeaderBlock
from src.types.sized_bytes import bytes32
from src.util.cbor_message import cbor_message

View File

@ -7,7 +7,7 @@ from src.types.full_block import FullBlock
from src.types.header import Header
from src.types.sized_bytes import bytes32
from src.util.ints import uint16
from src.types.hashable.coin_record import CoinRecord
from src.types.coin_record import CoinRecord
class RpcClient:

View File

@ -23,12 +23,7 @@ from src.types.peer_info import PeerInfo
from src.types.sized_bytes import bytes32
from src.util import partial_func
from src.util.config import load_config
from src.util.errors import (
IncompatibleProtocolVersion,
InvalidAck,
InvalidHandshake,
InvalidProtocolMessage,
)
from src.util.errors import Err, ProtocolError
from src.util.ints import uint16
from src.util.network import create_node_id
import traceback
@ -385,19 +380,17 @@ class ChiaServer:
or not inbound_handshake
or not inbound_handshake.node_type
):
raise InvalidHandshake("Invalid handshake")
raise ProtocolError(Err.INVALID_HANDSHAKE)
if inbound_handshake.node_id == self._node_id:
raise InvalidHandshake(
f"Should not connect to ourselves, aborting handshake."
)
raise ProtocolError(Err.INVALID_HANDSHAKE)
# Makes sure that we only start one connection with each peer
connection.node_id = inbound_handshake.node_id
connection.peer_server_port = int(inbound_handshake.server_port)
connection.connection_type = inbound_handshake.node_type
if not self.global_connections.add(connection):
raise InvalidHandshake(f"Duplicate connection to {connection}")
raise ProtocolError(Err.INVALID_HANDSHAKE)
# Send Ack message
await connection.send(Message("handshake_ack", HandshakeAck()))
@ -405,12 +398,12 @@ class ChiaServer:
# Read Ack message
full_message = await connection.read_one_message()
if full_message.function != "handshake_ack":
raise InvalidAck("Invalid ack")
raise ProtocolError(Err.INVALID_ACK)
if inbound_handshake.version != protocol_version:
raise IncompatibleProtocolVersion(
f"Our node version {protocol_version} is not compatible with peer\
{connection} version {inbound_handshake.version}"
raise ProtocolError(
Err.INCOMPATIBLE_PROTOCOL_VERSION,
[protocol_version, inbound_handshake.version],
)
self.log.info(
@ -422,14 +415,7 @@ class ChiaServer:
)
# Only yield a connection if the handshake is succesful and the connection is not a duplicate.
yield connection
except (
IncompatibleProtocolVersion,
InvalidAck,
InvalidHandshake,
asyncio.IncompleteReadError,
OSError,
Exception,
) as e:
except (ProtocolError, asyncio.IncompleteReadError, OSError, Exception,) as e:
self.log.warning(f"{e}, handshake not completed. Connection not created.")
# Make sure to close the connection even if it's not in global connections
connection.close()
@ -481,7 +467,9 @@ class ChiaServer:
try:
if len(full_message.function) == 0 or full_message.function.startswith("_"):
# This prevents remote calling of private methods that start with "_"
raise InvalidProtocolMessage(full_message.function)
raise ProtocolError(
Err.INVALID_PROTOCOL_MESSAGE, [full_message.function]
)
self.log.info(
f"<- {full_message.function} from peer {connection.get_peername()}"
@ -508,7 +496,9 @@ class ChiaServer:
f = getattr(api, full_message.function, None)
if f is None:
raise InvalidProtocolMessage(full_message.function)
raise ProtocolError(
Err.INVALID_PROTOCOL_MESSAGE, [full_message.function]
)
result = f(full_message.data)

View File

@ -14,7 +14,7 @@ from src.full_node.mempool_manager import MempoolManager
from src.server.outbound_message import OutboundMessage
from src.server.server import ChiaServer
from src.types.full_block import FullBlock
from src.types.hashable.spend_bundle import SpendBundle
from src.types.spend_bundle import SpendBundle
from src.types.header import Header
from src.full_node.coin_store import CoinStore
from src.util.api_decorators import api_request

View File

@ -13,7 +13,7 @@ class BLSPublicKey(bytes48):
pass
# TODO Stop using this after BLSLibrary is
# TODO Stop using this after BLSLibrary is updated
@dataclass(frozen=True)
@streamable
class BLSSignature(Streamable):

View File

@ -1,6 +1,6 @@
from dataclasses import dataclass
from src.types.hashable.coin import Coin
from src.types.coin import Coin
from src.types.sized_bytes import bytes32
from src.util.streamable import Streamable, streamable
from src.util.ints import uint32

View File

@ -2,8 +2,8 @@ from dataclasses import dataclass
from typing import Tuple, List, Optional
from src.types.name_puzzle_condition import NPC
from src.types.hashable.program import Program
from src.types.hashable.coin import Coin
from src.types.program import Program
from src.types.coin import Coin
from src.types.header import Header
from src.types.sized_bytes import bytes32
from src.util.mempool_check_conditions import get_name_puzzle_conditions

View File

@ -6,8 +6,8 @@ from blspy import PrependSignature
from src.types.sized_bytes import bytes32
from src.util.ints import uint64, uint32, uint128
from src.util.streamable import Streamable, streamable
from src.types.hashable.BLSSignature import BLSSignature
from src.types.hashable.coin import Coin
from src.types.BLSSignature import BLSSignature
from src.types.coin import Coin
@dataclass(frozen=True)

View File

@ -1,6 +1,6 @@
from dataclasses import dataclass
from src.types.hashable.spend_bundle import SpendBundle
from src.types.spend_bundle import SpendBundle
from src.types.sized_bytes import bytes32
from src.util.ints import uint64

View File

@ -1,7 +1,7 @@
from dataclasses import dataclass
from typing import List, Dict
from src.types.hashable.coin import Coin
from src.types.coin import Coin
from src.types.sized_bytes import bytes32
from src.util.chain_utils import additions_for_solution
from src.util.streamable import Streamable, streamable

View File

@ -1,44 +0,0 @@
from enum import Enum
class ConsensusError(Exception):
def __init__(self, code, bad_object):
self.args = [code, bad_object]
self.message = str(bad_object)
class Err(Enum):
# temporary errors. Don't blacklist
DOES_NOT_EXTEND = -1
BAD_HEADER_SIGNATURE = -2
MISSING_FROM_STORAGE = -3
UNKNOWN = -9999
# permanent errors. Block is unsalvageable garbage.
BAD_COINBASE_SIGNATURE = 1
INVALID_BLOCK_SOLUTION = 2
INVALID_COIN_SOLUTION = 3
DUPLICATE_OUTPUT = 4
DOUBLE_SPEND = 5
UNKNOWN_UNSPENT = 6
BAD_AGGREGATE_SIGNATURE = 7
WRONG_PUZZLE_HASH = 8
BAD_COINBASE_REWARD = 9
INVALID_CONDITION = 10
ASSERT_MY_COIN_ID_FAILED = 11
ASSERT_COIN_CONSUMED_FAILED = 12
ASSERT_BLOCK_AGE_EXCEEDS_FAILED = 13
ASSERT_BLOCK_INDEX_EXCEEDS_FAILED = 14
ASSERT_TIME_EXCEEDS_FAILED = 15
COIN_AMOUNT_EXCEEDS_MAXIMUM = 16
SEXP_ERROR = 17
INVALID_FEE_LOW_FEE = 18
MEMPOOL_CONFLICT = 19
MINTING_COIN = 20
EXTENDS_UNKNOWN_BLOCK = 21
COINBASE_NOT_YET_SPENDABLE = 22
BLOCK_COST_EXCEEDS_MAX = 23
BAD_ADDITION_ROOT = 24
BAD_REMOVAL_ROOT = 25

View File

@ -4,11 +4,11 @@ from typing import Optional, Dict, List
from clvm.casts import int_from_bytes
from src.types.condition_var_pair import ConditionVarPair
from src.types.hashable.coin_record import CoinRecord
from src.types.coin_record import CoinRecord
from src.types.header import Header
from src.types.sized_bytes import bytes32
from src.util.condition_tools import ConditionOpcode
from src.util.ConsensusError import Err
from src.util.errors import Err
from src.util.ints import uint64

View File

@ -1,7 +1,7 @@
from clvm_tools import binutils
from src.types.hashable.program import Program
from src.types.hashable.spend_bundle import SpendBundle
from src.types.program import Program
from src.types.spend_bundle import SpendBundle
def best_solution_program(bundle: SpendBundle) -> Program:

View File

@ -1,6 +1,6 @@
from typing import List
from src.types.hashable.coin import Coin
from src.types.coin import Coin
from src.util.condition_tools import (
created_outputs_for_conditions_dict,
conditions_dict_for_solution,

View File

@ -8,12 +8,12 @@ from clvm.subclass_sexp import BaseSExp
from src.types.condition_var_pair import ConditionVarPair
from src.types.condition_opcodes import ConditionOpcode
from src.types.hashable.BLSSignature import BLSSignature, BLSPublicKey
from src.types.hashable.coin import Coin
from src.types.hashable.program import Program
from src.types.BLSSignature import BLSSignature, BLSPublicKey
from src.types.coin import Coin
from src.types.program import Program
from src.types.sized_bytes import bytes32
from src.util.ints import uint64
from .ConsensusError import Err, ConsensusError
from src.util.errors import Err, ConsensusError
def parse_sexp_to_condition(

View File

@ -3,9 +3,9 @@ from typing import Tuple, Optional, List
from src.consensus.constants import constants
from src.consensus.condition_costs import ConditionCost
from src.types.condition_opcodes import ConditionOpcode
from src.types.hashable.program import Program
from src.types.program import Program
from src.types.name_puzzle_condition import NPC
from src.util.ConsensusError import Err
from src.util.errors import Err
from src.util.ints import uint64
from src.util.mempool_check_conditions import get_name_puzzle_conditions

View File

@ -1,51 +1,79 @@
class InvalidProtocolMessage(Exception):
"""Invalid protocol message function name"""
from enum import Enum
from typing import List, Any
class InvalidHandshake(Exception):
"""Handshake message from peer is invalid"""
class Err(Enum):
# temporary errors. Don't blacklist
DOES_NOT_EXTEND = -1
BAD_HEADER_SIGNATURE = -2
MISSING_FROM_STORAGE = -3
INVALID_PROTOCOL_MESSAGE = -4
INVALID_HANDSHAKE = -5
INVALID_ACK = -6
INCOMPATIBLE_PROTOCOL_VERSION = -7
DUPLICATE_CONNECTION = -8
BLOCK_NOT_IN_BLOCKCHAIN = -9
NO_PROOF_OF_SPACE_FOUND = -10
PEERS_DONT_HAVE_BLOCK = -11
UNKNOWN = -9999
# permanent errors. Block is unsalvageable garbage.
BAD_COINBASE_SIGNATURE = 1
INVALID_BLOCK_SOLUTION = 2
INVALID_COIN_SOLUTION = 3
DUPLICATE_OUTPUT = 4
DOUBLE_SPEND = 5
UNKNOWN_UNSPENT = 6
BAD_AGGREGATE_SIGNATURE = 7
WRONG_PUZZLE_HASH = 8
BAD_COINBASE_REWARD = 9
INVALID_CONDITION = 10
ASSERT_MY_COIN_ID_FAILED = 11
ASSERT_COIN_CONSUMED_FAILED = 12
ASSERT_BLOCK_AGE_EXCEEDS_FAILED = 13
ASSERT_BLOCK_INDEX_EXCEEDS_FAILED = 14
ASSERT_TIME_EXCEEDS_FAILED = 15
COIN_AMOUNT_EXCEEDS_MAXIMUM = 16
SEXP_ERROR = 17
INVALID_FEE_LOW_FEE = 18
MEMPOOL_CONFLICT = 19
MINTING_COIN = 20
EXTENDS_UNKNOWN_BLOCK = 21
COINBASE_NOT_YET_SPENDABLE = 22
BLOCK_COST_EXCEEDS_MAX = 23
BAD_ADDITION_ROOT = 24
BAD_REMOVAL_ROOT = 25
INVALID_POSPACE_HASH = 26
INVALID_COINBASE_SIGNATURE = 27
INVALID_HARVESTER_SIGNATURE = 28
TIMESTAMP_TOO_FAR_IN_PAST = 29
TIMESTAMP_TOO_FAR_IN_FUTURE = 30
INVALID_TRANSACTIONS_FILTER_HASH = 31
INVALID_POSPACE_CHALLENGE = 32
INVALID_POSPACE = 33
INVALID_HEIGHT = 34
INVALID_COINBASE_AMOUNT = 35
INVALID_MERKLE_ROOT = 36
INVALID_BLOCK_FEE_AMOUNT = 37
INVALID_WEIGHT = 38
INVALID_TOTAL_ITERS = 39
BLOCK_IS_NOT_FINISHED = 40
INVALID_NUM_ITERATIONS = 41
INVALID_POT = 42
INVALID_POT_CHALLENGE = 43
INVALID_TRANSACTIONS_GENERATOR_HASH = 44
class InvalidAck(Exception):
"""Handshake message from peer is invalid"""
class ConsensusError(Exception):
def __init__(self, code: Err, errors: List[Any] = []):
super(ConsensusError, self).__init__(f"Error code: {code.name}")
self.errors = errors
class IncompatibleProtocolVersion(Exception):
"""Protocol versions incompatible"""
class DuplicateConnection(Exception):
"""Already have connection with peer"""
class TooManyheadersRequested(Exception):
"""Requested too many header blocks"""
class TooManyBlocksRequested(Exception):
"""Requested too many blocks"""
class BlockNotInBlockchain(Exception):
"""Block not in blockchain"""
class NoProofsOfSpaceFound(Exception):
"""No proofs of space found for this challenge"""
class PeersDontHaveBlock(Exception):
"""None of our peers have the block we want"""
# Consensus errors
class InvalidWeight(Exception):
"""The weight of this block can not be validated"""
class InvalidUnfinishedBlock(Exception):
"""The unfinished block we received is invalid"""
class InvalidGenesisBlock(Exception):
"""Genesis block is not valid according to the consensus constants and rules"""
class ProtocolError(Exception):
def __init__(self, code: Err, errors: List[Any] = []):
super(ProtocolError, self).__init__(f"Error code: {code.name}")
self.errors = errors

View File

@ -5,14 +5,14 @@ from clvm import EvalError
from clvm.casts import int_from_bytes
from src.types.condition_var_pair import ConditionVarPair
from src.types.hashable.program import Program
from src.types.hashable.spend_bundle import SpendBundle
from src.types.hashable.coin_record import CoinRecord
from src.types.program import Program
from src.types.spend_bundle import SpendBundle
from src.types.coin_record import CoinRecord
from src.types.name_puzzle_condition import NPC
from src.full_node.mempool import Mempool
from src.types.sized_bytes import bytes32
from src.util.condition_tools import ConditionOpcode, conditions_dict_for_solution
from src.util.ConsensusError import Err
from src.util.errors import Err
import time
from src.util.ints import uint64

View File

@ -8,7 +8,7 @@ import json
from enum import Enum
from typing import Any, BinaryIO, List, Type, get_type_hints, Union, Dict
from src.util.byte_types import hexstr_to_bytes
from src.types.hashable.program import Program
from src.types.program import Program
from src.util.hash import std_hash
from blspy import (

View File

@ -2,7 +2,7 @@ import dataclasses
import blspy
from src.types.hashable.BLSSignature import BLSSignature, BLSPublicKey
from src.types.BLSSignature import BLSSignature, BLSPublicKey
from src.types.sized_bytes import bytes32

View File

@ -1,7 +1,7 @@
from dataclasses import dataclass
from typing import List, Optional
from src.types.hashable.coin import Coin
from src.types.coin import Coin
from src.types.sized_bytes import bytes32
from src.util.ints import uint128, uint32, uint64
from src.util.streamable import Streamable, streamable

View File

@ -1,4 +1,4 @@
from src.types.hashable.BLSSignature import BLSSignature
from src.types.BLSSignature import BLSSignature
from src.util.condition_tools import (
conditions_by_opcode,
hash_key_pairs_for_conditions_dict,

View File

@ -1,6 +1,6 @@
import pkg_resources
from src.types.hashable.program import Program
from src.types.program import Program
def load_clvm(filename):

View File

@ -12,7 +12,7 @@ the doctor ordered.
from clvm_tools import binutils
from src.types.hashable.program import Program
from src.types.program import Program
# contract:

View File

@ -13,7 +13,7 @@ the doctor ordered.
from clvm_tools import binutils
from src.types.condition_opcodes import ConditionOpcode
from src.types.hashable.program import Program
from src.types.program import Program
def puzzle_for_pk(public_key):

View File

@ -17,7 +17,7 @@ from typing import List
from clvm_tools import binutils
from src.types.condition_opcodes import ConditionOpcode
from src.types.hashable.program import Program
from src.types.program import Program
from . import p2_conditions

View File

@ -17,7 +17,7 @@ import hashlib
from clvm_tools import binutils
from src.types.hashable.program import Program
from src.types.program import Program
from clvm import run_program
from .load_clvm import load_clvm

View File

@ -5,7 +5,7 @@ This puzzle program is like p2_delegated_puzzle except instead of one public key
it includes N public keys, any M of which needs to sign the delegated puzzle.
"""
from src.types.hashable.program import Program
from src.types.program import Program
from clvm_tools import binutils
from .load_clvm import load_clvm

View File

@ -7,7 +7,7 @@ hash along with its solution.
from clvm_tools import binutils
from src.types.hashable.program import Program
from src.types.program import Program
"""
solution: (puzzle_reveal . solution_to_puzzle)

View File

@ -12,11 +12,11 @@ from blspy import ExtendedPrivateKey
from clvm_tools import binutils
from src.server.server import ChiaServer
from src.types.hashable.BLSSignature import BLSSignature
from src.types.hashable.coin import Coin
from src.types.hashable.coin_solution import CoinSolution
from src.types.hashable.program import Program
from src.types.hashable.spend_bundle import SpendBundle
from src.types.BLSSignature import BLSSignature
from src.types.coin import Coin
from src.types.coin_solution import CoinSolution
from src.types.program import Program
from src.types.spend_bundle import SpendBundle
from src.types.sized_bytes import bytes32
from src.util.ints import uint64
from src.util.streamable import streamable, Streamable

View File

@ -4,7 +4,7 @@ from binascii import hexlify
from clvm_tools import binutils
from src.types.condition_opcodes import ConditionOpcode
from src.types.hashable.program import Program
from src.types.program import Program
from src.types.sized_bytes import bytes32
from src.util.ints import uint64

View File

@ -1,8 +1,8 @@
from dataclasses import dataclass
from typing import Optional, List, Tuple
from src.types.hashable.coin import Coin
from src.types.hashable.spend_bundle import SpendBundle
from src.types.coin import Coin
from src.types.spend_bundle import SpendBundle
from src.types.sized_bytes import bytes32
from src.util.streamable import Streamable, streamable
from src.util.ints import uint32, uint64, uint8

View File

@ -2,11 +2,11 @@ from typing import Dict, Optional, List, Tuple, Set, Any
import clvm
from blspy import ExtendedPrivateKey, PublicKey
import logging
from src.types.hashable.BLSSignature import BLSSignature
from src.types.hashable.coin import Coin
from src.types.hashable.coin_solution import CoinSolution
from src.types.hashable.program import Program
from src.types.hashable.spend_bundle import SpendBundle
from src.types.BLSSignature import BLSSignature
from src.types.coin import Coin
from src.types.coin_solution import CoinSolution
from src.types.program import Program
from src.types.spend_bundle import SpendBundle
from src.types.sized_bytes import bytes32
from src.util.condition_tools import (
conditions_for_solution,

View File

@ -1,6 +1,6 @@
from dataclasses import dataclass
from src.types.hashable.coin import Coin
from src.types.coin import Coin
from src.types.sized_bytes import bytes32
from src.util.streamable import Streamable, streamable
from src.util.ints import uint32

View File

@ -31,10 +31,10 @@ from src.wallet.wallet_state_manager import WalletStateManager
from src.wallet.block_record import BlockRecord
from src.types.header_block import HeaderBlock
from src.types.full_block import FullBlock
from src.types.hashable.coin import Coin, hash_coin_list
from src.types.coin import Coin, hash_coin_list
from src.full_node.blockchain import ReceiveBlockResult
from src.types.mempool_inclusion_status import MempoolInclusionStatus
from src.util.ConsensusError import Err
from src.util.errors import Err
class WalletNode:

View File

@ -8,8 +8,8 @@ import asyncio
import aiosqlite
from chiabip158 import PyBIP158
from src.types.hashable.coin import Coin
from src.types.hashable.spend_bundle import SpendBundle
from src.types.coin import Coin
from src.types.spend_bundle import SpendBundle
from src.types.sized_bytes import bytes32
from src.types.full_block import FullBlock
from src.types.challenge import Challenge
@ -29,7 +29,7 @@ from src.consensus.pot_iterations import calculate_iterations_quality
from src.util.significant_bits import truncate_to_significant_bits
from src.wallet.wallet_user_store import WalletUserStore
from src.types.mempool_inclusion_status import MempoolInclusionStatus
from src.util.ConsensusError import Err
from src.util.errors import Err
class WalletStateManager:

View File

@ -1,6 +1,6 @@
from typing import Dict, Optional, List, Set
import aiosqlite
from src.types.hashable.coin import Coin
from src.types.coin import Coin
from src.wallet.block_record import BlockRecord
from src.types.sized_bytes import bytes32
from src.util.ints import uint32

View File

@ -4,7 +4,7 @@ from src.types.sized_bytes import bytes32
from src.util.ints import uint32, uint8
from src.wallet.transaction_record import TransactionRecord
from src.types.mempool_inclusion_status import MempoolInclusionStatus
from src.util.ConsensusError import Err
from src.util.errors import Err
class WalletTransactionStore:

View File

@ -13,19 +13,18 @@ from src.consensus import block_rewards, pot_iterations
from src.consensus.constants import constants
from src.consensus.pot_iterations import calculate_min_iters_from_iterations
from src.path import mkdir, path_from_root
from src.pool import create_coinbase_coin_and_signature
from src.consensus.coinbase import create_coinbase_coin_and_signature
from src.types.challenge import Challenge
from src.types.classgroup import ClassgroupElement
from src.types.full_block import FullBlock, additions_for_npc
from src.types.hashable.BLSSignature import BLSSignature
from src.types.hashable.coin import Coin, hash_coin_list
from src.types.hashable.program import Program
from src.types.BLSSignature import BLSSignature
from src.types.coin import Coin, hash_coin_list
from src.types.program import Program
from src.types.header import Header, HeaderData
from src.types.proof_of_space import ProofOfSpace
from src.types.proof_of_time import ProofOfTime
from src.types.sized_bytes import bytes32
from src.util.merkle_set import MerkleSet
from src.util.errors import NoProofsOfSpaceFound
from src.util.ints import uint8, uint32, uint64, uint128, int512
from src.util.hash import std_hash
from src.util.significant_bits import truncate_to_significant_bits
@ -98,6 +97,12 @@ class BlockTools:
(self.plot_dir / filename).unlink()
sys.exit(1)
@staticmethod
def get_harvester_signature(header_data: HeaderData, plot_pk: PublicKey):
for i, pk in enumerate(plot_pks):
if pk == plot_pk:
return plot_sks[i].sign_prepend(header_data.get_hash())
def get_consecutive_blocks(
self,
input_constants: Dict,
@ -399,7 +404,7 @@ class BlockTools:
assert plot_pk
assert plot_sk
if len(qualities) == 0:
raise NoProofsOfSpaceFound("No proofs for this challenge")
raise RuntimeError("No proofs for this challenge")
proof_xs: bytes = prover.get_full_proof(challenge_hash, 0)
proof_of_space: ProofOfSpace = ProofOfSpace(

View File

@ -5,18 +5,21 @@ from pathlib import Path
import aiosqlite
import pytest
from blspy import PrivateKey
from blspy import PrivateKey, PrependSignature
from src.full_node.blockchain import Blockchain, ReceiveBlockResult
from src.full_node.store import FullNodeStore
from src.types.full_block import FullBlock
from src.types.hashable.coin import Coin
from src.types.coin import Coin
from src.types.header import Header, HeaderData
from src.types.proof_of_space import ProofOfSpace
from src.full_node.coin_store import CoinStore
from src.util.ints import uint8, uint64
from src.consensus.constants import constants as consensus_constants
from tests.block_tools import BlockTools
from tests.block_tools import BlockTools, plot_sks
from src.util.errors import Err
from src.consensus.coinbase import create_coinbase_coin_and_signature
from src.types.sized_bytes import bytes32
bt = BlockTools()
test_constants: Dict[str, Any] = consensus_constants.copy()
@ -76,7 +79,7 @@ class TestBlockValidation:
unspent_store = await CoinStore.create(connection)
b: Blockchain = await Blockchain.create(unspent_store, store, test_constants)
for i in range(1, 9):
result, removed = await b.receive_block(blocks[i])
result, removed, error_code = await b.receive_block(blocks[i])
assert result == ReceiveBlockResult.ADDED_TO_HEAD
yield (blocks, b)
@ -112,106 +115,129 @@ class TestBlockValidation:
blocks[9].transactions_generator,
blocks[9].transactions_filter,
)
result, removed = await b.receive_block(block_bad)
result, removed, error_code = await b.receive_block(block_bad)
assert (result) == ReceiveBlockResult.DISCONNECTED_BLOCK
assert error_code is None
@pytest.mark.asyncio
async def test_prev_block(self, initial_blockchain):
blocks, b = initial_blockchain
block_bad = blocks[10]
result, removed, error_code = await b.receive_block(block_bad)
assert (result) == ReceiveBlockResult.DISCONNECTED_BLOCK
assert error_code is None
@pytest.mark.asyncio
async def test_timestamp(self, initial_blockchain):
blocks, b = initial_blockchain
# Time too far in the past
new_header_data = HeaderData(
blocks[9].header.data.height,
blocks[9].header.data.prev_header_hash,
blocks[9].header.data.timestamp - 1000,
blocks[9].header.data.filter_hash,
blocks[9].header.data.proof_of_space_hash,
blocks[9].header.data.weight,
blocks[9].header.data.total_iters,
blocks[9].header.data.additions_root,
blocks[9].header.data.removals_root,
blocks[9].header.data.coinbase,
blocks[9].header.data.coinbase_signature,
blocks[9].header.data.fees_coin,
blocks[9].header.data.aggregated_signature,
blocks[9].header.data.cost,
blocks[9].header.data.extension_data,
blocks[9].header.data.generator_hash,
)
block_bad = FullBlock(
blocks[9].proof_of_space,
blocks[9].proof_of_time,
Header(
HeaderData(
blocks[9].header.data.height,
blocks[9].header.data.prev_header_hash,
blocks[9].header.data.timestamp - 1000,
blocks[9].header.data.filter_hash,
blocks[9].header.data.proof_of_space_hash,
blocks[9].header.data.weight,
blocks[9].header.data.total_iters,
blocks[9].header.data.additions_root,
blocks[9].header.data.removals_root,
blocks[9].header.data.coinbase,
blocks[9].header.data.coinbase_signature,
blocks[9].header.data.fees_coin,
blocks[9].header.data.aggregated_signature,
blocks[9].header.data.cost,
blocks[9].header.data.extension_data,
blocks[9].header.data.generator_hash,
new_header_data,
BlockTools.get_harvester_signature(
new_header_data, blocks[9].proof_of_space.plot_pubkey
),
blocks[9].header.harvester_signature,
),
blocks[9].transactions_generator,
blocks[9].transactions_filter,
)
result, removed = await b.receive_block(block_bad)
result, removed, error_code = await b.receive_block(block_bad)
assert (result) == ReceiveBlockResult.INVALID_BLOCK
assert error_code == Err.TIMESTAMP_TOO_FAR_IN_PAST
# Time too far in the future
new_header_data = HeaderData(
blocks[9].header.data.height,
blocks[9].header.data.prev_header_hash,
uint64(int(time.time() + 3600 * 3)),
blocks[9].header.data.filter_hash,
blocks[9].header.data.proof_of_space_hash,
blocks[9].header.data.weight,
blocks[9].header.data.total_iters,
blocks[9].header.data.additions_root,
blocks[9].header.data.removals_root,
blocks[9].header.data.coinbase,
blocks[9].header.data.coinbase_signature,
blocks[9].header.data.fees_coin,
blocks[9].header.data.aggregated_signature,
blocks[9].header.data.cost,
blocks[9].header.data.extension_data,
blocks[9].header.data.generator_hash,
)
block_bad = FullBlock(
blocks[9].proof_of_space,
blocks[9].proof_of_time,
Header(
HeaderData(
blocks[9].header.data.height,
blocks[9].header.data.prev_header_hash,
uint64(int(time.time() + 3600 * 3)),
blocks[9].header.data.filter_hash,
blocks[9].header.data.proof_of_space_hash,
blocks[9].header.data.weight,
blocks[9].header.data.total_iters,
blocks[9].header.data.additions_root,
blocks[9].header.data.removals_root,
blocks[9].header.data.coinbase,
blocks[9].header.data.coinbase_signature,
blocks[9].header.data.fees_coin,
blocks[9].header.data.aggregated_signature,
blocks[9].header.data.cost,
blocks[9].header.data.extension_data,
blocks[9].header.data.generator_hash,
new_header_data,
BlockTools.get_harvester_signature(
new_header_data, blocks[9].proof_of_space.plot_pubkey
),
blocks[9].header.harvester_signature,
),
blocks[9].transactions_generator,
blocks[9].transactions_filter,
)
result, removed = await b.receive_block(block_bad)
result, removed, error_code = await b.receive_block(block_bad)
assert (result) == ReceiveBlockResult.INVALID_BLOCK
assert error_code == Err.TIMESTAMP_TOO_FAR_IN_FUTURE
@pytest.mark.asyncio
async def test_generator_hash(self, initial_blockchain):
blocks, b = initial_blockchain
new_header_data = HeaderData(
blocks[9].header.data.height,
blocks[9].header.data.prev_header_hash,
blocks[9].header.data.timestamp,
blocks[9].header.data.filter_hash,
blocks[9].header.data.proof_of_space_hash,
blocks[9].header.data.weight,
blocks[9].header.data.total_iters,
blocks[9].header.data.additions_root,
blocks[9].header.data.removals_root,
blocks[9].header.data.coinbase,
blocks[9].header.data.coinbase_signature,
blocks[9].header.data.fees_coin,
blocks[9].header.data.aggregated_signature,
blocks[9].header.data.cost,
blocks[9].header.data.extension_data,
bytes([1] * 32),
)
block_bad = FullBlock(
blocks[9].proof_of_space,
blocks[9].proof_of_time,
Header(
HeaderData(
blocks[9].header.data.height,
blocks[9].header.data.prev_header_hash,
blocks[9].header.data.timestamp,
blocks[9].header.data.filter_hash,
blocks[9].header.data.proof_of_space_hash,
blocks[9].header.data.weight,
blocks[9].header.data.total_iters,
blocks[9].header.data.additions_root,
blocks[9].header.data.removals_root,
blocks[9].header.data.coinbase,
blocks[9].header.data.coinbase_signature,
blocks[9].header.data.fees_coin,
blocks[9].header.data.aggregated_signature,
blocks[9].header.data.cost,
blocks[9].header.data.extension_data,
bytes([1] * 32),
new_header_data,
BlockTools.get_harvester_signature(
new_header_data, blocks[9].proof_of_space.plot_pubkey
),
blocks[9].header.harvester_signature,
),
blocks[9].transactions_generator,
blocks[9].transactions_filter,
)
result, removed = await b.receive_block(block_bad)
result, removed, error_code = await b.receive_block(block_bad)
assert result == ReceiveBlockResult.INVALID_BLOCK
assert error_code == Err.INVALID_TRANSACTIONS_GENERATOR_HASH
@pytest.mark.asyncio
async def test_harvester_signature(self, initial_blockchain):
@ -227,70 +253,238 @@ class TestBlockValidation:
blocks[9].transactions_generator,
blocks[9].transactions_filter,
)
result, removed = await b.receive_block(block_bad)
result, removed, error_code = await b.receive_block(block_bad)
assert result == ReceiveBlockResult.INVALID_BLOCK
assert error_code == Err.INVALID_HARVESTER_SIGNATURE
@pytest.mark.asyncio
async def test_invalid_pos(self, initial_blockchain):
blocks, b = initial_blockchain
bad_pos = bytearray([i for i in blocks[9].proof_of_space.proof])
bad_pos[0] = uint8((bad_pos[0] + 1) % 256)
bad_pos_proof = bytearray([i for i in blocks[9].proof_of_space.proof])
bad_pos_proof[0] = uint8((bad_pos_proof[0] + 1) % 256)
bad_pos = ProofOfSpace(
blocks[9].proof_of_space.challenge_hash,
blocks[9].proof_of_space.pool_pubkey,
blocks[9].proof_of_space.plot_pubkey,
blocks[9].proof_of_space.size,
bytes(bad_pos_proof),
)
new_header_data = HeaderData(
blocks[9].header.data.height,
blocks[9].header.data.prev_header_hash,
blocks[9].header.data.timestamp,
blocks[9].header.data.filter_hash,
bad_pos.get_hash(),
blocks[9].header.data.weight,
blocks[9].header.data.total_iters,
blocks[9].header.data.additions_root,
blocks[9].header.data.removals_root,
blocks[9].header.data.coinbase,
blocks[9].header.data.coinbase_signature,
blocks[9].header.data.fees_coin,
blocks[9].header.data.aggregated_signature,
blocks[9].header.data.cost,
blocks[9].header.data.extension_data,
blocks[9].header.data.generator_hash,
)
# Proof of space invalid
block_bad = FullBlock(
ProofOfSpace(
blocks[9].proof_of_space.challenge_hash,
blocks[9].proof_of_space.pool_pubkey,
blocks[9].proof_of_space.plot_pubkey,
blocks[9].proof_of_space.size,
bytes(bad_pos),
),
bad_pos,
blocks[9].proof_of_time,
blocks[9].header,
Header(
new_header_data,
BlockTools.get_harvester_signature(
new_header_data, blocks[9].proof_of_space.plot_pubkey
),
),
blocks[9].transactions_generator,
blocks[9].transactions_filter,
)
result, removed = await b.receive_block(block_bad)
result, removed, error_code = await b.receive_block(block_bad)
assert result == ReceiveBlockResult.INVALID_BLOCK
assert error_code == Err.INVALID_POSPACE
@pytest.mark.asyncio
async def test_invalid_coinbase_height(self, initial_blockchain):
async def test_invalid_pos_hash(self, initial_blockchain):
blocks, b = initial_blockchain
# Coinbase height invalid
bad_pos_proof = bytearray([i for i in blocks[9].proof_of_space.proof])
bad_pos_proof[0] = uint8((bad_pos_proof[0] + 1) % 256)
bad_pos = ProofOfSpace(
blocks[9].proof_of_space.challenge_hash,
blocks[9].proof_of_space.pool_pubkey,
blocks[9].proof_of_space.plot_pubkey,
blocks[9].proof_of_space.size,
bytes(bad_pos_proof),
)
new_header_data = HeaderData(
blocks[9].header.data.height,
blocks[9].header.data.prev_header_hash,
blocks[9].header.data.timestamp,
blocks[9].header.data.filter_hash,
bad_pos.get_hash(),
blocks[9].header.data.weight,
blocks[9].header.data.total_iters,
blocks[9].header.data.additions_root,
blocks[9].header.data.removals_root,
blocks[9].header.data.coinbase,
blocks[9].header.data.coinbase_signature,
blocks[9].header.data.fees_coin,
blocks[9].header.data.aggregated_signature,
blocks[9].header.data.cost,
blocks[9].header.data.extension_data,
blocks[9].header.data.generator_hash,
)
# Proof of space has invalid
block_bad = FullBlock(
blocks[9].proof_of_space,
blocks[9].proof_of_time,
Header(
HeaderData(
blocks[9].header.data.height,
blocks[9].header.data.prev_header_hash,
blocks[9].header.data.timestamp,
blocks[9].header.data.filter_hash,
blocks[9].header.data.proof_of_space_hash,
blocks[9].header.data.weight,
blocks[9].header.data.total_iters,
blocks[9].header.data.additions_root,
blocks[9].header.data.removals_root,
Coin(
blocks[9].header.data.coinbase.parent_coin_info,
blocks[9].header.data.coinbase.puzzle_hash,
uint64(9999999999),
),
blocks[9].header.data.coinbase_signature,
blocks[9].header.data.fees_coin,
blocks[9].header.data.aggregated_signature,
blocks[9].header.data.cost,
blocks[9].header.data.extension_data,
blocks[9].header.data.generator_hash,
new_header_data,
BlockTools.get_harvester_signature(
new_header_data, blocks[9].proof_of_space.plot_pubkey
),
blocks[9].header.harvester_signature,
),
blocks[9].transactions_generator,
blocks[9].transactions_filter,
)
result, removed = await b.receive_block(block_bad)
result, removed, error_code = await b.receive_block(block_bad)
assert result == ReceiveBlockResult.INVALID_BLOCK
assert error_code == Err.INVALID_POSPACE_HASH
@pytest.mark.asyncio
async def test_invalid_filter_hash(self, initial_blockchain):
blocks, b = initial_blockchain
new_header_data = HeaderData(
blocks[9].header.data.height,
blocks[9].header.data.prev_header_hash,
blocks[9].header.data.timestamp,
bytes32(bytes([3] * 32)),
blocks[9].header.data.proof_of_space_hash,
blocks[9].header.data.weight,
blocks[9].header.data.total_iters,
blocks[9].header.data.additions_root,
blocks[9].header.data.removals_root,
blocks[9].header.data.coinbase,
blocks[9].header.data.coinbase_signature,
blocks[9].header.data.fees_coin,
blocks[9].header.data.aggregated_signature,
blocks[9].header.data.cost,
blocks[9].header.data.extension_data,
blocks[9].header.data.generator_hash,
)
block_bad = FullBlock(
blocks[9].proof_of_space,
blocks[9].proof_of_time,
Header(
new_header_data,
BlockTools.get_harvester_signature(
new_header_data, blocks[9].proof_of_space.plot_pubkey
),
),
blocks[9].transactions_generator,
blocks[9].transactions_filter,
)
result, removed, error_code = await b.receive_block(block_bad)
assert result == ReceiveBlockResult.INVALID_BLOCK
assert error_code == Err.INVALID_TRANSACTIONS_FILTER_HASH
@pytest.mark.asyncio
async def test_invalid_coinbase_amount(self, initial_blockchain):
blocks, b = initial_blockchain
coinbase_coin, coinbase_signature = create_coinbase_coin_and_signature(
blocks[9].header.data.height,
blocks[9].header.data.coinbase.puzzle_hash,
uint64(9991),
bt.pool_sk,
)
new_header_data = HeaderData(
blocks[9].header.data.height,
blocks[9].header.data.prev_header_hash,
blocks[9].header.data.timestamp,
blocks[9].header.data.filter_hash,
blocks[9].header.data.proof_of_space_hash,
blocks[9].header.data.weight,
blocks[9].header.data.total_iters,
blocks[9].header.data.additions_root,
blocks[9].header.data.removals_root,
coinbase_coin,
coinbase_signature,
blocks[9].header.data.fees_coin,
blocks[9].header.data.aggregated_signature,
blocks[9].header.data.cost,
blocks[9].header.data.extension_data,
blocks[9].header.data.generator_hash,
)
# Coinbase amount invalid
block_bad = FullBlock(
blocks[9].proof_of_space,
blocks[9].proof_of_time,
Header(
new_header_data,
BlockTools.get_harvester_signature(
new_header_data, blocks[9].proof_of_space.plot_pubkey
),
),
blocks[9].transactions_generator,
blocks[9].transactions_filter,
)
result, removed, error_code = await b.receive_block(block_bad)
assert result == ReceiveBlockResult.INVALID_BLOCK
assert error_code == Err.INVALID_COINBASE_AMOUNT
@pytest.mark.asyncio
async def test_invalid_coinbase_signature(self, initial_blockchain):
blocks, b = initial_blockchain
coinbase_coin, coinbase_signature = create_coinbase_coin_and_signature(
blocks[9].header.data.height,
blocks[9].header.data.coinbase.puzzle_hash,
uint64(9991),
bt.pool_sk,
)
new_header_data = HeaderData(
blocks[9].header.data.height,
blocks[9].header.data.prev_header_hash,
blocks[9].header.data.timestamp,
blocks[9].header.data.filter_hash,
blocks[9].header.data.proof_of_space_hash,
blocks[9].header.data.weight,
blocks[9].header.data.total_iters,
blocks[9].header.data.additions_root,
blocks[9].header.data.removals_root,
blocks[9].header.data.coinbase,
coinbase_signature,
blocks[9].header.data.fees_coin,
blocks[9].header.data.aggregated_signature,
blocks[9].header.data.cost,
blocks[9].header.data.extension_data,
blocks[9].header.data.generator_hash,
)
# Coinbase amount invalid
block_bad = FullBlock(
blocks[9].proof_of_space,
blocks[9].proof_of_time,
Header(
new_header_data,
BlockTools.get_harvester_signature(
new_header_data, blocks[9].proof_of_space.plot_pubkey
),
),
blocks[9].transactions_generator,
blocks[9].transactions_filter,
)
result, removed, error_code = await b.receive_block(block_bad)
assert result == ReceiveBlockResult.INVALID_BLOCK
assert error_code == Err.INVALID_COINBASE_SIGNATURE
@pytest.mark.asyncio
async def test_difficulty_change(self):
@ -304,8 +498,9 @@ class TestBlockValidation:
await store._clear_database()
b: Blockchain = await Blockchain.create(unspent_store, store, test_constants)
for i in range(1, num_blocks):
result, removed = await b.receive_block(blocks[i])
result, removed, error_code = await b.receive_block(blocks[i])
assert result == ReceiveBlockResult.ADDED_TO_HEAD
assert error_code is None
diff_25 = b.get_next_difficulty(blocks[24].header_hash)
diff_26 = b.get_next_difficulty(blocks[25].header_hash)
@ -344,13 +539,14 @@ class TestReorgs:
)
for i in range(1, len(blocks_reorg_chain)):
reorg_block = blocks_reorg_chain[i]
result, removed = await b.receive_block(reorg_block)
result, removed, error_code = await b.receive_block(reorg_block)
if reorg_block.height < 90:
assert result == ReceiveBlockResult.ALREADY_HAVE_BLOCK
elif reorg_block.height < 99:
assert result == ReceiveBlockResult.ADDED_AS_ORPHAN
elif reorg_block.height >= 100:
assert result == ReceiveBlockResult.ADDED_TO_HEAD
assert error_code is None
assert b.get_current_tips()[0].height == 119
await connection.close()
@ -374,7 +570,7 @@ class TestReorgs:
)
for i in range(1, len(blocks_reorg_chain)):
reorg_block = blocks_reorg_chain[i]
result, removed = await b.receive_block(reorg_block)
result, removed, error_code = await b.receive_block(reorg_block)
if reorg_block.height == 0:
assert result == ReceiveBlockResult.ALREADY_HAVE_BLOCK
elif reorg_block.height < 19:
@ -387,13 +583,13 @@ class TestReorgs:
blocks_reorg_chain_2 = bt.get_consecutive_blocks(
test_constants, 3, blocks[:-1], 9, b"4"
)
result, _ = await b.receive_block(blocks_reorg_chain_2[20])
result, _, error_code = await b.receive_block(blocks_reorg_chain_2[20])
assert result == ReceiveBlockResult.ADDED_AS_ORPHAN
result, _ = await b.receive_block(blocks_reorg_chain_2[21])
result, _, error_code = await b.receive_block(blocks_reorg_chain_2[21])
assert result == ReceiveBlockResult.ADDED_TO_HEAD
result, _ = await b.receive_block(blocks_reorg_chain_2[22])
result, _, error_code = await b.receive_block(blocks_reorg_chain_2[22])
assert result == ReceiveBlockResult.ADDED_TO_HEAD
await connection.close()

View File

@ -10,8 +10,8 @@ from src.util.bundle_tools import best_solution_program
from src.server.outbound_message import OutboundMessage
from src.protocols import full_node_protocol
from src.types.full_block import FullBlock
from src.types.hashable.spend_bundle import SpendBundle
from src.util.ConsensusError import Err
from src.types.spend_bundle import SpendBundle
from src.util.errors import Err
from src.util.ints import uint64
from tests.setup_nodes import setup_two_nodes, test_constants, bt
from tests.wallet_tools import WalletTool

View File

@ -12,7 +12,7 @@ from src.server.outbound_message import NodeType
from src.types.peer_info import PeerInfo
from src.types.full_block import FullBlock
from src.types.proof_of_space import ProofOfSpace
from src.types.hashable.spend_bundle import SpendBundle
from src.types.spend_bundle import SpendBundle
from src.util.bundle_tools import best_solution_program
from src.util.ints import uint16, uint32, uint64, uint8
from src.util.hash import std_hash
@ -22,13 +22,13 @@ from src.types.condition_opcodes import ConditionOpcode
from tests.setup_nodes import setup_two_nodes, test_constants, bt
from tests.wallet_tools import WalletTool
from src.types.mempool_inclusion_status import MempoolInclusionStatus
from src.types.hashable.coin import hash_coin_list
from src.types.coin import hash_coin_list
from src.util.merkle_set import (
MerkleSet,
confirm_included_already_hashed,
confirm_not_included_already_hashed,
)
from src.util.ConsensusError import Err
from src.util.errors import Err, ConsensusError
async def get_block_path(full_node: FullNode):
@ -720,10 +720,17 @@ class TestFullNodeProtocol:
blocks_new[-5].transactions_generator,
blocks_new[-5].transactions_filter,
)
msgs = [
_ async for _ in full_node_1.respond_block(fnp.RespondBlock(block_invalid))
]
assert len(msgs) == 0
threw = False
try:
msgs = [
_
async for _ in full_node_1.respond_block(
fnp.RespondBlock(block_invalid)
)
]
except ConsensusError:
threw = True
assert threw
# If a few blocks behind, request short sync
msgs = [

View File

@ -7,7 +7,7 @@ from src.server.outbound_message import OutboundMessage
from src.protocols import full_node_protocol
from src.types.condition_var_pair import ConditionVarPair
from src.types.condition_opcodes import ConditionOpcode
from src.types.hashable.spend_bundle import SpendBundle
from src.types.spend_bundle import SpendBundle
from src.util.ints import uint64
from tests.setup_nodes import setup_two_nodes, test_constants, bt
from tests.wallet_tools import WalletTool

View File

@ -159,7 +159,7 @@ class TestUnspent:
for i in range(1, len(blocks_reorg_chain)):
reorg_block = blocks_reorg_chain[i]
result, removed = await b.receive_block(reorg_block)
result, removed, error_code = await b.receive_block(reorg_block)
if reorg_block.height < 90:
assert result == ReceiveBlockResult.ALREADY_HAVE_BLOCK
elif reorg_block.height < 99:
@ -173,6 +173,7 @@ class TestUnspent:
assert unspent.confirmed_block_index == reorg_block.height
assert unspent.spent == 0
assert unspent.spent_block_index == 0
assert error_code is None
assert b.get_current_tips()[0].height == 119
except Exception as e:
await connection.close()

View File

@ -1,7 +1,7 @@
import blspy
from src.types.hashable.coin_solution import CoinSolution
from src.types.hashable.spend_bundle import SpendBundle
from src.types.coin_solution import CoinSolution
from src.types.spend_bundle import SpendBundle
from src.wallet.BLSPrivateKey import BLSPrivateKey
from src.wallet.keychain import Keychain

View File

@ -20,9 +20,9 @@ from src.wallet.wallet_node import WalletNode
from src.types.full_block import FullBlock
from src.full_node.coin_store import CoinStore
from tests.block_tools import BlockTools
from src.types.hashable.BLSSignature import BLSPublicKey
from src.types.BLSSignature import BLSPublicKey
from src.util.config import load_config
from src.pool import create_puzzlehash_for_pk
from src.consensus.coinbase import create_puzzlehash_for_pk
from src.harvester import Harvester
from src.farmer import Farmer
from src.introducer import Introducer

View File

@ -8,8 +8,8 @@ from clvm_tools import binutils
from src.types.condition_opcodes import ConditionOpcode
from src.types.condition_var_pair import ConditionVarPair
from src.types.hashable.BLSSignature import BLSSignature
from src.types.hashable.program import Program
from src.types.BLSSignature import BLSSignature
from src.types.program import Program
from src.util.condition_tools import parse_sexp_to_conditions
from src.wallet.BLSPrivateKey import BLSPrivateKey
from src.wallet.puzzles.p2_delegated_puzzle import puzzle_for_pk

View File

@ -3,7 +3,7 @@ from dataclasses import dataclass
from typing import List, Optional, Dict, Any
from src.util.ints import uint32
from src.types.hashable.coin import Coin
from src.types.coin import Coin
from src.types.sized_bytes import bytes32
from src.types.full_block import FullBlock
from src.util.streamable import Streamable, streamable

View File

@ -7,11 +7,11 @@ from blspy import ExtendedPrivateKey
from src.types.condition_var_pair import ConditionVarPair
from src.types.condition_opcodes import ConditionOpcode
from src.types.hashable.program import Program
from src.types.hashable.BLSSignature import BLSSignature
from src.types.hashable.coin import Coin
from src.types.hashable.coin_solution import CoinSolution
from src.types.hashable.spend_bundle import SpendBundle
from src.types.program import Program
from src.types.BLSSignature import BLSSignature
from src.types.coin import Coin
from src.types.coin_solution import CoinSolution
from src.types.spend_bundle import SpendBundle
from src.util.condition_tools import (
conditions_by_opcode,
hash_key_pairs_for_conditions_dict,