Skip to content

Commit

Permalink
refactor(verification): implement verification dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
glevco committed Nov 9, 2023
1 parent 1bef803 commit a89dde8
Show file tree
Hide file tree
Showing 17 changed files with 413 additions and 172 deletions.
3 changes: 2 additions & 1 deletion hathor/builder/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,8 @@ def _get_or_create_bit_signaling_service(self) -> BitSignalingService:
def _get_or_create_verification_service(self) -> VerificationService:
if self._verification_service is None:
verifiers = self._get_or_create_vertex_verifiers()
self._verification_service = VerificationService(verifiers=verifiers)
daa = self._get_or_create_daa()
self._verification_service = VerificationService(verifiers=verifiers, daa=daa)

return self._verification_service

Expand Down
2 changes: 1 addition & 1 deletion hathor/builder/cli_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ def create_manager(self, reactor: Reactor) -> HathorManager:
daa=daa,
feature_service=self.feature_service
)
verification_service = VerificationService(verifiers=vertex_verifiers)
verification_service = VerificationService(verifiers=vertex_verifiers, daa=daa)

cpu_mining_service = CpuMiningService()

Expand Down
22 changes: 20 additions & 2 deletions hathor/transaction/base_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@

from hathor.checkpoint import Checkpoint
from hathor.conf.get_settings import get_settings
from hathor.transaction.exceptions import InvalidOutputValue, WeightError
from hathor.daa import DifficultyAdjustmentAlgorithm
from hathor.transaction.exceptions import InvalidOutputValue, ParentDoesNotExist, WeightError
from hathor.transaction.transaction_metadata import TransactionMetadata
from hathor.transaction.util import VerboseCallback, int_to_bytes, unpack, unpack_len
from hathor.transaction.validation_state import ValidationState
Expand All @@ -39,6 +40,7 @@
from _hashlib import HASH

from hathor.transaction.storage import TransactionStorage # noqa: F401
from hathor.verification.verification_model import VertexVerificationModel

logger = get_logger()

Expand Down Expand Up @@ -324,7 +326,7 @@ def get_parents(self, *, existing_only: bool = False) -> Iterator['BaseTransacti
yield self.storage.get_transaction(parent_hash)
except TransactionDoesNotExist:
if not existing_only:
raise
raise ParentDoesNotExist(f'tx={self.hash_hex} parent={parent_hash.hex()}')

@property
def is_genesis(self) -> bool:
Expand Down Expand Up @@ -852,6 +854,22 @@ def is_ready_for_validation(self) -> bool:
return False
return True

@abstractmethod
def get_verification_model(
self,
*,
daa: DifficultyAdjustmentAlgorithm,
with_deps: bool = True
) -> 'VertexVerificationModel':
"""
Return the verification model for this vertex.
Args:
daa: the DAA used to create the verification dependencies.
with_deps: whether the model should include verification dependencies or not.
"""
raise NotImplementedError


class TxInput:
_tx: BaseTransaction # XXX: used for caching on hathor.transaction.Transaction.get_spent_tx
Expand Down
17 changes: 17 additions & 0 deletions hathor/transaction/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
from struct import pack
from typing import TYPE_CHECKING, Any, Optional

from typing_extensions import override

from hathor.checkpoint import Checkpoint
from hathor.daa import DifficultyAdjustmentAlgorithm
from hathor.feature_activation.feature import Feature
from hathor.feature_activation.model.feature_state import FeatureState
from hathor.profiler import get_cpu_profiler
Expand All @@ -30,6 +33,7 @@

if TYPE_CHECKING:
from hathor.transaction.storage import TransactionStorage # noqa: F401
from hathor.verification.verification_model import VertexVerificationModel

cpu = get_cpu_profiler()

Expand Down Expand Up @@ -401,3 +405,16 @@ def get_feature_activation_bit_value(self, bit: int) -> int:
bit_list = self._get_feature_activation_bit_list()

return bit_list[bit]

@override
def get_verification_model(
self,
*,
daa: DifficultyAdjustmentAlgorithm,
with_deps: bool = True
) -> 'VertexVerificationModel':
from hathor.verification.verification_model import BlockDependencies, BlockVerification
return BlockVerification(
vertex=self,
deps=BlockDependencies.create(self, daa) if with_deps else None
)
17 changes: 17 additions & 0 deletions hathor/transaction/merge_mined_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@

from typing import TYPE_CHECKING, Any, Optional

from typing_extensions import override

from hathor.daa import DifficultyAdjustmentAlgorithm
from hathor.transaction.aux_pow import BitcoinAuxPow
from hathor.transaction.base_transaction import TxOutput, TxVersion
from hathor.transaction.block import Block
from hathor.transaction.util import VerboseCallback

if TYPE_CHECKING:
from hathor.transaction.storage import TransactionStorage # noqa: F401
from hathor.verification.verification_model import VertexVerificationModel


class MergeMinedBlock(Block):
Expand Down Expand Up @@ -74,3 +78,16 @@ def to_json(self, decode_script: bool = False, include_metadata: bool = False) -
del json['nonce']
json['aux_pow'] = bytes(self.aux_pow).hex() if self.aux_pow else None
return json

@override
def get_verification_model(
self,
*,
daa: DifficultyAdjustmentAlgorithm,
with_deps: bool = True
) -> 'VertexVerificationModel':
from hathor.verification.verification_model import BlockDependencies, MergeMinedBlockVerification
return MergeMinedBlockVerification(
vertex=self,
deps=BlockDependencies.create(self, daa) if with_deps else None
)
14 changes: 9 additions & 5 deletions hathor/transaction/resources/create_tx.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
from hathor.transaction import Transaction, TxInput, TxOutput
from hathor.transaction.scripts import create_output_script
from hathor.util import api_catch_exceptions, json_dumpb, json_loadb
from hathor.verification.transaction_verifier import TransactionVerifier
from hathor.verification.verification_model import TransactionDependencies


def from_raw_output(raw_output: dict, tokens: list[bytes]) -> TxOutput:
Expand Down Expand Up @@ -107,15 +109,17 @@ def render_POST(self, request):
def _verify_unsigned_skip_pow(self, tx: Transaction) -> None:
""" Same as .verify but skipping pow and signature verification."""
assert type(tx) is Transaction
verifier = self.manager.verification_service.verifiers.tx
verifier: TransactionVerifier = self.manager.verification_service.verifiers.tx
deps = TransactionDependencies.create(tx)
verifier.verify_number_of_inputs(tx)
verifier.verify_number_of_outputs(tx)
verifier.verify_outputs(tx)
verifier.verify_sigops_output(tx)
verifier.verify_sigops_input(tx)
verifier.verify_inputs(tx, skip_script=True) # need to run verify_inputs first to check if all inputs exist
verifier.verify_parents(tx)
verifier.verify_sum(tx)
verifier.verify_sigops_input(tx, deps)
# need to run verify_inputs first to check if all inputs exist
verifier.verify_inputs(tx, deps=deps, skip_script=True)
verifier.verify_parents(tx, deps=deps)
verifier.verify_sum(tx, deps=deps)


CreateTxResource.openapi = {
Expand Down
19 changes: 18 additions & 1 deletion hathor/transaction/token_creation_tx.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,20 @@
# limitations under the License.

from struct import error as StructError, pack
from typing import Any, Optional
from typing import TYPE_CHECKING, Any, Optional

from typing_extensions import override

from hathor.daa import DifficultyAdjustmentAlgorithm
from hathor.transaction.base_transaction import TxInput, TxOutput, TxVersion
from hathor.transaction.storage import TransactionStorage # noqa: F401
from hathor.transaction.transaction import TokenInfo, Transaction
from hathor.transaction.util import VerboseCallback, int_to_bytes, unpack, unpack_len
from hathor.types import TokenUid

if TYPE_CHECKING:
from hathor.verification.verification_model import VertexVerificationModel

# Signal bits (B), version (B), inputs len (B), outputs len (B)
_FUNDS_FORMAT_STRING = '!BBBB'

Expand Down Expand Up @@ -226,6 +230,19 @@ def _get_token_info_from_inputs(self) -> dict[TokenUid, TokenInfo]:

return token_dict

@override
def get_verification_model(
self,
*,
daa: DifficultyAdjustmentAlgorithm,
with_deps: bool = True
) -> 'VertexVerificationModel':
from hathor.verification.verification_model import TokenCreationTxVerification, TransactionDependencies
return TokenCreationTxVerification(
vertex=self,
deps=TransactionDependencies.create(self) if with_deps else None
)


def decode_string_utf8(encoded: bytes, key: str) -> str:
""" Raises StructError in case it's not a valid utf-8 string
Expand Down
32 changes: 29 additions & 3 deletions hathor/transaction/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,22 @@
from struct import pack
from typing import TYPE_CHECKING, Any, Iterator, NamedTuple, Optional

from typing_extensions import override

from hathor.checkpoint import Checkpoint
from hathor.daa import DifficultyAdjustmentAlgorithm
from hathor.exception import InvalidNewTransaction
from hathor.profiler import get_cpu_profiler
from hathor.transaction import BaseTransaction, Block, TxInput, TxOutput, TxVersion
from hathor.transaction.base_transaction import TX_HASH_SIZE
from hathor.transaction.exceptions import InvalidToken
from hathor.transaction.exceptions import InexistentInput, InvalidToken
from hathor.transaction.util import VerboseCallback, unpack, unpack_len
from hathor.types import TokenUid, VertexId
from hathor.util import not_none

if TYPE_CHECKING:
from hathor.transaction.storage import TransactionStorage # noqa: F401
from hathor.verification.verification_model import VertexVerificationModel

cpu = get_cpu_profiler()

Expand Down Expand Up @@ -260,7 +264,11 @@ def get_token_uid(self, index: int) -> TokenUid:
"""
if index == 0:
return self._settings.HATHOR_TOKEN_UID
return self.tokens[index - 1]

try:
return self.tokens[index - 1]
except IndexError:
raise InvalidToken(f'token uid index not available: index {index - 1}')

def to_json(self, decode_script: bool = False, include_metadata: bool = False) -> dict[str, Any]:
json = super().to_json(decode_script=decode_script, include_metadata=include_metadata)
Expand Down Expand Up @@ -302,7 +310,12 @@ def _get_token_info_from_inputs(self) -> dict[TokenUid, TokenInfo]:

for tx_input in self.inputs:
spent_tx = self.get_spent_tx(tx_input)
spent_output = spent_tx.outputs[tx_input.index]
try:
spent_output = spent_tx.outputs[tx_input.index]
except IndexError:
raise InexistentInput(
f'Output spent by this input does not exist: {tx_input.tx_id.hex()} index {tx_input.index}'
)

token_uid = spent_tx.get_token_uid(spent_output.get_token_index())
(amount, can_mint, can_melt) = token_dict.get(token_uid, default_info)
Expand Down Expand Up @@ -415,3 +428,16 @@ def is_spending_voided_tx(self) -> bool:
if meta.voided_by:
return True
return False

@override
def get_verification_model(
self,
*,
daa: DifficultyAdjustmentAlgorithm,
with_deps: bool = True
) -> 'VertexVerificationModel':
from hathor.verification.verification_model import TransactionDependencies, TxVerification
return TxVerification(
vertex=self,
deps=TransactionDependencies.create(self) if with_deps else None
)
38 changes: 22 additions & 16 deletions hathor/verification/block_verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@
TransactionDataError,
WeightError,
)
from hathor.util import not_none
from hathor.verification.verification_model import BlockDependencies
from hathor.verification.vertex_verifier import VertexVerifier

cpu = get_cpu_profiler()


class BlockVerifier(VertexVerifier):
__slots__ = ('_feature_service', )
__slots__ = ('_feature_service',)

def __init__(
self,
Expand All @@ -44,14 +46,20 @@ def __init__(
super().__init__(settings=settings, daa=daa)
self._feature_service = feature_service

def verify_basic(self, block: Block, *, skip_block_weight_verification: bool = False) -> None:
def verify_basic(
self,
block: Block,
*,
deps: BlockDependencies,
skip_block_weight_verification: bool = False
) -> None:
"""Partially run validations, the ones that need parents/inputs are skipped."""
if not skip_block_weight_verification:
self.verify_weight(block)
self.verify_reward(block)
self.verify_weight(block, deps)
self.verify_reward(block, deps)

@cpu.profiler(key=lambda _, block: 'block-verify!{}'.format(block.hash.hex()))
def verify(self, block: Block) -> None:
def verify(self, block: Block, *, deps: BlockDependencies) -> None:
"""
(1) confirms at least two pending transactions and references last block
(2) solves the pow with the correct weight (done in HathorManager)
Expand All @@ -68,9 +76,9 @@ def verify(self, block: Block) -> None:
self.verify_without_storage(block)

# (1) and (4)
self.verify_parents(block)
self.verify_parents(block, deps)

self.verify_height(block)
self.verify_height(deps)

self.verify_mandatory_signaling(block)

Expand All @@ -83,25 +91,23 @@ def verify_without_storage(self, block: Block) -> None:
self.verify_data(block)
self.verify_sigops_output(block)

def verify_height(self, block: Block) -> None:
def verify_height(self, deps: BlockDependencies) -> None:
"""Validate that the block height is enough to confirm all transactions being confirmed."""
meta = block.get_metadata()
meta = deps.metadata
assert meta.height is not None
assert meta.min_height is not None
if meta.height < meta.min_height:
raise RewardLocked(f'Block needs {meta.min_height} height but has {meta.height}')

def verify_weight(self, block: Block) -> None:
def verify_weight(self, block: Block, deps: BlockDependencies) -> None:
"""Validate minimum block difficulty."""
min_block_weight = self._daa.calculate_block_difficulty(block)
if block.weight < min_block_weight - self._settings.WEIGHT_TOL:
if block.weight < deps.min_block_weight - self._settings.WEIGHT_TOL:
raise WeightError(f'Invalid new block {block.hash_hex}: weight ({block.weight}) is '
f'smaller than the minimum weight ({min_block_weight})')
f'smaller than the minimum weight ({deps.min_block_weight})')

def verify_reward(self, block: Block) -> None:
def verify_reward(self, block: Block, deps: BlockDependencies) -> None:
"""Validate reward amount."""
parent_block = block.get_block_parent()
tokens_issued_per_block = self._daa.get_tokens_issued_per_block(parent_block.get_height() + 1)
tokens_issued_per_block = self._daa.get_tokens_issued_per_block(not_none(deps.parent_block_height) + 1)
if block.sum_outputs != tokens_issued_per_block:
raise InvalidBlockReward(
f'Invalid number of issued tokens tag=invalid_issued_tokens tx.hash={block.hash_hex} '
Expand Down
Loading

0 comments on commit a89dde8

Please sign in to comment.