@@ -25,7 +25,7 @@ use clarity::vm::analysis::{CheckError, CheckErrors};
25
25
use clarity:: vm:: ast:: errors:: ParseErrors ;
26
26
use clarity:: vm:: ast:: ASTRules ;
27
27
use clarity:: vm:: clarity:: TransactionConnection ;
28
- use clarity:: vm:: costs:: ExecutionCost ;
28
+ use clarity:: vm:: costs:: { ExecutionCost , LimitedCostTracker , TrackerData } ;
29
29
use clarity:: vm:: database:: BurnStateDB ;
30
30
use clarity:: vm:: errors:: Error as InterpreterError ;
31
31
use clarity:: vm:: types:: { QualifiedContractIdentifier , TypeSignature } ;
@@ -124,6 +124,8 @@ pub struct NakamotoBlockBuilder {
124
124
txs : Vec < StacksTransaction > ,
125
125
/// header we're filling in
126
126
pub header : NakamotoBlockHeader ,
127
+ /// Optional soft limit for this block's budget usage
128
+ soft_limit : Option < ExecutionCost > ,
127
129
}
128
130
129
131
pub struct MinerTenureInfo < ' a > {
@@ -159,6 +161,7 @@ impl NakamotoBlockBuilder {
159
161
bytes_so_far : 0 ,
160
162
txs : vec ! [ ] ,
161
163
header : NakamotoBlockHeader :: genesis ( ) ,
164
+ soft_limit : None ,
162
165
}
163
166
}
164
167
@@ -176,13 +179,18 @@ impl NakamotoBlockBuilder {
176
179
///
177
180
/// * `coinbase` - the coinbase tx if this is going to start a new tenure
178
181
///
182
+ /// * `bitvec_len` - the length of the bitvec of reward addresses that should be punished or not in this block.
183
+ ///
184
+ /// * `soft_limit` - an optional soft limit for the block's clarity cost for this block
185
+ ///
179
186
pub fn new (
180
187
parent_stacks_header : & StacksHeaderInfo ,
181
188
tenure_id_consensus_hash : & ConsensusHash ,
182
189
total_burn : u64 ,
183
190
tenure_change : Option < & StacksTransaction > ,
184
191
coinbase : Option < & StacksTransaction > ,
185
192
bitvec_len : u16 ,
193
+ soft_limit : Option < ExecutionCost > ,
186
194
) -> Result < NakamotoBlockBuilder , Error > {
187
195
let next_height = parent_stacks_header
188
196
. anchored_header
@@ -222,6 +230,7 @@ impl NakamotoBlockBuilder {
222
230
. map ( |b| b. timestamp )
223
231
. unwrap_or ( 0 ) ,
224
232
) ,
233
+ soft_limit,
225
234
} )
226
235
}
227
236
@@ -509,6 +518,7 @@ impl NakamotoBlockBuilder {
509
518
tenure_info. tenure_change_tx ( ) ,
510
519
tenure_info. coinbase_tx ( ) ,
511
520
signer_bitvec_len,
521
+ None ,
512
522
) ?;
513
523
514
524
let ts_start = get_epoch_time_ms ( ) ;
@@ -521,6 +531,37 @@ impl NakamotoBlockBuilder {
521
531
. block_limit ( )
522
532
. expect ( "Failed to obtain block limit from miner's block connection" ) ;
523
533
534
+ let mut soft_limit = None ;
535
+ if let Some ( percentage) = settings
536
+ . mempool_settings
537
+ . tenure_cost_limit_per_block_percentage
538
+ {
539
+ // Make sure we aren't actually going to multiply by 0 or attempt to increase the block limit.
540
+ assert ! (
541
+ ( 1 ..=100 ) . contains( & percentage) ,
542
+ "BUG: tenure_cost_limit_per_block_percentage: {percentage}%. Must be between between 1 and 100"
543
+ ) ;
544
+ let mut remaining_limit = block_limit. clone ( ) ;
545
+ let cost_so_far = tenure_tx. cost_so_far ( ) ;
546
+ if remaining_limit. sub ( & cost_so_far) . is_ok ( ) {
547
+ if remaining_limit. divide ( 100 ) . is_ok ( ) {
548
+ remaining_limit. multiply ( percentage. into ( ) ) . expect (
549
+ "BUG: failed to multiply by {percentage} when previously divided by 100" ,
550
+ ) ;
551
+ remaining_limit. add ( & cost_so_far) . expect ( "BUG: unexpected overflow when adding cost_so_far, which was previously checked" ) ;
552
+ debug ! (
553
+ "Setting soft limit for clarity cost to {percentage}% of remaining block limit" ;
554
+ "remaining_limit" => %remaining_limit,
555
+ "cost_so_far" => %cost_so_far,
556
+ "block_limit" => %block_limit,
557
+ ) ;
558
+ soft_limit = Some ( remaining_limit) ;
559
+ }
560
+ } ;
561
+ }
562
+
563
+ builder. soft_limit = soft_limit;
564
+
524
565
let initial_txs: Vec < _ > = [
525
566
tenure_info. tenure_change_tx . clone ( ) ,
526
567
tenure_info. coinbase_tx . clone ( ) ,
@@ -607,26 +648,19 @@ impl BlockBuilder for NakamotoBlockBuilder {
607
648
return TransactionResult :: skipped_due_to_error ( & tx, Error :: BlockTooBigError ) ;
608
649
}
609
650
651
+ let non_boot_code_contract_call = match & tx. payload {
652
+ TransactionPayload :: ContractCall ( cc) => !cc. address . is_boot_code_addr ( ) ,
653
+ TransactionPayload :: SmartContract ( ..) => true ,
654
+ _ => false ,
655
+ } ;
656
+
610
657
match limit_behavior {
611
658
BlockLimitFunction :: CONTRACT_LIMIT_HIT => {
612
- match & tx. payload {
613
- TransactionPayload :: ContractCall ( cc) => {
614
- // once we've hit the runtime limit once, allow boot code contract calls, but do not try to eval
615
- // other contract calls
616
- if !cc. address . is_boot_code_addr ( ) {
617
- return TransactionResult :: skipped (
618
- & tx,
619
- "BlockLimitFunction::CONTRACT_LIMIT_HIT" . to_string ( ) ,
620
- ) ;
621
- }
622
- }
623
- TransactionPayload :: SmartContract ( ..) => {
624
- return TransactionResult :: skipped (
625
- & tx,
626
- "BlockLimitFunction::CONTRACT_LIMIT_HIT" . to_string ( ) ,
627
- ) ;
628
- }
629
- _ => { }
659
+ if non_boot_code_contract_call {
660
+ return TransactionResult :: skipped (
661
+ & tx,
662
+ "BlockLimitFunction::CONTRACT_LIMIT_HIT" . to_string ( ) ,
663
+ ) ;
630
664
}
631
665
}
632
666
BlockLimitFunction :: LIMIT_REACHED => {
@@ -653,70 +687,83 @@ impl BlockBuilder for NakamotoBlockBuilder {
653
687
) ;
654
688
return TransactionResult :: problematic ( & tx, Error :: NetError ( e) ) ;
655
689
}
656
- let ( fee, receipt) = match StacksChainState :: process_transaction (
657
- clarity_tx, tx, quiet, ast_rules,
658
- ) {
659
- Ok ( ( fee, receipt) ) => ( fee, receipt) ,
660
- Err ( e) => {
661
- let ( is_problematic, e) =
662
- TransactionResult :: is_problematic ( & tx, e, clarity_tx. get_epoch ( ) ) ;
663
- if is_problematic {
664
- return TransactionResult :: problematic ( & tx, e) ;
665
- } else {
666
- match e {
667
- Error :: CostOverflowError ( cost_before, cost_after, total_budget) => {
668
- clarity_tx. reset_cost ( cost_before. clone ( ) ) ;
669
- if total_budget. proportion_largest_dimension ( & cost_before)
670
- < TX_BLOCK_LIMIT_PROPORTION_HEURISTIC
671
- {
672
- warn ! (
673
- "Transaction {} consumed over {}% of block budget, marking as invalid; budget was {}" ,
674
- tx. txid( ) ,
675
- 100 - TX_BLOCK_LIMIT_PROPORTION_HEURISTIC ,
676
- & total_budget
677
- ) ;
678
- let mut measured_cost = cost_after;
679
- let measured_cost = if measured_cost. sub ( & cost_before) . is_ok ( ) {
680
- Some ( measured_cost)
681
- } else {
682
- warn ! (
683
- "Failed to compute measured cost of a too big transaction"
684
- ) ;
685
- None
686
- } ;
687
- return TransactionResult :: error (
688
- & tx,
689
- Error :: TransactionTooBigError ( measured_cost) ,
690
- ) ;
691
- } else {
692
- warn ! (
693
- "Transaction {} reached block cost {}; budget was {}" ,
694
- tx. txid( ) ,
695
- & cost_after,
696
- & total_budget
697
- ) ;
698
- return TransactionResult :: skipped_due_to_error (
699
- & tx,
700
- Error :: BlockTooBigError ,
701
- ) ;
702
- }
703
- }
704
- _ => return TransactionResult :: error ( & tx, e) ,
705
- }
690
+
691
+ let cost_before = clarity_tx. cost_so_far ( ) ;
692
+ let ( fee, receipt) =
693
+ match StacksChainState :: process_transaction ( clarity_tx, tx, quiet, ast_rules) {
694
+ Ok ( x) => x,
695
+ Err ( e) => {
696
+ return parse_process_transaction_error ( clarity_tx, tx, e) ;
706
697
}
698
+ } ;
699
+ let cost_after = clarity_tx. cost_so_far ( ) ;
700
+ let mut soft_limit_reached = false ;
701
+ // We only attempt to apply the soft limit to non-boot code contract calls.
702
+ if non_boot_code_contract_call {
703
+ if let Some ( soft_limit) = self . soft_limit . as_ref ( ) {
704
+ soft_limit_reached = cost_after. exceeds ( soft_limit) ;
707
705
}
708
- } ;
706
+ }
707
+
709
708
info ! ( "Include tx" ;
710
709
"tx" => %tx. txid( ) ,
711
710
"payload" => tx. payload. name( ) ,
712
- "origin" => %tx. origin_address( ) ) ;
711
+ "origin" => %tx. origin_address( ) ,
712
+ "soft_limit_reached" => soft_limit_reached,
713
+ "cost_after" => %cost_after,
714
+ "cost_before" => %cost_before,
715
+ ) ;
713
716
714
717
// save
715
718
self . txs . push ( tx. clone ( ) ) ;
716
- TransactionResult :: success ( & tx, fee, receipt)
719
+ TransactionResult :: success_with_soft_limit ( & tx, fee, receipt, soft_limit_reached )
717
720
} ;
718
721
719
722
self . bytes_so_far += tx_len;
720
723
result
721
724
}
722
725
}
726
+
727
+ fn parse_process_transaction_error (
728
+ clarity_tx : & mut ClarityTx ,
729
+ tx : & StacksTransaction ,
730
+ e : Error ,
731
+ ) -> TransactionResult {
732
+ let ( is_problematic, e) = TransactionResult :: is_problematic ( & tx, e, clarity_tx. get_epoch ( ) ) ;
733
+ if is_problematic {
734
+ TransactionResult :: problematic ( & tx, e)
735
+ } else {
736
+ match e {
737
+ Error :: CostOverflowError ( cost_before, cost_after, total_budget) => {
738
+ clarity_tx. reset_cost ( cost_before. clone ( ) ) ;
739
+ if total_budget. proportion_largest_dimension ( & cost_before)
740
+ < TX_BLOCK_LIMIT_PROPORTION_HEURISTIC
741
+ {
742
+ warn ! (
743
+ "Transaction {} consumed over {}% of block budget, marking as invalid; budget was {}" ,
744
+ tx. txid( ) ,
745
+ 100 - TX_BLOCK_LIMIT_PROPORTION_HEURISTIC ,
746
+ & total_budget
747
+ ) ;
748
+ let mut measured_cost = cost_after;
749
+ let measured_cost = if measured_cost. sub ( & cost_before) . is_ok ( ) {
750
+ Some ( measured_cost)
751
+ } else {
752
+ warn ! ( "Failed to compute measured cost of a too big transaction" ) ;
753
+ None
754
+ } ;
755
+ TransactionResult :: error ( & tx, Error :: TransactionTooBigError ( measured_cost) )
756
+ } else {
757
+ warn ! (
758
+ "Transaction {} reached block cost {}; budget was {}" ,
759
+ tx. txid( ) ,
760
+ & cost_after,
761
+ & total_budget
762
+ ) ;
763
+ TransactionResult :: skipped_due_to_error ( & tx, Error :: BlockTooBigError )
764
+ }
765
+ }
766
+ _ => TransactionResult :: error ( & tx, e) ,
767
+ }
768
+ }
769
+ }
0 commit comments