Skip to content

Commit 4513ddf

Browse files
authored
feat(dag-builder): improve DAGBuilder (#1225)
1 parent 91e2bd8 commit 4513ddf

File tree

6 files changed

+97
-60
lines changed

6 files changed

+97
-60
lines changed

hathor/dag_builder/artifacts.py

+38-1
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@
1414

1515
from __future__ import annotations
1616

17-
from typing import TYPE_CHECKING, Iterator, NamedTuple
17+
from typing import TYPE_CHECKING, Iterator, NamedTuple, TypeVar
1818

1919
from hathor.dag_builder.types import DAGNode
20+
from hathor.manager import HathorManager
2021

2122
if TYPE_CHECKING:
2223
from hathor.transaction import BaseTransaction
2324

25+
T = TypeVar('T', bound='BaseTransaction')
26+
2427

2528
class _Pair(NamedTuple):
2629
node: DAGNode
@@ -38,3 +41,37 @@ def __init__(self, items: Iterator[tuple[DAGNode, BaseTransaction]]) -> None:
3841
self.by_name[node.name] = p
3942

4043
self.list: tuple[_Pair, ...] = tuple(v)
44+
self._last_propagated: str | None = None
45+
46+
def get_typed_vertex(self, name: str, type_: type[T]) -> T:
47+
"""Get a vertex by name, asserting it is of the provided type."""
48+
_, vertex = self.by_name[name]
49+
assert isinstance(vertex, type_)
50+
return vertex
51+
52+
def get_typed_vertices(self, names: list[str], type_: type[T]) -> list[T]:
53+
"""Get a list of vertices by name, asserting they are of the provided type."""
54+
return [self.get_typed_vertex(name, type_) for name in names]
55+
56+
def propagate_with(self, manager: HathorManager, *, up_to: str | None = None) -> None:
57+
"""
58+
Propagate vertices using the provided manager up to the provided node name, included.
59+
Last propagation is preserved in memory so you can make a sequence of propagate_with().
60+
"""
61+
found_begin = self._last_propagated is None
62+
found_end = False
63+
64+
for node, vertex in self.list:
65+
if found_begin:
66+
assert manager.on_new_tx(vertex, fails_silently=False)
67+
self._last_propagated = node.name
68+
69+
if node.name == self._last_propagated:
70+
found_begin = True
71+
72+
if up_to and node.name == up_to:
73+
found_end = True
74+
break
75+
76+
assert found_begin, f'node "{self._last_propagated}" not found'
77+
assert up_to is None or found_end, f'node "{up_to}" not found'

hathor/dag_builder/tokenizer.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,18 @@
2727
a --> b --> c # c is a parent of b which is a parent of a
2828
a.out[i] <<< b c d # b, c, and d spend the i-th output of a
2929
a < b < c # a must be created before b and b must be created before c
30-
a > b > c # a must be created after b and b must be creater after c
31-
a.attr = value # set value of attribute attr to a
30+
a > b > c # a must be created after b and b must be created after c
31+
a.attr1 = value # set value of attribute attr to a
32+
a.attr2 = "value" # a string literal
33+
34+
Special keywords:
35+
36+
b10 < dummy # `dummy` is a tx created automatically that spends genesis tokens and provides
37+
# outputs to txs defined by the user. It's usually useful to set it after some
38+
# block to pass the reward lock
3239
3340
Special attributes:
41+
3442
a.out[i] = 100 HTR # set that the i-th output of a holds 100 HTR
3543
a.out[i] = 100 TOKEN # set that the i-th output of a holds 100 TOKEN where TOKEN is a custom token
3644
a.weight = 50 # set vertex weight

hathor/dag_builder/vertex_exporter.py

+1
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ def create_vertex(self, node: DAGNode) -> BaseTransaction:
291291
assert node.name not in self._vertices
292292
self._vertice_per_id[vertex.hash] = vertex
293293
self._vertices[node.name] = vertex
294+
vertex.name = node.name
294295
return vertex
295296

296297
def export(self) -> Iterator[tuple[DAGNode, BaseTransaction]]:

hathor/transaction/base_transaction.py

+3
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,9 @@ def __init__(
185185
self._hash: VertexId | None = hash # Stored as bytes.
186186
self._static_metadata = None
187187

188+
# A name solely for debugging purposes.
189+
self.name: str | None = None
190+
188191
@classproperty
189192
def log(cls):
190193
""" This is a workaround because of a bug on structlog (or abc).

tests/consensus/test_first_block.py

+6-15
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from hathor.transaction import Block, Transaction
12
from tests import unittest
23

34

@@ -40,21 +41,11 @@ def test_first_block(self) -> None:
4041
b33 --> tx50
4142
""")
4243

43-
for node, vertex in artifacts.list:
44-
self.manager.on_new_tx(vertex, fails_silently=False)
45-
46-
b31 = artifacts.by_name['b31'].vertex
47-
b32 = artifacts.by_name['b32'].vertex
48-
b33 = artifacts.by_name['b33'].vertex
49-
50-
tx10 = artifacts.by_name['tx10'].vertex
51-
tx20 = artifacts.by_name['tx20'].vertex
52-
tx30 = artifacts.by_name['tx30'].vertex
53-
tx40 = artifacts.by_name['tx40'].vertex
54-
tx41 = artifacts.by_name['tx41'].vertex
55-
tx42 = artifacts.by_name['tx42'].vertex
56-
tx43 = artifacts.by_name['tx43'].vertex
57-
tx50 = artifacts.by_name['tx50'].vertex
44+
artifacts.propagate_with(self.manager)
45+
46+
b31, b32, b33 = artifacts.get_typed_vertices(['b31', 'b32', 'b33'], Block)
47+
txs = ['tx10', 'tx20', 'tx30', 'tx40', 'tx41', 'tx42', 'tx43', 'tx50']
48+
tx10, tx20, tx30, tx40, tx41, tx42, tx43, tx50 = artifacts.get_typed_vertices(txs, Transaction)
5849

5950
self.assertEqual(tx10.get_metadata().first_block, b31.hash)
6051

tests/dag_builder/test_dag_builter.py renamed to tests/dag_builder/test_dag_builder.py

+39-42
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
from hathor.transaction import Block, Transaction
12
from hathor.transaction.token_creation_tx import TokenCreationTransaction
23
from tests import unittest
34

45

5-
class DAGCreatorTestCase(unittest.TestCase):
6+
class DAGBuilderTestCase(unittest.TestCase):
67
def setUp(self):
78
super().setUp()
89

@@ -26,14 +27,12 @@ def test_one_tx(self) -> None:
2627
b40 --> tx1
2728
""")
2829

29-
for node, vertex in artifacts.list:
30-
self.manager.on_new_tx(vertex, fails_silently=False)
30+
artifacts.propagate_with(self.manager)
3131

3232
v_order = [node.name for node, _ in artifacts.list]
3333

34-
tx1 = artifacts.by_name['tx1'].vertex
35-
b1 = artifacts.by_name['b1'].vertex
36-
b40 = artifacts.by_name['b40'].vertex
34+
b1, b40 = artifacts.get_typed_vertices(['b1', 'b40'], Block)
35+
tx1 = artifacts.get_typed_vertex('tx1', Transaction)
3736

3837
# blockchain genesis b[1..50]
3938
self.assertEqual(b1.parents[0], self._settings.GENESIS_BLOCK_HASH)
@@ -65,13 +64,11 @@ def test_weight(self) -> None:
6564
c1.weight = 80.6
6665
""")
6766

68-
for node, vertex in artifacts.list:
69-
self.manager.on_new_tx(vertex, fails_silently=False)
67+
artifacts.propagate_with(self.manager)
7068

71-
tx1 = artifacts.by_name['tx1'].vertex
72-
tka = artifacts.by_name['TKA'].vertex
73-
c1 = artifacts.by_name['c1'].vertex
74-
b38 = artifacts.by_name['b38'].vertex
69+
c1, b38 = artifacts.get_typed_vertices(['c1', 'b38'], Block)
70+
tx1 = artifacts.get_typed_vertex('tx1', Transaction)
71+
tka = artifacts.get_typed_vertex('TKA', TokenCreationTransaction)
7572

7673
self.assertAlmostEqual(tka.weight, 31.8)
7774
self.assertAlmostEqual(tx1.weight, 25.2)
@@ -85,10 +82,9 @@ def test_spend_unspecified_utxo(self) -> None:
8582
tx1.out[0] <<< tx2
8683
""")
8784

88-
for node, vertex in artifacts.list:
89-
self.manager.on_new_tx(vertex, fails_silently=False)
85+
artifacts.propagate_with(self.manager)
9086

91-
tx1 = artifacts.by_name['tx1'].vertex
87+
tx1 = artifacts.get_typed_vertex('tx1', Transaction)
9288
self.assertEqual(len(tx1.outputs), 1)
9389
# the default filler fills unspecified utxos with 1 HTR
9490
self.assertEqual(tx1.outputs[0].value, 1)
@@ -107,22 +103,11 @@ def test_block_parents(self) -> None:
107103
b36 --> tx4
108104
""")
109105

110-
for node, vertex in artifacts.list:
111-
self.manager.on_new_tx(vertex, fails_silently=False)
106+
artifacts.propagate_with(self.manager)
112107

113-
b0 = artifacts.by_name['b30'].vertex
114-
b1 = artifacts.by_name['b31'].vertex
115-
b2 = artifacts.by_name['b32'].vertex
116-
b3 = artifacts.by_name['b33'].vertex
117-
b4 = artifacts.by_name['b34'].vertex
118-
b5 = artifacts.by_name['b35'].vertex
119-
b6 = artifacts.by_name['b36'].vertex
120-
b7 = artifacts.by_name['b37'].vertex
121-
122-
tx1 = artifacts.by_name['tx1'].vertex
123-
tx2 = artifacts.by_name['tx2'].vertex
124-
tx3 = artifacts.by_name['tx3'].vertex
125-
tx4 = artifacts.by_name['tx4'].vertex
108+
blocks = ['b30', 'b31', 'b32', 'b33', 'b34', 'b35', 'b36', 'b37']
109+
b0, b1, b2, b3, b4, b5, b6, b7 = artifacts.get_typed_vertices(blocks, Block)
110+
tx1, tx2, tx3, tx4 = artifacts.get_typed_vertices(['tx1', 'tx2', 'tx3', 'tx4'], Transaction)
126111

127112
self.assertEqual(b2.parents[0], b1.hash)
128113
self.assertEqual(b3.parents[0], b2.hash)
@@ -149,14 +134,12 @@ def test_custom_token(self) -> None:
149134
b40 --> tx1
150135
""")
151136

152-
for node, vertex in artifacts.list:
153-
self.manager.on_new_tx(vertex, fails_silently=False)
137+
artifacts.propagate_with(self.manager)
154138

155-
tka = artifacts.by_name['TKA'].vertex
156-
tx1 = artifacts.by_name['tx1'].vertex
139+
tx1 = artifacts.get_typed_vertex('tx1', Transaction)
140+
tka = artifacts.get_typed_vertex('TKA', TokenCreationTransaction)
157141

158142
# TKA token creation transaction
159-
self.assertIsInstance(tka, TokenCreationTransaction)
160143
self.assertEqual(tka.token_name, 'TKA')
161144
self.assertEqual(tka.token_symbol, 'TKA')
162145

@@ -201,8 +184,7 @@ def test_big_dag(self) -> None:
201184
b16 < tx4
202185
""")
203186

204-
for node, vertex in artifacts.list:
205-
self.manager.on_new_tx(vertex, fails_silently=False)
187+
artifacts.propagate_with(self.manager)
206188

207189
def test_no_hash_conflict(self) -> None:
208190
artifacts = self.dag_builder.build_from_str("""
@@ -212,9 +194,24 @@ def test_no_hash_conflict(self) -> None:
212194
213195
tx10.out[0] <<< tx20 tx30 tx40
214196
""")
197+
artifacts.propagate_with(self.manager)
198+
199+
def test_propagate_with(self) -> None:
200+
tx_storage = self.manager.tx_storage
201+
artifacts = self.dag_builder.build_from_str('''
202+
blockchain genesis b[1..10]
203+
b10 < dummy
204+
tx1 <-- tx2
205+
''')
206+
207+
artifacts.propagate_with(self.manager, up_to='b5')
208+
assert len(list(tx_storage.get_all_transactions())) == 8 # 3 genesis + 5 blocks
209+
210+
artifacts.propagate_with(self.manager, up_to='b10')
211+
assert len(list(tx_storage.get_all_transactions())) == 13 # 3 genesis + 10 blocks
212+
213+
artifacts.propagate_with(self.manager, up_to='tx1')
214+
assert len(list(tx_storage.get_all_transactions())) == 15 # 3 genesis + 10 blocks + dummy + tx1
215215

216-
for node, vertex in artifacts.list:
217-
print()
218-
print(node.name)
219-
print()
220-
self.manager.on_new_tx(vertex, fails_silently=False)
216+
artifacts.propagate_with(self.manager)
217+
assert len(list(tx_storage.get_all_transactions())) == 16 # 3 genesis + 10 blocks + dummy + tx1 + tx2

0 commit comments

Comments
 (0)