near_primitives/
receipt.rs

1use crate::action::{GlobalContractIdentifier, base64};
2use crate::errors::EpochError;
3use crate::hash::CryptoHash;
4use crate::serialize::dec_format;
5use crate::shard_layout::ShardLayout;
6use crate::transaction::{Action, TransferAction};
7use crate::types::{AccountId, Balance, BlockHeight, ShardId};
8use borsh::{BorshDeserialize, BorshSerialize};
9use itertools::Itertools;
10use near_crypto::{KeyType, PublicKey};
11use near_fmt::AbbrBytes;
12use near_primitives_core::types::Gas;
13use near_schema_checker_lib::ProtocolSchema;
14use serde_with::base64::Base64;
15use serde_with::serde_as;
16use std::borrow::Cow;
17use std::collections::{BTreeMap, HashMap};
18use std::fmt;
19use std::io::{self, Read};
20use std::io::{Error, ErrorKind};
21use std::sync::Arc;
22
23/// The outgoing (egress) data which will be transformed
24/// to a `DataReceipt` to be sent to a `receipt.receiver`
25#[derive(
26    BorshSerialize,
27    BorshDeserialize,
28    Hash,
29    Clone,
30    Debug,
31    PartialEq,
32    Eq,
33    serde::Serialize,
34    serde::Deserialize,
35    ProtocolSchema,
36)]
37#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
38pub struct DataReceiver {
39    pub data_id: CryptoHash,
40    pub receiver_id: AccountId,
41}
42
43/// Receipts are used for a cross-shard communication.
44/// Receipts could be 2 types (determined by a `ReceiptEnum`): `ReceiptEnum::Action` of `ReceiptEnum::Data`.
45#[derive(
46    BorshSerialize,
47    BorshDeserialize,
48    Debug,
49    PartialEq,
50    Eq,
51    Clone,
52    serde::Serialize,
53    serde::Deserialize,
54    ProtocolSchema,
55)]
56#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
57pub struct ReceiptV0 {
58    /// An issuer account_id of a particular receipt.
59    /// `predecessor_id` could be either `Transaction` `signer_id` or intermediate contract's `account_id`.
60    pub predecessor_id: AccountId,
61    /// `receiver_id` is a receipt destination.
62    pub receiver_id: AccountId,
63    /// An unique id for the receipt
64    pub receipt_id: CryptoHash,
65    /// A receipt type
66    pub receipt: ReceiptEnum,
67}
68
69#[derive(
70    BorshSerialize,
71    BorshDeserialize,
72    Debug,
73    PartialEq,
74    Eq,
75    Clone,
76    serde::Serialize,
77    serde::Deserialize,
78    ProtocolSchema,
79)]
80#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
81pub struct ReceiptV1 {
82    /// An issuer account_id of a particular receipt.
83    /// `predecessor_id` could be either `Transaction` `signer_id` or intermediate contract's `account_id`.
84    pub predecessor_id: AccountId,
85    /// `receiver_id` is a receipt destination.
86    pub receiver_id: AccountId,
87    /// An unique id for the receipt
88    pub receipt_id: CryptoHash,
89    /// A receipt type
90    pub receipt: ReceiptEnum,
91    /// Priority of a receipt
92    pub priority: u64,
93}
94
95#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize, ProtocolSchema)]
96#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
97#[serde(untagged)]
98pub enum Receipt {
99    V0(ReceiptV0),
100    V1(ReceiptV1),
101}
102
103/// A receipt that is stored in the state with added metadata. A receipt may be
104/// stored in the state as a delayed receipt, buffered receipt or a promise
105/// yield receipt. The metadata contains additional information about receipt
106///
107/// Please note that the StateStoredReceipt implements custom serialization and
108/// deserialization. Please see the comment on [ReceiptOrStateStoredReceipt]
109/// for more details.
110///
111/// This struct is versioned so that it can be enhanced in the future.
112#[derive(PartialEq, Eq, Debug, ProtocolSchema)]
113pub enum StateStoredReceipt<'a> {
114    V0(StateStoredReceiptV0<'a>),
115    V1(StateStoredReceiptV1<'a>),
116}
117
118/// The V0 of StateStoredReceipt. It contains the receipt and metadata.
119#[derive(BorshDeserialize, BorshSerialize, PartialEq, Eq, Debug, ProtocolSchema)]
120pub struct StateStoredReceiptV0<'a> {
121    /// The receipt.
122    pub receipt: Cow<'a, Receipt>,
123    pub metadata: StateStoredReceiptMetadata,
124}
125
126/// The V1 of StateStoredReceipt.
127/// The data is the same as in V0.
128/// Outgoing buffer metadata is updated only for versions V1 and higher.
129/// The receipts start being stored as V1 after the protocol change that introduced
130/// outgoing buffer metadata. Having a separate variant makes it clear whether the
131/// outgoing buffer metadata should be updated when a receipt is stored/removed.
132#[derive(BorshDeserialize, BorshSerialize, PartialEq, Eq, Debug, ProtocolSchema)]
133pub struct StateStoredReceiptV1<'a> {
134    pub receipt: Cow<'a, Receipt>,
135    pub metadata: StateStoredReceiptMetadata,
136}
137
138/// The metadata associated with the receipt stored in state.
139#[derive(BorshDeserialize, BorshSerialize, PartialEq, Eq, Debug, ProtocolSchema)]
140pub struct StateStoredReceiptMetadata {
141    /// The congestion gas of the receipt when it was stored in the state.
142    /// Please see [compute_receipt_congestion_gas] for more details.
143    pub congestion_gas: Gas,
144    /// The congestion size of the receipt when it was stored in the state.
145    /// Please see [compute_receipt_size] for more details.
146    pub congestion_size: u64,
147}
148
149/// The tag that is used to differentiate between the Receipt and StateStoredReceipt.
150const STATE_STORED_RECEIPT_TAG: u8 = u8::MAX;
151
152/// This is a convenience struct for handling the migration from [Receipt] to
153/// [StateStoredReceipt]. Both variants can be directly serialized and
154/// deserialized to this struct.
155///
156/// This structure is only meant as a migration vehicle and should not be used
157/// for other purposes. In order to make any changes to how receipts are stored
158/// in state the StateStoredReceipt should be used. It supports versioning.
159///
160/// The receipt in both variants is stored as a Cow to allow for both owned and
161/// borrowed ownership. The owned receipt should be used when pulling receipts
162/// from the state. The borrowed ownership can be used when pushing receipts
163/// into the state. In that case the receipt should never need to be cloned. The
164/// serialization only needs a reference.
165#[derive(PartialEq, Eq, Debug, ProtocolSchema)]
166pub enum ReceiptOrStateStoredReceipt<'a> {
167    Receipt(Cow<'a, Receipt>),
168    StateStoredReceipt(StateStoredReceipt<'a>),
169}
170
171impl ReceiptOrStateStoredReceipt<'_> {
172    pub fn into_receipt(self) -> Receipt {
173        match self {
174            ReceiptOrStateStoredReceipt::Receipt(receipt) => receipt.into_owned(),
175            ReceiptOrStateStoredReceipt::StateStoredReceipt(receipt) => receipt.into_receipt(),
176        }
177    }
178
179    pub fn get_receipt(&self) -> &Receipt {
180        match self {
181            ReceiptOrStateStoredReceipt::Receipt(receipt) => receipt,
182            ReceiptOrStateStoredReceipt::StateStoredReceipt(receipt) => receipt.get_receipt(),
183        }
184    }
185
186    pub fn should_update_outgoing_metadatas(&self) -> bool {
187        match self {
188            ReceiptOrStateStoredReceipt::Receipt(_) => false,
189            ReceiptOrStateStoredReceipt::StateStoredReceipt(state_stored_receipt) => {
190                state_stored_receipt.should_update_outgoing_metadatas()
191            }
192        }
193    }
194}
195
196impl<'a> StateStoredReceipt<'a> {
197    pub fn new_owned(receipt: Receipt, metadata: StateStoredReceiptMetadata) -> Self {
198        let receipt = Cow::Owned(receipt);
199        Self::V1(StateStoredReceiptV1 { receipt, metadata })
200    }
201
202    pub fn new_borrowed(receipt: &'a Receipt, metadata: StateStoredReceiptMetadata) -> Self {
203        let receipt = Cow::Borrowed(receipt);
204
205        Self::V1(StateStoredReceiptV1 { receipt, metadata })
206    }
207
208    pub fn into_receipt(self) -> Receipt {
209        match self {
210            StateStoredReceipt::V0(v0) => v0.receipt.into_owned(),
211            StateStoredReceipt::V1(v1) => v1.receipt.into_owned(),
212        }
213    }
214
215    pub fn get_receipt(&self) -> &Receipt {
216        match self {
217            StateStoredReceipt::V0(v0) => &v0.receipt,
218            StateStoredReceipt::V1(v1) => &v1.receipt,
219        }
220    }
221
222    pub fn metadata(&self) -> &StateStoredReceiptMetadata {
223        match self {
224            StateStoredReceipt::V0(v0) => &v0.metadata,
225            StateStoredReceipt::V1(v1) => &v1.metadata,
226        }
227    }
228
229    pub fn should_update_outgoing_metadatas(&self) -> bool {
230        match self {
231            StateStoredReceipt::V0(_) => false,
232            StateStoredReceipt::V1(_) => true,
233        }
234    }
235}
236
237impl BorshSerialize for Receipt {
238    fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
239        match self {
240            Receipt::V0(receipt) => BorshSerialize::serialize(&receipt, writer),
241            Receipt::V1(receipt) => {
242                BorshSerialize::serialize(&1_u8, writer)?;
243                BorshSerialize::serialize(&receipt, writer)
244            }
245        }
246    }
247}
248
249impl BorshDeserialize for Receipt {
250    /// Deserialize based on the first and second bytes of the stream. For V0, we do backward compatible deserialization by deserializing
251    /// the entire stream into V0. For V1, we consume the first byte and then deserialize the rest.
252    fn deserialize_reader<R: Read>(reader: &mut R) -> std::io::Result<Self> {
253        // This is a ridiculous hackery: because the first field in `ReceiptV0` is an `AccountId`
254        // and an account id is at most 64 bytes, for all valid `ReceiptV0` the second byte must be 0
255        // because of the little endian encoding of the length of the account id.
256        // On the other hand, for `ReceiptV1`, since the first byte is 1 and an account id must have nonzero
257        // length, so the second byte must not be zero. Therefore, we can distinguish between the two versions
258        // by looking at the second byte.
259
260        let u1 = u8::deserialize_reader(reader)?;
261        let u2 = u8::deserialize_reader(reader)?;
262        let is_v0 = u2 == 0;
263
264        let prefix = if is_v0 { vec![u1, u2] } else { vec![u2] };
265        let mut reader = prefix.chain(reader);
266
267        let receipt = if is_v0 {
268            Receipt::V0(ReceiptV0::deserialize_reader(&mut reader)?)
269        } else {
270            Receipt::V1(ReceiptV1::deserialize_reader(&mut reader)?)
271        };
272        Ok(receipt)
273    }
274}
275
276impl BorshSerialize for StateStoredReceipt<'_> {
277    fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
278        // The serialization format for StateStored receipt is as follows:
279        // Byte 1: STATE_STORED_RECEIPT_TAG
280        // Byte 2: STATE_STORED_RECEIPT_TAG
281        // Byte 3: enum version (e.g. V0 => 0_u8)
282        // serialized variant value
283
284        BorshSerialize::serialize(&STATE_STORED_RECEIPT_TAG, writer)?;
285        BorshSerialize::serialize(&STATE_STORED_RECEIPT_TAG, writer)?;
286        match self {
287            StateStoredReceipt::V0(v0) => {
288                BorshSerialize::serialize(&0_u8, writer)?;
289                BorshSerialize::serialize(&v0, writer)?;
290            }
291            StateStoredReceipt::V1(v1) => {
292                BorshSerialize::serialize(&1_u8, writer)?;
293                BorshSerialize::serialize(&v1, writer)?;
294            }
295        }
296        Ok(())
297    }
298}
299
300impl BorshDeserialize for StateStoredReceipt<'_> {
301    fn deserialize_reader<R: Read>(reader: &mut R) -> io::Result<Self> {
302        let u1 = u8::deserialize_reader(reader)?;
303        let u2 = u8::deserialize_reader(reader)?;
304        let u3 = u8::deserialize_reader(reader)?;
305
306        if u1 != STATE_STORED_RECEIPT_TAG || u2 != STATE_STORED_RECEIPT_TAG {
307            let error = format!(
308                "Invalid tag found when deserializing StateStoredReceipt. Found: {}, {}. Expected: {}, {}",
309                u1, u2, STATE_STORED_RECEIPT_TAG, STATE_STORED_RECEIPT_TAG
310            );
311            let error = Error::new(ErrorKind::Other, error);
312            return Err(io::Error::new(ErrorKind::InvalidData, error));
313        }
314
315        match u3 {
316            0 => {
317                let v0 = StateStoredReceiptV0::deserialize_reader(reader)?;
318                Ok(StateStoredReceipt::V0(v0))
319            }
320            1 => {
321                let v1 = StateStoredReceiptV1::deserialize_reader(reader)?;
322                Ok(StateStoredReceipt::V1(v1))
323            }
324            v => {
325                let error = format!(
326                    "Invalid version found when deserializing StateStoredReceipt. Found: {}. Expected: 0",
327                    v
328                );
329                let error = Error::new(ErrorKind::Other, error);
330                Err(io::Error::new(ErrorKind::InvalidData, error))
331            }
332        }
333    }
334}
335
336impl BorshSerialize for ReceiptOrStateStoredReceipt<'_> {
337    fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
338        // This is custom serialization in order to provide backwards
339        // compatibility for migration from Receipt to StateStoredReceipt.
340
341        // Please see the comment in deserialize_reader for more details.
342        match self {
343            ReceiptOrStateStoredReceipt::Receipt(receipt) => {
344                BorshSerialize::serialize(receipt, writer)
345            }
346            ReceiptOrStateStoredReceipt::StateStoredReceipt(receipt) => {
347                BorshSerialize::serialize(receipt, writer)
348            }
349        }
350    }
351}
352
353impl BorshDeserialize for ReceiptOrStateStoredReceipt<'_> {
354    fn deserialize_reader<R: Read>(reader: &mut R) -> io::Result<Self> {
355        // This is custom deserialization in order to provide backwards
356        // compatibility for migration from Receipt to StateStoredReceipt.
357
358        // Both variants (Receipt and StateStoredReceipt) need to be directly
359        // deserializable into the ReceiptOrStateStoredReceipt.
360
361        // Read the first two bytes in order to discriminate between the Receipt
362        // and StateStoredReceipt.
363        // The StateStored receipt has the tag as the first two bytes.
364        // The Receipt::V0 has 0 as the second byte.
365        // The Receipt::V1 has 1 as the first byte.
366        let u1 = u8::deserialize_reader(reader)?;
367        let u2 = u8::deserialize_reader(reader)?;
368
369        // Put the read bytes back into the reader by chaining.
370        let prefix = [u1, u2];
371        let mut reader = prefix.chain(reader);
372
373        if u1 == STATE_STORED_RECEIPT_TAG && u2 == STATE_STORED_RECEIPT_TAG {
374            let receipt = StateStoredReceipt::deserialize_reader(&mut reader)?;
375            Ok(ReceiptOrStateStoredReceipt::StateStoredReceipt(receipt))
376        } else {
377            let receipt = Receipt::deserialize_reader(&mut reader)?;
378            let receipt = Cow::Owned(receipt);
379            Ok(ReceiptOrStateStoredReceipt::Receipt(receipt))
380        }
381    }
382}
383
384pub enum ReceiptPriority {
385    /// Used in ReceiptV1
386    Priority(u64),
387    /// Used in ReceiptV0
388    NoPriority,
389}
390
391impl ReceiptPriority {
392    pub fn value(&self) -> u64 {
393        match self {
394            ReceiptPriority::Priority(value) => *value,
395            ReceiptPriority::NoPriority => 0,
396        }
397    }
398}
399
400impl Receipt {
401    pub fn receiver_id(&self) -> &AccountId {
402        match self {
403            Receipt::V0(receipt) => &receipt.receiver_id,
404            Receipt::V1(receipt) => &receipt.receiver_id,
405        }
406    }
407
408    pub fn set_receiver_id(&mut self, receiver_id: AccountId) {
409        match self {
410            Receipt::V0(receipt) => receipt.receiver_id = receiver_id,
411            Receipt::V1(receipt) => receipt.receiver_id = receiver_id,
412        }
413    }
414
415    pub fn predecessor_id(&self) -> &AccountId {
416        match self {
417            Receipt::V0(receipt) => &receipt.predecessor_id,
418            Receipt::V1(receipt) => &receipt.predecessor_id,
419        }
420    }
421
422    pub fn set_predecessor_id(&mut self, predecessor_id: AccountId) {
423        match self {
424            Receipt::V0(receipt) => receipt.predecessor_id = predecessor_id,
425            Receipt::V1(receipt) => receipt.predecessor_id = predecessor_id,
426        }
427    }
428
429    pub fn receipt(&self) -> &ReceiptEnum {
430        match self {
431            Receipt::V0(receipt) => &receipt.receipt,
432            Receipt::V1(receipt) => &receipt.receipt,
433        }
434    }
435
436    pub fn receipt_mut(&mut self) -> &mut ReceiptEnum {
437        match self {
438            Receipt::V0(receipt) => &mut receipt.receipt,
439            Receipt::V1(receipt) => &mut receipt.receipt,
440        }
441    }
442
443    pub fn take_receipt(self) -> ReceiptEnum {
444        match self {
445            Receipt::V0(receipt) => receipt.receipt,
446            Receipt::V1(receipt) => receipt.receipt,
447        }
448    }
449
450    pub fn receipt_id(&self) -> &CryptoHash {
451        match self {
452            Receipt::V0(receipt) => &receipt.receipt_id,
453            Receipt::V1(receipt) => &receipt.receipt_id,
454        }
455    }
456
457    pub fn set_receipt_id(&mut self, receipt_id: CryptoHash) {
458        match self {
459            Receipt::V0(receipt) => receipt.receipt_id = receipt_id,
460            Receipt::V1(receipt) => receipt.receipt_id = receipt_id,
461        }
462    }
463
464    pub fn priority(&self) -> ReceiptPriority {
465        match self {
466            Receipt::V0(_) => ReceiptPriority::NoPriority,
467            Receipt::V1(receipt) => ReceiptPriority::Priority(receipt.priority),
468        }
469    }
470
471    /// It's not a content hash, but receipt_id is unique.
472    pub fn get_hash(&self) -> CryptoHash {
473        *self.receipt_id()
474    }
475
476    pub fn receiver_shard_id(&self, shard_layout: &ShardLayout) -> Result<ShardId, EpochError> {
477        let shard_id = match self.receipt() {
478            ReceiptEnum::Action(_)
479            | ReceiptEnum::Data(_)
480            | ReceiptEnum::PromiseYield(_)
481            | ReceiptEnum::PromiseResume(_) => {
482                shard_layout.account_id_to_shard_id(self.receiver_id())
483            }
484            ReceiptEnum::GlobalContractDistribution(receipt) => {
485                let target_shard = receipt.target_shard();
486                if shard_layout.shard_ids().contains(&target_shard) {
487                    target_shard
488                } else {
489                    let Some(children_shards) = shard_layout.get_children_shards_ids(target_shard)
490                    else {
491                        return Err(EpochError::ShardingError(format!(
492                            "Shard {target_shard} does not exist in the parent shard layout",
493                        )));
494                    };
495                    // It is enough to send the receipt to the first child shard, it will be forwarded
496                    // to the rest of the children as part the receipt processing logic
497                    children_shards[0]
498                }
499            }
500        };
501        Ok(shard_id)
502    }
503
504    /// Generates a receipt with a transfer from system for a given balance without a receipt_id.
505    /// This should be used for token refunds instead of gas refunds. It inherits priority from the parent receipt.
506    /// It doesn't refund the allowance of the access key. For gas refunds use `new_gas_refund`.
507    pub fn new_balance_refund(
508        receiver_id: &AccountId,
509        refund: Balance,
510        priority: ReceiptPriority,
511    ) -> Self {
512        match priority {
513            ReceiptPriority::Priority(priority) => Receipt::V1(ReceiptV1 {
514                predecessor_id: "system".parse().unwrap(),
515                receiver_id: receiver_id.clone(),
516                receipt_id: CryptoHash::default(),
517
518                receipt: ReceiptEnum::Action(ActionReceipt {
519                    signer_id: "system".parse().unwrap(),
520                    signer_public_key: PublicKey::empty(KeyType::ED25519),
521                    gas_price: 0,
522                    output_data_receivers: vec![],
523                    input_data_ids: vec![],
524                    actions: vec![Action::Transfer(TransferAction { deposit: refund })],
525                }),
526                priority,
527            }),
528            ReceiptPriority::NoPriority => Receipt::V0(ReceiptV0 {
529                predecessor_id: "system".parse().unwrap(),
530                receiver_id: receiver_id.clone(),
531                receipt_id: CryptoHash::default(),
532
533                receipt: ReceiptEnum::Action(ActionReceipt {
534                    signer_id: "system".parse().unwrap(),
535                    signer_public_key: PublicKey::empty(KeyType::ED25519),
536                    gas_price: 0,
537                    output_data_receivers: vec![],
538                    input_data_ids: vec![],
539                    actions: vec![Action::Transfer(TransferAction { deposit: refund })],
540                }),
541            }),
542        }
543    }
544
545    /// Generates a receipt with a transfer action from system for a given balance without a
546    /// receipt_id. It contains `signer_id` and `signer_public_key` to indicate this is a gas
547    /// refund. The execution of this receipt will try to refund the allowance of the
548    /// access key with the given public key.
549    /// Gas refund does not inherit priority from its parent receipt and has no priority associated with it
550    /// NOTE: The access key may be replaced by the owner, so the execution can't rely that the
551    /// access key is the same and it should use best effort for the refund.
552    pub fn new_gas_refund(
553        receiver_id: &AccountId,
554        refund: Balance,
555        signer_public_key: PublicKey,
556        priority: ReceiptPriority,
557    ) -> Self {
558        match priority {
559            ReceiptPriority::Priority(priority) => Receipt::V1(ReceiptV1 {
560                predecessor_id: "system".parse().unwrap(),
561                receiver_id: receiver_id.clone(),
562                receipt_id: CryptoHash::default(),
563
564                receipt: ReceiptEnum::Action(ActionReceipt {
565                    signer_id: receiver_id.clone(),
566                    signer_public_key,
567                    gas_price: 0,
568                    output_data_receivers: vec![],
569                    input_data_ids: vec![],
570                    actions: vec![Action::Transfer(TransferAction { deposit: refund })],
571                }),
572                priority,
573            }),
574            ReceiptPriority::NoPriority => Receipt::V0(ReceiptV0 {
575                predecessor_id: "system".parse().unwrap(),
576                receiver_id: receiver_id.clone(),
577                receipt_id: CryptoHash::default(),
578
579                receipt: ReceiptEnum::Action(ActionReceipt {
580                    signer_id: receiver_id.clone(),
581                    signer_public_key,
582                    gas_price: 0,
583                    output_data_receivers: vec![],
584                    input_data_ids: vec![],
585                    actions: vec![Action::Transfer(TransferAction { deposit: refund })],
586                }),
587            }),
588        }
589    }
590
591    pub fn new_global_contract_distribution(
592        predecessor_id: AccountId,
593        receipt: GlobalContractDistributionReceipt,
594    ) -> Self {
595        Self::V0(ReceiptV0 {
596            predecessor_id,
597            receiver_id: "system".parse().unwrap(),
598            receipt_id: CryptoHash::default(),
599            receipt: ReceiptEnum::GlobalContractDistribution(receipt),
600        })
601    }
602}
603
604/// Receipt could be either ActionReceipt or DataReceipt
605#[derive(
606    BorshSerialize,
607    BorshDeserialize,
608    Clone,
609    Debug,
610    PartialEq,
611    Eq,
612    serde::Serialize,
613    serde::Deserialize,
614    ProtocolSchema,
615)]
616#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
617pub enum ReceiptEnum {
618    Action(ActionReceipt),
619    Data(DataReceipt),
620    PromiseYield(ActionReceipt),
621    PromiseResume(DataReceipt),
622    GlobalContractDistribution(GlobalContractDistributionReceipt),
623}
624
625/// ActionReceipt is derived from an Action from `Transaction or from Receipt`
626#[derive(
627    BorshSerialize,
628    BorshDeserialize,
629    Debug,
630    PartialEq,
631    Eq,
632    Clone,
633    serde::Serialize,
634    serde::Deserialize,
635    ProtocolSchema,
636)]
637#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
638pub struct ActionReceipt {
639    /// A signer of the original transaction
640    pub signer_id: AccountId,
641    /// An access key which was used to sign the original transaction
642    pub signer_public_key: PublicKey,
643    /// A gas_price which has been used to buy gas in the original transaction
644    #[serde(with = "dec_format")]
645    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
646    pub gas_price: Balance,
647    /// If present, where to route the output data
648    pub output_data_receivers: Vec<DataReceiver>,
649    /// A list of the input data dependencies for this Receipt to process.
650    /// If all `input_data_ids` for this receipt are delivered to the account
651    /// that means we have all the `ReceivedData` input which will be than converted to a
652    /// `PromiseResult::Successful(value)` or `PromiseResult::Failed`
653    /// depending on `ReceivedData` is `Some(_)` or `None`
654    pub input_data_ids: Vec<CryptoHash>,
655    /// A list of actions to process when all input_data_ids are filled
656    pub actions: Vec<Action>,
657}
658
659/// An incoming (ingress) `DataReceipt` which is going to a Receipt's `receiver` input_data_ids
660/// Which will be converted to `PromiseResult::Successful(value)` or `PromiseResult::Failed`
661#[serde_as]
662#[derive(
663    BorshSerialize,
664    BorshDeserialize,
665    Hash,
666    PartialEq,
667    Eq,
668    Clone,
669    serde::Serialize,
670    serde::Deserialize,
671    ProtocolSchema,
672)]
673#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
674pub struct DataReceipt {
675    pub data_id: CryptoHash,
676    #[serde_as(as = "Option<Base64>")]
677    #[cfg_attr(feature = "schemars", schemars(with = "Option<String>"))]
678    pub data: Option<Vec<u8>>,
679}
680
681impl fmt::Debug for DataReceipt {
682    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
683        f.debug_struct("DataReceipt")
684            .field("data_id", &self.data_id)
685            .field("data", &format_args!("{}", AbbrBytes(self.data.as_deref())))
686            .finish()
687    }
688}
689
690/// A temporary data which is created by processing of DataReceipt
691/// stored in a state trie with a key = `account_id` + `data_id` until
692/// `input_data_ids` of all incoming Receipts are satisfied
693/// None means data retrieval was failed
694#[derive(BorshSerialize, BorshDeserialize, Hash, PartialEq, Eq, Clone, ProtocolSchema)]
695pub struct ReceivedData {
696    pub data: Option<Vec<u8>>,
697}
698
699impl fmt::Debug for ReceivedData {
700    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
701        f.debug_struct("ReceivedData")
702            .field("data", &format_args!("{}", AbbrBytes(self.data.as_deref())))
703            .finish()
704    }
705}
706
707#[derive(
708    BorshSerialize,
709    BorshDeserialize,
710    Hash,
711    PartialEq,
712    Eq,
713    Clone,
714    Debug,
715    serde::Deserialize,
716    serde::Serialize,
717    ProtocolSchema,
718)]
719#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
720pub enum GlobalContractDistributionReceipt {
721    V1(GlobalContractDistributionReceiptV1),
722}
723
724impl GlobalContractDistributionReceipt {
725    pub fn new(
726        id: GlobalContractIdentifier,
727        target_shard: ShardId,
728        already_delivered_shards: Vec<ShardId>,
729        code: Arc<[u8]>,
730    ) -> Self {
731        Self::V1(GlobalContractDistributionReceiptV1 {
732            id,
733            target_shard,
734            already_delivered_shards,
735            code,
736        })
737    }
738
739    pub fn id(&self) -> &GlobalContractIdentifier {
740        match &self {
741            Self::V1(v1) => &v1.id,
742        }
743    }
744
745    pub fn target_shard(&self) -> ShardId {
746        match &self {
747            Self::V1(v1) => v1.target_shard,
748        }
749    }
750
751    pub fn already_delivered_shards(&self) -> &[ShardId] {
752        match &self {
753            Self::V1(v1) => &v1.already_delivered_shards,
754        }
755    }
756
757    pub fn code(&self) -> &Arc<[u8]> {
758        match &self {
759            Self::V1(v1) => &v1.code,
760        }
761    }
762}
763
764#[serde_as]
765#[derive(
766    BorshSerialize,
767    BorshDeserialize,
768    Hash,
769    PartialEq,
770    Eq,
771    Clone,
772    serde::Deserialize,
773    serde::Serialize,
774    ProtocolSchema,
775)]
776#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
777pub struct GlobalContractDistributionReceiptV1 {
778    id: GlobalContractIdentifier,
779    target_shard: ShardId,
780    already_delivered_shards: Vec<ShardId>,
781    #[serde_as(as = "Base64")]
782    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
783    code: Arc<[u8]>,
784}
785
786impl fmt::Debug for GlobalContractDistributionReceiptV1 {
787    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
788        f.debug_struct("GlobalContractDistributionReceipt")
789            .field("id", &self.id)
790            .field("target_shard", &self.target_shard)
791            .field("already_delivered_shards", &self.already_delivered_shards)
792            .field("code", &format_args!("{}", base64(&self.code)))
793            .finish()
794    }
795}
796
797/// Stores indices for a persistent queue for delayed receipts that didn't fit into a block.
798#[derive(Default, BorshSerialize, BorshDeserialize, Clone, PartialEq, Debug, ProtocolSchema)]
799pub struct DelayedReceiptIndices {
800    // First inclusive index in the queue.
801    pub first_index: u64,
802    // Exclusive end index of the queue
803    pub next_available_index: u64,
804}
805
806impl DelayedReceiptIndices {
807    pub fn len(&self) -> u64 {
808        self.next_available_index - self.first_index
809    }
810}
811
812/// Stores indices for a persistent queue for PromiseYield timeouts.
813#[derive(Default, BorshSerialize, BorshDeserialize, Clone, PartialEq, Debug, ProtocolSchema)]
814pub struct PromiseYieldIndices {
815    // First inclusive index in the queue.
816    pub first_index: u64,
817    // Exclusive end index of the queue
818    pub next_available_index: u64,
819}
820
821impl PromiseYieldIndices {
822    pub fn len(&self) -> u64 {
823        self.next_available_index - self.first_index
824    }
825}
826
827/// Entries in the queue of PromiseYield timeouts.
828#[derive(BorshSerialize, BorshDeserialize, Clone, PartialEq, Debug, ProtocolSchema)]
829pub struct PromiseYieldTimeout {
830    /// The account on which the yielded promise was created
831    pub account_id: AccountId,
832    /// The `data_id` used to identify the awaited input data
833    pub data_id: CryptoHash,
834    /// The block height before which the data must be submitted
835    pub expires_at: BlockHeight,
836}
837
838/// Stores indices for a persistent queue in the state trie.
839#[derive(Default, BorshSerialize, BorshDeserialize, Clone, PartialEq, Debug, ProtocolSchema)]
840pub struct TrieQueueIndices {
841    // First inclusive index in the queue.
842    pub first_index: u64,
843    // Exclusive end index of the queue
844    pub next_available_index: u64,
845}
846
847impl TrieQueueIndices {
848    pub fn len(&self) -> u64 {
849        self.next_available_index - self.first_index
850    }
851
852    pub fn is_default(&self) -> bool {
853        self.next_available_index == 0
854    }
855}
856
857impl From<DelayedReceiptIndices> for TrieQueueIndices {
858    fn from(other: DelayedReceiptIndices) -> Self {
859        Self { first_index: other.first_index, next_available_index: other.next_available_index }
860    }
861}
862
863impl From<TrieQueueIndices> for DelayedReceiptIndices {
864    fn from(other: TrieQueueIndices) -> Self {
865        Self { first_index: other.first_index, next_available_index: other.next_available_index }
866    }
867}
868
869/// Stores indices for a persistent queue for buffered receipts that couldn't be
870/// forwarded.
871///
872/// This is the singleton value stored in the `BUFFERED_RECEIPT_INDICES` trie
873/// column.
874#[derive(Default, BorshSerialize, BorshDeserialize, Clone, PartialEq, Debug, ProtocolSchema)]
875pub struct BufferedReceiptIndices {
876    pub shard_buffers: BTreeMap<ShardId, TrieQueueIndices>,
877}
878
879/// Map of shard to list of receipts to send to it.
880pub type ReceiptResult = HashMap<ShardId, Vec<Receipt>>;
881
882#[cfg(test)]
883mod tests {
884    use super::*;
885
886    fn get_receipt_v0() -> Receipt {
887        let receipt_v0 = Receipt::V0(ReceiptV0 {
888            predecessor_id: "predecessor_id".parse().unwrap(),
889            receiver_id: "receiver_id".parse().unwrap(),
890            receipt_id: CryptoHash::default(),
891            receipt: ReceiptEnum::Action(ActionReceipt {
892                signer_id: "signer_id".parse().unwrap(),
893                signer_public_key: PublicKey::empty(KeyType::ED25519),
894                gas_price: 0,
895                output_data_receivers: vec![],
896                input_data_ids: vec![],
897                actions: vec![Action::Transfer(TransferAction { deposit: 0 })],
898            }),
899        });
900        receipt_v0
901    }
902
903    fn get_receipt_v1() -> Receipt {
904        let receipt_v1 = Receipt::V1(ReceiptV1 {
905            predecessor_id: "predecessor_id".parse().unwrap(),
906            receiver_id: "receiver_id".parse().unwrap(),
907            receipt_id: CryptoHash::default(),
908            receipt: ReceiptEnum::Action(ActionReceipt {
909                signer_id: "signer_id".parse().unwrap(),
910                signer_public_key: PublicKey::empty(KeyType::ED25519),
911                gas_price: 0,
912                output_data_receivers: vec![],
913                input_data_ids: vec![],
914                actions: vec![Action::Transfer(TransferAction { deposit: 0 })],
915            }),
916            priority: 1,
917        });
918        receipt_v1
919    }
920
921    #[test]
922    fn test_receipt_v0_serialization() {
923        let receipt_v0 = get_receipt_v0();
924        let serialized_receipt = borsh::to_vec(&receipt_v0).unwrap();
925        let receipt2 = Receipt::try_from_slice(&serialized_receipt).unwrap();
926        assert_eq!(receipt_v0, receipt2);
927    }
928
929    #[test]
930    fn test_receipt_v1_serialization() {
931        let receipt_v1 = get_receipt_v1();
932        let serialized_receipt = borsh::to_vec(&receipt_v1).unwrap();
933        let receipt2 = Receipt::try_from_slice(&serialized_receipt).unwrap();
934        assert_eq!(receipt_v1, receipt2);
935    }
936
937    fn test_state_stored_receipt_serialization_impl(receipt: Receipt) {
938        let metadata = StateStoredReceiptMetadata { congestion_gas: 42, congestion_size: 43 };
939        let receipt = StateStoredReceipt::new_owned(receipt, metadata);
940
941        let serialized_receipt = borsh::to_vec(&receipt).unwrap();
942        let deserialized_receipt = StateStoredReceipt::try_from_slice(&serialized_receipt).unwrap();
943
944        assert_eq!(receipt, deserialized_receipt);
945    }
946
947    #[test]
948    fn test_state_stored_receipt_serialization_v0() {
949        let receipt = get_receipt_v0();
950        test_state_stored_receipt_serialization_impl(receipt);
951    }
952
953    #[test]
954    fn test_state_stored_receipt_serialization_v1() {
955        let receipt = get_receipt_v1();
956        test_state_stored_receipt_serialization_impl(receipt);
957    }
958
959    #[test]
960    fn test_receipt_or_state_stored_receipt_serialization() {
961        // Case 1:
962        // Receipt V0 can be deserialized as ReceiptOrStateStoredReceipt
963        {
964            let receipt = get_receipt_v0();
965            let receipt = Cow::Owned(receipt);
966
967            let serialized_receipt = borsh::to_vec(&receipt).unwrap();
968            let deserialized_receipt =
969                ReceiptOrStateStoredReceipt::try_from_slice(&serialized_receipt).unwrap();
970
971            assert_eq!(ReceiptOrStateStoredReceipt::Receipt(receipt), deserialized_receipt);
972        }
973
974        // Case 2:
975        // Receipt V1 can be deserialized as ReceiptOrStateStoredReceipt
976        {
977            let receipt = get_receipt_v1();
978            let receipt = Cow::Owned(receipt);
979
980            let serialized_receipt = borsh::to_vec(&receipt).unwrap();
981            let deserialized_receipt =
982                ReceiptOrStateStoredReceipt::try_from_slice(&serialized_receipt).unwrap();
983
984            assert_eq!(ReceiptOrStateStoredReceipt::Receipt(receipt), deserialized_receipt);
985        }
986
987        // Case 3:
988        // StateStoredReceipt can be deserialized as ReceiptOrStateStoredReceipt
989        {
990            let receipt = get_receipt_v0();
991            let metadata = StateStoredReceiptMetadata { congestion_gas: 42, congestion_size: 43 };
992            let state_stored_receipt = StateStoredReceipt::new_owned(receipt, metadata);
993
994            let serialized_receipt = borsh::to_vec(&state_stored_receipt).unwrap();
995            let deserialized_receipt =
996                ReceiptOrStateStoredReceipt::try_from_slice(&serialized_receipt).unwrap();
997
998            assert_eq!(
999                ReceiptOrStateStoredReceipt::StateStoredReceipt(state_stored_receipt),
1000                deserialized_receipt
1001            );
1002        }
1003
1004        // Case 4:
1005        // ReceiptOrStateStoredReceipt::Receipt
1006        {
1007            let receipt = get_receipt_v0();
1008            let receipt = Cow::Owned(receipt);
1009
1010            let receipt_or_state_stored_receipt = ReceiptOrStateStoredReceipt::Receipt(receipt);
1011
1012            let serialized_receipt = borsh::to_vec(&receipt_or_state_stored_receipt).unwrap();
1013            let deserialized_receipt =
1014                ReceiptOrStateStoredReceipt::try_from_slice(&serialized_receipt).unwrap();
1015
1016            assert_eq!(receipt_or_state_stored_receipt, deserialized_receipt);
1017        }
1018
1019        // Case 5:
1020        // ReceiptOrStateStoredReceipt::StateStoredReceipt
1021        {
1022            let receipt = get_receipt_v0();
1023            let metadata = StateStoredReceiptMetadata { congestion_gas: 42, congestion_size: 43 };
1024            let state_stored_receipt = StateStoredReceipt::new_owned(receipt, metadata);
1025            let receipt_or_state_stored_receipt =
1026                ReceiptOrStateStoredReceipt::StateStoredReceipt(state_stored_receipt);
1027
1028            let serialized_receipt = borsh::to_vec(&receipt_or_state_stored_receipt).unwrap();
1029            let deserialized_receipt =
1030                ReceiptOrStateStoredReceipt::try_from_slice(&serialized_receipt).unwrap();
1031
1032            assert_eq!(receipt_or_state_stored_receipt, deserialized_receipt);
1033        }
1034    }
1035}