1use std::collections::BTreeMap;
2
3use mina_signer::CompressedPubKey;
4use openmina_core::constants::ConstraintConstants;
5
6use crate::{
7 scan_state::{
8 currency::{Amount, Fee, Magnitude},
9 scan_state::transaction_snark::work,
10 transaction_logic::{valid, CoinbaseFeeTransfer, GenericCommand},
11 },
12 MyCow,
13};
14
15use super::{
16 diff::AtMostTwo,
17 pre_diff_info::{fee_transfers_map, sum_fees},
18 staged_ledger::StagedLedger,
19};
20
21#[derive(Clone)]
22pub struct Discarded {
23 pub commands_rev: Vec<valid::UserCommand>,
24 pub completed_work: Vec<work::Checked>,
25}
26
27impl std::fmt::Debug for Discarded {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 let Self {
30 commands_rev,
31 completed_work,
32 } = self;
33
34 f.debug_struct("Discarded")
35 .field("commands_rev", &commands_rev.len())
36 .field("completed_work", &completed_work.len())
37 .finish()
38 }
39}
40
41impl Discarded {
42 fn add_user_command(&mut self, cmd: valid::UserCommand) {
43 self.commands_rev.push(cmd);
44 }
45
46 fn add_completed_work(&mut self, work: work::Checked) {
47 self.completed_work.push(work);
48 }
49}
50
51pub enum IncreaseBy {
52 One,
53 Two,
54}
55
56#[derive(Clone)]
57pub struct Resources {
58 max_space: u64,
59 max_jobs: u64,
60 pub commands_rev: Vec<valid::UserCommand>,
61 pub completed_work_rev: Vec<work::Checked>, fee_transfers: BTreeMap<CompressedPubKey, Fee>,
63 add_coinbase: bool,
64 pub coinbase: AtMostTwo<CoinbaseFeeTransfer>,
65 supercharge_coinbase: bool,
66 receiver_pk: CompressedPubKey,
67 budget: Result<Fee, String>,
68 pub discarded: Discarded,
69 is_coinbase_receiver_new: bool,
70 _logger: (),
71}
72
73impl std::fmt::Debug for Resources {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 let Self {
76 max_space,
77 max_jobs,
78 commands_rev,
79 completed_work_rev,
80 fee_transfers,
81 add_coinbase,
82 coinbase,
83 supercharge_coinbase,
84 receiver_pk,
85 budget,
86 discarded,
87 is_coinbase_receiver_new,
88 _logger,
89 } = self;
90
91 f.debug_struct("Resources")
92 .field("max_space", max_space)
93 .field("max_jobs", max_jobs)
94 .field("commands_rev", &commands_rev.len())
95 .field("completed_work_rev", &completed_work_rev.len())
96 .field("fee_transfers", fee_transfers)
97 .field("add_coinbase", add_coinbase)
98 .field("coinbase", coinbase)
99 .field("supercharge_coinbase", supercharge_coinbase)
100 .field("receiver_pk", receiver_pk)
101 .field("budget", budget)
102 .field("discarded", discarded)
103 .field("is_coinbase_receiver_new", is_coinbase_receiver_new)
104 .field("_logger", _logger)
105 .finish()
106 }
107}
108
109impl Resources {
110 fn coinbase_ft(work: work::Unchecked) -> Option<CoinbaseFeeTransfer> {
112 if !work.fee.is_zero() {
117 Some(CoinbaseFeeTransfer::create(work.prover, work.fee))
118 } else {
119 None
120 }
121 }
122
123 fn cheapest_two_work(works: &[work::Checked]) -> (Option<work::Work>, Option<work::Work>) {
125 let (w1, w2) = works
126 .iter()
127 .fold((None, None), |(w1, w2), w| match (w1, w2) {
128 (None, _) => (Some(w), None),
129 (Some(x), None) => {
130 if w.fee < x.fee {
131 (Some(w), w1)
132 } else {
133 (w1, Some(w))
134 }
135 }
136 (Some(x), Some(y)) => {
137 if w.fee < x.fee {
138 (Some(w), w1)
139 } else if w.fee < y.fee {
140 (w1, Some(w))
141 } else {
142 (w1, w2)
143 }
144 }
145 });
146
147 (w1.cloned(), w2.cloned())
148 }
149
150 fn coinbase_work(
152 constraint_constants: &ConstraintConstants,
153 is_two: Option<bool>,
154 works: &[work::Checked],
155 is_coinbase_receiver_new: bool,
156 supercharge_coinbase: bool,
157 ) -> Option<(AtMostTwo<CoinbaseFeeTransfer>, Vec<work::Work>)> {
158 let is_two = is_two.unwrap_or(false);
159
160 let (min1, min2) = Self::cheapest_two_work(works);
161
162 let diff = |ws: &[work::Unchecked], ws2: &[work::Statement]| -> Vec<work::Unchecked> {
163 ws.iter()
164 .filter(|w| {
165 let wstatement = w.statement();
166 !ws2.iter().any(|w2| &wstatement == w2)
167 })
168 .cloned()
169 .collect()
170 };
171
172 let coinbase_amount =
173 StagedLedger::coinbase_amount(supercharge_coinbase, constraint_constants)?;
174
175 let budget = if is_coinbase_receiver_new {
178 coinbase_amount
179 .checked_sub(&Amount::from_u64(constraint_constants.account_creation_fee))?
180 } else {
181 coinbase_amount
182 };
183
184 if is_two {
185 match (min1, min2) {
186 (None, _) => None,
187 (Some(w), None) => {
188 if Amount::of_fee(&w.fee) <= budget {
189 let stmt = w.statement();
190 let cb = AtMostTwo::Two(Self::coinbase_ft(w).map(|ft| (ft, None)));
191 Some((cb, diff(works, &[stmt])))
192 } else {
193 let cb = AtMostTwo::Two(None);
194 Some((cb, works.to_vec()))
195 }
196 }
197 (Some(w1), Some(w2)) => {
198 let sum = w1.fee.checked_add(&w2.fee)?;
199
200 if Amount::of_fee(&sum) < budget {
201 let stmt1 = w1.statement();
202 let stmt2 = w2.statement();
203 let cb = AtMostTwo::Two(
204 Self::coinbase_ft(w1).map(|ft| (ft, Self::coinbase_ft(w2))),
205 );
206
207 Some((cb, diff(works, &[stmt1, stmt2])))
216 } else if Amount::of_fee(&w1.fee) <= coinbase_amount {
217 let stmt = w1.statement();
218 let cb = AtMostTwo::Two(Self::coinbase_ft(w1).map(|ft| (ft, None)));
219 Some((cb, diff(works, &[stmt])))
220 } else {
221 let cb = AtMostTwo::Two(None);
222 Some((cb, works.to_vec()))
223 }
224 }
225 }
226 } else {
227 min1.map(|w| {
228 if Amount::of_fee(&w.fee) <= budget {
229 let stmt = w.statement();
230 let cb = AtMostTwo::One(Self::coinbase_ft(w));
231 (cb, diff(works, &[stmt]))
232 } else {
233 let cb = AtMostTwo::One(None);
234 (cb, works.to_vec())
235 }
236 })
237 }
238 }
239
240 fn init_coinbase_and_fee_transfers(
242 constraint_constants: &ConstraintConstants,
243 cw_seq: &[work::Unchecked],
244 add_coinbase: bool,
245 job_count: u64,
246 slots: u64,
247 is_coinbase_receiver_new: bool,
248 supercharge_coinbase: bool,
249 ) -> (AtMostTwo<CoinbaseFeeTransfer>, Vec<(CompressedPubKey, Fee)>) {
250 let cw_unchecked =
251 |works: Vec<work::Unchecked>| works.into_iter().map(|w| w.forget()).collect::<Vec<_>>();
252
253 let (coinbase, rem_cw) = match (
254 add_coinbase,
255 Self::coinbase_work(
256 constraint_constants,
257 None,
258 cw_seq,
259 is_coinbase_receiver_new,
260 supercharge_coinbase,
261 ),
262 ) {
263 (true, Some((ft, rem_cw))) => (ft, rem_cw),
264 (true, None) => {
265 if job_count == 0 || slots - job_count >= 1 {
267 (AtMostTwo::One(None), cw_seq.to_vec())
270 } else {
271 (AtMostTwo::Zero, cw_seq.to_vec())
272 }
273 }
274 _ => (AtMostTwo::Zero, cw_seq.to_vec()),
275 };
276
277 let rem_cw = cw_unchecked(rem_cw);
278 let singles = rem_cw
279 .into_iter()
280 .filter_map(|work::Unchecked { fee, prover, .. }| {
281 if fee.is_zero() {
282 None
283 } else {
284 Some((prover, fee))
285 }
286 })
287 .rev()
288 .collect();
289
290 (coinbase, singles)
291 }
292
293 pub fn init(
295 constraint_constants: &ConstraintConstants,
296 uc_seq: Vec<valid::UserCommand>,
297 mut cw_seq: Vec<work::Checked>,
298 (slots, job_count): (u64, u64),
299 receiver_pk: CompressedPubKey,
300 add_coinbase: bool,
301 supercharge_coinbase: bool,
302 _logger: (),
303 is_coinbase_receiver_new: bool,
304 ) -> Self {
305 let (coinbase, singles) = Self::init_coinbase_and_fee_transfers(
306 constraint_constants,
307 &cw_seq,
308 add_coinbase,
309 job_count,
310 slots,
311 is_coinbase_receiver_new,
312 supercharge_coinbase,
313 );
314
315 let fee_transfers = fee_transfers_map(singles.clone()).expect("OCaml throw here");
316
317 let budget1 = sum_fees(&uc_seq, |c| c.fee());
318 let budget2 = sum_fees(singles.iter().filter(|(k, _)| k != &receiver_pk), |c| c.1);
319
320 let budget = match (budget1, budget2) {
321 (Ok(r), Ok(c)) => r
322 .checked_sub(&c)
323 .ok_or_else(|| "budget did not suffice".to_string()),
324 (_, Err(e)) | (Err(e), _) => Err(e),
325 };
326
327 let discarded = Discarded {
328 commands_rev: Vec::with_capacity(256),
329 completed_work: Vec::with_capacity(256),
330 };
331
332 Self {
333 max_space: slots,
334 max_jobs: job_count,
335 commands_rev: uc_seq,
336 completed_work_rev: {
337 cw_seq.reverse();
339 cw_seq
340 },
341 fee_transfers,
342 add_coinbase,
343 coinbase,
344 supercharge_coinbase,
345 receiver_pk,
346 budget,
347 discarded,
348 is_coinbase_receiver_new,
349 _logger,
350 }
351 }
352
353 fn reselect_coinbase_work(&mut self, constraint_constants: &ConstraintConstants) {
355 let cw_unchecked = |works: &[work::Unchecked]| {
356 works.iter().map(|w| w.clone().forget()).collect::<Vec<_>>()
357 };
358
359 let (coinbase, rem_cw) = match &self.coinbase {
360 AtMostTwo::Zero => (None, MyCow::Borrow(&self.completed_work_rev)),
361 AtMostTwo::One(_) => {
362 match Self::coinbase_work(
363 constraint_constants,
364 None,
365 &self.completed_work_rev,
366 self.is_coinbase_receiver_new,
367 self.supercharge_coinbase,
368 ) {
369 None => (
370 Some(AtMostTwo::One(None)),
371 MyCow::Borrow(&self.completed_work_rev),
372 ),
373 Some((ft, rem_cw)) => (Some(ft), MyCow::Own(rem_cw)),
374 }
375 }
376 AtMostTwo::Two(_) => {
377 match Self::coinbase_work(
378 constraint_constants,
379 Some(true),
380 &self.completed_work_rev,
381 self.is_coinbase_receiver_new,
382 self.supercharge_coinbase,
383 ) {
384 None => (
385 Some(AtMostTwo::Two(None)),
386 MyCow::Borrow(&self.completed_work_rev),
387 ),
388 Some((fts, rem_cw)) => (Some(fts), MyCow::Own(rem_cw)),
389 }
390 }
391 };
392
393 let rem_cw = cw_unchecked(rem_cw.as_ref());
394
395 let singles = rem_cw
396 .into_iter()
397 .filter_map(|work::Unchecked { fee, prover, .. }| {
398 if fee.is_zero() {
399 None
400 } else {
401 Some((prover, fee))
402 }
403 })
404 .rev();
405
406 let fee_transfers = fee_transfers_map(singles).expect("OCaml throw here");
407
408 if let Some(coinbase) = coinbase {
409 self.coinbase = coinbase;
410 };
411 self.fee_transfers = fee_transfers;
412 }
413
414 fn rebudget(&self) -> Result<Fee, String> {
416 let payment_fees = sum_fees(&self.commands_rev, |c| c.fee());
418
419 let prover_fee_others =
420 self.fee_transfers
421 .iter()
422 .try_fold(Fee::zero(), |accum, (key, fee)| {
423 if &self.receiver_pk == key {
424 Ok(accum)
425 } else {
426 accum
427 .checked_add(fee)
428 .ok_or_else(|| "Fee overflow".to_string())
429 }
430 });
431
432 let revenue = payment_fees;
433 let cost = prover_fee_others;
434
435 match (revenue, cost) {
436 (Ok(r), Ok(c)) => r
437 .checked_sub(&c)
438 .ok_or_else(|| "budget did not suffice".to_string()),
439 (Err(e), _) | (_, Err(e)) => Err(e),
440 }
441 }
442
443 pub fn budget_sufficient(&self) -> bool {
445 self.budget.is_ok()
446 }
447
448 pub fn coinbase_added(&self) -> u64 {
450 match &self.coinbase {
451 AtMostTwo::Zero => 0,
452 AtMostTwo::One(_) => 1,
453 AtMostTwo::Two(_) => 2,
454 }
455 }
456
457 #[allow(clippy::bool_to_int_with_if)]
459 pub fn slots_occupied(&self) -> u64 {
460 let fee_for_self = match &self.budget {
461 Err(_) => 0,
462 Ok(b) => {
463 if b.is_zero() {
464 0
465 } else {
466 1
467 }
468 }
469 };
470
471 let other_provers = self
472 .fee_transfers
473 .iter()
474 .filter(|&(pk, _)| pk != &self.receiver_pk)
475 .count() as u64;
476
477 let total_fee_transfer_pks = other_provers + fee_for_self;
478
479 self.commands_rev.len() as u64 + ((total_fee_transfer_pks + 1) / 2) + self.coinbase_added()
480 }
481
482 #[allow(clippy::bool_to_int_with_if)]
483 pub fn slots_occupied_dbg(&self) -> u64 {
484 let fee_for_self = match &self.budget {
485 Err(_) => 0,
486 Ok(b) => {
487 if b.is_zero() {
488 0
489 } else {
490 1
491 }
492 }
493 };
494
495 let other_provers = self
496 .fee_transfers
497 .iter()
498 .filter(|&(pk, _)| pk != &self.receiver_pk)
499 .count() as u64;
500
501 let total_fee_transfer_pks = other_provers + fee_for_self;
502
503 self.commands_rev.len() as u64 + ((total_fee_transfer_pks + 1) / 2) + self.coinbase_added()
504 }
505
506 pub fn space_available(&self) -> bool {
508 let slots = self.slots_occupied();
509 self.max_space > slots
510 }
511
512 fn work_done(&self) -> bool {
514 let no_of_proof_bundles = self.completed_work_rev.len() as u64;
515 let slots = self.slots_occupied();
516
517 no_of_proof_bundles == self.max_jobs || slots <= self.max_space - self.max_jobs
520 }
521
522 pub fn space_constraint_satisfied(&self) -> bool {
524 let occupied = self.slots_occupied();
525 occupied <= self.max_space
526 }
527
528 pub fn work_constraint_satisfied(&self) -> bool {
530 let all_proofs = self.work_done();
532 let slots = self.slots_occupied();
534 let cw_count = self.completed_work_rev.len() as u64;
535 let enough_work = cw_count >= slots;
536 all_proofs || slots == 0 || enough_work
538 }
539
540 pub fn available_space(&self) -> u64 {
542 self.max_space - self.slots_occupied()
543 }
544
545 pub fn available_space_dbg(&self) -> u64 {
546 self.max_space - self.slots_occupied_dbg()
547 }
548
549 pub fn discard_last_work(
551 &mut self,
552 constraint_constants: &ConstraintConstants,
553 ) -> Option<work::Work> {
554 if self.completed_work_rev.is_empty() {
555 return None;
556 }
557
558 let w = self.completed_work_rev.remove(0);
559
560 let to_be_discarded_fee = w.fee;
561 self.discarded.add_completed_work(w.clone());
562
563 let current_budget = self.budget.clone();
564
565 self.reselect_coinbase_work(constraint_constants);
566
567 let budget = match current_budget {
568 Ok(b) => b
569 .checked_add(&to_be_discarded_fee)
570 .ok_or_else(|| "Currency overflow".to_string()),
571 Err(_) => self.rebudget(),
572 };
573
574 self.budget = budget;
575
576 Some(w)
577 }
578
579 pub fn discard_user_command(&mut self) -> Option<valid::UserCommand> {
581 if self.commands_rev.is_empty() {
582 let update_fee_transfers =
583 |this: &mut Self,
584 ft: CoinbaseFeeTransfer,
585 coinbase: AtMostTwo<CoinbaseFeeTransfer>| {
586 this.fee_transfers.insert(ft.receiver_pk, ft.fee);
587 this.coinbase = coinbase;
588 this.budget = this.rebudget();
589 };
590
591 match &self.coinbase {
592 AtMostTwo::Zero => {}
593 AtMostTwo::One(None) => {
594 self.coinbase = AtMostTwo::Zero;
595 }
596 AtMostTwo::Two(None) => {
597 self.coinbase = AtMostTwo::One(None);
598 }
599 AtMostTwo::Two(Some((ft, None))) => {
600 self.coinbase = AtMostTwo::One(Some(ft.clone()));
601 }
602 AtMostTwo::One(Some(ft)) => {
603 update_fee_transfers(self, ft.clone(), AtMostTwo::Zero);
604 }
605 AtMostTwo::Two(Some((ft1, Some(ft2)))) => {
606 update_fee_transfers(self, ft2.clone(), AtMostTwo::One(Some(ft1.clone())));
607 }
608 }
609
610 None
611 } else {
612 let uc = self.commands_rev.remove(0);
613
614 let current_budget = self.budget.clone();
615
616 let to_be_discarded_fee = uc.fee();
617 self.discarded.add_user_command(uc.clone());
618
619 let budget = match current_budget {
620 Ok(b) => b
621 .checked_sub(&to_be_discarded_fee)
622 .ok_or_else(|| "Fee insufficient".to_string()),
623 Err(_) => self.rebudget(),
624 };
625
626 self.budget = budget;
627
628 Some(uc)
629 }
630 }
631
632 pub fn worked_more(&self, constraint_constants: &ConstraintConstants) -> bool {
634 let mut this = self.clone(); this.discard_last_work(constraint_constants);
639
640 let slots = this.slots_occupied();
641 let cw_count = this.completed_work_rev.len() as u64;
642 cw_count > 0 && cw_count >= slots
643 }
644
645 pub fn incr_coinbase_part_by(
647 &mut self,
648 constraint_constants: &ConstraintConstants,
649 count: IncreaseBy,
650 ) {
651 let incr = |cb: &AtMostTwo<CoinbaseFeeTransfer>| {
652 Ok(match cb {
653 AtMostTwo::Zero => AtMostTwo::One(None),
654 AtMostTwo::One(None) => {
655 eprintln!("Coinbase one(none) to two");
656 AtMostTwo::Two(None)
657 }
658 AtMostTwo::One(Some(ft)) => {
659 eprintln!("Coinbase one(some) to two {:?}", ft);
660 AtMostTwo::Two(Some((ft.clone(), None)))
661 }
662 AtMostTwo::Two(_) => {
663 return Err("Coinbase count cannot be more than two".to_string())
664 }
665 })
666 };
667
668 let by_one = |this: &mut Self| -> Result<(), String> {
669 if !this.discarded.completed_work.is_empty() {
670 let coinbase = incr(&this.coinbase)?;
671 let w = this.discarded.completed_work.remove(0);
672
673 this.completed_work_rev.insert(0, w);
675 this.coinbase = coinbase;
676
677 this.reselect_coinbase_work(constraint_constants);
678 } else {
679 let coinbase = incr(&this.coinbase)?;
680 this.coinbase = coinbase;
681
682 if !this.work_done() {
683 return Err("Could not increment coinbase transaction count because of \
684 insufficient work"
685 .to_string());
686 }
687 }
688
689 Ok(())
690 };
691
692 let apply = |this: &mut Self| match count {
693 IncreaseBy::One => by_one(this),
694 IncreaseBy::Two => {
695 by_one(this)?;
696 by_one(this)
697 }
698 };
699
700 if let Err(e) = apply(self) {
701 eprintln!("Error when increasing coinbase: {:?}", e);
702 };
703 }
704}