Skip to content

Commit 77b087c

Browse files
calculate sig ops on fly (#597)
* initial impl of calculation sig operations on fly * refactor/rename add docs * impl test for runtime calculation of signature ops * hf activation test * refactor: rename sig_op_required to used_sig_ops * fix error message * add comment * add comments describing SignatureScriptBuilder used in tests * impl consume sig op function * refactor: update test case names for clarity and consistency * style: fmt * update misleading comment * fix misleading comment * fix doctest * refactor: ensure signature count is within u8::MAX limit * add comment describing Compile-time check * txscript: Make RuntimeSigOpCounter fields private with public accessors Make the fields of RuntimeSigOpCounter private and add: - A constructor (new) that ensures consistency between limit and remaining - Getter methods for accessing the private fields - Updated example code to use the new API This change improves encapsulation by ensuring the counter's internal state can only be modified through the consume_sig_ops method, preventing potential inconsistencies between sig_op_limit and sig_op_remaining that could occur with direct field access. * refactor simulation fn * rename functions and update documentation related to sig op counting * txscript: Move sig op counting to signature verification * txscript: Move RuntimeSigOpCounter to separate module, improve docs * style: fmt * fix misleading comment * decrease cache size due to fact it wont be used
1 parent 1ef4bdc commit 77b087c

File tree

14 files changed

+742
-113
lines changed

14 files changed

+742
-113
lines changed

consensus/benches/check_scripts.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ fn benchmark_check_scripts(c: &mut Criterion) {
8989
let cache = Cache::new(inputs_count as u64);
9090
b.iter(|| {
9191
cache.clear();
92-
check_scripts_sequential(black_box(&cache), black_box(&tx.as_verifiable()), false).unwrap();
92+
check_scripts_sequential(black_box(&cache), black_box(&tx.as_verifiable()), false, false).unwrap();
9393
})
9494
});
9595

@@ -98,7 +98,7 @@ fn benchmark_check_scripts(c: &mut Criterion) {
9898
let cache = Cache::new(inputs_count as u64);
9999
b.iter(|| {
100100
cache.clear();
101-
check_scripts_par_iter(black_box(&cache), black_box(&tx.as_verifiable()), false).unwrap();
101+
check_scripts_par_iter(black_box(&cache), black_box(&tx.as_verifiable()), false, false).unwrap();
102102
})
103103
});
104104

@@ -110,8 +110,14 @@ fn benchmark_check_scripts(c: &mut Criterion) {
110110
let cache = Cache::new(inputs_count as u64);
111111
b.iter(|| {
112112
cache.clear();
113-
check_scripts_par_iter_pool(black_box(&cache), black_box(&tx.as_verifiable()), black_box(&pool), false)
114-
.unwrap();
113+
check_scripts_par_iter_pool(
114+
black_box(&cache),
115+
black_box(&tx.as_verifiable()),
116+
black_box(&pool),
117+
false,
118+
false,
119+
)
120+
.unwrap();
115121
})
116122
});
117123
}
@@ -147,7 +153,7 @@ fn benchmark_check_scripts_with_payload(c: &mut Criterion) {
147153
let cache = Cache::new(inputs_count as u64);
148154
b.iter(|| {
149155
cache.clear();
150-
check_scripts_par_iter(black_box(&cache), black_box(&tx.as_verifiable()), false).unwrap();
156+
check_scripts_par_iter(black_box(&cache), black_box(&tx.as_verifiable()), false, false).unwrap();
151157
})
152158
});
153159
}

consensus/core/src/config/params.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ pub struct Params {
133133

134134
/// Activation rules for when to enable using the payload field in transactions
135135
pub payload_activation: ForkActivation,
136+
pub runtime_sig_op_counting: ForkActivation,
136137
}
137138

138139
fn unix_now() -> u64 {
@@ -411,6 +412,7 @@ pub const MAINNET_PARAMS: Params = Params {
411412
pruning_proof_m: 1000,
412413

413414
payload_activation: ForkActivation::never(),
415+
runtime_sig_op_counting: ForkActivation::never(),
414416
};
415417

416418
pub const TESTNET_PARAMS: Params = Params {
@@ -476,6 +478,7 @@ pub const TESTNET_PARAMS: Params = Params {
476478
pruning_proof_m: 1000,
477479

478480
payload_activation: ForkActivation::never(),
481+
runtime_sig_op_counting: ForkActivation::never(),
479482
};
480483

481484
pub const TESTNET11_PARAMS: Params = Params {
@@ -539,6 +542,8 @@ pub const TESTNET11_PARAMS: Params = Params {
539542

540543
skip_proof_of_work: false,
541544
max_block_level: 250,
545+
546+
runtime_sig_op_counting: ForkActivation::never(),
542547
};
543548

544549
pub const SIMNET_PARAMS: Params = Params {
@@ -595,6 +600,7 @@ pub const SIMNET_PARAMS: Params = Params {
595600
max_block_level: 250,
596601

597602
payload_activation: ForkActivation::never(),
603+
runtime_sig_op_counting: ForkActivation::never(),
598604
};
599605

600606
pub const DEVNET_PARAMS: Params = Params {
@@ -654,4 +660,5 @@ pub const DEVNET_PARAMS: Params = Params {
654660
pruning_proof_m: 1000,
655661

656662
payload_activation: ForkActivation::never(),
663+
runtime_sig_op_counting: ForkActivation::never(),
657664
};

consensus/src/consensus/services.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ impl ConsensusServices {
148148
params.storage_mass_activation,
149149
params.kip10_activation,
150150
params.payload_activation,
151+
params.runtime_sig_op_counting,
151152
);
152153

153154
let pruning_point_manager = PruningPointManager::new(

consensus/src/processes/transaction_validator/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub struct TransactionValidator {
3131
/// KIP-10 hardfork DAA score
3232
kip10_activation: ForkActivation,
3333
payload_activation: ForkActivation,
34+
runtime_sig_op_counting: ForkActivation,
3435
}
3536

3637
impl TransactionValidator {
@@ -48,6 +49,7 @@ impl TransactionValidator {
4849
storage_mass_activation: ForkActivation,
4950
kip10_activation: ForkActivation,
5051
payload_activation: ForkActivation,
52+
runtime_sig_op_counting: ForkActivation,
5153
) -> Self {
5254
Self {
5355
max_tx_inputs,
@@ -62,6 +64,7 @@ impl TransactionValidator {
6264
storage_mass_activation,
6365
kip10_activation,
6466
payload_activation,
67+
runtime_sig_op_counting,
6568
}
6669
}
6770

@@ -88,6 +91,7 @@ impl TransactionValidator {
8891
storage_mass_activation: ForkActivation::never(),
8992
kip10_activation: ForkActivation::never(),
9093
payload_activation: ForkActivation::never(),
94+
runtime_sig_op_counting: ForkActivation::never(),
9195
}
9296
}
9397
}

consensus/src/processes/transaction_validator/tx_validation_in_utxo_context.rs

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use kaspa_consensus_core::{
44
tx::{TransactionInput, VerifiableTransaction},
55
};
66
use kaspa_core::warn;
7-
use kaspa_txscript::{caches::Cache, get_sig_op_count, SigCacheKey, TxScriptEngine};
7+
use kaspa_txscript::{caches::Cache, get_sig_op_count_upper_bound, SigCacheKey, TxScriptEngine};
88
use kaspa_txscript_errors::TxScriptError;
99
use rayon::iter::{IntoParallelIterator, ParallelIterator};
1010
use rayon::ThreadPool;
@@ -59,7 +59,9 @@ impl TransactionValidator {
5959

6060
match flags {
6161
TxValidationFlags::Full | TxValidationFlags::SkipMassCheck => {
62-
Self::check_sig_op_counts(tx)?;
62+
if !self.runtime_sig_op_counting.is_active(pov_daa_score) {
63+
Self::check_sig_op_counts(tx)?;
64+
}
6365
self.check_scripts(tx, pov_daa_score)?;
6466
}
6567
TxValidationFlags::SkipScriptChecks => {}
@@ -162,7 +164,8 @@ impl TransactionValidator {
162164

163165
fn check_sig_op_counts<T: VerifiableTransaction>(tx: &T) -> TxResult<()> {
164166
for (i, (input, entry)) in tx.populated_inputs().enumerate() {
165-
let calculated = get_sig_op_count::<T, SigHashReusedValuesUnsync>(&input.signature_script, &entry.script_public_key);
167+
let calculated =
168+
get_sig_op_count_upper_bound::<T, SigHashReusedValuesUnsync>(&input.signature_script, &entry.script_public_key);
166169
if calculated != input.sig_op_count as u64 {
167170
return Err(TxRuleError::WrongSigOpCount(i, input.sig_op_count as u64, calculated));
168171
}
@@ -171,30 +174,37 @@ impl TransactionValidator {
171174
}
172175

173176
pub fn check_scripts(&self, tx: &(impl VerifiableTransaction + Sync), pov_daa_score: u64) -> TxResult<()> {
174-
check_scripts(&self.sig_cache, tx, self.kip10_activation.is_active(pov_daa_score))
177+
check_scripts(
178+
&self.sig_cache,
179+
tx,
180+
self.kip10_activation.is_active(pov_daa_score),
181+
self.runtime_sig_op_counting.is_active(pov_daa_score),
182+
)
175183
}
176184
}
177185

178186
pub fn check_scripts(
179187
sig_cache: &Cache<SigCacheKey, bool>,
180188
tx: &(impl VerifiableTransaction + Sync),
181189
kip10_enabled: bool,
190+
runtime_sig_op_counting: bool,
182191
) -> TxResult<()> {
183192
if tx.inputs().len() > CHECK_SCRIPTS_PARALLELISM_THRESHOLD {
184-
check_scripts_par_iter(sig_cache, tx, kip10_enabled)
193+
check_scripts_par_iter(sig_cache, tx, kip10_enabled, runtime_sig_op_counting)
185194
} else {
186-
check_scripts_sequential(sig_cache, tx, kip10_enabled)
195+
check_scripts_sequential(sig_cache, tx, kip10_enabled, runtime_sig_op_counting)
187196
}
188197
}
189198

190199
pub fn check_scripts_sequential(
191200
sig_cache: &Cache<SigCacheKey, bool>,
192201
tx: &impl VerifiableTransaction,
193202
kip10_enabled: bool,
203+
runtime_sig_op_counting: bool,
194204
) -> TxResult<()> {
195205
let reused_values = SigHashReusedValuesUnsync::new();
196206
for (i, (input, entry)) in tx.populated_inputs().enumerate() {
197-
TxScriptEngine::from_transaction_input(tx, input, i, entry, &reused_values, sig_cache, kip10_enabled)
207+
TxScriptEngine::from_transaction_input(tx, input, i, entry, &reused_values, sig_cache, kip10_enabled, runtime_sig_op_counting)
198208
.execute()
199209
.map_err(|err| map_script_err(err, input))?;
200210
}
@@ -205,11 +215,12 @@ pub fn check_scripts_par_iter(
205215
sig_cache: &Cache<SigCacheKey, bool>,
206216
tx: &(impl VerifiableTransaction + Sync),
207217
kip10_enabled: bool,
218+
runtime_sig_op_counting: bool,
208219
) -> TxResult<()> {
209220
let reused_values = SigHashReusedValuesSync::new();
210221
(0..tx.inputs().len()).into_par_iter().try_for_each(|idx| {
211222
let (input, utxo) = tx.populated_input(idx);
212-
TxScriptEngine::from_transaction_input(tx, input, idx, utxo, &reused_values, sig_cache, kip10_enabled)
223+
TxScriptEngine::from_transaction_input(tx, input, idx, utxo, &reused_values, sig_cache, kip10_enabled, runtime_sig_op_counting)
213224
.execute()
214225
.map_err(|err| map_script_err(err, input))
215226
})
@@ -220,8 +231,9 @@ pub fn check_scripts_par_iter_pool(
220231
tx: &(impl VerifiableTransaction + Sync),
221232
pool: &ThreadPool,
222233
kip10_enabled: bool,
234+
runtime_sig_op_counting: bool,
223235
) -> TxResult<()> {
224-
pool.install(|| check_scripts_par_iter(sig_cache, tx, kip10_enabled))
236+
pool.install(|| check_scripts_par_iter(sig_cache, tx, kip10_enabled, runtime_sig_op_counting))
225237
}
226238

227239
fn map_script_err(script_err: TxScriptError, input: &TransactionInput) -> TxRuleError {

crypto/txscript/errors/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ pub enum TxScriptError {
7373
InvalidOutputIndex(i32, usize),
7474
#[error(transparent)]
7575
Serialization(#[from] SerializationError),
76+
#[error("sig op count exceeds passed limit of {0}")]
77+
ExceededSigOpLimit(u8),
7678
}
7779

7880
#[derive(Error, PartialEq, Eq, Debug, Clone, Copy)]

crypto/txscript/examples/kip-10.rs

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@ fn threshold_scenario() -> ScriptBuilderResult<()> {
126126
}
127127

128128
let tx = tx.as_verifiable();
129-
let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &reused_values, &sig_cache, true);
129+
let mut vm =
130+
TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &reused_values, &sig_cache, true, false);
130131
assert_eq!(vm.execute(), Ok(()));
131132
println!("[STANDARD] Owner branch execution successful");
132133
}
@@ -136,7 +137,8 @@ fn threshold_scenario() -> ScriptBuilderResult<()> {
136137
println!("[STANDARD] Checking borrower branch");
137138
tx.inputs[0].signature_script = ScriptBuilder::new().add_op(OpFalse)?.add_data(&script)?.drain();
138139
let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]);
139-
let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &reused_values, &sig_cache, true);
140+
let mut vm =
141+
TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &reused_values, &sig_cache, true, false);
140142
assert_eq!(vm.execute(), Ok(()));
141143
println!("[STANDARD] Borrower branch execution successful");
142144
}
@@ -147,7 +149,8 @@ fn threshold_scenario() -> ScriptBuilderResult<()> {
147149
// Less than threshold
148150
tx.outputs[0].value -= 1;
149151
let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]);
150-
let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &reused_values, &sig_cache, true);
152+
let mut vm =
153+
TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &reused_values, &sig_cache, true, false);
151154
assert_eq!(vm.execute(), Err(EvalFalse));
152155
println!("[STANDARD] Borrower branch with threshold not reached failed as expected");
153156
}
@@ -295,7 +298,8 @@ fn threshold_scenario_limited_one_time() -> ScriptBuilderResult<()> {
295298
}
296299

297300
let tx = tx.as_verifiable();
298-
let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &reused_values, &sig_cache, true);
301+
let mut vm =
302+
TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &reused_values, &sig_cache, true, false);
299303
assert_eq!(vm.execute(), Ok(()));
300304
println!("[ONE-TIME] Owner branch execution successful");
301305
}
@@ -305,7 +309,8 @@ fn threshold_scenario_limited_one_time() -> ScriptBuilderResult<()> {
305309
println!("[ONE-TIME] Checking borrower branch");
306310
tx.inputs[0].signature_script = ScriptBuilder::new().add_op(OpFalse)?.add_data(&script)?.drain();
307311
let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]);
308-
let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &reused_values, &sig_cache, true);
312+
let mut vm =
313+
TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &reused_values, &sig_cache, true, false);
309314
assert_eq!(vm.execute(), Ok(()));
310315
println!("[ONE-TIME] Borrower branch execution successful");
311316
}
@@ -316,7 +321,8 @@ fn threshold_scenario_limited_one_time() -> ScriptBuilderResult<()> {
316321
// Less than threshold
317322
tx.outputs[0].value -= 1;
318323
let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]);
319-
let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &reused_values, &sig_cache, true);
324+
let mut vm =
325+
TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &reused_values, &sig_cache, true, false);
320326
assert_eq!(vm.execute(), Err(EvalFalse));
321327
println!("[ONE-TIME] Borrower branch with threshold not reached failed as expected");
322328
}
@@ -346,6 +352,7 @@ fn threshold_scenario_limited_one_time() -> ScriptBuilderResult<()> {
346352
&reused_values,
347353
&sig_cache,
348354
true,
355+
false,
349356
);
350357
assert_eq!(vm.execute(), Err(VerifyError));
351358
println!("[ONE-TIME] Borrower branch with output going to wrong address failed as expected");
@@ -455,7 +462,8 @@ fn threshold_scenario_limited_2_times() -> ScriptBuilderResult<()> {
455462
}
456463

457464
let tx = tx.as_verifiable();
458-
let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &reused_values, &sig_cache, true);
465+
let mut vm =
466+
TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &reused_values, &sig_cache, true, false);
459467
assert_eq!(vm.execute(), Ok(()));
460468
println!("[TWO-TIMES] Owner branch execution successful");
461469
}
@@ -465,7 +473,8 @@ fn threshold_scenario_limited_2_times() -> ScriptBuilderResult<()> {
465473
println!("[TWO-TIMES] Checking borrower branch (first borrowing)");
466474
tx.inputs[0].signature_script = ScriptBuilder::new().add_op(OpFalse)?.add_data(&two_times_script)?.drain();
467475
let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]);
468-
let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &reused_values, &sig_cache, true);
476+
let mut vm =
477+
TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &reused_values, &sig_cache, true, false);
469478
assert_eq!(vm.execute(), Ok(()));
470479
println!("[TWO-TIMES] Borrower branch (first borrowing) execution successful");
471480
}
@@ -476,7 +485,8 @@ fn threshold_scenario_limited_2_times() -> ScriptBuilderResult<()> {
476485
// Less than threshold
477486
tx.outputs[0].value -= 1;
478487
let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]);
479-
let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &reused_values, &sig_cache, true);
488+
let mut vm =
489+
TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &reused_values, &sig_cache, true, false);
480490
assert_eq!(vm.execute(), Err(EvalFalse));
481491
println!("[TWO-TIMES] Borrower branch with threshold not reached failed as expected");
482492
}
@@ -506,6 +516,7 @@ fn threshold_scenario_limited_2_times() -> ScriptBuilderResult<()> {
506516
&reused_values,
507517
&sig_cache,
508518
true,
519+
false,
509520
);
510521
assert_eq!(vm.execute(), Err(VerifyError));
511522
println!("[TWO-TIMES] Borrower branch with output going to wrong address failed as expected");
@@ -617,7 +628,8 @@ fn shared_secret_scenario() -> ScriptBuilderResult<()> {
617628
}
618629

619630
let tx = tx.as_verifiable();
620-
let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &reused_values, &sig_cache, true);
631+
let mut vm =
632+
TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &reused_values, &sig_cache, true, false);
621633
assert_eq!(vm.execute(), Ok(()));
622634
println!("[SHARED-SECRET] Owner branch execution successful");
623635
}
@@ -635,7 +647,8 @@ fn shared_secret_scenario() -> ScriptBuilderResult<()> {
635647
}
636648

637649
let tx = tx.as_verifiable();
638-
let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &reused_values, &sig_cache, true);
650+
let mut vm =
651+
TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &reused_values, &sig_cache, true, false);
639652
assert_eq!(vm.execute(), Ok(()));
640653
println!("[SHARED-SECRET] Borrower branch with correct shared secret execution successful");
641654
}
@@ -653,7 +666,8 @@ fn shared_secret_scenario() -> ScriptBuilderResult<()> {
653666
}
654667

655668
let tx = tx.as_verifiable();
656-
let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &reused_values, &sig_cache, true);
669+
let mut vm =
670+
TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &reused_values, &sig_cache, true, false);
657671
assert_eq!(vm.execute(), Err(VerifyError));
658672
println!("[SHARED-SECRET] Borrower branch with incorrect secret failed as expected");
659673
}

0 commit comments

Comments
 (0)