1use crate::dust::{DustGenerationInfo, DustNullifier, DustRegistration, DustSpend};
15use crate::error::coin::UserAddress;
16use crate::structure::MAX_SUPPLY;
17use crate::structure::{ClaimKind, ContractOperationVersion, Utxo, UtxoOutput, UtxoSpend};
18use base_crypto::cost_model::CostDuration;
19use base_crypto::fab::{Alignment, Value};
20use base_crypto::hash::HashOutput;
21use base_crypto::signatures::VerifyingKey;
22use base_crypto::time::Timestamp;
23use coin_structure::coin::{self, Commitment, Nullifier, PublicAddress, TokenType};
24use coin_structure::contract::ContractAddress;
25use derive_where::derive_where;
26use onchain_runtime::context::Effects;
27use onchain_runtime::error::TranscriptRejected;
28use onchain_runtime::state::EntryPointBuf;
29use onchain_runtime::transcript::Transcript;
30use std::error::Error;
31use std::fmt::{self, Display, Formatter};
32use storage::db::DB;
33use transient_crypto::curve::EmbeddedGroupAffine;
34use transient_crypto::curve::Fr;
35use transient_crypto::merkle_tree::InvalidUpdate;
36use transient_crypto::proofs::{KeyLocation, ProvingError, VerifyingError};
37use zswap::error::MalformedOffer;
38use zswap::{Input, Output};
39
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub enum InvariantViolation {
42 NightBalance(u128),
43}
44
45impl Display for InvariantViolation {
46 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
47 match self {
48 InvariantViolation::NightBalance(x) => {
49 write!(
50 f,
51 "total supply of night is {MAX_SUPPLY}, but the transaction would imply a total of: {x}"
52 )
53 }
54 }
55 }
56}
57
58impl From<InvariantViolation> for SystemTransactionError {
59 fn from(e: InvariantViolation) -> Self {
60 SystemTransactionError::InvariantViolation(e)
61 }
62}
63
64impl<D: DB> From<InvariantViolation> for TransactionInvalid<D> {
65 fn from(e: InvariantViolation) -> Self {
66 TransactionInvalid::InvariantViolation(e)
67 }
68}
69
70#[derive(Debug)]
71pub enum SystemTransactionError {
72 IllegalPayout {
73 claimed_amount: Option<u128>,
74 supply: u128,
75 bridged_amount: Option<u128>,
76 locked: u128,
77 },
78 InsufficientTreasuryFunds {
79 requested: Option<u128>,
80 actual: u128,
81 token_type: TokenType,
82 },
83 CommitmentAlreadyPresent(Commitment),
84 ReplayProtectionFailure(TransactionApplicationError),
85 IllegalReserveDistribution {
86 distributed_amount: u128,
87 reserve_supply: u128,
88 },
89 GenerationInfoAlreadyPresent(GenerationInfoAlreadyPresentError),
90 InvalidBasisPoints(u32),
91 InvariantViolation(InvariantViolation),
92 TreasuryDisabled,
93}
94
95impl Display for SystemTransactionError {
96 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
97 match self {
98 SystemTransactionError::IllegalPayout {
99 claimed_amount: Some(amount),
100 supply,
101 bridged_amount: None,
102 locked: _,
103 } => write!(
104 f,
105 "illegal payout of {amount} native tokens, exceeding remaining supply of {supply}"
106 ),
107 SystemTransactionError::IllegalPayout {
108 claimed_amount: None,
109 supply: _,
110 bridged_amount: Some(amount),
111 locked,
112 } => write!(
113 f,
114 "illegal bridge of {amount} native tokens, exceeding remaining pool of {locked}"
115 ),
116 SystemTransactionError::IllegalPayout {
117 claimed_amount: None,
118 supply,
119 bridged_amount: None,
120 locked,
121 } => write!(
122 f,
123 "illegal bridge or payout of > 2^128 native tokens, exceeding remaining reserve pool of {supply} and/or bridge pool of {locked}"
124 ),
125 SystemTransactionError::IllegalPayout {
126 claimed_amount: Some(amount_claimed),
127 supply,
128 bridged_amount: Some(amount_bridged),
129 locked,
130 } => write!(
131 f,
132 "illegal payout of {amount_claimed} native tokens, exceeding remaining supply of {supply}; illegal bridge of {amount_bridged} native tokens, exceeding remaining pool of {locked}"
133 ),
134 SystemTransactionError::InsufficientTreasuryFunds {
135 requested: Some(requested),
136 actual,
137 token_type,
138 } => write!(
139 f,
140 "insufficient funds in the treasury; {requested} of token {token_type:?} requested, but only {actual} available"
141 ),
142 SystemTransactionError::InsufficientTreasuryFunds {
143 requested: None,
144 actual,
145 token_type,
146 } => write!(
147 f,
148 "insufficient funds in the treasury; > 2^128 of token {token_type:?} requested, but only {actual} available"
149 ),
150 SystemTransactionError::CommitmentAlreadyPresent(cm) => {
151 write!(f, "faerie-gold attempt with commitment {:?}", cm)
152 }
153 SystemTransactionError::ReplayProtectionFailure(e) => {
154 write!(f, "Replay protection violation: {e}")
155 }
156 SystemTransactionError::IllegalReserveDistribution {
157 distributed_amount,
158 reserve_supply,
159 } => {
160 write!(
161 f,
162 "illegal distribution of {distributed_amount} reserve tokens, exceeding remaining supply of {reserve_supply}"
163 )
164 }
165 SystemTransactionError::GenerationInfoAlreadyPresent(e) => e.fmt(f),
166 SystemTransactionError::InvalidBasisPoints(bp) => {
167 write!(
168 f,
169 "cardano_to_midnight_bridge_fee_basis_points must be less than 10_000, but was set to: {bp}"
170 )
171 }
172 SystemTransactionError::InvariantViolation(e) => e.fmt(f),
173 SystemTransactionError::TreasuryDisabled => write!(
174 f,
175 "invalid attempt to access treasury; the treasury is disabled until governance for it has been agreed"
176 ),
177 }
178 }
179}
180
181impl Error for SystemTransactionError {
182 fn cause(&self) -> Option<&dyn Error> {
183 match self {
184 SystemTransactionError::GenerationInfoAlreadyPresent(e) => Some(e),
185 _ => None,
186 }
187 }
188}
189
190impl From<GenerationInfoAlreadyPresentError> for SystemTransactionError {
191 fn from(err: GenerationInfoAlreadyPresentError) -> SystemTransactionError {
192 SystemTransactionError::GenerationInfoAlreadyPresent(err)
193 }
194}
195
196#[derive(Debug, Clone)]
197#[non_exhaustive]
198pub enum TransactionInvalid<D: DB> {
199 EffectsMismatch {
200 declared: Box<Effects<D>>,
201 actual: Box<Effects<D>>,
202 },
203 ContractAlreadyDeployed(ContractAddress),
204 ContractNotPresent(ContractAddress),
205 Zswap(zswap::error::TransactionInvalid),
206 Transcript(onchain_runtime::error::TranscriptRejected<D>),
207 InsufficientClaimable {
208 requested: u128,
209 claimable: u128,
210 claimant: UserAddress,
211 kind: ClaimKind,
212 },
213 VerifierKeyNotFound(EntryPointBuf, ContractOperationVersion),
214 VerifierKeyAlreadyPresent(EntryPointBuf, ContractOperationVersion),
215 ReplayCounterMismatch(ContractAddress),
216 ReplayProtectionViolation(TransactionApplicationError),
217 BalanceCheckOutOfBounds {
218 token_type: TokenType,
219 current_balance: u128,
220 operation_value: u128,
221 operation: BalanceOperation,
222 },
223 InputNotInUtxos(Box<Utxo>),
224 DustDoubleSpend(DustNullifier),
225 DustDeregistrationNotRegistered(UserAddress),
226 GenerationInfoAlreadyPresent(GenerationInfoAlreadyPresentError),
227 InvariantViolation(InvariantViolation),
228 RewardTooSmall {
229 claimed: u128,
230 minimum: u128,
231 },
232 DivideByZero,
233}
234
235impl<D: DB> Display for TransactionInvalid<D> {
236 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
237 use TransactionInvalid::*;
238 match self {
239 EffectsMismatch { declared, actual } => write!(
240 formatter,
241 "declared effects {declared:?} don't match computed effects {actual:?}"
242 ),
243 ContractNotPresent(addr) => {
244 write!(formatter, "call to non-existant contract {:?}", addr)
245 }
246 ContractAlreadyDeployed(addr) => {
247 write!(formatter, "contract already deployed {:?}", addr)
248 }
249 Zswap(err) => err.fmt(formatter),
250 Transcript(err) => err.fmt(formatter),
251 InsufficientClaimable {
252 requested,
253 claimable,
254 claimant,
255 kind,
256 } => {
257 write!(
258 formatter,
259 "insufficient funds for requested {kind} claim: {requested} tokens of Night requested by {claimant:?}; only {claimable} available"
260 )
261 }
262 VerifierKeyNotFound(ep, ver) => write!(
263 formatter,
264 "the verifier key for {ep:?} version {ver:?} was not present"
265 ),
266 VerifierKeyAlreadyPresent(ep, ver) => write!(
267 formatter,
268 "the verifier key for {ep:?} version {ver:?} was already present"
269 ),
270 ReplayCounterMismatch(addr) => write!(
271 formatter,
272 "the signed counter for {addr:?} did not match the expected one; likely replay attack"
273 ),
274 ReplayProtectionViolation(err) => {
275 write!(formatter, "replay protection has been violated: {err:?}")
276 }
277 BalanceCheckOutOfBounds {
278 token_type,
279 current_balance,
280 operation_value,
281 operation,
282 } => {
283 let (reason_str, to_from, op_str) = match operation {
284 BalanceOperation::Addition => ("overflow", "to", "add"),
285 BalanceOperation::Subtraction => ("underflow", "from", "subtract"),
286 };
287 write!(
288 formatter,
289 "Balance check failed: couldn't {op_str} {operation_value} {to_from} {current_balance} for token {token_type:?}: {reason_str}"
290 )
291 }
292 InputNotInUtxos(utxo) => write!(formatter, "input missing from utxos set: {:?}", utxo),
293 DustDoubleSpend(nullifier) => write!(
294 formatter,
295 "attempted to double spend Dust UTXO with nullifier {nullifier:?}"
296 ),
297 DustDeregistrationNotRegistered(addr) => write!(
298 formatter,
299 "attempted to deregister the Dust address associated with the Night address {addr:?}, but no such registration exists"
300 ),
301 RewardTooSmall { claimed, minimum } => write!(
302 formatter,
303 "claimed reward ({claimed} STARs) below payout threshold ({minimum} STARs)"
304 ),
305 GenerationInfoAlreadyPresent(e) => e.fmt(formatter),
306 InvariantViolation(e) => e.fmt(formatter),
307 DivideByZero => write!(formatter, "attempted to divide by zero"),
308 }
309 }
310}
311
312impl<D: DB> Error for TransactionInvalid<D> {
313 fn cause(&self) -> Option<&dyn Error> {
314 match self {
315 TransactionInvalid::Zswap(e) => Some(e),
316 TransactionInvalid::Transcript(e) => Some(e),
317 TransactionInvalid::ReplayProtectionViolation(e) => Some(e),
318 TransactionInvalid::GenerationInfoAlreadyPresent(e) => Some(e),
319 _ => None,
320 }
321 }
322}
323
324impl<D: DB> From<zswap::error::TransactionInvalid> for TransactionInvalid<D> {
325 fn from(err: zswap::error::TransactionInvalid) -> TransactionInvalid<D> {
326 TransactionInvalid::Zswap(err)
327 }
328}
329
330impl<D: DB> From<onchain_runtime::error::TranscriptRejected<D>> for TransactionInvalid<D> {
331 fn from(err: onchain_runtime::error::TranscriptRejected<D>) -> TransactionInvalid<D> {
332 TransactionInvalid::Transcript(err)
333 }
334}
335
336impl<D: DB> From<GenerationInfoAlreadyPresentError> for TransactionInvalid<D> {
337 fn from(err: GenerationInfoAlreadyPresentError) -> TransactionInvalid<D> {
338 TransactionInvalid::GenerationInfoAlreadyPresent(err)
339 }
340}
341
342#[derive(Debug, PartialEq, Eq)]
343pub enum FeeCalculationError {
344 OutsideTimeToDismiss {
345 time_to_dismiss: CostDuration,
346 allowed_time_to_dismiss: CostDuration,
347 size: u64,
348 },
349 BlockLimitExceeded,
350}
351
352impl Display for FeeCalculationError {
353 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
354 match self {
355 FeeCalculationError::BlockLimitExceeded => write!(
356 formatter,
357 "exceeded block limit in transaction fee computation"
358 ),
359 FeeCalculationError::OutsideTimeToDismiss {
360 time_to_dismiss,
361 allowed_time_to_dismiss,
362 size,
363 } => write!(
364 formatter,
365 "exceeded the maximum time to dismiss for transaction size; this transaction would take {time_to_dismiss:?} to dismiss, but given its size of {size} bytes, it may take at most {allowed_time_to_dismiss:?}"
366 ),
367 }
368 }
369}
370
371impl Error for FeeCalculationError {}
372
373#[derive(Debug)]
374#[non_exhaustive]
375pub enum MalformedContractDeploy {
376 NonZeroBalance(std::collections::BTreeMap<TokenType, u128>),
377 IncorrectChargedState,
378}
379
380impl Display for MalformedContractDeploy {
381 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
382 use MalformedContractDeploy::*;
383 match self {
384 NonZeroBalance(balance) => {
385 let filtered: std::collections::BTreeMap<_, _> =
386 balance.iter().filter(|&(_, value)| *value > 0).collect();
387 write!(
388 formatter,
389 "contract deployment was sent with the non-zero balance members: {:?}",
390 filtered
391 )
392 }
393 IncorrectChargedState => write!(
394 formatter,
395 "contract deployment contained an incorrectly computed map of charged keys"
396 ),
397 }
398 }
399}
400
401impl Error for MalformedContractDeploy {}
402
403#[derive(Debug)]
404#[non_exhaustive]
405#[allow(clippy::large_enum_variant)]
406pub enum MalformedTransaction<D: DB> {
407 InvalidNetworkId {
408 expected: String,
409 found: String,
410 },
411 VerifierKeyNotSet {
412 address: ContractAddress,
413 operation: EntryPointBuf,
414 },
415 TransactionTooLarge {
416 tx_size: usize,
417 limit: u64,
418 },
419 VerifierKeyTooLarge {
420 actual: u64,
421 limit: u64,
422 },
423 VerifierKeyNotPresent {
424 address: ContractAddress,
425 operation: EntryPointBuf,
426 },
427 ContractNotPresent(ContractAddress),
428 InvalidProof(VerifyingError),
429 BindingCommitmentOpeningInvalid,
430 NotNormalized,
431 FallibleWithoutCheckpoint,
432 IllegallyDeclaredGuaranteed,
433 ClaimReceiveFailed(coin::Commitment),
434 ClaimSpendFailed(coin::Commitment),
435 ClaimNullifierFailed(coin::Nullifier),
436 InvalidSchnorrProof,
437 UnclaimedCoinCom(coin::Commitment),
438 UnclaimedNullifier(coin::Nullifier),
439 Unbalanced(TokenType, i128), Zswap(zswap::error::MalformedOffer),
441 BuiltinDecode(base_crypto::fab::InvalidBuiltinDecode),
442 FeeCalculation(FeeCalculationError),
443 CantMergeTypes,
444 ClaimOverflow,
445 ClaimCoinMismatch,
446 KeyNotInCommittee {
447 address: ContractAddress,
448 key_id: usize,
449 },
450 InvalidCommitteeSignature {
451 address: ContractAddress,
452 key_id: usize,
453 },
454 InvalidDustRegistrationSignature {
455 registration: Box<DustRegistration<(), D>>,
456 },
457 InvalidDustSpendProof {
458 declared_time: Timestamp,
459 dust_spend: Box<DustSpend<(), D>>,
460 },
461 OutOfDustValidityWindow {
462 dust_ctime: Timestamp,
463 validity_start: Timestamp,
464 validity_end: Timestamp,
465 },
466 MultipleDustRegistrationsForKey {
467 key: VerifyingKey,
468 },
469 InsufficientDustForRegistrationFee {
470 registration: Box<DustRegistration<(), D>>,
471 available_dust: u128,
472 },
473 ThresholdMissed {
474 address: ContractAddress,
475 signatures: usize,
476 threshold: usize,
477 },
478 TooManyZswapEntries,
479 MalformedContractDeploy(MalformedContractDeploy),
480 IntentSignatureVerificationFailure,
481 IntentSignatureKeyMismatch,
482 IntentSegmentIdCollision(u16),
483 IntentAtGuaranteedSegmentId,
484 UnsupportedProofVersion {
485 op_version: String,
486 },
487 GuaranteedTranscriptVersion {
488 op_version: String,
489 },
490 FallibleTranscriptVersion {
491 op_version: String,
492 },
493 TransactionApplicationError(TransactionApplicationError),
494 BalanceCheckOutOfBounds {
495 token_type: TokenType,
496 segment: u16,
497 current_balance: i128,
498 operation_value: i128,
499 operation: BalanceOperation,
500 },
501 BalanceCheckConversionFailure {
502 token_type: TokenType,
503 segment: u16,
504 operation_value: u128,
505 },
506 PedersenCheckFailure {
507 expected: Box<EmbeddedGroupAffine>,
508 calculated: Box<EmbeddedGroupAffine>,
509 },
510 BalanceCheckOverspend {
511 token_type: TokenType,
512 segment: u16,
513 overspent_value: i128,
514 },
515 EffectsCheckFailure(EffectsCheckError),
516 DisjointCheckFailure(DisjointCheckError<D>),
517 SequencingCheckFailure(SequencingCheckError),
518 InputsNotSorted(Vec<UtxoSpend>),
519 OutputsNotSorted(Vec<UtxoOutput>),
520 DuplicateInputs(Vec<UtxoSpend>),
521 InputsSignaturesLengthMismatch {
522 inputs: Vec<UtxoSpend>,
523 erased_signatures: Vec<()>,
524 },
525}
526
527#[derive(Clone, Debug)]
528pub enum SequencingCheckError {
529 CallSequencingViolation {
530 call_predecessor: u32,
531 call_successor: u32,
532 call_predecessor_address: ContractAddress,
533 call_successor_address: ContractAddress,
534 },
535 SequencingCorrelationViolation {
536 address_1: ContractAddress,
537 address_2: ContractAddress,
538 call_position_1: u32,
539 call_position_2: u32,
540 },
541 GuaranteedInFallibleContextViolation {
542 caller: u32,
543 callee: u32,
544 caller_address: ContractAddress,
545 callee_address: ContractAddress,
546 },
547 FallibleInGuaranteedContextViolation {
548 caller: u32,
549 callee: u32,
550 caller_address: ContractAddress,
551 callee_address: ContractAddress,
552 },
553 CausalityConstraintViolation {
554 call_predecessor: u32,
555 call_successor: u32,
556 call_predecessor_address: ContractAddress,
557 call_successor_address: ContractAddress,
558 segment_id_predecessor: u16,
559 segment_id_successor: u16,
560 },
561 CallHasEmptyTranscripts {
562 segment_id: u16,
563 addr: ContractAddress,
564 call_index: u32,
565 },
566}
567
568impl std::fmt::Display for SequencingCheckError {
569 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
570 match self {
571 SequencingCheckError::CallSequencingViolation {
572 call_predecessor: call_position_x,
573 call_successor: call_position_succ_x,
574 call_predecessor_address: call_x_address,
575 call_successor_address: call_succ_x_address,
576 } => {
577 write!(
578 formatter,
579 "sequencing violation: Expected call position {call_position_x} (for call at address: {call_x_address:?}) < {call_position_succ_x} (for call at at address: {call_succ_x_address:?}), but this ordering constraint was violated"
580 )
581 }
582 SequencingCheckError::SequencingCorrelationViolation {
583 address_1,
584 address_2,
585 call_position_1,
586 call_position_2,
587 } => {
588 write!(
589 formatter,
590 "sequencing correlation violation: The order of addresses ({address_1:?} vs {address_2:?}) does not match the order of call positions ({call_position_1} vs {call_position_2}); expected both orderings to be consistent"
591 )
592 }
593 SequencingCheckError::GuaranteedInFallibleContextViolation {
594 caller: parent_call_id,
595 callee: child_call_id,
596 caller_address: parent_address,
597 callee_address: child_address,
598 } => {
599 write!(
600 formatter,
601 "fallible context violation: Call at position {child_call_id} (address: {child_address:?}) contains a guaranteed transcript but is called from a fallible context in call at position {parent_call_id} (address: {parent_address:?})"
602 )
603 }
604 SequencingCheckError::FallibleInGuaranteedContextViolation {
605 caller: parent_call_id,
606 callee: child_call_id,
607 caller_address: parent_address,
608 callee_address: child_address,
609 } => {
610 write!(
611 formatter,
612 "guaranteed context violation: Call at position {child_call_id} (address: {child_address:?}) contains a fallible transcript but is called from a guaranteed context in call at position {parent_call_id} (address: {parent_address:?})"
613 )
614 }
615 SequencingCheckError::CausalityConstraintViolation {
616 call_predecessor,
617 call_successor,
618 call_predecessor_address,
619 call_successor_address,
620 segment_id_predecessor,
621 segment_id_successor,
622 } => {
623 write!(
624 formatter,
625 "causality violation: Calls must be arranged to ensure causality constraints are met, but found call at segment_id: {segment_id_predecessor} (address: {call_predecessor_address:?}, position: {call_predecessor}) with fallible transcript and call at segment_id: {segment_id_successor} (address: {call_successor_address:?}, position: {call_successor}) with guaranteed transcript"
626 )
627 }
628 SequencingCheckError::CallHasEmptyTranscripts {
629 segment_id,
630 addr,
631 call_index,
632 } => {
633 write!(
634 formatter,
635 "call composition violation: Calls cannot have empty guaranteed and fallible transcripts, but found violating call at segment_id: {segment_id} (address: {addr:?}, position: {call_index})"
636 )
637 }
638 }
639 }
640}
641
642impl Error for SequencingCheckError {}
643
644#[derive_where(Clone, Debug)]
645pub enum DisjointCheckError<D: DB> {
646 ShieldedInputsDisjointFailure {
647 shielded_inputs: std::collections::BTreeSet<Input<(), D>>,
648 transient_inputs: std::collections::BTreeSet<Input<(), D>>,
649 },
650 ShieldedOutputsDisjointFailure {
651 shielded_outputs: std::collections::BTreeSet<Output<(), D>>,
652 transient_outputs: std::collections::BTreeSet<Output<(), D>>,
653 },
654 UnshieldedInputsDisjointFailure {
655 unshielded_inputs: std::collections::BTreeSet<UtxoSpend>,
656 offer_inputs: std::collections::BTreeSet<UtxoSpend>,
657 },
658}
659
660impl<D: DB> std::fmt::Display for DisjointCheckError<D> {
661 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
662 match self {
663 DisjointCheckError::ShieldedInputsDisjointFailure {
664 shielded_inputs,
665 transient_inputs,
666 } => {
667 write!(
668 formatter,
669 "shielded_inputs and transient_inputs must be disjoint. shielded_inputs: {shielded_inputs:?}, transient_inputs: {transient_inputs:?}"
670 )
671 }
672 DisjointCheckError::ShieldedOutputsDisjointFailure {
673 shielded_outputs,
674 transient_outputs,
675 } => {
676 write!(
677 formatter,
678 "shielded_outputs and transient_outputs must be disjoint. shielded_outputs: {shielded_outputs:?}, transient_outputs: {transient_outputs:?}"
679 )
680 }
681 DisjointCheckError::UnshieldedInputsDisjointFailure {
682 unshielded_inputs,
683 offer_inputs,
684 } => {
685 write!(
686 formatter,
687 "unshielded_inputs and offer_inputs must be disjoint. unshielded_inputs: {unshielded_inputs:?}, offer_inputs: {offer_inputs:?}"
688 )
689 }
690 }
691 }
692}
693
694impl<D: DB> Error for DisjointCheckError<D> {}
695
696#[derive(Clone, Debug)]
697pub struct SubsetCheckFailure<T> {
698 pub superset: Vec<T>,
699 pub subset: Vec<T>,
700}
701
702#[derive(Clone, Debug)]
703pub enum EffectsCheckError {
704 RealCallsSubsetCheckFailure(SubsetCheckFailure<(u16, (ContractAddress, HashOutput, Fr))>),
705 AllCommitmentsSubsetCheckFailure(SubsetCheckFailure<(u16, Commitment)>),
706 #[allow(clippy::type_complexity)]
707 RealUnshieldedSpendsSubsetCheckFailure(
708 SubsetCheckFailure<((u16, bool), ((TokenType, PublicAddress), u128))>,
709 ),
710 ClaimedUnshieldedSpendsUniquenessFailure(Vec<((u16, Commitment), usize)>),
711 #[allow(clippy::type_complexity)]
712 ClaimedCallsUniquenessFailure(Vec<((u16, (ContractAddress, HashOutput, Fr)), usize)>),
713 NullifiersNEClaimedNullifiers {
714 nullifiers: Vec<(u16, Nullifier, ContractAddress)>,
715 claimed_nullifiers: Vec<(u16, Nullifier, ContractAddress)>,
716 },
717 CommitmentsNEClaimedShieldedReceives {
718 commitments: Vec<(u16, Commitment, ContractAddress)>,
719 claimed_shielded_receives: Vec<(u16, Commitment, ContractAddress)>,
720 },
721}
722
723impl std::fmt::Display for EffectsCheckError {
724 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
725 match self {
726 EffectsCheckError::RealCallsSubsetCheckFailure(e) => write!(
727 formatter,
728 "claimed_calls is not a subset of real_calls. {}",
729 e
730 ),
731 EffectsCheckError::AllCommitmentsSubsetCheckFailure(e) => write!(
732 formatter,
733 "claimed_shielded_spends is not a subset of all_commitments. \n {}",
734 e
735 ),
736 EffectsCheckError::RealUnshieldedSpendsSubsetCheckFailure(e) => write!(
737 formatter,
738 "claimed_unshielded_spends is not a subset of real_unshielded_spends. \n {:?}",
739 e
740 ),
741 EffectsCheckError::ClaimedUnshieldedSpendsUniquenessFailure(items) => {
742 write!(formatter, "non-unique spends found: {:?}", items)
743 }
744 EffectsCheckError::ClaimedCallsUniquenessFailure(items) => {
745 write!(formatter, "non-unique claimed calls found: {:?}", items)
746 }
747 EffectsCheckError::NullifiersNEClaimedNullifiers {
748 nullifiers,
749 claimed_nullifiers,
750 } => write!(
751 formatter,
752 "all contract-associated nullifiers must be claimed by exactly one instance of the same contract in the same segment. nullifiers: {nullifiers:?}, claimed_nullifiers: {claimed_nullifiers:?}",
753 ),
754 EffectsCheckError::CommitmentsNEClaimedShieldedReceives {
755 commitments,
756 claimed_shielded_receives,
757 } => write!(
758 formatter,
759 "all contract-associated commitments must be claimed by exactly one instance of the same contract in the same segment. commitments: {commitments:?}, claimed_shielded_receives: {claimed_shielded_receives:?}"
760 ),
761 }
762 }
763}
764
765impl Error for EffectsCheckError {}
766
767impl<T: std::fmt::Debug> Display for SubsetCheckFailure<T> {
768 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
769 let SubsetCheckFailure { superset, subset } = self;
770 write!(formatter, "subset: {subset:?} \n superset: {superset:?}")
771 }
772}
773
774#[derive(Clone, Copy, Debug)]
775pub enum BalanceOperation {
776 Addition,
777 Subtraction,
778}
779
780#[derive(Clone, Copy, Debug)]
781pub enum BalanceCheckFailureReason {
782 TypeConversionFailure,
783 OutOfBounds,
784}
785
786fn sanitize_network_id(network_id: &str) -> String {
787 let char_not_permitted = |ch: char| !ch.is_ascii_alphanumeric() && ch != '-';
788 network_id.replace(char_not_permitted, "�")
789}
790
791impl<D: DB> Display for MalformedTransaction<D> {
792 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
793 use MalformedTransaction::*;
794 match self {
795 InvalidNetworkId { expected, found } => {
796 let expected = sanitize_network_id(expected);
797 let found = sanitize_network_id(found);
798 write!(
799 formatter,
800 "invalid network ID - expect '{expected}' found '{found}'"
801 )
802 }
803 ContractNotPresent(addr) => {
804 write!(formatter, "call to non-existant contract {:?}", addr)
805 }
806 VerifierKeyNotPresent { address, operation } => write!(
807 formatter,
808 "operation {address:?}/{operation:?} does not have a verifier key",
809 ),
810 InvalidProof(err) => {
811 write!(formatter, "failed to verify proof: ")?;
812 err.fmt(formatter)
813 }
814 TransactionTooLarge { tx_size, limit } => write!(
815 formatter,
816 "transaction too large (size: {tx_size}, limit: {limit})"
817 ),
818 VerifierKeyTooLarge { actual, limit } => write!(
819 formatter,
820 "verifier key for operation too large for deserialization (size: {actual}, limit: {limit})"
821 ),
822 VerifierKeyNotSet { address, operation } => write!(
823 formatter,
824 "tried to deploy {:?}/{:?} without a verifier key",
825 address, operation
826 ),
827 BindingCommitmentOpeningInvalid => write!(
828 formatter,
829 "transaction binding commitment was incorrectly opened"
830 ),
831 IllegallyDeclaredGuaranteed => write!(
832 formatter,
833 "guaranteed segment (0) declared in a context where only fallible segments are permitted"
834 ),
835 FallibleWithoutCheckpoint => write!(
836 formatter,
837 "fallible transcript did not start with a checkpoint"
838 ),
839 NotNormalized => write!(formatter, "transaction is not in normal form"),
840 ClaimReceiveFailed(com) => write!(
841 formatter,
842 "failed to claim coin commitment receive for {:?}",
843 com
844 ),
845 ClaimSpendFailed(com) => write!(
846 formatter,
847 "failed to claim coin commitment spend for {:?}",
848 com
849 ),
850 ClaimNullifierFailed(nul) => write!(
851 formatter,
852 "failed to claim coin commitment nullifier for {:?}",
853 nul
854 ),
855 InvalidSchnorrProof => write!(
856 formatter,
857 "failed to verify Fiat-Shamir transformed Schnorr proof"
858 ),
859 UnclaimedCoinCom(com) => write!(
860 formatter,
861 "a contract-owned coin output was left unclaimed: {:?}",
862 com
863 ),
864 UnclaimedNullifier(nul) => write!(
865 formatter,
866 "a contract-owned coin input was unauthorized: {:?}",
867 nul
868 ),
869 Unbalanced(tt, bal) => write!(
870 formatter,
871 "the transaction has negative balance {} in token type {:?}",
872 bal, tt
873 ),
874 Zswap(err) => err.fmt(formatter),
875 BuiltinDecode(err) => err.fmt(formatter),
876 FeeCalculation(err) => err.fmt(formatter),
877 CantMergeTypes => write!(
878 formatter,
879 "attempted to merge transaction types that are not mergable"
880 ),
881 ClaimOverflow => write!(formatter, "claimed coin value overflows deltas"),
882 ClaimCoinMismatch => write!(
883 formatter,
884 "declared coin in ClaimRewards doesn't match real coin"
885 ),
886 KeyNotInCommittee { address, key_id } => write!(
887 formatter,
888 "declared signture for key id {key_id} does not correspond to a committee member for contract {address:?}"
889 ),
890 InvalidCommitteeSignature { address, key_id } => write!(
891 formatter,
892 "signature for key id {key_id} invalid for contract {address:?}"
893 ),
894 InvalidDustRegistrationSignature { registration } => write!(
895 formatter,
896 "failed to verify signature of dust registration: {registration:?}"
897 ),
898 InvalidDustSpendProof {
899 declared_time,
900 dust_spend,
901 } => write!(
902 formatter,
903 "dust spend proof failed to verify; this is just as likely a disagreement on dust state on the declared time ({declared_time:?}) as the proof being invalid: {dust_spend:?}"
904 ),
905 OutOfDustValidityWindow {
906 dust_ctime,
907 validity_start,
908 validity_end,
909 } => write!(
910 formatter,
911 "dust is outside of its validity window (declared time: {dust_ctime:?}, window: [{validity_start:?}, {validity_end:?}])"
912 ),
913 MultipleDustRegistrationsForKey { key } => write!(
914 formatter,
915 "multiple dust registrations for key in the same intent: {key:?}"
916 ),
917 InsufficientDustForRegistrationFee {
918 registration,
919 available_dust,
920 } => write!(
921 formatter,
922 "insufficient dust to cover registration fee allowance: {available_dust} available, {} requested",
923 registration.allow_fee_payment
924 ),
925 ThresholdMissed {
926 address,
927 signatures,
928 threshold,
929 } => write!(
930 formatter,
931 "threshold update for contract {address:?} does not meet required threshold ({signatures}/{threshold} signatures)"
932 ),
933 TooManyZswapEntries => write!(
934 formatter,
935 "excessive Zswap entries exceeding 2^16 safety margin"
936 ),
937 MalformedContractDeploy(mcd) => write!(formatter, "{:?}", mcd),
938 IntentSignatureVerificationFailure => write!(
939 formatter,
940 "signature verification failed for supplied intent"
941 ),
942 IntentSignatureKeyMismatch => write!(
943 formatter,
944 "supplied signing key does not match verifying key"
945 ),
946 IntentSegmentIdCollision(segment_id) => write!(
947 formatter,
948 "key (segment_id) collision during intents merge: {:?}",
949 segment_id
950 ),
951 IntentAtGuaranteedSegmentId => {
952 write!(formatter, "intents are not allowed at segment_id: 0")
953 }
954 UnsupportedProofVersion { op_version } => write!(
955 formatter,
956 "unsupported proof version provided for contract operation: {op_version}"
957 ),
958 GuaranteedTranscriptVersion { op_version } => write!(
959 formatter,
960 "unsupported guaranteed transcript version provided for contract operation: {op_version}"
961 ),
962 FallibleTranscriptVersion { op_version } => write!(
963 formatter,
964 "unsupported fallible transcript version provided for contract operation: {op_version}"
965 ),
966 TransactionApplicationError(transaction_application_error) => write!(
967 formatter,
968 "transaction application error detected during verification: {transaction_application_error}"
969 ),
970 BalanceCheckOutOfBounds {
971 token_type,
972 segment,
973 current_balance,
974 operation_value,
975 operation,
976 } => {
977 let (reason_str, to_from, op_str) = match operation {
978 BalanceOperation::Addition => ("overflow", "to", "add"),
979 BalanceOperation::Subtraction => ("underflow", "from", "subtract"),
980 };
981 write!(
982 formatter,
983 "Balance check failed: couldn't {op_str} {operation_value} {to_from} {current_balance} for token {token_type:?} in segment {segment}: {reason_str}"
984 )
985 }
986 BalanceCheckConversionFailure {
987 token_type,
988 segment,
989 operation_value,
990 } => {
991 write!(
992 formatter,
993 "Balance check failed: couldn't convert {operation_value} to type i128 for token {token_type:?} in segment {segment}"
994 )
995 }
996 PedersenCheckFailure {
997 expected,
998 calculated,
999 } => write!(
1000 formatter,
1001 "binding commitment calculation mismatch: expected {expected:?}, but calculated {calculated:?}"
1002 ),
1003 BalanceCheckOverspend {
1004 token_type,
1005 segment,
1006 overspent_value,
1007 } => {
1008 write!(
1009 formatter,
1010 "invalid balance {overspent_value} for token {token_type:?} in segment {segment}; balance must be positive"
1011 )
1012 }
1013 EffectsCheckFailure(effects_check) => effects_check.fmt(formatter),
1014 DisjointCheckFailure(disjoint_check) => disjoint_check.fmt(formatter),
1015 SequencingCheckFailure(sequencing_check) => sequencing_check.fmt(formatter),
1016 InputsNotSorted(utxo_spends) => {
1017 write!(
1018 formatter,
1019 "unshielded offer validation error: inputs are not sorted: {:?}",
1020 utxo_spends
1021 )
1022 }
1023 OutputsNotSorted(utxo_outputs) => {
1024 write!(
1025 formatter,
1026 "unshielded offer validation error: outputs are not sorted: {:?}",
1027 utxo_outputs
1028 )
1029 }
1030 DuplicateInputs(utxo_spends) => {
1031 write!(
1032 formatter,
1033 "unshielded offer validation error: found duplicate inputs: {:?}",
1034 utxo_spends
1035 )
1036 }
1037 InputsSignaturesLengthMismatch {
1038 inputs,
1039 erased_signatures,
1040 } => {
1041 write!(
1042 formatter,
1043 "unshielded offer action validation error: mismatch between number of inputs ({}) and signatures ({})",
1044 inputs.len(),
1045 erased_signatures.len()
1046 )
1047 }
1048 }
1049 }
1050}
1051
1052impl<D: DB> Error for MalformedTransaction<D> {
1053 fn cause(&self) -> Option<&dyn Error> {
1054 match self {
1055 MalformedTransaction::MalformedContractDeploy(e) => Some(e),
1056 MalformedTransaction::TransactionApplicationError(e) => Some(e),
1057 MalformedTransaction::EffectsCheckFailure(e) => Some(e),
1058 MalformedTransaction::DisjointCheckFailure(e) => Some(e),
1059 MalformedTransaction::SequencingCheckFailure(e) => Some(e),
1060 _ => None,
1061 }
1062 }
1063}
1064
1065impl<D: DB> From<zswap::error::MalformedOffer> for MalformedTransaction<D> {
1066 fn from(err: zswap::error::MalformedOffer) -> MalformedTransaction<D> {
1067 MalformedTransaction::Zswap(err)
1068 }
1069}
1070
1071impl<D: DB> From<base_crypto::fab::InvalidBuiltinDecode> for MalformedTransaction<D> {
1072 fn from(err: base_crypto::fab::InvalidBuiltinDecode) -> MalformedTransaction<D> {
1073 MalformedTransaction::BuiltinDecode(err)
1074 }
1075}
1076
1077impl<D: DB> From<FeeCalculationError> for MalformedTransaction<D> {
1078 fn from(err: FeeCalculationError) -> MalformedTransaction<D> {
1079 MalformedTransaction::FeeCalculation(err)
1080 }
1081}
1082
1083#[derive(Clone, Debug, PartialEq, Eq, Copy)]
1084pub enum TransactionApplicationError {
1085 IntentTtlExpired(Timestamp, Timestamp),
1086 IntentTtlTooFarInFuture(Timestamp, Timestamp),
1087 IntentAlreadyExists,
1088}
1089
1090impl Display for TransactionApplicationError {
1091 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
1092 match self {
1093 TransactionApplicationError::IntentTtlExpired(ttl, current_block) => write!(
1094 formatter,
1095 "Intent TTL has expired. TTL: {:?}, Current block: {:?}",
1096 ttl, current_block
1097 ),
1098 TransactionApplicationError::IntentTtlTooFarInFuture(ttl, max_allowed) => write!(
1099 formatter,
1100 "Intent TTL is too far in the future. TTL: {:?}, Maximum allowed: {:?}",
1101 ttl, max_allowed
1102 ),
1103 TransactionApplicationError::IntentAlreadyExists => write!(
1104 formatter,
1105 "Intent already exists; duplicate intents are not allowed",
1106 ),
1107 }
1108 }
1109}
1110
1111impl Error for TransactionApplicationError {}
1112
1113#[derive(Debug)]
1114#[non_exhaustive]
1115pub enum QueryFailed<D: DB> {
1116 MissingCall,
1117 InvalidContract(ContractAddress),
1118 InvalidInput { value: Value, ty: Alignment },
1119 Runtime(onchain_runtime::error::TranscriptRejected<D>),
1120 Zswap(zswap::error::OfferCreationFailed),
1121}
1122
1123impl<D: DB> Display for QueryFailed<D> {
1124 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
1125 use QueryFailed::*;
1126 match self {
1127 MissingCall => write!(formatter, "attempted to run query prior to starting a call"),
1128 InvalidContract(addr) => write!(formatter, "contract {:?} does not exist", addr),
1129 InvalidInput { value, ty } => write!(
1130 formatter,
1131 "invalid input value {:?} for type {:?}",
1132 value, ty
1133 ),
1134 Runtime(err) => err.fmt(formatter),
1135 Zswap(err) => err.fmt(formatter),
1136 }
1137 }
1138}
1139
1140impl<D: DB> Error for QueryFailed<D> {}
1141
1142impl<D: DB> From<zswap::error::OfferCreationFailed> for QueryFailed<D> {
1143 fn from(err: zswap::error::OfferCreationFailed) -> QueryFailed<D> {
1144 QueryFailed::Zswap(err)
1145 }
1146}
1147
1148#[derive(Debug)]
1149#[non_exhaustive]
1150pub enum TransactionConstructionError {
1151 TransactionEmpty,
1152 UnfinishedCall {
1153 address: ContractAddress,
1154 operation: EntryPointBuf,
1155 },
1156 ProofFailed(ProvingError),
1157 MissingVerifierKey {
1158 address: ContractAddress,
1159 operation: EntryPointBuf,
1160 },
1161}
1162
1163impl Display for TransactionConstructionError {
1164 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
1165 use TransactionConstructionError::*;
1166 match self {
1167 TransactionEmpty => write!(formatter, "attempted to create empty transaction"),
1168 UnfinishedCall { address, operation } => write!(
1169 formatter,
1170 "unfinished call to {:?}/{:?}",
1171 address, operation
1172 ),
1173 ProofFailed(err) => {
1174 err.fmt(formatter)?;
1175 write!(formatter, " -- while assembling transaction")
1176 }
1177 MissingVerifierKey { address, operation } => write!(
1178 formatter,
1179 "attempted to create proof for {:?}/{:?}, which lacks a verifier key",
1180 address, operation,
1181 ),
1182 }
1183 }
1184}
1185
1186impl Error for TransactionConstructionError {}
1187
1188#[derive(Debug)]
1189#[allow(clippy::large_enum_variant)]
1190pub enum TransactionProvingError<D: DB> {
1191 LeftoverEntries {
1192 address: ContractAddress,
1193 entry_point: EntryPointBuf,
1194 entries: Box<Transcript<D>>,
1195 },
1196 RanOutOfEntries {
1197 address: ContractAddress,
1198 entry_point: EntryPointBuf,
1199 },
1200 MissingKeyset(KeyLocation),
1201 Proving(ProvingError),
1202 Tokio(std::io::Error),
1203}
1204
1205impl<D: DB> Display for TransactionProvingError<D> {
1206 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
1207 use TransactionProvingError::*;
1208 match self {
1209 LeftoverEntries {
1210 address,
1211 entry_point,
1212 entries,
1213 } => write!(
1214 formatter,
1215 "too many transcript entries for {:?}/{:?}: {:?} leftover",
1216 address, entry_point, entries
1217 ),
1218 RanOutOfEntries {
1219 address,
1220 entry_point,
1221 } => write!(
1222 formatter,
1223 "ran out of transcript entries for {:?}/{:?}",
1224 address, entry_point
1225 ),
1226 MissingKeyset(keyloc) => write!(
1227 formatter,
1228 "attempted proof, but couldn't find keys with ID {keyloc:?}"
1229 ),
1230 Proving(e) => e.fmt(formatter),
1231 Tokio(e) => e.fmt(formatter),
1232 }
1233 }
1234}
1235
1236impl<D: DB> Error for TransactionProvingError<D> {
1237 fn cause(&self) -> Option<&dyn Error> {
1238 match self {
1239 TransactionProvingError::Tokio(e) => Some(e),
1240 _ => None,
1241 }
1242 }
1243}
1244
1245impl<D: DB> From<ProvingError> for TransactionProvingError<D> {
1246 fn from(err: ProvingError) -> TransactionProvingError<D> {
1247 TransactionProvingError::Proving(err)
1248 }
1249}
1250
1251#[derive(Debug)]
1252pub enum PartitionFailure<D: DB> {
1253 Transcript(TranscriptRejected<D>),
1254 NonForest,
1255 GuaranteedOnlyUnsatisfied,
1256 IllegalSegmentZero,
1257 Merge(MalformedOffer),
1258}
1259
1260impl<D: DB> From<TranscriptRejected<D>> for PartitionFailure<D> {
1261 fn from(err: TranscriptRejected<D>) -> Self {
1262 PartitionFailure::Transcript(err)
1263 }
1264}
1265
1266impl<D: DB> Display for PartitionFailure<D> {
1267 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1268 match self {
1269 PartitionFailure::NonForest => {
1270 write!(f, "call graph was not a forest; cannot partition")
1271 }
1272 PartitionFailure::Transcript(e) => e.fmt(f),
1273 PartitionFailure::GuaranteedOnlyUnsatisfied => write!(
1274 f,
1275 "transaction could not be constructed to satisfy 'guaranteed only' segment specifier: the call was too expensive"
1276 ),
1277 PartitionFailure::IllegalSegmentZero => {
1278 write!(f, "illegal manual specification of segment 0")
1279 }
1280 PartitionFailure::Merge(e) => write!(f, "failed zswap merge: {e}"),
1281 }
1282 }
1283}
1284
1285impl<D: DB> Error for PartitionFailure<D> {
1286 fn cause(&self) -> Option<&dyn Error> {
1287 match self {
1288 PartitionFailure::Transcript(err) => Some(err),
1289 PartitionFailure::Merge(err) => Some(err),
1290 _ => None,
1291 }
1292 }
1293}
1294
1295#[derive(Debug, Clone)]
1296pub struct GenerationInfoAlreadyPresentError(pub DustGenerationInfo);
1297
1298impl Display for GenerationInfoAlreadyPresentError {
1299 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
1300 write!(
1301 formatter,
1302 "attempted to insert new Dust generation info {:?}, but this already exists",
1303 self.0,
1304 )
1305 }
1306}
1307
1308impl Error for GenerationInfoAlreadyPresentError {}
1309
1310#[derive(Clone, Debug)]
1311#[non_exhaustive]
1312pub enum EventReplayError {
1313 NonLinearInsertion {
1314 expected_next: u64,
1315 received: u64,
1316 tree_name: &'static str,
1317 },
1318 DtimeUpdateForUntracked {
1319 updated: u64,
1320 tracked_up_to_index: u64,
1321 },
1322 EventForPastTime {
1323 synced: Timestamp,
1324 event: Timestamp,
1325 },
1326 MerkleTreeUpdate(InvalidUpdate),
1327}
1328
1329impl Display for EventReplayError {
1330 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1331 use EventReplayError::*;
1332 match self {
1333 NonLinearInsertion {
1334 expected_next,
1335 received,
1336 tree_name,
1337 } => write!(
1338 f,
1339 "values inserted non-linearly into {tree_name} tree; expected to insert index {expected_next}, but received {received}."
1340 ),
1341 DtimeUpdateForUntracked {
1342 updated,
1343 tracked_up_to_index,
1344 } => write!(
1345 f,
1346 "attempted to update the dtime of a dust generation entry that isn't tracked; tracking up to index {tracked_up_to_index}, but received an update for {updated}"
1347 ),
1348 EventForPastTime { synced, event } => write!(
1349 f,
1350 "received an event with a timestamp prior to the time already synced to (synced to: {synced:?}, event time: {event:?})"
1351 ),
1352 MerkleTreeUpdate(err) => err.fmt(f),
1353 }
1354 }
1355}
1356
1357impl Error for EventReplayError {
1358 fn cause(&self) -> Option<&dyn Error> {
1359 match self {
1360 EventReplayError::MerkleTreeUpdate(err) => Some(err),
1361 _ => None,
1362 }
1363 }
1364}
1365
1366impl From<InvalidUpdate> for EventReplayError {
1367 fn from(err: InvalidUpdate) -> Self {
1368 EventReplayError::MerkleTreeUpdate(err)
1369 }
1370}
1371
1372#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1373pub struct BlockLimitExceeded;
1374
1375impl Display for BlockLimitExceeded {
1376 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1377 write!(
1378 f,
1379 "exceeded block limit during post-block update declaration"
1380 )
1381 }
1382}
1383
1384impl Error for BlockLimitExceeded {}