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
5 changes: 4 additions & 1 deletion hathor/dag_builder/artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ def propagate_with(self, manager: HathorManager, *, up_to: str | None = None) ->

for node, vertex in self.list:
if found_begin:
assert manager.on_new_tx(vertex)
try:
assert manager.on_new_tx(vertex)
except Exception as e:
raise Exception(f'failed on_new_tx({node.name})') from e
self._last_propagated = node.name

if node.name == self._last_propagated:
Expand Down
79 changes: 79 additions & 0 deletions hathor/indexes/blueprint_history_index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Copyright 2025 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 __future__ import annotations

from abc import abstractmethod
from typing import Iterator

from hathor.indexes.scope import Scope
from hathor.indexes.tx_group_index import TxGroupIndex
from hathor.transaction import BaseTransaction, Transaction

SCOPE = Scope(
include_blocks=False,
include_txs=True,
include_voided=True,
)


class BlueprintHistoryIndex(TxGroupIndex[bytes]):
"""Index of all Nano Contracts of a Blueprint."""

def get_scope(self) -> Scope:
return SCOPE

def init_loop_step(self, tx: BaseTransaction) -> None:
self.add_tx(tx)

@abstractmethod
def add_tx(self, tx: BaseTransaction) -> None:
"""Add tx to this index.
"""
raise NotImplementedError

@abstractmethod
def remove_tx(self, tx: BaseTransaction) -> None:
"""Remove tx from this index.
"""
raise NotImplementedError

def _extract_keys(self, tx: BaseTransaction) -> Iterator[bytes]:
if not tx.is_nano_contract():
return
assert isinstance(tx, Transaction)
nano_header = tx.get_nano_header()
if not nano_header.is_creating_a_new_contract():
return
yield nano_header.nc_id

def get_newest(self, blueprint_id: bytes) -> Iterator[bytes]:
"""Get a list of nano_contract_ids sorted by timestamp for a given blueprint_id starting from the newest."""
return self._get_sorted_from_key(blueprint_id, reverse=True)

def get_oldest(self, blueprint_id: bytes) -> Iterator[bytes]:
"""Get a list of nano_contract_ids sorted by timestamp for a given blueprint_id starting from the oldest."""
return self._get_sorted_from_key(blueprint_id)

def get_older(self, blueprint_id: bytes, tx_start: BaseTransaction) -> Iterator[bytes]:
"""
Get a list of nano_contract_ids sorted by timestamp for a given blueprint_id that are older than tx_start.
"""
return self._get_sorted_from_key(blueprint_id, tx_start=tx_start, reverse=True)

def get_newer(self, blueprint_id: bytes, tx_start: BaseTransaction) -> Iterator[bytes]:
"""
Get a list of nano_contract_ids sorted by timestamp for a given blueprint_id that are newer than tx_start.
"""
return self._get_sorted_from_key(blueprint_id, tx_start=tx_start)
39 changes: 39 additions & 0 deletions hathor/indexes/blueprint_timestamp_index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Copyright 2025 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 typing import final

from hathor.indexes.rocksdb_vertex_timestamp_index import RocksDBVertexTimestampIndex
from hathor.indexes.scope import Scope
from hathor.transaction import BaseTransaction

SCOPE = Scope(
include_blocks=False,
include_txs=True,
include_voided=True,
)


class BlueprintTimestampIndex(RocksDBVertexTimestampIndex):
"""Index of on-chain Blueprints sorted by their timestamps."""
cf_name = b'blueprint-index'
db_name = 'on-chain-blueprints'

def get_scope(self) -> Scope:
return SCOPE

@final
def _should_add(self, tx: BaseTransaction) -> bool:
from hathor.nanocontracts import OnChainBlueprint
return isinstance(tx, OnChainBlueprint)
39 changes: 39 additions & 0 deletions hathor/indexes/nc_creation_index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Copyright 2025 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 hathor.indexes.rocksdb_vertex_timestamp_index import RocksDBVertexTimestampIndex
from hathor.indexes.scope import Scope
from hathor.transaction import BaseTransaction, Transaction

SCOPE = Scope(
include_blocks=False,
include_txs=True,
include_voided=True,
)


class NCCreationIndex(RocksDBVertexTimestampIndex):
"""Index of Nano Contract creation txs sorted by their timestamps."""
cf_name = b'nc-creation-index'
db_name = 'nc-creation'

def get_scope(self) -> Scope:
return SCOPE

def _should_add(self, tx: BaseTransaction) -> bool:
if not tx.is_nano_contract():
return False
assert isinstance(tx, Transaction)
nano_header = tx.get_nano_header()
return nano_header.is_creating_a_new_contract()
90 changes: 90 additions & 0 deletions hathor/indexes/nc_history_index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# 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 abc import abstractmethod
from typing import Iterable, Optional

from structlog import get_logger
from typing_extensions import override

from hathor.indexes.scope import Scope
from hathor.indexes.tx_group_index import TxGroupIndex
from hathor.transaction import BaseTransaction, Transaction

logger = get_logger()

SCOPE = Scope(
include_blocks=False,
include_txs=True,
include_voided=True,
)


class NCHistoryIndex(TxGroupIndex[bytes]):
"""Index of all transactions of a Nano Contract."""

def get_scope(self) -> Scope:
return SCOPE

def init_loop_step(self, tx: BaseTransaction) -> None:
self.add_tx(tx)

@abstractmethod
def add_tx(self, tx: BaseTransaction) -> None:
"""Add tx to this index.
"""
raise NotImplementedError

@abstractmethod
def remove_tx(self, tx: BaseTransaction) -> None:
"""Remove tx from this index.
"""
raise NotImplementedError

@override
def _extract_keys(self, tx: BaseTransaction) -> Iterable[bytes]:
if not tx.is_nano_contract():
return
assert isinstance(tx, Transaction)
nano_header = tx.get_nano_header()
yield nano_header.get_contract_id()

def get_sorted_from_contract_id(self, contract_id: bytes) -> Iterable[bytes]:
"""Get a list of tx_ids sorted by timestamp for a given contract_id.
"""
return self._get_sorted_from_key(contract_id)

def get_newest(self, contract_id: bytes) -> Iterable[bytes]:
"""Get a list of tx_ids sorted by timestamp for a given contract_id starting from the newest.
"""
return self._get_sorted_from_key(contract_id, reverse=True)

def get_older(self, contract_id: bytes, tx_start: Optional[BaseTransaction] = None) -> Iterable[bytes]:
"""Get a list of tx_ids sorted by timestamp for a given contract_id that are older than tx_start.
"""
return self._get_sorted_from_key(contract_id, tx_start=tx_start, reverse=True)

def get_newer(self, contract_id: bytes, tx_start: Optional[BaseTransaction] = None) -> Iterable[bytes]:
"""Get a list of tx_ids sorted by timestamp for a given contract_id that are newer than tx_start.
"""
return self._get_sorted_from_key(contract_id, tx_start=tx_start)

@abstractmethod
def get_transaction_count(self, contract_id: bytes) -> int:
"""Get the count of transactions for the given contract_id."""
raise NotImplementedError

def get_last_tx_timestamp(self, contract_id: bytes) -> int | None:
"""Get the timestamp of the last tx in the given contract_id, or None if it doesn't exist."""
return self.get_latest_tx_timestamp(contract_id)
2 changes: 1 addition & 1 deletion hathor/indexes/rocksdb_address_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def add_tx(self, tx: BaseTransaction) -> None:
self._publish_tx(tx)

def get_from_address(self, address: str) -> list[bytes]:
return list(self._get_from_key(address))
return list(self._get_sorted_from_key(address))

def get_sorted_from_address(self, address: str, tx_start: Optional[BaseTransaction] = None) -> Iterable[bytes]:
return self._get_sorted_from_key(address, tx_start)
Expand Down
42 changes: 42 additions & 0 deletions hathor/indexes/rocksdb_blueprint_history_index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Copyright 2025 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.

import rocksdb
from typing_extensions import override

from hathor.indexes.blueprint_history_index import BlueprintHistoryIndex
from hathor.indexes.rocksdb_tx_group_index import RocksDBTxGroupIndex
from hathor.indexes.rocksdb_utils import RocksDBIndexUtils

_CF_NAME_BLUEPRINT_HISTORY_INDEX = b'blueprint-history-index'
_DB_NAME: str = 'blueprint-history'


class RocksDBBlueprintHistoryIndex(RocksDBTxGroupIndex[bytes], BlueprintHistoryIndex, RocksDBIndexUtils):
_KEY_SIZE = 32

def __init__(self, db: rocksdb.DB) -> None:
RocksDBTxGroupIndex.__init__(self, db, _CF_NAME_BLUEPRINT_HISTORY_INDEX)

@override
def _serialize_key(self, key: bytes) -> bytes:
return key

@override
def _deserialize_key(self, key_bytes: bytes) -> bytes:
return key_bytes

@override
def get_db_name(self) -> str | None:
return _DB_NAME
53 changes: 53 additions & 0 deletions hathor/indexes/rocksdb_nc_history_index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# 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 typing import TYPE_CHECKING, Optional

from structlog import get_logger

from hathor.indexes.nc_history_index import NCHistoryIndex
from hathor.indexes.rocksdb_tx_group_index import RocksDBTxGroupIndex
from hathor.indexes.rocksdb_utils import RocksDBIndexUtils

if TYPE_CHECKING: # pragma: no cover
import rocksdb

logger = get_logger()

_CF_NAME_NC_HISTORY_INDEX = b'nc-history-index'
_CF_NAME_NC_HISTORY_INDEX_STATS = b'nc-history-index-stats'
_DB_NAME: str = 'nc-history'


class RocksDBNCHistoryIndex(RocksDBTxGroupIndex[bytes], NCHistoryIndex, RocksDBIndexUtils):
"""RocksDB-persistent index of all transactions of a Nano Contract."""

_KEY_SIZE = 32

def __init__(self, db: 'rocksdb.DB', *, cf_name: Optional[bytes] = None) -> None:
RocksDBTxGroupIndex.__init__(self, db, cf_name or _CF_NAME_NC_HISTORY_INDEX, _CF_NAME_NC_HISTORY_INDEX_STATS)

def _serialize_key(self, key: bytes) -> bytes:
return key

def _deserialize_key(self, key_bytes: bytes) -> bytes:
return key_bytes

def get_db_name(self) -> Optional[str]:
# XXX: we don't need it to be parametrizable, so this is fine
return _DB_NAME

def get_transaction_count(self, contract_id: bytes) -> int:
assert self._stats is not None
return self._stats.get_group_count(contract_id)
Loading