Skip to content

Commit

Permalink
refactor(verification): remove verification dependencies from the ver…
Browse files Browse the repository at this point in the history
…ification process
  • Loading branch information
glevco committed May 7, 2024
1 parent 42f7cbf commit aad0eab
Show file tree
Hide file tree
Showing 19 changed files with 285 additions and 207 deletions.
2 changes: 2 additions & 0 deletions hathor/builder/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -515,10 +515,12 @@ def _get_or_create_verification_service(self) -> VerificationService:
verifiers = self._get_or_create_vertex_verifiers()
storage = self._get_or_create_tx_storage()
settings = self._get_or_create_settings()
daa = self._get_or_create_daa()
self._verification_service = VerificationService(
verifiers=verifiers,
tx_storage=storage,
settings=settings,
daa=daa,
)

return self._verification_service
Expand Down
1 change: 1 addition & 0 deletions hathor/builder/cli_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ def create_manager(self, reactor: Reactor) -> HathorManager:
verifiers=vertex_verifiers,
tx_storage=tx_storage,
settings=settings,
daa=daa,
)

cpu_mining_service = CpuMiningService()
Expand Down
2 changes: 1 addition & 1 deletion hathor/cli/mining.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def execute(args: Namespace) -> None:
settings = get_global_settings()
daa = DifficultyAdjustmentAlgorithm(settings=settings)
verifiers = VertexVerifiers.create_defaults(settings=settings, daa=daa)
verification_service = VerificationService(verifiers=verifiers, settings=settings)
verification_service = VerificationService(verifiers=verifiers, settings=settings, daa=daa)
verification_service.verify_without_storage(block)
except HathorError:
print('[{}] ERROR: Block has not been pushed because it is not valid.'.format(datetime.datetime.now()))
Expand Down
10 changes: 5 additions & 5 deletions hathor/daa.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,17 +77,17 @@ def get_block_dependencies(
self,
block: 'Block',
parent_block_getter: Callable[['Block'], 'Block'],
) -> list[VertexId]:
) -> dict[VertexId, 'Block']:
"""Return the ids of the required blocks to call `calculate_block_difficulty` for the provided block."""
parent_block = parent_block_getter(block)
N = self._calculate_N(parent_block)
ids: list[VertexId] = [parent_block.hash]
deps = {parent_block.hash: parent_block}

while len(ids) <= N + 1:
while len(deps) <= N + 1:
parent_block = parent_block_getter(parent_block)
ids.append(parent_block.hash)
deps[parent_block.hash] = parent_block

return ids
return deps

def calculate_next_weight(
self,
Expand Down
2 changes: 1 addition & 1 deletion hathor/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -906,7 +906,7 @@ def push_tx(self, tx: Transaction, allow_non_standard_script: bool = False,
if is_spending_voided_tx:
raise SpendingVoidedError('Invalid transaction. At least one input is voided.')

if is_spent_reward_locked(tx):
if is_spent_reward_locked(tx, self.tx_storage):
raise RewardLockedError('Spent reward is locked.')

# We are using here the method from lib because the property
Expand Down
3 changes: 1 addition & 2 deletions hathor/reward_lock/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from hathor.reward_lock.reward_lock import get_spent_reward_locked_info, is_spent_reward_locked, iter_spent_rewards
from hathor.reward_lock.reward_lock import get_spent_reward_locked_info, is_spent_reward_locked

__all__ = [
'iter_spent_rewards',
'is_spent_reward_locked',
'get_spent_reward_locked_info',
]
55 changes: 28 additions & 27 deletions hathor/reward_lock/reward_lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,55 +12,56 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import TYPE_CHECKING, Iterator, Optional
from typing import TYPE_CHECKING, Callable, Optional

from hathor.conf.get_settings import get_global_settings
from hathor.transaction import Block
from hathor.util import not_none
from hathor.transaction import Block, Vertex
from hathor.types import VertexId

if TYPE_CHECKING:
from hathor.transaction.storage.vertex_storage_protocol import VertexStorageProtocol
from hathor.transaction.storage import TransactionStorage
from hathor.transaction.transaction import RewardLockedInfo, Transaction


def iter_spent_rewards(tx: 'Transaction', storage: 'VertexStorageProtocol') -> Iterator[Block]:
"""Iterate over all the rewards being spent, assumes tx has been verified."""
for input_tx in tx.inputs:
spent_tx = storage.get_vertex(input_tx.tx_id)
if spent_tx.is_block:
assert isinstance(spent_tx, Block)
yield spent_tx


def is_spent_reward_locked(tx: 'Transaction') -> bool:
def is_spent_reward_locked(tx: 'Transaction', storage: 'TransactionStorage') -> bool:
""" Check whether any spent reward is currently locked, considering only the block rewards spent by this tx
itself, and not the inherited `min_height`"""
return get_spent_reward_locked_info(tx, not_none(tx.storage)) is not None
info = get_spent_reward_locked_info(tx, storage.get_vertex, storage.get_best_block_tips)
return info is not None


def get_spent_reward_locked_info(tx: 'Transaction', storage: 'VertexStorageProtocol') -> Optional['RewardLockedInfo']:
def get_spent_reward_locked_info(
tx: 'Transaction',
vertex_getter: Callable[[VertexId], Vertex],
best_block_tips_getter: Callable[[], list[VertexId]],
) -> Optional['RewardLockedInfo']:
"""Check if any input block reward is locked, returning the locked information if any, or None if they are all
unlocked."""
from hathor.transaction.transaction import RewardLockedInfo
best_height = get_minimum_best_height(storage)
for blk in iter_spent_rewards(tx, storage):
needed_height = _spent_reward_needed_height(blk, best_height)
if needed_height > 0:
return RewardLockedInfo(blk.hash, needed_height)
best_height = get_minimum_best_height(vertex_getter, best_block_tips_getter)
for tx_input in tx.inputs:
spent_tx = vertex_getter(tx_input.tx_id)
if isinstance(spent_tx, Block):
needed_height = _spent_reward_needed_height(spent_tx, best_height)
if needed_height > 0:
return RewardLockedInfo(spent_tx.hash, needed_height)

return None


def get_minimum_best_height(storage: 'VertexStorageProtocol') -> int:
def get_minimum_best_height(
vertex_getter: Callable[[VertexId], Vertex],
best_block_tips_getter: Callable[[], list[VertexId]],
) -> int:
"""Return the height of the current best block that shall be used for `min_height` verification."""
import math

# omitting timestamp to get the current best block, this will usually hit the cache instead of being slow
tips = storage.get_best_block_tips()
tips = best_block_tips_getter()
assert len(tips) > 0
best_height = math.inf
for tip in tips:
blk = storage.get_block(tip)
best_height = min(best_height, blk.get_height())
block = vertex_getter(tip)
assert isinstance(block, Block)
best_height = min(best_height, block.static_metadata.height)
assert isinstance(best_height, int)
return best_height

Expand Down
10 changes: 6 additions & 4 deletions hathor/transaction/resources/create_tx.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
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.verification_dependencies import TransactionDependencies


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


CreateTxResource.openapi = {
Expand Down
4 changes: 3 additions & 1 deletion hathor/transaction/storage/transaction_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from hathor.indexes.height_index import HeightInfo
from hathor.profiler import get_cpu_profiler
from hathor.pubsub import PubSubManager
from hathor.reward_lock import get_spent_reward_locked_info
from hathor.transaction.base_transaction import BaseTransaction, TxOutput
from hathor.transaction.block import Block
from hathor.transaction.exceptions import RewardLocked
Expand Down Expand Up @@ -1119,9 +1120,10 @@ def compute_transactions_that_became_invalid(self, new_best_height: int) -> list
from hathor.transaction.validation_state import ValidationState
to_remove: list[BaseTransaction] = []
for tx in self.iter_mempool_from_best_index():
info = get_spent_reward_locked_info(tx, self.get_vertex, self.get_best_block_tips)
try:
TransactionVerifier.verify_reward_locked_for_height(
tx, new_best_height, assert_min_height_verification=False
tx, info, new_best_height, assert_min_height_verification=False
)
except RewardLocked:
tx.set_validation(ValidationState.INVALID)
Expand Down
15 changes: 12 additions & 3 deletions hathor/transaction/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from hathor.profiler import get_cpu_profiler
from hathor.transaction import TxInput, TxOutput, TxVersion
from hathor.transaction.base_transaction import TX_HASH_SIZE, GenericVertex
from hathor.transaction.exceptions import InvalidToken
from hathor.transaction.exceptions import InexistentInput, InvalidToken
from hathor.transaction.static_metadata import TransactionStaticMetadata
from hathor.transaction.util import VerboseCallback, unpack, unpack_len
from hathor.types import TokenUid, VertexId
Expand Down Expand Up @@ -227,7 +227,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}')

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 @@ -269,7 +273,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
12 changes: 6 additions & 6 deletions hathor/verification/block_verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
TransactionDataError,
WeightError,
)
from hathor.util import not_none
from hathor.verification.verification_dependencies import BasicBlockDependencies


class BlockVerifier:
Expand All @@ -44,17 +44,17 @@ def verify_height(self, block: Block) -> None:
f'Block needs {block.static_metadata.min_height} height but has {block.static_metadata.height}'
)

def verify_weight(self, block: Block) -> None:
def verify_weight(self, block: Block, block_deps: BasicBlockDependencies) -> None:
"""Validate minimum block difficulty."""
min_block_weight = self._daa.calculate_block_difficulty(block, not_none(block.storage).get_parent_block)
min_block_weight = self._daa.calculate_block_difficulty(block, block_deps.get_parent_block_for_daa)
if block.weight < 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})')

def verify_reward(self, block: Block) -> None:
def verify_reward(self, block: Block, block_deps: BasicBlockDependencies) -> 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)
parent_block = block_deps.get_parent_block()
tokens_issued_per_block = self._daa.get_tokens_issued_per_block(parent_block.static_metadata.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
7 changes: 3 additions & 4 deletions hathor/verification/token_creation_transaction_verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@
from hathor.conf.settings import HathorSettings
from hathor.transaction.exceptions import InvalidToken, TransactionDataError
from hathor.transaction.token_creation_tx import TokenCreationTransaction
from hathor.transaction.transaction import TokenInfo
from hathor.transaction.util import clean_token_string
from hathor.types import TokenUid
from hathor.verification.verification_dependencies import TransactionDependencies


class TokenCreationTransactionVerifier:
Expand All @@ -26,7 +25,7 @@ class TokenCreationTransactionVerifier:
def __init__(self, *, settings: HathorSettings) -> None:
self._settings = settings

def verify_minted_tokens(self, tx: TokenCreationTransaction, token_dict: dict[TokenUid, TokenInfo]) -> None:
def verify_minted_tokens(self, tx: TokenCreationTransaction, tx_deps: TransactionDependencies) -> None:
""" Besides all checks made on regular transactions, a few extra ones are made:
- only HTR tokens on the inputs;
- new tokens are actually being minted;
Expand All @@ -35,7 +34,7 @@ def verify_minted_tokens(self, tx: TokenCreationTransaction, token_dict: dict[To
:raises InputOutputMismatch: if sum of inputs is not equal to outputs and there's no mint/melt
"""
# make sure tokens are being minted
token_info = token_dict[tx.hash]
token_info = tx_deps.token_info[tx.hash]
if token_info.amount <= 0:
raise InvalidToken('Token creation transaction must mint new tokens')

Expand Down
Loading

0 comments on commit aad0eab

Please sign in to comment.