Skip to content

Commit 1eebd4d

Browse files
Fix for the pruning proof rebuild issue (issue #444) (#449)
* add a strict assertion which should catch the pruning bug before actual data is pruned * possible fix: add `block_at_depth_2m` as an additional traversal root * rollback: rollback the previous fix since it's not the root cause * add additional dbg info to assertion * bug fix: write level relations for trusted blocks (blocks in the pruning point anticone of a newly synced node) * enable mainnet mining by default * simplify kip 9 beta condition + more mass tests * set default tracked addresses to 1M * fix tracker prealloc property + adds compile time assertion for upper bound
1 parent 5c437cb commit 1eebd4d

File tree

8 files changed

+180
-150
lines changed

8 files changed

+180
-150
lines changed

consensus/src/pipeline/header_processor/processor.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,17 +452,24 @@ impl HeaderProcessor {
452452
let mut batch = WriteBatch::default();
453453

454454
for (level, datum) in ghostdag_data.iter().enumerate() {
455-
// The data might have been already written when applying the pruning proof.
455+
// This data might have been already written when applying the pruning proof.
456456
self.ghostdag_stores[level].insert_batch(&mut batch, ctx.hash, datum).unwrap_or_exists();
457457
}
458458

459+
let mut relations_write = self.relations_stores.write();
460+
ctx.known_parents.into_iter().enumerate().for_each(|(level, parents_by_level)| {
461+
// This data might have been already written when applying the pruning proof.
462+
relations_write[level].insert_batch(&mut batch, ctx.hash, parents_by_level).unwrap_or_exists();
463+
});
464+
459465
let statuses_write = self.statuses_store.set_batch(&mut batch, ctx.hash, StatusHeaderOnly).unwrap();
460466

461467
// Flush the batch to the DB
462468
self.db.write(batch).unwrap();
463469

464470
// Calling the drops explicitly after the batch is written in order to avoid possible errors.
465471
drop(statuses_write);
472+
drop(relations_write);
466473
}
467474

468475
pub fn process_genesis(&self) {

consensus/src/pipeline/virtual_processor/utxo_validation.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ impl VirtualStateProcessor {
294294
self.populate_mempool_transaction_in_utxo_context(mutable_tx, utxo_view)?;
295295

296296
// For non-activated nets (mainnet, TN10) we can update mempool rules to KIP9 beta asap. For
297-
// TN11 we need to hard-fork consensus first (since the new beta rules are more relaxed)
297+
// TN11 we need to hard-fork consensus first (since the new beta rules are more permissive)
298298
let kip9_version = if self.storage_mass_activation_daa_score == u64::MAX { Kip9Version::Beta } else { Kip9Version::Alpha };
299299

300300
// Calc the full contextual mass including storage mass

consensus/src/processes/mass.rs

Lines changed: 73 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,13 @@ impl MassCalculator {
9191
let ins_len = tx.tx().inputs.len() as u64;
9292

9393
/*
94-
KIP-0009 relaxed formula for the cases |O| = 1 OR |O| <= |I| <= 2:
95-
max( 0 , C·( |O|/H(O) - |I|/H(I) ) )
94+
KIP-0009 relaxed formula for the cases |O| = 1 OR |O| <= |I| <= 2:
95+
max( 0 , C·( |O|/H(O) - |I|/H(I) ) )
96+
97+
Note: in the case |I| = 1 both formulas are equal, yet the following code (harmonic_ins) is a bit more efficient.
98+
Hence, we transform the condition to |O| = 1 OR |I| = 1 OR |O| = |I| = 2 which is equivalent (and faster).
9699
*/
97-
if version == Kip9Version::Beta && (outs_len == 1 || (outs_len <= ins_len && ins_len <= 2)) {
100+
if version == Kip9Version::Beta && (outs_len == 1 || ins_len == 1 || (outs_len == 2 && ins_len == 2)) {
98101
let harmonic_ins = tx
99102
.populated_inputs()
100103
.map(|(_, entry)| self.storage_mass_parameter / entry.amount)
@@ -144,63 +147,8 @@ mod tests {
144147

145148
#[test]
146149
fn test_mass_storage() {
147-
let script_pub_key = ScriptVec::from_slice(&[]);
148-
let prev_tx_id = TransactionId::from_str("880eb9819a31821d9d2399e2f35e2433b72637e393d71ecc9b8d0250f49153c3").unwrap();
149-
150150
// Tx with less outs than ins
151-
let tx = Transaction::new(
152-
0,
153-
vec![
154-
TransactionInput {
155-
previous_outpoint: TransactionOutpoint { transaction_id: prev_tx_id, index: 0 },
156-
signature_script: vec![],
157-
sequence: 0,
158-
sig_op_count: 0,
159-
},
160-
TransactionInput {
161-
previous_outpoint: TransactionOutpoint { transaction_id: prev_tx_id, index: 1 },
162-
signature_script: vec![],
163-
sequence: 1,
164-
sig_op_count: 0,
165-
},
166-
TransactionInput {
167-
previous_outpoint: TransactionOutpoint { transaction_id: prev_tx_id, index: 2 },
168-
signature_script: vec![],
169-
sequence: 2,
170-
sig_op_count: 0,
171-
},
172-
],
173-
vec![
174-
TransactionOutput { value: 300, script_public_key: ScriptPublicKey::new(0, script_pub_key.clone()) },
175-
TransactionOutput { value: 300, script_public_key: ScriptPublicKey::new(0, script_pub_key.clone()) },
176-
],
177-
1615462089000,
178-
SubnetworkId::from_bytes([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
179-
0,
180-
vec![],
181-
);
182-
183-
let entries = vec![
184-
UtxoEntry {
185-
amount: 100,
186-
script_public_key: ScriptPublicKey::new(0, script_pub_key.clone()),
187-
block_daa_score: 0,
188-
is_coinbase: false,
189-
},
190-
UtxoEntry {
191-
amount: 200,
192-
script_public_key: ScriptPublicKey::new(0, script_pub_key.clone()),
193-
block_daa_score: 0,
194-
is_coinbase: false,
195-
},
196-
UtxoEntry {
197-
amount: 300,
198-
script_public_key: ScriptPublicKey::new(0, script_pub_key.clone()),
199-
block_daa_score: 0,
200-
is_coinbase: false,
201-
},
202-
];
203-
let mut tx = MutableTransaction::with_entries(tx, entries);
151+
let mut tx = generate_tx_from_amounts(&[100, 200, 300], &[300, 300]);
204152
let test_version = Kip9Version::Alpha;
205153

206154
// Assert the formula: max( 0 , C·( |O|/H(O) - |I|/A(I) ) )
@@ -218,74 +166,8 @@ mod tests {
218166
assert_eq!(storage_mass, storage_mass_parameter / 50 + storage_mass_parameter / 550 - 3 * (storage_mass_parameter / 200));
219167

220168
// Create a tx with more outs than ins
221-
let tx = Transaction::new(
222-
0,
223-
vec![
224-
TransactionInput {
225-
previous_outpoint: TransactionOutpoint { transaction_id: prev_tx_id, index: 0 },
226-
signature_script: vec![],
227-
sequence: 0,
228-
sig_op_count: 0,
229-
},
230-
TransactionInput {
231-
previous_outpoint: TransactionOutpoint { transaction_id: prev_tx_id, index: 1 },
232-
signature_script: vec![],
233-
sequence: 1,
234-
sig_op_count: 0,
235-
},
236-
TransactionInput {
237-
previous_outpoint: TransactionOutpoint { transaction_id: prev_tx_id, index: 2 },
238-
signature_script: vec![],
239-
sequence: 2,
240-
sig_op_count: 0,
241-
},
242-
],
243-
vec![
244-
TransactionOutput {
245-
value: 10_000 * SOMPI_PER_KASPA,
246-
script_public_key: ScriptPublicKey::new(0, script_pub_key.clone()),
247-
},
248-
TransactionOutput {
249-
value: 10_000 * SOMPI_PER_KASPA,
250-
script_public_key: ScriptPublicKey::new(0, script_pub_key.clone()),
251-
},
252-
TransactionOutput {
253-
value: 10_000 * SOMPI_PER_KASPA,
254-
script_public_key: ScriptPublicKey::new(0, script_pub_key.clone()),
255-
},
256-
TransactionOutput {
257-
value: 10_000 * SOMPI_PER_KASPA,
258-
script_public_key: ScriptPublicKey::new(0, script_pub_key.clone()),
259-
},
260-
],
261-
1615462089000,
262-
SubnetworkId::from_bytes([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
263-
0,
264-
vec![],
265-
);
266-
267-
let entries = vec![
268-
UtxoEntry {
269-
amount: 10_000 * SOMPI_PER_KASPA,
270-
script_public_key: ScriptPublicKey::new(0, script_pub_key.clone()),
271-
block_daa_score: 0,
272-
is_coinbase: false,
273-
},
274-
UtxoEntry {
275-
amount: 10_000 * SOMPI_PER_KASPA,
276-
script_public_key: ScriptPublicKey::new(0, script_pub_key.clone()),
277-
block_daa_score: 0,
278-
is_coinbase: false,
279-
},
280-
UtxoEntry {
281-
amount: 20_000 * SOMPI_PER_KASPA,
282-
script_public_key: ScriptPublicKey::new(0, script_pub_key.clone()),
283-
block_daa_score: 0,
284-
is_coinbase: false,
285-
},
286-
];
287-
let mut tx = MutableTransaction::with_entries(tx, entries);
288-
169+
let base_value = 10_000 * SOMPI_PER_KASPA;
170+
let mut tx = generate_tx_from_amounts(&[base_value, base_value, base_value * 2], &[base_value; 4]);
289171
let storage_mass_parameter = STORAGE_MASS_PARAMETER;
290172
let storage_mass =
291173
MassCalculator::new(0, 0, 0, storage_mass_parameter).calc_tx_storage_mass(&tx.as_verifiable(), test_version).unwrap();
@@ -305,7 +187,70 @@ mod tests {
305187
let storage_mass =
306188
MassCalculator::new(0, 0, 0, storage_mass_parameter).calc_tx_storage_mass(&tx.as_verifiable(), test_version).unwrap();
307189
assert_eq!(storage_mass, 0);
190+
}
191+
192+
#[test]
193+
fn test_mass_storage_beta() {
194+
// 2:2 transaction
195+
let mut tx = generate_tx_from_amounts(&[100, 200], &[50, 250]);
196+
let storage_mass_parameter = 10u64.pow(12);
197+
let test_version = Kip9Version::Beta;
198+
// Assert the formula: max( 0 , C·( |O|/H(O) - |I|/O(I) ) )
199+
200+
let storage_mass =
201+
MassCalculator::new(0, 0, 0, storage_mass_parameter).calc_tx_storage_mass(&tx.as_verifiable(), test_version).unwrap();
202+
assert_eq!(storage_mass, 9000000000);
203+
204+
// Set outputs to be equal to inputs
205+
tx.tx.outputs[0].value = 100;
206+
tx.tx.outputs[1].value = 200;
207+
let storage_mass =
208+
MassCalculator::new(0, 0, 0, storage_mass_parameter).calc_tx_storage_mass(&tx.as_verifiable(), test_version).unwrap();
209+
assert_eq!(storage_mass, 0);
308210

309-
drop(script_pub_key);
211+
// Remove an output and make sure the other is small enough to make storage mass greater than zero
212+
tx.tx.outputs.pop();
213+
tx.tx.outputs[0].value = 50;
214+
let storage_mass =
215+
MassCalculator::new(0, 0, 0, storage_mass_parameter).calc_tx_storage_mass(&tx.as_verifiable(), test_version).unwrap();
216+
assert_eq!(storage_mass, 5000000000);
217+
}
218+
219+
fn generate_tx_from_amounts(ins: &[u64], outs: &[u64]) -> MutableTransaction<Transaction> {
220+
let script_pub_key = ScriptVec::from_slice(&[]);
221+
let prev_tx_id = TransactionId::from_str("880eb9819a31821d9d2399e2f35e2433b72637e393d71ecc9b8d0250f49153c3").unwrap();
222+
let tx = Transaction::new(
223+
0,
224+
(0..ins.len())
225+
.map(|i| TransactionInput {
226+
previous_outpoint: TransactionOutpoint { transaction_id: prev_tx_id, index: i as u32 },
227+
signature_script: vec![],
228+
sequence: 0,
229+
sig_op_count: 0,
230+
})
231+
.collect(),
232+
outs.iter()
233+
.copied()
234+
.map(|out_amount| TransactionOutput {
235+
value: out_amount,
236+
script_public_key: ScriptPublicKey::new(0, script_pub_key.clone()),
237+
})
238+
.collect(),
239+
1615462089000,
240+
SubnetworkId::from_bytes([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
241+
0,
242+
vec![],
243+
);
244+
let entries = ins
245+
.iter()
246+
.copied()
247+
.map(|in_amount| UtxoEntry {
248+
amount: in_amount,
249+
script_public_key: ScriptPublicKey::new(0, script_pub_key.clone()),
250+
block_daa_score: 0,
251+
is_coinbase: false,
252+
})
253+
.collect();
254+
MutableTransaction::with_entries(tx, entries)
310255
}
311256
}

consensus/src/processes/pruning_proof/mod.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,11 +691,67 @@ impl PruningProofManager {
691691
}
692692
}
693693

694+
// Temp assertion for verifying a bug fix: assert that the full 2M chain is actually contained in the composed level proof
695+
let set = BlockHashSet::from_iter(headers.iter().map(|h| h.hash));
696+
let chain_2m = self
697+
.chain_up_to_depth(&*self.ghostdag_stores[level], selected_tip, 2 * self.pruning_proof_m)
698+
.map_err(|err| {
699+
dbg!(level, selected_tip, block_at_depth_2m, root);
700+
format!("Assert 2M chain -- level: {}, err: {}", level, err)
701+
})
702+
.unwrap();
703+
let chain_2m_len = chain_2m.len();
704+
for (i, chain_hash) in chain_2m.into_iter().enumerate() {
705+
if !set.contains(&chain_hash) {
706+
let next_level_tip = selected_tip_by_level[level + 1];
707+
let next_level_chain_m =
708+
self.chain_up_to_depth(&*self.ghostdag_stores[level + 1], next_level_tip, self.pruning_proof_m).unwrap();
709+
let next_level_block_m = next_level_chain_m.last().copied().unwrap();
710+
dbg!(next_level_chain_m.len());
711+
dbg!(self.ghostdag_stores[level + 1].get_compact_data(next_level_tip).unwrap().blue_score);
712+
dbg!(self.ghostdag_stores[level + 1].get_compact_data(next_level_block_m).unwrap().blue_score);
713+
dbg!(self.ghostdag_stores[level].get_compact_data(selected_tip).unwrap().blue_score);
714+
dbg!(self.ghostdag_stores[level].get_compact_data(block_at_depth_2m).unwrap().blue_score);
715+
dbg!(level, selected_tip, block_at_depth_2m, root);
716+
panic!("Assert 2M chain -- missing block {} at index {} out of {} chain blocks", chain_hash, i, chain_2m_len);
717+
}
718+
}
719+
694720
headers
695721
})
696722
.collect_vec()
697723
}
698724

725+
/// Copy of `block_at_depth` which returns the full chain up to depth. Temporarily used for assertion purposes.
726+
fn chain_up_to_depth(
727+
&self,
728+
ghostdag_store: &impl GhostdagStoreReader,
729+
high: Hash,
730+
depth: u64,
731+
) -> Result<Vec<Hash>, PruningProofManagerInternalError> {
732+
let high_gd = ghostdag_store
733+
.get_compact_data(high)
734+
.map_err(|err| PruningProofManagerInternalError::BlockAtDepth(format!("high: {high}, depth: {depth}, {err}")))?;
735+
let mut current_gd = high_gd;
736+
let mut current = high;
737+
let mut res = vec![current];
738+
while current_gd.blue_score + depth >= high_gd.blue_score {
739+
if current_gd.selected_parent.is_origin() {
740+
break;
741+
}
742+
let prev = current;
743+
current = current_gd.selected_parent;
744+
res.push(current);
745+
current_gd = ghostdag_store.get_compact_data(current).map_err(|err| {
746+
PruningProofManagerInternalError::BlockAtDepth(format!(
747+
"high: {}, depth: {}, current: {}, high blue score: {}, current blue score: {}, {}",
748+
high, depth, prev, high_gd.blue_score, current_gd.blue_score, err
749+
))
750+
})?;
751+
}
752+
Ok(res)
753+
}
754+
699755
fn block_at_depth(
700756
&self,
701757
ghostdag_store: &impl GhostdagStoreReader,

kaspad/src/args.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,9 @@ impl Default for Args {
106106
outbound_target: 8,
107107
inbound_limit: 128,
108108
rpc_max_clients: 128,
109-
max_tracked_addresses: Tracker::DEFAULT_MAX_ADDRESSES,
109+
max_tracked_addresses: 0,
110110
enable_unsynced_mining: false,
111-
enable_mainnet_mining: false,
111+
enable_mainnet_mining: true,
112112
testnet: false,
113113
testnet_suffix: 10,
114114
devnet: false,
@@ -308,16 +308,17 @@ pub fn cli() -> Command {
308308
.long("enable-mainnet-mining")
309309
.action(ArgAction::SetTrue)
310310
.hide(true)
311-
.help("Allow mainnet mining (do not use unless you know what you are doing)"),
311+
.help("Allow mainnet mining (currently enabled by default while the flag is kept for backwards compatibility)"),
312312
)
313313
.arg(arg!(--utxoindex "Enable the UTXO index"))
314314
.arg(
315315
Arg::new("max-tracked-addresses")
316316
.long("max-tracked-addresses")
317317
.require_equals(true)
318318
.value_parser(clap::value_parser!(usize))
319-
.help(format!("Max preallocated number of addresses tracking UTXO changed events (default: {}, maximum: {}).
320-
Value 0 prevents the preallocation, leading to a 0 memory footprint as long as unused but then to a sub-optimal footprint when used.", Tracker::DEFAULT_MAX_ADDRESSES, Tracker::MAX_ADDRESS_UPPER_BOUND)),
319+
.help(format!("Max (preallocated) number of addresses being tracked for UTXO changed events (default: {}, maximum: {}).
320+
Setting to 0 prevents the preallocation and sets the maximum to {}, leading to 0 memory footprint as long as unused but to sub-optimal footprint if used.",
321+
0, Tracker::MAX_ADDRESS_UPPER_BOUND, Tracker::DEFAULT_MAX_ADDRESSES)),
321322
)
322323
.arg(arg!(--testnet "Use the test network"))
323324
.arg(
@@ -455,6 +456,11 @@ impl Args {
455456
#[cfg(feature = "devnet-prealloc")]
456457
prealloc_amount: arg_match_unwrap_or::<u64>(&m, "prealloc-amount", defaults.prealloc_amount),
457458
};
459+
460+
if arg_match_unwrap_or::<bool>(&m, "enable-mainnet-mining", false) {
461+
println!("\nNOTE: The flag --enable-mainnet-mining is deprecated and defaults to true also w/o explicit setting\n")
462+
}
463+
458464
Ok(args)
459465
}
460466
}

0 commit comments

Comments
 (0)