Skip to content

Commit 2c99ea3

Browse files
Identify & Warn miners with outdated mining rpc flow (#658)
* warn miners re zero mass * make msg more severe * some improvements * make the storage mass sanity check hermetically true
1 parent b9bbd71 commit 2c99ea3

File tree

2 files changed

+66
-15
lines changed

2 files changed

+66
-15
lines changed

rpc/core/src/convert/tx.rs

+1-8
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,14 @@ impl From<&Transaction> for RpcTransaction {
1818
gas: item.gas,
1919
payload: item.payload.clone(),
2020
mass: item.mass(),
21-
// TODO: Implement a populating process inspired from kaspad\app\rpc\rpccontext\verbosedata.go
2221
verbose_data: None,
2322
}
2423
}
2524
}
2625

2726
impl From<&TransactionOutput> for RpcTransactionOutput {
2827
fn from(item: &TransactionOutput) -> Self {
29-
Self {
30-
value: item.value,
31-
script_public_key: item.script_public_key.clone(),
32-
// TODO: Implement a populating process inspired from kaspad\app\rpc\rpccontext\verbosedata.go
33-
verbose_data: None,
34-
}
28+
Self { value: item.value, script_public_key: item.script_public_key.clone(), verbose_data: None }
3529
}
3630
}
3731

@@ -42,7 +36,6 @@ impl From<&TransactionInput> for RpcTransactionInput {
4236
signature_script: item.signature_script.clone(),
4337
sequence: item.sequence,
4438
sig_op_count: item.sig_op_count,
45-
// TODO: Implement a populating process inspired from kaspad\app\rpc\rpccontext\verbosedata.go
4639
verbose_data: None,
4740
}
4841
}

rpc/service/src/service.rs

+65-7
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use async_trait::async_trait;
77
use kaspa_consensus_core::api::counters::ProcessingCounters;
88
use kaspa_consensus_core::daa_score_timestamp::DaaScoreTimestamp;
99
use kaspa_consensus_core::errors::block::RuleError;
10+
use kaspa_consensus_core::mass::{calc_storage_mass, UtxoCell};
1011
use kaspa_consensus_core::utxo::utxo_inquirer::UtxoInquirerError;
1112
use kaspa_consensus_core::{
1213
block::Block,
@@ -284,6 +285,62 @@ impl RpcCoreService {
284285
(false, false) => Ok(TransactionQuery::TransactionsOnly),
285286
}
286287
}
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+
}
287344
}
288345

289346
#[async_trait]
@@ -334,11 +391,15 @@ impl RpcApi for RpcCoreService {
334391

335392
trace!("incoming SubmitBlockRequest for block {}", hash);
336393
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+
}
338398
Err(ProtocolError::RuleError(RuleError::BadMerkleRoot(h1, h2))) => {
339399
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,
342403
stringify!(RuleError::BadMerkleRoot),
343404
RuleError::BadMerkleRoot(h1, h2)
344405
);
@@ -348,10 +409,7 @@ NOTE: This error usually indicates an RPC conversion error between the node and
348409
Ok(SubmitBlockResponse { report: SubmitBlockReport::Reject(SubmitBlockRejectReason::BlockInvalid) })
349410
}
350411
Err(err) => {
351-
warn!(
352-
"The RPC submitted block triggered an error: {}\nPrinting the full header for debug purposes:\n{:?}",
353-
err, block
354-
);
412+
warn!("The RPC submitted block triggered an error: {}\nPrinting the full block for debug purposes:\n{:?}", err, block);
355413
Ok(SubmitBlockResponse { report: SubmitBlockReport::Reject(SubmitBlockRejectReason::BlockInvalid) })
356414
}
357415
}

0 commit comments

Comments
 (0)