1use crate::dust::DustActions;
15use crate::error::{MalformedTransaction, PartitionFailure};
16use crate::structure::{
17 ContractAction, ContractCall, ContractDeploy, Intent, LedgerParameters, MIN_PROOF_SIZE,
18 MaintenanceUpdate, ProofPreimageMarker, ProofPreimageVersioned, SignatureKind, SignaturesValue,
19 SingleUpdate, StandardTransaction, Transaction, UnshieldedOffer, UtxoOutput, UtxoSpend,
20};
21use crate::structure::{
22 EXPECTED_CONTRACT_DEPTH, EXPECTED_OPERATIONS_DEPTH, SegIntent, VERIFIER_KEY_SIZE,
23};
24use crate::structure::{PedersenDowngradeable, ProofKind};
25use base_crypto::cost_model::CostDuration;
26use base_crypto::cost_model::RunningCost;
27use base_crypto::fab::AlignedValue;
28use base_crypto::signatures::Signature;
29use base_crypto::signatures::SigningKey;
30use base_crypto::time::Timestamp;
31use coin_structure::coin::TokenType;
32use coin_structure::contract::ContractAddress;
33use itertools::Itertools;
34use onchain_runtime::context::{Effects, QueryContext, QueryResults};
35use onchain_runtime::error::TranscriptRejected;
36use onchain_runtime::ops::Op;
37use onchain_runtime::result_mode::ResultModeVerify;
38use onchain_runtime::state::{ContractOperation, ContractState, EntryPointBuf};
39use onchain_runtime::transcript::Transcript;
40use rand::{CryptoRng, Rng};
41use serde::{Deserialize, Serialize};
42use serialize::Serializable;
43use std::iter::once;
44use std::ops::Deref;
45use storage::Storable;
46use storage::arena::Sp;
47use storage::db::DB;
48use transient_crypto::commitment::PedersenRandomness;
49use transient_crypto::curve::Fr;
50use transient_crypto::fab::{AlignedValueExt, ValueReprAlignedValue};
51use transient_crypto::hash::transient_commit;
52use transient_crypto::proofs::{KeyLocation, ProofPreimage};
53use transient_crypto::repr::FieldRepr;
54use zswap::{
55 Input as ZswapInput, Offer as ZswapOffer, Output as ZswapOutput, Transient as ZswapTransient,
56};
57
58impl<S: SignatureKind<D>, D: DB> Transaction<S, ProofPreimageMarker, PedersenRandomness, D> {
59 pub fn from_intents(
60 network_id: impl Into<String>,
61 intents: storage::storage::HashMap<
62 u16,
63 Intent<S, ProofPreimageMarker, PedersenRandomness, D>,
64 D,
65 >,
66 ) -> Self {
67 Self::new(network_id, intents, None, storage::storage::HashMap::new())
68 }
69}
70
71impl<S: SignatureKind<D>, D: DB>
72 StandardTransaction<S, ProofPreimageMarker, PedersenRandomness, D>
73{
74 pub fn new(
75 network_id: impl Into<String>,
76 intents: storage::storage::HashMap<
77 u16,
78 Intent<S, ProofPreimageMarker, PedersenRandomness, D>,
79 D,
80 >,
81 guaranteed_coins: Option<ZswapOffer<ProofPreimage, D>>,
82 fallible_coins: storage::storage::HashMap<u16, ZswapOffer<ProofPreimage, D>, D>,
83 ) -> Self {
84 let mut res = StandardTransaction {
85 network_id: network_id.into(),
86 binding_randomness: Default::default(),
87 intents,
88 guaranteed_coins: guaranteed_coins.map(|x| Sp::new(x)),
89 fallible_coins: fallible_coins.into_iter().collect(),
90 };
91 res.recompute_binding_randomness();
92 res
93 }
94
95 pub fn add_calls<P>(
96 &self,
97 rng: &mut (impl Rng + CryptoRng),
98 segment: SegmentSpecifier,
99 calls: &[PrePartitionContractCall<D>],
100 params: &LedgerParameters,
101 ttl: Timestamp,
102 zswap_inputs: &[ZswapInput<ProofPreimage, D>],
103 zswap_outputs: &[ZswapOutput<ProofPreimage, D>],
104 zswap_transients: &[ZswapTransient<ProofPreimage, D>],
105 ) -> Result<Self, PartitionFailure<D>>
106 where
107 P: ContractCallExt<D>,
108 {
109 let pre_transcripts = calls
110 .iter()
111 .map(|call| PreTranscript {
112 comm_comm: Some(communication_commitment(
113 call.input.clone(),
114 call.output.clone(),
115 call.communication_commitment_rand,
116 )),
117 ..call.pre_transcript.clone()
118 })
119 .collect::<Vec<_>>();
120 let partitioned = partition_transcripts(&pre_transcripts, params)?;
121 let segment = match segment {
122 SegmentSpecifier::First => 1,
123 SegmentSpecifier::GuaranteedOnly
124 if partitioned.iter().any(|(_, snd)| snd.is_some()) =>
125 {
126 return Err(PartitionFailure::GuaranteedOnlyUnsatisfied);
127 }
128 SegmentSpecifier::GuaranteedOnly | SegmentSpecifier::Random => rng.r#gen(),
129 SegmentSpecifier::Specific(0) => return Err(PartitionFailure::IllegalSegmentZero),
130 SegmentSpecifier::Specific(seg) => seg,
131 };
132 let prototypes = calls
133 .iter()
134 .zip(partitioned)
135 .map(|(call, (guaranteed, fallible))| ContractCallPrototype {
136 address: call.address,
137 entry_point: call.entry_point.clone(),
138 op: call.op.clone(),
139 guaranteed_public_transcript: guaranteed,
140 fallible_public_transcript: fallible,
141 private_transcript_outputs: call.private_transcript_outputs.clone(),
142 input: call.input.clone(),
143 output: call.output.clone(),
144 communication_commitment_rand: call.communication_commitment_rand,
145 key_location: call.key_location.clone(),
146 })
147 .collect::<Vec<_>>();
148 let zswap_input_is_fallible = zswap_inputs
149 .iter()
150 .map(|inp| {
151 prototypes.iter().any(|pt| {
152 pt.fallible_public_transcript
153 .as_ref()
154 .is_some_and(|pt| pt.effects.claimed_nullifiers.member(&inp.nullifier))
155 })
156 })
157 .collect::<Vec<_>>();
158 let zswap_output_is_fallible = zswap_outputs
159 .iter()
160 .map(|out| {
161 prototypes.iter().any(|pt| {
162 pt.fallible_public_transcript.as_ref().is_some_and(|pt| {
163 pt.effects
164 .claimed_shielded_spends
165 .union(&pt.effects.claimed_shielded_receives)
166 .member(&out.coin_com)
167 })
168 })
169 })
170 .collect::<Vec<_>>();
171 let zswap_transient_is_fallible = zswap_transients
172 .iter()
173 .map(|trans| {
174 prototypes.iter().any(|pt| {
175 pt.fallible_public_transcript.as_ref().is_some_and(|pt| {
176 pt.effects
177 .claimed_shielded_spends
178 .union(&pt.effects.claimed_shielded_receives)
179 .member(&trans.coin_com)
180 || pt.effects.claimed_nullifiers.member(&trans.nullifier)
181 })
182 })
183 })
184 .collect::<Vec<_>>();
185 let intent_before = self
186 .intents
187 .get(&segment)
188 .map(|i| (*i).clone())
189 .unwrap_or_else(|| Intent::empty(rng, ttl));
190 let intent = prototypes
191 .into_iter()
192 .fold(intent_before, |i, proto| i.add_call::<P>(proto));
193 let mut res = self.set_intent(segment, intent);
194 let guaranteed_coins = ZswapOffer::new(
195 zswap_inputs
196 .iter()
197 .zip(zswap_input_is_fallible.iter())
198 .filter(|(_, f)| !**f)
199 .map(|(i, _)| i.clone())
200 .collect(),
201 zswap_outputs
202 .iter()
203 .zip(zswap_output_is_fallible.iter())
204 .filter(|(_, f)| !**f)
205 .map(|(o, _)| o.clone())
206 .collect(),
207 zswap_transients
208 .iter()
209 .zip(zswap_transient_is_fallible.iter())
210 .filter(|(_, f)| !**f)
211 .map(|(t, _)| t.clone())
212 .collect(),
213 )
214 .map(|o| o.retarget_segment(0));
215 let fallible_coins = ZswapOffer::new(
216 zswap_inputs
217 .iter()
218 .zip(zswap_input_is_fallible.iter())
219 .filter(|(_, f)| **f)
220 .map(|(i, _)| i.clone())
221 .collect(),
222 zswap_outputs
223 .iter()
224 .zip(zswap_output_is_fallible.iter())
225 .filter(|(_, f)| **f)
226 .map(|(o, _)| o.clone())
227 .collect(),
228 zswap_transients
229 .iter()
230 .zip(zswap_transient_is_fallible.iter())
231 .filter(|(_, f)| **f)
232 .map(|(t, _)| t.clone())
233 .collect(),
234 )
235 .map(|o| o.retarget_segment(segment));
236 if let Some(offer) = guaranteed_coins {
237 res.guaranteed_coins = Some(Sp::new(if let Some(old_offer) = &res.guaranteed_coins {
238 old_offer.merge(&offer).map_err(PartitionFailure::Merge)?
239 } else {
240 offer
241 }));
242 }
243 if let Some(offer) = fallible_coins {
244 let new_offer = if let Some(old_offer) = res.fallible_coins.get(&segment) {
245 old_offer.merge(&offer).map_err(PartitionFailure::Merge)?
246 } else {
247 offer
248 };
249 res.fallible_coins = res.fallible_coins.insert(segment, new_offer);
250 }
251 res.recompute_binding_randomness();
252 Ok(res)
253 }
254
255 pub fn set_intent(
256 &self,
257 segment: u16,
258 intent: Intent<S, ProofPreimageMarker, PedersenRandomness, D>,
259 ) -> Self {
260 let mut res = self.clone();
261 res.intents = self.intents.insert(segment, intent);
262 res.recompute_binding_randomness();
263 res
264 }
265
266 pub fn recompute_binding_randomness(&mut self) {
267 self.binding_randomness = self
268 .guaranteed_coins
269 .as_ref()
270 .map(|o| o.binding_randomness())
271 .unwrap_or_else(|| PedersenRandomness::from(0))
272 + self
273 .fallible_coins
274 .values()
275 .fold(PedersenRandomness::from(0), |acc, o| {
276 acc + o.binding_randomness()
277 })
278 + self
279 .intents
280 .values()
281 .map(|i| i.binding_randomness())
282 .fold(0.into(), |a, b| a + b);
283 }
284}
285
286impl<D: DB> ContractDeploy<D> {
287 pub fn new<R: Rng + CryptoRng + ?Sized>(rng: &mut R, initial_state: ContractState<D>) -> Self {
288 ContractDeploy {
289 initial_state,
290 nonce: rng.r#gen(),
291 }
292 }
293}
294
295impl<D: DB> MaintenanceUpdate<D> {
296 pub fn new(address: ContractAddress, updates: Vec<SingleUpdate>, counter: u32) -> Self {
297 MaintenanceUpdate {
298 address,
299 updates: updates.into(),
300 counter,
301 signatures: vec![].into(),
302 }
303 }
304
305 pub fn add_signature(mut self, idx: u32, signature: Signature) -> Self {
306 self.signatures = self
307 .signatures
308 .push(SignaturesValue(idx, signature))
309 .iter_deref()
310 .cloned()
311 .sorted()
312 .collect();
313 self
314 }
315}
316
317impl<S: SignatureKind<D>, D: DB> Intent<S, ProofPreimageMarker, PedersenRandomness, D> {
318 pub fn empty<R: Rng + CryptoRng + ?Sized>(
319 rng: &mut R,
320 ttl: Timestamp,
321 ) -> Intent<S, ProofPreimageMarker, PedersenRandomness, D> {
322 Intent::new(rng, None, None, vec![], vec![], vec![], None, ttl)
323 }
324
325 #[allow(clippy::too_many_arguments)]
326 pub fn new<R: Rng + CryptoRng + ?Sized>(
327 rng: &mut R,
328 guaranteed_unshielded_offer: Option<UnshieldedOffer<S, D>>,
329 fallible_unshielded_offer: Option<UnshieldedOffer<S, D>>,
330 calls: Vec<ContractCallPrototype<D>>,
331 updates: Vec<MaintenanceUpdate<D>>,
332 deploys: Vec<ContractDeploy<D>>,
333 dust_actions: Option<DustActions<S, ProofPreimageMarker, D>>,
334 ttl: Timestamp,
335 ) -> Intent<S, ProofPreimageMarker, PedersenRandomness, D> {
336 let intent = Intent {
337 guaranteed_unshielded_offer: guaranteed_unshielded_offer.map(|x| Sp::new(x)),
338 fallible_unshielded_offer: fallible_unshielded_offer.map(|x| Sp::new(x)),
339 actions: vec![].into(),
340 dust_actions: dust_actions.map(Sp::new),
341 ttl,
342 binding_commitment: rng.r#gen(),
343 };
344
345 let intent = calls
346 .into_iter()
347 .fold(intent, |acc, x| acc.add_call::<ProofPreimage>(x));
348
349 let intent = updates
350 .into_iter()
351 .fold(intent, |acc, x| acc.add_maintenance_update(x));
352
353 deploys.into_iter().fold(intent, |acc, x| acc.add_deploy(x))
354 }
355}
356
357impl<
358 S: SignatureKind<D>,
359 P: ProofKind<D>,
360 B: Storable<D> + PedersenDowngradeable<D> + Serializable,
361 D: DB,
362> Intent<S, P, B, D>
363{
364 #[allow(clippy::result_large_err)]
365 pub fn sign(
366 mut self,
367 rng: &mut (impl Rng + CryptoRng),
368 segment_id: u16,
369 guaranteed_signing_keys: &[SigningKey],
370 fallible_signing_keys: &[SigningKey],
371 dust_registration_signing_keys: &[SigningKey],
372 ) -> Result<Self, MalformedTransaction<D>>
373 where
374 UnshieldedOffer<S, D>: Clone,
375 P: ProofKind<D>,
376 {
377 let data = self
378 .erase_proofs()
379 .erase_signatures()
380 .data_to_sign(segment_id);
381
382 let mut sign_unshielded_offers =
383 |unshielded_offer: &mut Option<Sp<UnshieldedOffer<S, D>, D>>,
384 signing_keys: &[SigningKey]|
385 -> Result<(), MalformedTransaction<D>> {
386 if let Some(offer) = unshielded_offer {
387 let signatures: Vec<<S as SignatureKind<D>>::Signature<SegIntent<D>>> = offer
388 .inputs
389 .iter()
390 .zip(signing_keys)
391 .map(|(spend, sk)| {
392 if spend.owner != sk.verifying_key() {
393 return Err(MalformedTransaction::<D>::IntentSignatureKeyMismatch);
394 }
395 Ok(S::sign(sk, rng, &data))
396 })
397 .collect::<Result<Vec<_>, _>>()?;
398
399 let mut new = (*offer).deref().clone();
400 new.add_signatures(signatures);
401 *unshielded_offer = Some(Sp::new(new.clone()));
402 }
403 Ok(())
404 };
405
406 sign_unshielded_offers(
407 &mut self.guaranteed_unshielded_offer,
408 guaranteed_signing_keys,
409 )?;
410 sign_unshielded_offers(&mut self.fallible_unshielded_offer, fallible_signing_keys)?;
411
412 if let Some(da) = self.dust_actions {
413 let registrations = da
414 .registrations
415 .iter()
416 .zip(dust_registration_signing_keys.iter())
417 .map(|(reg, sk)| {
418 let mut reg = (*reg).clone();
419 if reg.night_key != sk.verifying_key() {
420 return Err(MalformedTransaction::<D>::IntentSignatureKeyMismatch);
421 }
422 reg.signature = Some(Sp::new(S::sign(sk, rng, &data)));
423 Ok(reg)
424 })
425 .collect::<Result<_, _>>()?;
426 self.dust_actions = Some(Sp::new(DustActions {
427 registrations,
428 spends: da.spends.clone(),
429 ctime: da.ctime,
430 }));
431 }
432
433 Ok(self)
434 }
435}
436
437impl<S: SignatureKind<D>, D: DB> Transaction<S, ProofPreimageMarker, PedersenRandomness, D> {
438 pub fn new(
439 network_id: impl Into<String>,
440 intents: storage::storage::HashMap<
441 u16,
442 Intent<S, ProofPreimageMarker, PedersenRandomness, D>,
443 D,
444 >,
445 guaranteed_coins: Option<ZswapOffer<ProofPreimage, D>>,
446 fallible_coins: storage::storage::HashMap<u16, ZswapOffer<ProofPreimage, D>, D>,
447 ) -> Self {
448 let mut stx = StandardTransaction {
449 network_id: network_id.into(),
450 intents,
451 guaranteed_coins: guaranteed_coins.map(|x| Sp::new(x.retarget_segment(0))),
452 fallible_coins: fallible_coins
453 .into_iter()
454 .map(|(seg, offer)| (seg, offer.retarget_segment(seg)))
455 .collect(),
456 binding_randomness: Default::default(),
457 };
458 stx.recompute_binding_randomness();
459 Transaction::Standard(stx)
460 }
461}
462
463#[derive_where::derive_where(Clone, Debug)]
464pub struct PrePartitionContractCall<D: DB> {
465 pub address: ContractAddress,
466 pub entry_point: EntryPointBuf,
467 pub op: ContractOperation,
468 pub pre_transcript: PreTranscript<D>,
469 pub private_transcript_outputs: Vec<AlignedValue>,
470 pub input: AlignedValue,
471 pub output: AlignedValue,
472 pub communication_commitment_rand: Fr,
473 pub key_location: KeyLocation,
474}
475
476#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
477#[serde(tag = "tag", content = "value", rename_all = "camelCase")]
478pub enum SegmentSpecifier {
479 First,
480 GuaranteedOnly,
481 Random,
482 Specific(u16),
483}
484
485#[derive(Clone, Debug)]
486pub struct ContractCallPrototype<D: DB> {
487 pub address: ContractAddress,
488 pub entry_point: EntryPointBuf,
489 pub op: ContractOperation,
490 pub guaranteed_public_transcript: Option<Transcript<D>>,
491 pub fallible_public_transcript: Option<Transcript<D>>,
492 pub private_transcript_outputs: Vec<AlignedValue>,
493 pub input: AlignedValue,
494 pub output: AlignedValue,
495 pub communication_commitment_rand: Fr,
496 pub key_location: KeyLocation,
497}
498
499pub trait ContractCallExt<D: DB> {
500 type AssociatedProof;
501 fn construct_proof(
502 call: &ContractCallPrototype<D>,
503 communication_commitment: Fr,
504 ) -> ProofPreimageVersioned;
505}
506
507impl<D: DB> ContractCallExt<D> for ProofPreimage {
508 type AssociatedProof = ProofPreimage;
509 fn construct_proof(
510 call: &ContractCallPrototype<D>,
511 communication_commitment: Fr,
512 ) -> ProofPreimageVersioned {
513 let inputs = ValueReprAlignedValue(call.input.clone()).field_vec();
514 let mut private_transcript = Vec::with_capacity(
515 call.private_transcript_outputs
516 .iter()
517 .map(|o| o.value_only_field_size())
518 .sum(),
519 );
520 for o in call.private_transcript_outputs.iter() {
521 o.value_only_field_repr(&mut private_transcript);
522 }
523 let public_transcript_iter = call
524 .guaranteed_public_transcript
525 .iter()
526 .flat_map(|t| t.program.iter_deref())
527 .chain(
528 call.fallible_public_transcript
529 .iter()
530 .flat_map(|t| t.program.iter_deref()),
531 );
532
533 let public_transcript_ops: Vec<_> = public_transcript_iter.collect();
534
535 let mut public_transcript_inputs =
536 Vec::with_capacity(public_transcript_ops.iter().map(|op| op.field_size()).sum());
537
538 for op in &public_transcript_ops {
539 op.field_repr(&mut public_transcript_inputs);
540 }
541
542 let mut public_transcript_outputs = Vec::new();
543 for op in &public_transcript_ops {
544 if let Op::Popeq { result, .. } = op {
545 result.value_only_field_repr(&mut public_transcript_outputs);
546 }
547 }
548
549 let binding_input = 0u8.into();
552
553 let proof = ProofPreimage {
554 inputs,
555 private_transcript,
556 public_transcript_inputs,
557 public_transcript_outputs,
558 binding_input,
559 communications_commitment: Some((
560 communication_commitment,
561 call.communication_commitment_rand,
562 )),
563 key_location: call.key_location.clone(),
564 };
565
566 ProofPreimageVersioned::V2(std::sync::Arc::new(proof))
567 }
568}
569
570impl<D: DB> ContractCall<ProofPreimageMarker, D> {
571 pub fn new(
572 address: ContractAddress,
573 entry_point: EntryPointBuf,
574 guaranteed_transcript: Option<Transcript<D>>,
575 fallible_transcript: Option<Transcript<D>>,
576 communication_commitment: Fr,
577 proof: ProofPreimageVersioned,
578 ) -> Self {
579 ContractCall {
580 address,
581 entry_point,
582 guaranteed_transcript: guaranteed_transcript.map(Sp::new),
583 fallible_transcript: fallible_transcript.map(Sp::new),
584 communication_commitment,
585 proof,
586 }
587 }
588}
589
590impl<S: SignatureKind<D>, D: DB> Intent<S, ProofPreimageMarker, PedersenRandomness, D> {
591 pub fn add_call<P>(&self, call: ContractCallPrototype<D>) -> Self
592 where
593 P: ContractCallExt<D>,
594 {
595 let mut io_repr = Vec::with_capacity(
596 call.input.value_only_field_size() + call.output.value_only_field_size(),
597 );
598 call.input.value_only_field_repr(&mut io_repr);
599 call.output.value_only_field_repr(&mut io_repr);
600 let communication_commitment =
601 transient_commit(&io_repr, call.communication_commitment_rand);
602
603 let proof = P::construct_proof(&call, communication_commitment);
604 let call = ContractCall {
605 address: call.address,
606 entry_point: call.entry_point,
607 guaranteed_transcript: call.guaranteed_public_transcript.map(|x| Sp::new(x)),
608 fallible_transcript: call.fallible_public_transcript.map(|x| Sp::new(x)),
609 communication_commitment,
610 proof,
611 };
612 let mut actions = Vec::new();
616 let mut already_inserted = false;
617 fn references<D: DB>(
618 caller: &ContractCall<ProofPreimageMarker, D>,
619 callee: &ContractAction<ProofPreimageMarker, D>,
620 ) -> bool {
621 match callee {
622 ContractAction::Call(call) => caller
623 .guaranteed_transcript
624 .iter()
625 .chain(caller.fallible_transcript.iter())
626 .flat_map(|t| {
627 t.effects
628 .claimed_contract_calls
629 .iter()
630 .map(|x| (*x).deref().into_inner())
631 })
632 .any(|(_seq, addr, ep_hash, cc)| {
633 addr == call.address
634 && cc == call.communication_commitment
635 && ep_hash == call.entry_point.ep_hash()
636 }),
637 _ => false,
638 }
639 }
640 for c in self.actions.iter_deref().cloned() {
641 if !already_inserted && references(&call, &c) {
642 actions.push(call.clone().into());
643 already_inserted = true;
644 }
645 actions.push(c);
646 }
647 if !already_inserted {
648 actions.push(call.into());
649 }
650 Intent {
651 guaranteed_unshielded_offer: self.guaranteed_unshielded_offer.clone(),
652 fallible_unshielded_offer: self.fallible_unshielded_offer.clone(),
653 actions: actions.into(),
654 dust_actions: self.dust_actions.clone(),
655 ttl: self.ttl,
656 binding_commitment: self.binding_commitment,
657 }
658 }
659
660 pub fn add_deploy(&self, deploy: ContractDeploy<D>) -> Self {
661 Intent {
662 guaranteed_unshielded_offer: self.guaranteed_unshielded_offer.clone(),
663 fallible_unshielded_offer: self.fallible_unshielded_offer.clone(),
664 actions: self
665 .actions
666 .iter_deref()
667 .cloned()
668 .chain(once(deploy.into()))
669 .collect(),
670 dust_actions: self.dust_actions.clone(),
671 ttl: self.ttl,
672 binding_commitment: self.binding_commitment,
673 }
674 }
675
676 pub fn add_maintenance_update(&self, upd: MaintenanceUpdate<D>) -> Self {
677 Intent {
678 guaranteed_unshielded_offer: self.guaranteed_unshielded_offer.clone(),
679 fallible_unshielded_offer: self.fallible_unshielded_offer.clone(),
680 actions: self
681 .actions
682 .iter_deref()
683 .cloned()
684 .chain(once(upd.into()))
685 .collect(),
686 dust_actions: self.dust_actions.clone(),
687 ttl: self.ttl,
688 binding_commitment: self.binding_commitment,
689 }
690 }
691
692 pub fn binding_randomness(&self) -> PedersenRandomness {
693 self.binding_commitment
694 }
695}
696
697#[derive(Debug)]
698pub struct PreTranscript<D: DB> {
699 pub context: QueryContext<D>,
700 pub program: Vec<Op<ResultModeVerify, D>>,
701 pub comm_comm: Option<Fr>,
702}
703
704impl<D: DB> Clone for PreTranscript<D> {
705 fn clone(&self) -> Self {
706 PreTranscript {
707 context: self.context.clone(),
708 program: self.program.clone(),
709 comm_comm: self.comm_comm,
710 }
711 }
712}
713
714impl<D: DB> PreTranscript<D> {
715 fn no_checkpoints(&self) -> usize {
716 self.program
717 .iter()
718 .filter(|op| matches!(op, Op::Ckpt))
719 .count()
720 }
721
722 fn run_to_ckpt_no(
724 &self,
725 mut n: usize,
726 params: &LedgerParameters,
727 ) -> Result<QueryResults<ResultModeVerify, D>, TranscriptRejected<D>> {
728 n += 1;
729 let prog = self
730 .program
731 .iter()
732 .cloned()
733 .take_while(|op| {
734 if matches!(op, Op::Ckpt) {
735 n -= 1;
736 n != 0
737 } else {
738 true
739 }
740 })
741 .collect::<Vec<_>>();
742 self.context
743 .query(&prog, None, ¶ms.cost_model.runtime_cost_model)
744 }
745
746 fn guaranteed_budget(&self, params: &LedgerParameters) -> CostDuration {
747 let est_size = MIN_PROOF_SIZE
748 + Serializable::serialized_size(&(
749 ContractAddress::default(),
750 Effects::<D>::default(),
751 &self.program.iter().collect::<Vec<_>>(),
752 &self.comm_comm,
753 ));
754 params.limits.time_to_dismiss_per_byte * est_size as u64
755 }
756
757 #[allow(clippy::type_complexity)]
759 fn split_at(
760 &self,
761 mut n: usize,
762 params: &LedgerParameters,
763 ) -> Result<(Option<Transcript<D>>, Option<Transcript<D>>), TranscriptRejected<D>> {
764 let mut prog_guaranteed = Vec::new();
765 let mut prog_fallible = Vec::new();
766 for op in self.program.iter() {
767 if n > 0 && matches!(op, Op::Ckpt) {
768 n -= 1;
769 }
770 if n == 0 {
771 prog_fallible.push(op.clone());
772 } else {
773 prog_guaranteed.push(op.clone());
774 }
775 }
776 let guaranteed_res = self.context.query(
777 &prog_guaranteed,
778 None,
779 ¶ms.cost_model.runtime_cost_model,
780 )?;
781 let mut continuation_context = guaranteed_res.context.clone();
782 continuation_context.effects = Effects::default();
783 let fallible_res = continuation_context.query(
784 &prog_fallible,
785 None,
786 ¶ms.cost_model.runtime_cost_model,
787 )?;
788 let mk_transcript = |prog: Vec<Op<ResultModeVerify, D>>,
789 res: QueryResults<ResultModeVerify, D>| {
790 if prog.is_empty() {
791 None
792 } else {
793 Some(Transcript {
794 gas: res.gas_heuristic(params, false, 0),
795 effects: res.context.effects.clone(),
796 program: prog.into(),
797 version: Some(Sp::new(Transcript::<D>::VERSION)),
798 })
799 }
800 };
801 Ok((
802 mk_transcript(prog_guaranteed, guaranteed_res),
803 mk_transcript(prog_fallible, fallible_res),
804 ))
805 }
806}
807
808trait QueryResultsExt {
809 fn gas_heuristic(
810 &self,
811 params: &LedgerParameters,
812 include_external: bool,
813 public_input_count: usize,
814 ) -> RunningCost;
815}
816
817fn test_tx_from_unshielded_offer<D: DB>(
818 offer: UnshieldedOffer<Signature, D>,
819) -> Transaction<Signature, ProofPreimageMarker, PedersenRandomness, D> {
820 use rand::{SeedableRng, rngs::StdRng};
821 let intent = Intent::new(
822 &mut StdRng::seed_from_u64(0x42),
823 Some(offer),
824 None,
825 Vec::new(),
826 Vec::new(),
827 Vec::new(),
828 None,
829 Timestamp::from_secs(0),
830 );
831 Transaction::from_intents("local-test", [(1, intent)].into_iter().collect())
832}
833impl<D: DB> QueryResultsExt for QueryResults<ResultModeVerify, D> {
834 fn gas_heuristic(
835 &self,
836 params: &LedgerParameters,
837 include_external: bool,
838 public_input_count: usize,
839 ) -> RunningCost {
840 let transcript_gas_cost_with_overhead = self.gas_cost * 1.2;
841 if !include_external {
842 return transcript_gas_cost_with_overhead;
843 }
844 let expected_unshielded_inputs_overhead = self
845 .context
846 .effects
847 .unshielded_inputs
848 .keys()
849 .map(|tt| {
850 let TokenType::Unshielded(tt) = tt else {
851 unreachable!()
852 };
853 let input = UtxoSpend {
854 intent_hash: Default::default(),
855 output_no: 0,
856 owner: Default::default(),
857 type_: tt,
858 value: 1,
859 };
860 let output = UtxoOutput {
861 owner: Default::default(),
862 type_: tt,
863 value: 1,
864 };
865 let offer = UnshieldedOffer::<_, D> {
866 inputs: vec![input].into(),
867 outputs: vec![output].into(),
868 signatures: Default::default(),
869 };
870 let tx = test_tx_from_unshielded_offer(offer);
871 tx.cost(params, false)
872 .map(RunningCost::from)
873 .unwrap_or(RunningCost::ZERO)
874 })
875 .sum();
876 let expected_unshielded_outputs_overhead = self
877 .context
878 .effects
879 .unshielded_outputs
880 .keys()
881 .map(|tt| {
882 let TokenType::Unshielded(tt) = tt else {
883 unreachable!()
884 };
885 let output = UtxoOutput {
886 owner: Default::default(),
887 type_: tt,
888 value: 1,
889 };
890 let offer = UnshieldedOffer::<_, D> {
891 inputs: Default::default(),
892 outputs: vec![output].into(),
893 signatures: Default::default(),
894 };
895 let tx = test_tx_from_unshielded_offer(offer);
896 tx.cost(params, false)
897 .map(RunningCost::from)
898 .unwrap_or(RunningCost::ZERO)
899 })
900 .sum();
901 let proof_verify = params.cost_model.proof_verify(public_input_count)
902 + params.cost_model.cell_read(VERIFIER_KEY_SIZE as u64)
903 + params.cost_model.map_index(EXPECTED_CONTRACT_DEPTH)
904 + params.cost_model.map_index(EXPECTED_OPERATIONS_DEPTH);
905 transcript_gas_cost_with_overhead
906 + expected_unshielded_inputs_overhead
907 + expected_unshielded_outputs_overhead
908 + proof_verify
909 }
910}
911
912pub fn communication_commitment(input: AlignedValue, output: AlignedValue, rand: Fr) -> Fr {
913 transient_commit(&AlignedValue::concat([&input, &output]), rand)
914}
915
916pub type TranscriptPair<D> = (Option<Transcript<D>>, Option<Transcript<D>>);
917
918pub fn partition_transcripts<D: DB>(
919 calls: &[PreTranscript<D>],
920 params: &LedgerParameters,
921) -> Result<Vec<TranscriptPair<D>>, PartitionFailure<D>> {
922 let n = calls.len();
923 let no_ckpts = calls
928 .iter()
929 .map(PreTranscript::no_checkpoints)
930 .collect::<Vec<_>>();
931 let full_runs = calls
932 .iter()
933 .zip(no_ckpts.iter())
934 .map(|(pt, n)| pt.run_to_ckpt_no(*n, params))
935 .collect::<Result<Vec<_>, _>>()?;
936 let call_graph = full_runs
939 .iter()
940 .map(|qr| {
941 let claimed_commitments = qr
942 .context
943 .effects
944 .claimed_contract_calls
945 .iter()
946 .map(|sp| (*sp).deref().into_inner().3)
947 .collect::<Vec<_>>();
948 (0..n)
949 .filter(|i| {
950 calls[*i]
951 .comm_comm
952 .map(|comm| claimed_commitments.contains(&comm))
953 .unwrap_or(false)
954 })
955 .collect::<Vec<_>>()
956 })
957 .collect::<Vec<_>>();
958 for i in 0..n {
964 let mut visited = vec![i];
965 let mut reachable = call_graph[i].clone();
966 while let Some(next) = reachable.pop() {
967 if visited.contains(&next) {
968 return Err(PartitionFailure::NonForest);
969 }
970 visited.push(next);
971 reachable.extend(&call_graph[next]);
972 }
973 }
974
975 let n_callers = (0..n)
979 .map(|i| (0..n).filter(|j| call_graph[*j].contains(&i)).count())
980 .collect::<Vec<_>>();
981 if n_callers.iter().any(|n| *n > 1) {
982 return Err(PartitionFailure::NonForest);
983 }
984 let root_nodes = n_callers
985 .iter()
986 .enumerate()
987 .filter(|(_, n)| **n == 0)
988 .map(|(i, _)| i)
989 .collect::<Vec<_>>();
990
991 let closures = root_nodes
993 .iter()
994 .map(|r| {
995 let mut visited = vec![];
996 let mut frontier = vec![*r];
997 while let Some(item) = frontier.pop() {
998 visited.push(item);
999 frontier.extend(&call_graph[item]);
1000 }
1001 visited
1002 })
1003 .collect::<Vec<_>>();
1004 let mut closure_budgets = closures
1005 .iter()
1006 .map(|closure| {
1007 closure
1008 .iter()
1009 .map(|i| calls[*i].guaranteed_budget(params))
1010 .sum::<CostDuration>()
1011 })
1012 .collect::<Vec<_>>();
1013 let spare_min_time_to_dismiss =
1015 params.limits.min_time_to_dismiss * 0.7f64 - closure_budgets.iter().copied().sum();
1016 for budget in closure_budgets.iter_mut() {
1017 *budget += spare_min_time_to_dismiss * (1.0 / closures.len() as f64);
1018 }
1019
1020 let preliminary_results = root_nodes
1031 .iter()
1032 .enumerate()
1033 .map(|(root_n, &root)| {
1034 for n in (1..no_ckpts[root] + 2).rev() {
1038 let partial_res = calls[root].run_to_ckpt_no(n - 1, params)?;
1039 let claimed = partial_res
1040 .context
1041 .effects
1042 .claimed_contract_calls
1043 .iter()
1044 .map(|sp| (*sp).deref().into_inner().3)
1045 .collect::<Vec<_>>();
1046 let claimed_idx = calls
1047 .iter()
1048 .enumerate()
1049 .filter(|(_, pt)| {
1050 pt.comm_comm
1051 .map(|cc| claimed.contains(&cc))
1052 .unwrap_or(false)
1053 })
1054 .map(|(i, _)| i)
1055 .collect::<Vec<_>>();
1056 let public_input_count = calls[root].program.field_size() + 2;
1057 let mut required_budget = partial_res
1058 .gas_heuristic(params, true, public_input_count)
1059 .max_time();
1060 let mut frontier = claimed_idx.clone();
1061 while let Some(next) = frontier.pop() {
1062 required_budget += full_runs[next]
1063 .gas_heuristic(params, true, public_input_count)
1064 .max_time();
1065 frontier.extend(&call_graph[next]);
1066 }
1067 if required_budget <= closure_budgets[root_n] {
1068 return Ok((n, claimed_idx));
1069 }
1070 }
1071 Ok::<_, PartitionFailure<D>>((0, vec![]))
1072 })
1073 .collect::<Result<Vec<_>, _>>()?;
1074 let mut sections_in_guaranteed = vec![0; n];
1075 for (root_n, &root) in root_nodes.iter().enumerate() {
1076 sections_in_guaranteed[root] = preliminary_results[root_n].0;
1077 let mut frontier = preliminary_results[root_n].1.clone();
1078 while let Some(next) = frontier.pop() {
1079 sections_in_guaranteed[next] = no_ckpts[next] + 1;
1080 frontier.extend(&call_graph[next]);
1081 }
1082 }
1083
1084 Ok(sections_in_guaranteed
1085 .into_iter()
1086 .enumerate()
1087 .map(|(i, sections)| calls[i].split_at(sections, params))
1088 .collect::<Result<Vec<_>, _>>()?)
1089}