Skip to content

Commit

Permalink
Merge pull request #1048 from HathorNetwork/master
Browse files Browse the repository at this point in the history
Release v0.61.0
  • Loading branch information
jansegre authored Jun 3, 2024
2 parents b06dd2d + c346ef2 commit b955005
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 65 deletions.
2 changes: 1 addition & 1 deletion hathor/cli/openapi_files/openapi_base.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
],
"info": {
"title": "Hathor API",
"version": "0.60.1"
"version": "0.61.0"
},
"consumes": [
"application/json"
Expand Down
21 changes: 16 additions & 5 deletions hathor/cli/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import json
import sys
import traceback
from argparse import ArgumentParser
from collections import OrderedDict
from datetime import datetime
Expand Down Expand Up @@ -230,7 +232,12 @@ def setup_logging(

def kwargs_formatter(_, __, event_dict):
if event_dict and event_dict.get('event') and isinstance(event_dict['event'], str):
event_dict['event'] = event_dict['event'].format(**event_dict)
try:
event_dict['event'] = event_dict['event'].format(**event_dict)
except KeyError:
# The event string may contain '{}'s that are not used for formatting, resulting in a KeyError in the
# event_dict. In this case, we don't format it.
pass
return event_dict

processors: list[Any] = [
Expand Down Expand Up @@ -275,10 +282,14 @@ def twisted_structlog_observer(event):
if failure is not None:
kwargs['exc_info'] = (failure.type, failure.value, failure.getTracebackObject())
twisted_logger.log(level, msg, **kwargs)
except Exception as e:
print('error when logging event', e)
for k, v in event.items():
print(k, v)
except Exception:
new_event = dict(
event='error when logging event',
original_event=event,
traceback=traceback.format_exc()
)
new_event_json = json.dumps(new_event, default=str)
print(new_event_json, file=sys.stderr)

# start logging to std logger so structlog can catch it
twisted.python.log.startLoggingWithObserver(twisted_structlog_observer, setStdout=capture_stdout)
Expand Down
13 changes: 12 additions & 1 deletion hathor/transaction/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from itertools import starmap, zip_longest
from operator import add
from struct import pack
from typing import TYPE_CHECKING, Any, Optional
from typing import TYPE_CHECKING, Any, Iterator, Optional

from hathor.checkpoint import Checkpoint
from hathor.feature_activation.feature import Feature
Expand Down Expand Up @@ -401,3 +401,14 @@ def get_feature_activation_bit_value(self, bit: int) -> int:
bit_list = self._get_feature_activation_bit_list()

return bit_list[bit]

def iter_transactions_in_this_block(self) -> Iterator[BaseTransaction]:
"""Return an iterator of the transactions that have this block as meta.first_block."""
from hathor.transaction.storage.traversal import BFSOrderWalk
bfs = BFSOrderWalk(self.storage, is_dag_verifications=True, is_dag_funds=True, is_left_to_right=False)
for tx in bfs.run(self, skip_root=True):
tx_meta = tx.get_metadata()
if tx_meta.first_block != self.hash:
bfs.skip_neighbors(tx)
continue
yield tx
68 changes: 48 additions & 20 deletions hathor/transaction/resources/block_at_height.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any

from hathor.api_util import Resource, get_args, get_missing_params_msg, parse_args, parse_int, set_cors
from hathor.api_util import Resource, set_cors
from hathor.cli.openapi_files.register import register_resource
from hathor.util import json_dumpb
from hathor.utils.api import ErrorResponse, QueryParams

if TYPE_CHECKING:
from twisted.web.http import Request
Expand Down Expand Up @@ -48,38 +49,52 @@ def render_GET(self, request: 'Request') -> bytes:
request.setHeader(b'content-type', b'application/json; charset=utf-8')
set_cors(request, 'GET')

# Height parameter is required
parsed = parse_args(get_args(request), ['height'])
if not parsed['success']:
return get_missing_params_msg(parsed['missing'])
params = BlockAtHeightParams.from_request(request)
if isinstance(params, ErrorResponse):
return params.json_dumpb()

args = parsed['args']
# Get hash of the block with the height
block_hash = self.manager.tx_storage.indexes.height.get(params.height)

# Height parameter must be an integer
try:
height = parse_int(args['height'])
except ValueError as e:
# If there is no block in the index with this height, block_hash will be None
if block_hash is None:
return json_dumpb({
'success': False,
'message': f'Failed to parse \'height\': {e}'
'message': 'No block with height {}.'.format(params.height)
})

# Get hash of the block with the height
block_hash = self.manager.tx_storage.indexes.height.get(height)
block = self.manager.tx_storage.get_block(block_hash)
data = {'success': True, 'block': block.to_json_extended()}

# If there is no block in the index with this height, block_hash will be None
if block_hash is None:
if params.include_transactions is None:
pass

elif params.include_transactions == 'txid':
tx_ids: list[str] = []
for tx in block.iter_transactions_in_this_block():
tx_ids.append(tx.hash.hex())
data['tx_ids'] = tx_ids

elif params.include_transactions == 'full':
tx_list: list[Any] = []
for tx in block.iter_transactions_in_this_block():
tx_list.append(tx.to_json_extended())
data['transactions'] = tx_list

else:
return json_dumpb({
'success': False,
'message': 'No block with height {}.'.format(height)
'message': 'Invalid include_transactions. Choices are: txid or full.'
})

block = self.manager.tx_storage.get_transaction(block_hash)

data = {'success': True, 'block': block.to_json_extended()}
return json_dumpb(data)


class BlockAtHeightParams(QueryParams):
height: int
include_transactions: str | None


BlockAtHeightResource.openapi = {
'/block_at_height': {
'x-visibility': 'public',
Expand Down Expand Up @@ -114,6 +129,19 @@ def render_GET(self, request: 'Request') -> bytes:
'type': 'int'
}
},
{
'name': 'include_transactions',
'in': 'query',
'description': 'Add transactions confirmed by this block.',
'required': False,
'schema': {
'type': 'string',
'enum': [
'txid',
'full',
],
}
},
],
'responses': {
'200': {
Expand Down
2 changes: 1 addition & 1 deletion hathor/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

from structlog import get_logger

BASE_VERSION = '0.60.1'
BASE_VERSION = '0.61.0'

DEFAULT_VERSION_SUFFIX = "local"
BUILD_VERSION_FILE_PATH = "./BUILD_VERSION"
Expand Down
42 changes: 7 additions & 35 deletions hathor/vertex_handler/vertex_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
from hathor.conf.settings import HathorSettings
from hathor.consensus import ConsensusAlgorithm
from hathor.exception import HathorError, InvalidNewTransaction
from hathor.feature_activation.feature import Feature
from hathor.feature_activation.feature_service import FeatureService
from hathor.p2p.manager import ConnectionsManager
from hathor.pubsub import HathorEvents, PubSubManager
Expand Down Expand Up @@ -209,7 +208,6 @@ def _post_consensus(
self._wallet.on_new_tx(vertex)

self._log_new_object(vertex, 'new {}', quiet=quiet)
self._log_feature_states(vertex)

if propagate_to_peers:
# Propagate to our peers.
Expand All @@ -231,43 +229,17 @@ def _log_new_object(self, tx: BaseTransaction, message_fmt: str, *, quiet: bool)
if tx.is_block:
message = message_fmt.format('block')
if isinstance(tx, Block):
kwargs['height'] = tx.get_height()
feature_descriptions = self._feature_service.get_bits_description(block=tx)
feature_states = {
feature.value: description.state.value
for feature, description in feature_descriptions.items()
}
kwargs['_height'] = tx.get_height()
kwargs['feature_states'] = feature_states
else:
message = message_fmt.format('tx')
if not quiet:
log_func = self._log.info
else:
log_func = self._log.debug
log_func(message, **kwargs)

def _log_feature_states(self, vertex: BaseTransaction) -> None:
"""Log features states for a block. Used as part of the Feature Activation Phased Testing."""
if not isinstance(vertex, Block):
return

feature_descriptions = self._feature_service.get_bits_description(block=vertex)
state_by_feature = {
feature.value: description.state.value
for feature, description in feature_descriptions.items()
}

self._log.info(
'New block accepted with feature activation states',
block_hash=vertex.hash_hex,
block_height=vertex.get_height(),
features_states=state_by_feature
)

features = [Feature.NOP_FEATURE_1, Feature.NOP_FEATURE_2]
for feature in features:
self._log_if_feature_is_active(vertex, feature)

def _log_if_feature_is_active(self, block: Block, feature: Feature) -> None:
"""Log if a feature is ACTIVE for a block. Used as part of the Feature Activation Phased Testing."""
if self._feature_service.is_feature_active(block=block, feature=feature):
self._log.info(
'Feature is ACTIVE for block',
feature=feature.value,
block_hash=block.hash_hex,
block_height=block.get_height()
)
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

[tool.poetry]
name = "hathor"
version = "0.60.1"
version = "0.61.0"
description = "Hathor Network full-node"
authors = ["Hathor Team <[email protected]>"]
license = "Apache-2.0"
Expand Down
59 changes: 58 additions & 1 deletion tests/resources/transaction/test_block_at_height.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from twisted.internet.defer import inlineCallbacks

from hathor.simulator.utils import add_new_blocks
from hathor.simulator.utils import add_new_block, add_new_blocks
from hathor.transaction.resources import BlockAtHeightResource
from tests import unittest
from tests.resources.base_resource import StubSite, _BaseResourceTest
from tests.utils import add_blocks_unlock_reward, add_new_tx


class BaseBlockAtHeightTest(_BaseResourceTest._ResourceTest):
Expand All @@ -14,6 +15,62 @@ def setUp(self):
self.web = StubSite(BlockAtHeightResource(self.manager))
self.manager.wallet.unlock(b'MYPASS')

@inlineCallbacks
def test_include_full(self):
add_new_block(self.manager, advance_clock=1)
add_blocks_unlock_reward(self.manager)
address = self.manager.wallet.get_unused_address()

confirmed_tx_list = []
for _ in range(15):
confirmed_tx_list.append(add_new_tx(self.manager, address, 1))

block = add_new_block(self.manager, advance_clock=1)
height = block.get_height()

# non-confirmed transactions
for _ in range(15):
add_new_tx(self.manager, address, 1)

response = yield self.web.get("block_at_height", {
b'height': str(height).encode('ascii'),
b'include_transactions': b'full',
})
data = response.json_value()

self.assertTrue(data['success'])
response_tx_ids = set(x['tx_id'] for x in data['transactions'])
expected_tx_ids = set(tx.hash.hex() for tx in confirmed_tx_list)
self.assertTrue(response_tx_ids.issubset(expected_tx_ids))

@inlineCallbacks
def test_include_txids(self):
add_new_block(self.manager, advance_clock=1)
add_blocks_unlock_reward(self.manager)
address = self.manager.wallet.get_unused_address()

confirmed_tx_list = []
for _ in range(15):
confirmed_tx_list.append(add_new_tx(self.manager, address, 1))

block = add_new_block(self.manager, advance_clock=1)
height = block.get_height()

# non-confirmed transactions
for _ in range(15):
add_new_tx(self.manager, address, 1)

response = yield self.web.get("block_at_height", {
b'height': str(height).encode('ascii'),
b'include_transactions': b'txid',
})
data = response.json_value()

self.assertTrue(data['success'])
response_tx_ids = set(data['tx_ids'])
expected_tx_ids = set(tx.hash.hex() for tx in confirmed_tx_list)
self.assertTrue(response_tx_ids.issubset(expected_tx_ids))

@inlineCallbacks
def test_get(self):
blocks = add_new_blocks(self.manager, 4, advance_clock=1)
Expand Down

0 comments on commit b955005

Please sign in to comment.