Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion hathor/transaction/base_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@
TxValidationError,
WeightError,
)
from hathor.transaction.transaction_metadata import TransactionMetadata, ValidationState
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
from hathor.util import classproperty

if TYPE_CHECKING:
Expand Down
2 changes: 1 addition & 1 deletion hathor/transaction/storage/transaction_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -1025,7 +1025,7 @@ def iter_mempool_from_best_index(self) -> Iterator[Transaction]:
def compute_transactions_that_became_invalid(self) -> List[BaseTransaction]:
""" This method will look for transactions in the mempool that have became invalid due to the reward lock.
"""
from hathor.transaction.transaction_metadata import ValidationState
from hathor.transaction.validation_state import ValidationState
to_remove: List[BaseTransaction] = []
for tx in self.iter_mempool_from_best_index():
if tx.is_spent_reward_locked():
Expand Down
76 changes: 1 addition & 75 deletions hathor/transaction/transaction_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
# limitations under the License.

from collections import defaultdict
from enum import IntEnum, unique
from typing import TYPE_CHECKING, Any, Dict, FrozenSet, List, Optional, Set

from hathor.transaction.validation_state import ValidationState
from hathor.util import practically_equal

if TYPE_CHECKING:
Expand All @@ -25,80 +25,6 @@
from hathor.transaction.storage import TransactionStorage


@unique
class ValidationState(IntEnum):
"""

Possible transitions:

- Initial
-> Basic: parents exist, graph information checks-out
-> Invalid: all information to reach `Basic` was available, but something doesn't check out
-> Checkpoint: is a block which hash matches a known checkpoint is a parent of a Checkpoint-valid tx
- Basic
-> Full: all parents reached `Full`, and validation+consensus ran successfully
-> Invalid: all information to reach `Full` was available, but something doesn't check out
- Checkpoint
-> Checkpoint-Full: when all the chain of parents and inputs up to the genesis exist in the database
- Full: final
- Checkpoint-Full: final
- Invalid: final

`BASIC` means only the validations that can run without access to the dependencies (parents+inputs, except for
blocks the block parent has to exist and be at least BASIC) have been run. For example, if it's `BASIC` the weight
of a tx has been validated and is correct, but it may be spending a tx that has already been spent, we will not run
this validation until _all_ the dependencies have reached `FULL` or any of them `INVALID` (which should
automatically invalidate this tx). In theory it should be possible to have even more granular validation (if one of
the inputs exists, validate that we can spend it), but the complexity for that is too high.

"""
INITIAL = 0 # aka, not validated
BASIC = 1 # only graph info has been validated
CHECKPOINT = 2 # validation can be safely assumed because it traces up to a known checkpoint
FULL = 3 # fully validated
CHECKPOINT_FULL = 4 # besides being checkpoint valid, it is fully connected
INVALID = -1 # not valid, this does not mean not best chain, orphan chains can be valid

def is_initial(self) -> bool:
"""Short-hand property"""
return self is ValidationState.INITIAL

def is_at_least_basic(self) -> bool:
"""Until a validation is final, it is possible to change its state when more information is available."""
return self >= ValidationState.BASIC

def is_valid(self) -> bool:
"""Short-hand property."""
return self in {ValidationState.FULL, ValidationState.CHECKPOINT, ValidationState.CHECKPOINT_FULL}

def is_checkpoint(self) -> bool:
"""Short-hand property."""
return self in {ValidationState.CHECKPOINT, ValidationState.CHECKPOINT_FULL}

def is_fully_connected(self) -> bool:
"""Short-hand property."""
return self in {ValidationState.FULL, ValidationState.CHECKPOINT_FULL}

def is_partial(self) -> bool:
"""Short-hand property."""
return self in {ValidationState.INITIAL, ValidationState.BASIC, ValidationState.CHECKPOINT}

def is_invalid(self) -> bool:
"""Short-hand property."""
return self is ValidationState.INVALID

def is_final(self) -> bool:
"""Until a validation is final, it is possible to change its state when more information is available."""
return self in {ValidationState.FULL, ValidationState.CHECKPOINT_FULL, ValidationState.INVALID}

@classmethod
def from_name(cls, name: str) -> 'ValidationState':
value = getattr(cls, name.upper(), None)
if value is None:
raise ValueError('invalid name')
return value


class TransactionMetadata:
hash: Optional[bytes]
spent_outputs: Dict[int, List[bytes]]
Expand Down
89 changes: 89 additions & 0 deletions hathor/transaction/validation_state.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Copyright 2023 Hathor Labs
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from enum import IntEnum, unique


@unique
class ValidationState(IntEnum):
"""

Possible transitions:

- Initial
-> Basic: parents exist, graph information checks-out
-> Invalid: all information to reach `Basic` was available, but something doesn't check out
-> Checkpoint: is a block which hash matches a known checkpoint is a parent of a Checkpoint-valid tx
- Basic
-> Full: all parents reached `Full`, and validation+consensus ran successfully
-> Invalid: all information to reach `Full` was available, but something doesn't check out
- Checkpoint
-> Checkpoint-Full: when all the chain of parents and inputs up to the genesis exist in the database
- Full: final
- Checkpoint-Full: final
- Invalid: final

`BASIC` means only the validations that can run without access to the dependencies (parents+inputs, except for
blocks the block parent has to exist and be at least BASIC) have been run. For example, if it's `BASIC` the weight
of a tx has been validated and is correct, but it may be spending a tx that has already been spent, we will not run
this validation until _all_ the dependencies have reached `FULL` or any of them `INVALID` (which should
automatically invalidate this tx). In theory it should be possible to have even more granular validation (if one of
the inputs exists, validate that we can spend it), but the complexity for that is too high.

"""
INITIAL = 0 # aka, not validated
BASIC = 1 # only graph info has been validated
CHECKPOINT = 2 # validation can be safely assumed because it traces up to a known checkpoint
FULL = 3 # fully validated
CHECKPOINT_FULL = 4 # besides being checkpoint valid, it is fully connected
INVALID = -1 # not valid, this does not mean not best chain, orphan chains can be valid

def is_initial(self) -> bool:
"""Short-hand property"""
return self is ValidationState.INITIAL

def is_at_least_basic(self) -> bool:
"""Until a validation is final, it is possible to change its state when more information is available."""
return self >= ValidationState.BASIC

def is_valid(self) -> bool:
"""Short-hand property."""
return self in {ValidationState.FULL, ValidationState.CHECKPOINT, ValidationState.CHECKPOINT_FULL}

def is_checkpoint(self) -> bool:
"""Short-hand property."""
return self in {ValidationState.CHECKPOINT, ValidationState.CHECKPOINT_FULL}

def is_fully_connected(self) -> bool:
"""Short-hand property."""
return self in {ValidationState.FULL, ValidationState.CHECKPOINT_FULL}

def is_partial(self) -> bool:
"""Short-hand property."""
return self in {ValidationState.INITIAL, ValidationState.BASIC, ValidationState.CHECKPOINT}

def is_invalid(self) -> bool:
"""Short-hand property."""
return self is ValidationState.INVALID

def is_final(self) -> bool:
"""Until a validation is final, it is possible to change its state when more information is available."""
return self in {ValidationState.FULL, ValidationState.CHECKPOINT_FULL, ValidationState.INVALID}

@classmethod
def from_name(cls, name: str) -> 'ValidationState':
value = getattr(cls, name.upper(), None)
if value is None:
raise ValueError('invalid name')
return value
2 changes: 1 addition & 1 deletion tests/resources/transaction/test_tx.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from hathor.transaction import Transaction
from hathor.transaction.resources import TransactionResource
from hathor.transaction.token_creation_tx import TokenCreationTransaction
from hathor.transaction.transaction_metadata import ValidationState
from hathor.transaction.validation_state import ValidationState
from tests import unittest
from tests.resources.base_resource import StubSite, _BaseResourceTest
from tests.utils import add_blocks_unlock_reward, add_new_blocks, add_new_transactions
Expand Down
2 changes: 1 addition & 1 deletion tests/tx/test_cache_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def tearDown(self):
super().tearDown()

def _get_new_tx(self, nonce):
from hathor.transaction.transaction_metadata import ValidationState
from hathor.transaction.validation_state import ValidationState
tx = Transaction(nonce=nonce, storage=self.cache_storage)
tx.update_hash()
meta = TransactionMetadata(hash=tx.hash)
Expand Down
2 changes: 1 addition & 1 deletion tests/tx/test_tx.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@
)
from hathor.transaction.scripts import P2PKH, parse_address_script
from hathor.transaction.storage import TransactionMemoryStorage
from hathor.transaction.transaction_metadata import ValidationState
from hathor.transaction.util import int_to_bytes
from hathor.transaction.validation_state import ValidationState
from hathor.wallet import Wallet
from tests import unittest
from tests.utils import (
Expand Down
2 changes: 1 addition & 1 deletion tests/tx/test_tx_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from hathor.transaction.scripts import P2PKH
from hathor.transaction.storage import TransactionCacheStorage, TransactionMemoryStorage, TransactionRocksDBStorage
from hathor.transaction.storage.exceptions import TransactionDoesNotExist
from hathor.transaction.transaction_metadata import ValidationState
from hathor.transaction.validation_state import ValidationState
from tests.unittest import TestBuilder
from tests.utils import (
BURN_ADDRESS,
Expand Down
2 changes: 1 addition & 1 deletion tests/tx/test_validation_states.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from hathor.transaction.transaction_metadata import ValidationState
from hathor.transaction.validation_state import ValidationState


def test_validation_states_list_unchanged():
Expand Down
2 changes: 1 addition & 1 deletion tests/wallet/test_wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def test_wallet_keys_storage(self):
self.assertEqual(key, key2)

def test_wallet_create_transaction(self):
from hathor.transaction.transaction_metadata import ValidationState
from hathor.transaction.validation_state import ValidationState

genesis_private_key_bytes = get_private_key_bytes(
self.genesis_private_key,
Expand Down
2 changes: 1 addition & 1 deletion tests/wallet/test_wallet_hd.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def setUp(self):
self.TOKENS = self.BLOCK_TOKENS

def test_transaction_and_balance(self):
from hathor.transaction.transaction_metadata import ValidationState
from hathor.transaction.validation_state import ValidationState

# generate a new block and check if we increase balance
new_address = self.wallet.get_unused_address()
Expand Down