12
12
# See the License for the specific language governing permissions and
13
13
# limitations under the License.
14
14
15
- from typing import TYPE_CHECKING , Iterator
15
+ from collections import deque
16
+ from typing import TYPE_CHECKING , Any , Generator , Iterator , Optional
16
17
17
18
from structlog import get_logger
18
- from twisted .internet .defer import Deferred
19
+ from twisted .internet .defer import Deferred , inlineCallbacks
19
20
20
21
from hathor .p2p .sync_v2 .exception import (
21
22
InvalidVertexError ,
@@ -45,23 +46,43 @@ def __init__(self,
45
46
self .protocol = self .sync_agent .protocol
46
47
self .tx_storage = self .sync_agent .tx_storage
47
48
self .manager = self .sync_agent .manager
49
+ self .reactor = self .manager .reactor
48
50
49
51
self .log = logger .new (peer = self .protocol .get_short_peer_id ())
50
52
53
+ # List of blocks from which we will receive transactions.
51
54
self .partial_blocks = partial_blocks
52
55
56
+ # True if we are processing a transaction.
57
+ self ._is_processing : bool = False
58
+
59
+ # Deferred return to the sync agent.
53
60
self ._deferred : Deferred [StreamEnd ] = Deferred ()
54
61
62
+ # Number of transactions received.
55
63
self ._tx_received : int = 0
56
64
65
+ # Maximum number of transactions to be received.
57
66
self ._tx_max_quantity = limit
58
67
68
+ # Queue of transactions waiting to be processed.
69
+ self ._queue : deque [BaseTransaction ] = deque ()
70
+
71
+ # Keeps the response code if the streaming has ended.
72
+ self ._response_code : Optional [StreamEnd ] = None
73
+
74
+ # Index to the current block.
59
75
self ._idx : int = 0
60
- self ._buffer : list [VertexId ] = []
76
+
77
+ # Set of hashes we are waiting to receive.
61
78
self ._waiting_for : set [VertexId ] = set ()
79
+
80
+ # In-memory database of transactions already received but still
81
+ # waiting for dependencies.
62
82
self ._db : dict [VertexId , BaseTransaction ] = {}
63
83
64
84
self ._prepare_block (self .partial_blocks [0 ])
85
+ assert self ._waiting_for
65
86
66
87
def wait (self ) -> Deferred [StreamEnd ]:
67
88
"""Return the deferred."""
@@ -71,6 +92,7 @@ def resume(self) -> Deferred[StreamEnd]:
71
92
"""Resume receiving vertices."""
72
93
assert self ._deferred .called
73
94
self ._tx_received = 0
95
+ self ._response_code = None
74
96
self ._deferred = Deferred ()
75
97
return self ._deferred
76
98
@@ -92,9 +114,37 @@ def handle_transaction(self, tx: BaseTransaction) -> None:
92
114
return
93
115
94
116
assert tx .hash is not None
95
-
96
117
self .log .debug ('tx received' , tx_id = tx .hash .hex ())
97
118
119
+ self ._queue .append (tx )
120
+ assert len (self ._queue ) <= self ._tx_max_quantity
121
+ if not self ._is_processing :
122
+ self .reactor .callLater (0 , self .process_queue )
123
+
124
+ @inlineCallbacks
125
+ def process_queue (self ) -> Generator [Any , Any , None ]:
126
+ """Process next transaction in the queue."""
127
+ if self ._is_processing :
128
+ return
129
+
130
+ if not self ._queue :
131
+ self .check_end ()
132
+ return
133
+
134
+ self ._is_processing = True
135
+ try :
136
+ tx = self ._queue .popleft ()
137
+ yield self ._process_transaction (tx )
138
+ finally :
139
+ self ._is_processing = False
140
+
141
+ self .reactor .callLater (0 , self .process_queue )
142
+
143
+ @inlineCallbacks
144
+ def _process_transaction (self , tx : BaseTransaction ) -> Generator [Any , Any , None ]:
145
+ """Process transaction."""
146
+ assert tx .hash is not None
147
+
98
148
# Run basic verification.
99
149
if not tx .is_genesis :
100
150
try :
@@ -120,11 +170,13 @@ def handle_transaction(self, tx: BaseTransaction) -> None:
120
170
self ._waiting_for .add (dep )
121
171
122
172
self ._db [tx .hash ] = tx
123
- self ._buffer .append (tx .hash )
124
173
125
174
if not self ._waiting_for :
126
175
self .log .debug ('no pending dependencies, processing buffer' )
127
- self ._execute_and_prepare_next ()
176
+ while not self ._waiting_for :
177
+ result = yield self ._execute_and_prepare_next ()
178
+ if not result :
179
+ break
128
180
else :
129
181
self .log .debug ('pending dependencies' , counter = len (self ._waiting_for ))
130
182
@@ -144,38 +196,49 @@ def handle_transactions_end(self, response_code: StreamEnd) -> None:
144
196
"""This method is called by the sync agent when a TRANSACTIONS-END message is received."""
145
197
if self ._deferred .called :
146
198
return
147
- self .log .info ('transactions streaming ended' , reason = response_code , waiting_for = len (self ._waiting_for ))
148
- self ._deferred .callback (response_code )
199
+ assert self ._response_code is None
200
+ self ._response_code = response_code
201
+ self .check_end ()
202
+
203
+ def check_end (self ) -> None :
204
+ """Check if the streaming has ended."""
205
+ if self ._response_code is None :
206
+ return
207
+
208
+ if self ._queue :
209
+ return
210
+
211
+ self .log .info ('transactions streaming ended' , reason = self ._response_code , waiting_for = len (self ._waiting_for ))
212
+ self ._deferred .callback (self ._response_code )
149
213
150
- def _execute_and_prepare_next (self ) -> None :
214
+ @inlineCallbacks
215
+ def _execute_and_prepare_next (self ) -> Generator [Any , Any , bool ]:
151
216
"""Add the block and its vertices to the DAG."""
152
217
assert not self ._waiting_for
153
218
154
219
blk = self .partial_blocks [self ._idx ]
155
- vertex_list = [ self ._db [ _id ] for _id in self . _buffer ]
220
+ vertex_list = list ( self ._db . values ())
156
221
vertex_list .sort (key = lambda v : v .timestamp )
157
222
158
223
try :
159
- self .sync_agent .on_block_complete (blk , vertex_list )
224
+ yield self .sync_agent .on_block_complete (blk , vertex_list )
160
225
except HathorError as e :
161
226
self .fails (InvalidVertexError (repr (e )))
162
- return
227
+ return False
163
228
164
229
self ._idx += 1
165
- if self ._idx < len (self .partial_blocks ):
166
- self ._prepare_block (self .partial_blocks [self ._idx ])
230
+ if self ._idx >= len (self .partial_blocks ):
231
+ return False
232
+
233
+ self ._prepare_block (self .partial_blocks [self ._idx ])
234
+ return True
167
235
168
236
def _prepare_block (self , blk : 'Block' ) -> None :
169
237
"""Reset everything for the next block. It also adds blocks that have no dependencies."""
170
- self ._buffer .clear ()
171
238
self ._waiting_for .clear ()
172
239
self ._db .clear ()
173
240
174
241
# Add pending dependencies from block.
175
242
for dep in blk .get_all_dependencies ():
176
243
if not self .tx_storage .transaction_exists (dep ):
177
244
self ._waiting_for .add (dep )
178
-
179
- # If block is ready to be added then do it.
180
- if not self ._waiting_for :
181
- self ._execute_and_prepare_next ()
0 commit comments