Skip to content

Commit d4b1031

Browse files
committed
refactor(daa): externalize block dependencies
1 parent 7dded42 commit d4b1031

File tree

4 files changed

+106
-10
lines changed

4 files changed

+106
-10
lines changed

hathor/daa.py

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@
2727

2828
from hathor.conf.settings import HathorSettings
2929
from hathor.profiler import get_cpu_profiler
30-
from hathor.util import iwindows
30+
from hathor.types import VertexId
31+
from hathor.util import iwindows, not_none
3132

3233
if TYPE_CHECKING:
3334
from hathor.transaction import Block, Transaction
35+
from hathor.transaction.storage.simple_memory_storage import SimpleMemoryStorage
3436

3537
logger = get_logger()
3638
cpu = get_cpu_profiler()
@@ -57,17 +59,38 @@ def __init__(self, *, settings: HathorSettings, test_mode: TestMode = TestMode.D
5759
DifficultyAdjustmentAlgorithm.singleton = self
5860

5961
@cpu.profiler(key=lambda _, block: 'calculate_block_difficulty!{}'.format(block.hash.hex()))
60-
def calculate_block_difficulty(self, block: 'Block') -> float:
61-
""" Calculate block weight according to the ascendents of `block`, using calculate_next_weight."""
62+
def calculate_block_difficulty(self, block: 'Block', memory_storage: 'SimpleMemoryStorage') -> float:
63+
""" Calculate block weight according to the ascendants of `block`, using calculate_next_weight."""
6264
if self.TEST_MODE & TestMode.TEST_BLOCK_WEIGHT:
6365
return 1.0
6466

6567
if block.is_genesis:
6668
return self.MIN_BLOCK_WEIGHT
6769

68-
return self.calculate_next_weight(block.get_block_parent(), block.timestamp)
70+
parent_block = memory_storage.get_parent_block(block)
6971

70-
def calculate_next_weight(self, parent_block: 'Block', timestamp: int) -> float:
72+
return self.calculate_next_weight(parent_block, block.timestamp, memory_storage)
73+
74+
def calculate_N(self, parent_block: 'Block') -> int:
75+
return min(2 * self._settings.BLOCK_DIFFICULTY_N_BLOCKS, parent_block.get_height() - 1)
76+
77+
def get_block_dependencies(self, block: 'Block') -> list[VertexId]:
78+
parent_block = block.get_block_parent()
79+
N = self.calculate_N(parent_block)
80+
ids: list[VertexId] = [not_none(parent_block.hash)]
81+
82+
while len(ids) <= N + 1:
83+
parent_block = parent_block.get_block_parent()
84+
ids.append(not_none(parent_block.hash))
85+
86+
return ids
87+
88+
def calculate_next_weight(
89+
self,
90+
parent_block: 'Block',
91+
timestamp: int,
92+
memory_storage: Optional['SimpleMemoryStorage'] = None,
93+
) -> float:
7194
""" Calculate the next block weight, aka DAA/difficulty adjustment algorithm.
7295
7396
The algorithm used is described in [RFC 22](https://gitlab.com/HathorNetwork/rfcs/merge_requests/22).
@@ -80,7 +103,7 @@ def calculate_next_weight(self, parent_block: 'Block', timestamp: int) -> float:
80103
from hathor.transaction import sum_weights
81104

82105
root = parent_block
83-
N = min(2 * self._settings.BLOCK_DIFFICULTY_N_BLOCKS, parent_block.get_height() - 1)
106+
N = self.calculate_N(parent_block)
84107
K = N // 2
85108
T = self.AVG_TIME_BETWEEN_BLOCKS
86109
S = 5
@@ -90,7 +113,7 @@ def calculate_next_weight(self, parent_block: 'Block', timestamp: int) -> float:
90113
blocks: list['Block'] = []
91114
while len(blocks) < N + 1:
92115
blocks.append(root)
93-
root = root.get_block_parent()
116+
root = memory_storage.get_parent_block(root) if memory_storage else root.get_block_parent()
94117
assert root is not None
95118

96119
# TODO: revise if this assertion can be safely removed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Copyright 2023 Hathor Labs
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from hathor.transaction import BaseTransaction, Block, Transaction
16+
from hathor.transaction.storage import TransactionStorage
17+
from hathor.transaction.storage.exceptions import TransactionDoesNotExist
18+
from hathor.types import VertexId
19+
20+
21+
class SimpleMemoryStorage:
22+
__slots__ = ('_blocks', '_transactions',)
23+
24+
def __init__(self) -> None:
25+
self._blocks: dict[VertexId, Block] = {}
26+
self._transactions: dict[VertexId, Transaction] = {}
27+
28+
@property
29+
def _vertices(self) -> dict[VertexId, BaseTransaction]:
30+
return {**self._blocks, **self._transactions}
31+
32+
def get_block(self, block_id: VertexId) -> Block:
33+
if block := self._blocks.get(block_id):
34+
return block
35+
36+
raise TransactionDoesNotExist(f'Block "{block_id.hex()}" does not exist in this SimpleMemoryStorage.')
37+
38+
def get_transaction(self, tx_id: VertexId) -> Transaction:
39+
if tx := self._transactions.get(tx_id):
40+
return tx
41+
42+
raise TransactionDoesNotExist(f'Transaction "{tx_id.hex()}" does not exist in this SimpleMemoryStorage.')
43+
44+
def get_parent_block(self, block: Block) -> Block:
45+
parent_hash = block.get_block_parent_hash()
46+
47+
return self.get_block(parent_hash)
48+
49+
def add_vertices_from_storage(self, storage: TransactionStorage, ids: list[VertexId]) -> None:
50+
for vertex_id in ids:
51+
self.add_vertex_from_storage(storage, vertex_id)
52+
53+
def add_vertex_from_storage(self, storage: TransactionStorage, vertex_id: VertexId) -> None:
54+
if vertex_id in self._vertices:
55+
return
56+
57+
vertex = storage.get_transaction(vertex_id)
58+
59+
match vertex:
60+
case Block():
61+
self._blocks[vertex_id] = vertex
62+
case Transaction():
63+
self._transactions[vertex_id] = vertex
64+
case _:
65+
raise NotImplementedError

hathor/verification/block_verifier.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
TransactionDataError,
2626
WeightError,
2727
)
28+
from hathor.transaction.storage.simple_memory_storage import SimpleMemoryStorage
29+
from hathor.util import not_none
2830

2931

3032
class BlockVerifier:
@@ -51,7 +53,11 @@ def verify_height(self, block: Block) -> None:
5153

5254
def verify_weight(self, block: Block) -> None:
5355
"""Validate minimum block difficulty."""
54-
min_block_weight = self._daa.calculate_block_difficulty(block)
56+
memory_storage = SimpleMemoryStorage()
57+
dependencies = self._daa.get_block_dependencies(block)
58+
memory_storage.add_vertices_from_storage(not_none(block.storage), dependencies)
59+
60+
min_block_weight = self._daa.calculate_block_difficulty(block, memory_storage)
5561
if block.weight < min_block_weight - self._settings.WEIGHT_TOL:
5662
raise WeightError(f'Invalid new block {block.hash_hex}: weight ({block.weight}) is '
5763
f'smaller than the minimum weight ({min_block_weight})')

tests/tx/test_genesis.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from unittest.mock import Mock
2+
13
from hathor.conf import HathorSettings
24
from hathor.daa import DifficultyAdjustmentAlgorithm, TestMode
35
from hathor.transaction.storage import TransactionMemoryStorage
@@ -72,9 +74,9 @@ def test_genesis_weight(self):
7274
# Validate the block and tx weight
7375
# in test mode weight is always 1
7476
self._daa.TEST_MODE = TestMode.TEST_ALL_WEIGHT
75-
self.assertEqual(self._daa.calculate_block_difficulty(genesis_block), 1)
77+
self.assertEqual(self._daa.calculate_block_difficulty(genesis_block, Mock()), 1)
7678
self.assertEqual(self._daa.minimum_tx_weight(genesis_tx), 1)
7779

7880
self._daa.TEST_MODE = TestMode.DISABLED
79-
self.assertEqual(self._daa.calculate_block_difficulty(genesis_block), genesis_block.weight)
81+
self.assertEqual(self._daa.calculate_block_difficulty(genesis_block, Mock()), genesis_block.weight)
8082
self.assertEqual(self._daa.minimum_tx_weight(genesis_tx), genesis_tx.weight)

0 commit comments

Comments
 (0)