5
5
from enum import IntEnum
6
6
from typing import Dict , Optional , Tuple , List
7
7
8
- from aleph_message .models import MessageType , ItemType
8
+ from aleph_message .models import MessageConfirmation
9
9
from bson import ObjectId
10
10
from pymongo import UpdateOne
11
11
23
23
from aleph .model .filepin import PermanentPin
24
24
from aleph .model .messages import CappedMessage , Message
25
25
from aleph .model .pending import PendingMessage , PendingTX
26
- from aleph .network import check_message as check_message_fn
26
+ from aleph .network import verify_signature
27
27
from aleph .permissions import check_sender_authorization
28
28
from aleph .storage import get_json , pin_hash , add_json , get_message_content
29
29
from .tx_context import TxContext
30
- from ..schemas .pending_messages import BasePendingMessage
31
- from ..utils import item_type_from_hash
30
+ from aleph .schemas .pending_messages import (
31
+ BasePendingMessage ,
32
+ )
33
+ from aleph .schemas .validated_message import (
34
+ validate_pending_message ,
35
+ ValidatedStoreMessage ,
36
+ ValidatedForgetMessage ,
37
+ make_confirmation_update_query ,
38
+ make_message_upsert_query ,
39
+ )
32
40
33
41
LOGGER = logging .getLogger ("chains.common" )
34
42
@@ -54,12 +62,17 @@ async def mark_confirmed_data(chain_name, tx_hash, height):
54
62
}
55
63
56
64
57
- async def delayed_incoming (message , chain_name = None , tx_hash = None , height = None ):
65
+ async def delayed_incoming (
66
+ message : BasePendingMessage ,
67
+ chain_name : Optional [str ] = None ,
68
+ tx_hash : Optional [str ] = None ,
69
+ height : Optional [int ] = None ,
70
+ ):
58
71
if message is None :
59
72
return
60
73
await PendingMessage .collection .insert_one (
61
74
{
62
- "message" : message ,
75
+ "message" : message . dict ( exclude = { "content" }) ,
63
76
"source" : dict (
64
77
chain_name = chain_name ,
65
78
tx_hash = tx_hash ,
@@ -77,18 +90,20 @@ class IncomingStatus(IntEnum):
77
90
78
91
79
92
async def mark_message_for_retry (
80
- message : Dict ,
93
+ message : BasePendingMessage ,
81
94
chain_name : Optional [str ],
82
95
tx_hash : Optional [str ],
83
96
height : Optional [int ],
84
97
check_message : bool ,
85
98
retrying : bool ,
86
99
existing_id ,
87
100
):
101
+ message_dict = message .dict (exclude = {"content" })
102
+
88
103
if not retrying :
89
104
await PendingMessage .collection .insert_one (
90
105
{
91
- "message" : message ,
106
+ "message" : message_dict ,
92
107
"source" : dict (
93
108
chain_name = chain_name ,
94
109
tx_hash = tx_hash ,
@@ -105,25 +120,8 @@ async def mark_message_for_retry(
105
120
LOGGER .debug (f"Update result { result } " )
106
121
107
122
108
- def update_message_item_type (message_dict : Dict ) -> Dict :
109
- """
110
- Ensures that the item_type field of a message is present.
111
- Sets it to the default value if the field is not specified.
112
- """
113
- if "item_type" in message_dict :
114
- return message_dict
115
-
116
- if "item_content" in message_dict :
117
- item_type = ItemType .inline
118
- else :
119
- item_type = item_type_from_hash (message_dict ["item_hash" ])
120
-
121
- message_dict ["item_type" ] = item_type
122
- return message_dict
123
-
124
-
125
123
async def incoming (
126
- message : Dict ,
124
+ pending_message : BasePendingMessage ,
127
125
chain_name : Optional [str ] = None ,
128
126
tx_hash : Optional [str ] = None ,
129
127
height : Optional [int ] = None ,
@@ -138,77 +136,47 @@ async def incoming(
138
136
if existing in database, created if not.
139
137
"""
140
138
141
- # TODO: this is a temporary fix to set the item_type of the message to the correct
142
- # value. This should be replaced by a full use of Pydantic models.
143
- message = update_message_item_type (message )
144
-
145
- item_hash = message ["item_hash" ]
146
- sender = message ["sender" ]
139
+ item_hash = pending_message .item_hash
140
+ sender = pending_message .sender
141
+ confirmations = []
147
142
ids_key = (item_hash , sender , chain_name )
148
143
149
- if chain_name and tx_hash and height and seen_ids is not None :
150
- if ids_key in seen_ids .keys ():
151
- if height > seen_ids [ids_key ]:
152
- return IncomingStatus .MESSAGE_HANDLED , []
144
+ if chain_name and tx_hash and height :
145
+ if seen_ids is not None :
146
+ if ids_key in seen_ids .keys ():
147
+ if height > seen_ids [ids_key ]:
148
+ return IncomingStatus .MESSAGE_HANDLED , []
149
+
150
+ confirmations .append (
151
+ MessageConfirmation (chain = chain_name , hash = tx_hash , height = height )
152
+ )
153
153
154
154
filters = {
155
155
"item_hash" : item_hash ,
156
- "chain" : message [ " chain" ] ,
157
- "sender" : message [ " sender" ] ,
158
- "type" : message [ " type" ] ,
156
+ "chain" : pending_message . chain ,
157
+ "sender" : pending_message . sender ,
158
+ "type" : pending_message . type ,
159
159
}
160
160
existing = await Message .collection .find_one (
161
161
filters ,
162
162
projection = {"confirmed" : 1 , "confirmations" : 1 , "time" : 1 , "signature" : 1 },
163
163
)
164
164
165
165
if check_message :
166
- if existing is None or (existing ["signature" ] != message [ " signature" ] ):
166
+ if existing is None or (existing ["signature" ] != pending_message . signature ):
167
167
# check/sanitize the message if needed
168
168
try :
169
- message = await check_message_fn (
170
- message , from_chain = (chain_name is not None )
171
- )
169
+ await verify_signature (pending_message )
172
170
except InvalidMessageError :
173
171
return IncomingStatus .FAILED_PERMANENTLY , []
174
172
175
- if message is None :
176
- return IncomingStatus .MESSAGE_HANDLED , []
177
-
178
173
if retrying :
179
174
LOGGER .debug ("(Re)trying %s." % item_hash )
180
175
else :
181
176
LOGGER .info ("Incoming %s." % item_hash )
182
177
183
- # we set the incoming chain as default for signature
184
- message ["chain" ] = message .get ("chain" , chain_name )
185
-
186
- # if existing is None:
187
- # # TODO: verify if search key is ok. do we need an unique key for messages?
188
- # existing = await Message.collection.find_one(
189
- # filters, projection={'confirmed': 1, 'confirmations': 1, 'time': 1})
190
-
191
- if chain_name and tx_hash and height :
192
- # We are getting a confirmation here
193
- new_values = await mark_confirmed_data (chain_name , tx_hash , height )
194
-
195
- updates = {
196
- "$set" : {
197
- "confirmed" : True ,
198
- },
199
- "$min" : {"time" : message ["time" ]},
200
- "$addToSet" : {"confirmations" : new_values ["confirmations" ][0 ]},
201
- }
202
- else :
203
- updates = {
204
- "$max" : {
205
- "confirmed" : False ,
206
- },
207
- "$min" : {"time" : message ["time" ]},
208
- }
178
+ updates : Dict [str , Dict ] = {}
209
179
210
- # new_values = {'confirmed': False} # this should be our default.
211
- should_commit = False
212
180
if existing :
213
181
if seen_ids is not None and height is not None :
214
182
if ids_key in seen_ids .keys ():
@@ -219,25 +187,14 @@ async def incoming(
219
187
else :
220
188
seen_ids [ids_key ] = height
221
189
222
- # THIS CODE SHOULD BE HERE...
223
- # But, if a race condition appeared, we might have the message twice.
224
- # if (existing['confirmed'] and
225
- # chain_name in [c['chain'] for c in existing['confirmations']]):
226
- # return
227
-
228
190
LOGGER .debug ("Updating %s." % item_hash )
229
191
230
- if chain_name and tx_hash and height :
231
- # we need to update messages adding the confirmation
232
- # await Message.collection.update_many(filters, updates)
233
- should_commit = True
192
+ if confirmations :
193
+ updates = make_confirmation_update_query (confirmations )
234
194
235
195
else :
236
- # if not (chain_name and tx_hash and height):
237
- # new_values = {'confirmed': False} # this should be our default.
238
-
239
196
try :
240
- content = await get_message_content (message )
197
+ content = await get_message_content (pending_message )
241
198
242
199
except InvalidContent :
243
200
LOGGER .warning ("Can't get content of object %r, won't retry." % item_hash )
@@ -247,7 +204,7 @@ async def incoming(
247
204
if not isinstance (e , ContentCurrentlyUnavailable ):
248
205
LOGGER .exception ("Can't get content of object %r" % item_hash )
249
206
await mark_message_for_retry (
250
- message = message ,
207
+ message = pending_message ,
251
208
chain_name = chain_name ,
252
209
tx_hash = tx_hash ,
253
210
height = height ,
@@ -257,26 +214,23 @@ async def incoming(
257
214
)
258
215
return IncomingStatus .RETRYING_LATER , []
259
216
260
- json_content = content .value
261
- if json_content .get ("address" , None ) is None :
262
- json_content ["address" ] = message ["sender" ]
263
-
264
- if json_content .get ("time" , None ) is None :
265
- json_content ["time" ] = message ["time" ]
217
+ validated_message = validate_pending_message (
218
+ pending_message = pending_message , content = content , confirmations = confirmations
219
+ )
266
220
267
221
# warning: those handlers can modify message and content in place
268
222
# and return a status. None has to be retried, -1 is discarded, True is
269
223
# handled and kept.
270
224
# TODO: change this, it's messy.
271
225
try :
272
- if message [ "type" ] == MessageType . store :
273
- handling_result = await handle_new_storage (message , json_content )
274
- elif message [ "type" ] == MessageType . forget :
226
+ if isinstance ( validated_message , ValidatedStoreMessage ) :
227
+ handling_result = await handle_new_storage (validated_message )
228
+ elif isinstance ( validated_message , ValidatedForgetMessage ) :
275
229
# Handling it here means that there we ensure that the message
276
230
# has been forgotten before it is saved on the node.
277
231
# We may want the opposite instead: ensure that the message has
278
232
# been saved before it is forgotten.
279
- handling_result = await handle_forget_message (message , json_content )
233
+ handling_result = await handle_forget_message (validated_message )
280
234
else :
281
235
handling_result = True
282
236
except UnknownHashError :
@@ -289,7 +243,7 @@ async def incoming(
289
243
if handling_result is None :
290
244
LOGGER .debug ("Message type handler has failed, retrying later." )
291
245
await mark_message_for_retry (
292
- message = message ,
246
+ message = pending_message ,
293
247
chain_name = chain_name ,
294
248
tx_hash = tx_hash ,
295
249
height = height ,
@@ -306,7 +260,7 @@ async def incoming(
306
260
)
307
261
return IncomingStatus .FAILED_PERMANENTLY , []
308
262
309
- if not await check_sender_authorization (message , json_content ):
263
+ if not await check_sender_authorization (validated_message ):
310
264
LOGGER .warning ("Invalid sender for %s" % item_hash )
311
265
return IncomingStatus .MESSAGE_HANDLED , []
312
266
@@ -320,19 +274,10 @@ async def incoming(
320
274
seen_ids [ids_key ] = height
321
275
322
276
LOGGER .debug ("New message to store for %s." % item_hash )
323
- # message.update(new_values)
324
- updates ["$set" ] = {
325
- "content" : json_content ,
326
- "size" : len (content .raw_value ),
327
- "item_content" : message .get ("item_content" ),
328
- "item_type" : message .get ("item_type" ),
329
- "channel" : message .get ("channel" ),
330
- "signature" : message .get ("signature" ),
331
- ** updates .get ("$set" , {}),
332
- }
333
- should_commit = True
334
277
335
- if should_commit :
278
+ updates = make_message_upsert_query (validated_message )
279
+
280
+ if updates :
336
281
update_op = UpdateOne (filters , updates , upsert = True )
337
282
bulk_ops = [DbBulkOperation (Message , update_op )]
338
283
@@ -346,7 +291,7 @@ async def incoming(
346
291
return IncomingStatus .MESSAGE_HANDLED , []
347
292
348
293
349
- async def process_one_message (message : Dict , * args , ** kwargs ):
294
+ async def process_one_message (message : BasePendingMessage , * args , ** kwargs ):
350
295
"""
351
296
Helper function to process a message on the spot.
352
297
"""
0 commit comments