@@ -7,6 +7,7 @@ use async_trait::async_trait;
7
7
use kaspa_consensus_core:: api:: counters:: ProcessingCounters ;
8
8
use kaspa_consensus_core:: daa_score_timestamp:: DaaScoreTimestamp ;
9
9
use kaspa_consensus_core:: errors:: block:: RuleError ;
10
+ use kaspa_consensus_core:: mass:: { calc_storage_mass, UtxoCell } ;
10
11
use kaspa_consensus_core:: utxo:: utxo_inquirer:: UtxoInquirerError ;
11
12
use kaspa_consensus_core:: {
12
13
block:: Block ,
@@ -284,6 +285,62 @@ impl RpcCoreService {
284
285
( false , false ) => Ok ( TransactionQuery :: TransactionsOnly ) ,
285
286
}
286
287
}
288
+
289
+ fn sanity_check_storage_mass ( & self , block : Block ) {
290
+ // [Crescendo]: warn non updated miners to upgrade their rpc flow before Crescendo activation
291
+ if self . config . crescendo_activation . is_active ( block. header . daa_score ) {
292
+ return ;
293
+ }
294
+
295
+ // It is sufficient to witness a single transaction with non default mass to conclude that miner rpc flow is correct
296
+ if block. transactions . iter ( ) . any ( |tx| tx. mass ( ) > 0 ) {
297
+ return ;
298
+ }
299
+
300
+ // Iterate over non-coinbase transactions and search for a transaction which is proven to have positive storage mass
301
+ for tx in block. transactions . iter ( ) . skip ( 1 ) {
302
+ /*
303
+ Below we apply a workaround to compute a lower bound to the storage mass even without having full UTXO context (thus lacking input amounts).
304
+ Notes:
305
+ 1. We know that plurality is always 1 for std tx ins/outs (assuming the submitted block was built via the local std mempool).
306
+ 2. The submitted block was accepted by consensus hence all transactions passed the basic in-isolation validity checks
307
+
308
+ |O| > |I| means that the formula used is C·|O| / H(O) - C·|I| / A(I). Additionally we know that sum(O) <= sum(I) (outs = ins minus fee).
309
+ Combined, we can use sum(O)/|I| as a lower bound for A(I). We simulate this by using sum(O)/|I| as the value of each (unknown) input.
310
+ Plugging in to the storage formula we obtain a lower bound for the real storage mass (intuitively, making inputs smaller only decreases the mass).
311
+ */
312
+ if tx. outputs . len ( ) > tx. inputs . len ( ) {
313
+ let num_ins = tx. inputs . len ( ) as u64 ;
314
+ let sum_outs = tx. outputs . iter ( ) . map ( |o| o. value ) . sum :: < u64 > ( ) ;
315
+ if num_ins == 0 || sum_outs < num_ins {
316
+ // Sanity checks
317
+ continue ;
318
+ }
319
+
320
+ let avg_ins_lower = sum_outs / num_ins; // >= 1
321
+ let storage_mass_lower = calc_storage_mass (
322
+ tx. is_coinbase ( ) ,
323
+ tx. inputs . iter ( ) . map ( |_| UtxoCell { plurality : 1 , amount : avg_ins_lower } ) ,
324
+ tx. outputs . iter ( ) . map ( |o| o. into ( ) ) ,
325
+ self . config . storage_mass_parameter ,
326
+ )
327
+ . unwrap_or ( u64:: MAX ) ;
328
+
329
+ // Despite being a lower bound, storage mass is still calculated to be positive, so we found our problem
330
+ if storage_mass_lower > 0 {
331
+ warn ! ( "The RPC submitted block {} contains a transaction {} with mass = 0 while it should have been strictly positive.
332
+ This indicates that the RPC conversion flow used by the miner does not preserve the mass values received from GetBlockTemplate.
333
+ You must upgrade your miner flow to propagate the mass field correctly prior to the Crescendo hardfork activation.
334
+ Failure to do so will result in your blocks being considered invalid when Crescendo activates." ,
335
+ block. hash( ) ,
336
+ tx. id( )
337
+ ) ;
338
+ // A single warning is sufficient
339
+ break ;
340
+ }
341
+ }
342
+ }
343
+ }
287
344
}
288
345
289
346
#[ async_trait]
@@ -334,11 +391,15 @@ impl RpcApi for RpcCoreService {
334
391
335
392
trace ! ( "incoming SubmitBlockRequest for block {}" , hash) ;
336
393
match self . flow_context . submit_rpc_block ( & session, block. clone ( ) ) . await {
337
- Ok ( _) => Ok ( SubmitBlockResponse { report : SubmitBlockReport :: Success } ) ,
394
+ Ok ( _) => {
395
+ self . sanity_check_storage_mass ( block) ;
396
+ Ok ( SubmitBlockResponse { report : SubmitBlockReport :: Success } )
397
+ }
338
398
Err ( ProtocolError :: RuleError ( RuleError :: BadMerkleRoot ( h1, h2) ) ) => {
339
399
warn ! (
340
- "The RPC submitted block triggered a {} error: {}.
341
- NOTE: This error usually indicates an RPC conversion error between the node and the miner. If you are on TN11 this is likely to reflect using a NON-SUPPORTED miner." ,
400
+ "The RPC submitted block {} triggered a {} error: {}.
401
+ NOTE: This error usually indicates an RPC conversion error between the node and the miner. This is likely to reflect using a NON-SUPPORTED miner." ,
402
+ hash,
342
403
stringify!( RuleError :: BadMerkleRoot ) ,
343
404
RuleError :: BadMerkleRoot ( h1, h2)
344
405
) ;
@@ -348,10 +409,7 @@ NOTE: This error usually indicates an RPC conversion error between the node and
348
409
Ok ( SubmitBlockResponse { report : SubmitBlockReport :: Reject ( SubmitBlockRejectReason :: BlockInvalid ) } )
349
410
}
350
411
Err ( err) => {
351
- warn ! (
352
- "The RPC submitted block triggered an error: {}\n Printing the full header for debug purposes:\n {:?}" ,
353
- err, block
354
- ) ;
412
+ warn ! ( "The RPC submitted block triggered an error: {}\n Printing the full block for debug purposes:\n {:?}" , err, block) ;
355
413
Ok ( SubmitBlockResponse { report : SubmitBlockReport :: Reject ( SubmitBlockRejectReason :: BlockInvalid ) } )
356
414
}
357
415
}
0 commit comments