mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2024-11-10 12:29:49 +03:00
Tweak block validation, rename some files, and concentrate error codes
This commit is contained in:
parent
f721c58ba5
commit
920043c1d4
2
setup.py
2
setup.py
@ -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",
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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):
|
@ -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
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
@ -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
|
@ -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
|
||||
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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,
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 (
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import pkg_resources
|
||||
|
||||
from src.types.hashable.program import Program
|
||||
from src.types.program import Program
|
||||
|
||||
|
||||
def load_clvm(filename):
|
||||
|
@ -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:
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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(
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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 = [
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user