use crate::action::GlobalContractIdentifier;
use crate::errors::EpochError;
use crate::hash::CryptoHash;
use crate::shard_layout::ShardLayout;
use crate::transaction::{Action, TransferAction};
use crate::types::{AccountId, Balance, BlockHeight, ShardId};
use borsh::{BorshDeserialize, BorshSerialize};
use itertools::Itertools;
use near_crypto::{KeyType, PublicKey};
use near_fmt::AbbrBytes;
use near_primitives_core::types::{Gas, ProtocolVersion};
use near_primitives_core::version::ProtocolFeature;
use near_schema_checker_lib::ProtocolSchema;
use serde_with::base64::Base64;
use serde_with::serde_as;
use std::borrow::Cow;
use std::collections::{BTreeMap, HashMap};
use std::fmt;
use std::io::{self, Read};
use std::io::{Error, ErrorKind};
use std::sync::Arc;
#[derive(
BorshSerialize,
BorshDeserialize,
Hash,
Clone,
Debug,
PartialEq,
Eq,
serde::Serialize,
serde::Deserialize,
ProtocolSchema,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct DataReceiver {
pub data_id: CryptoHash,
pub receiver_id: AccountId,
}
#[derive(
BorshSerialize,
BorshDeserialize,
Debug,
PartialEq,
Eq,
Clone,
serde::Serialize,
serde::Deserialize,
ProtocolSchema,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct ReceiptV0 {
pub predecessor_id: AccountId,
pub receiver_id: AccountId,
pub receipt_id: CryptoHash,
pub receipt: ReceiptEnum,
}
#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize, ProtocolSchema)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
pub enum Receipt {
V0(ReceiptV0),
}
#[derive(PartialEq, Eq, Debug, ProtocolSchema)]
pub enum StateStoredReceipt<'a> {
V0(StateStoredReceiptV0<'a>),
V1(StateStoredReceiptV1<'a>),
}
#[derive(BorshDeserialize, BorshSerialize, PartialEq, Eq, Debug, ProtocolSchema)]
pub struct StateStoredReceiptV0<'a> {
pub receipt: Cow<'a, Receipt>,
pub metadata: StateStoredReceiptMetadata,
}
#[derive(BorshDeserialize, BorshSerialize, PartialEq, Eq, Debug, ProtocolSchema)]
pub struct StateStoredReceiptV1<'a> {
pub receipt: Cow<'a, Receipt>,
pub metadata: StateStoredReceiptMetadata,
}
#[derive(BorshDeserialize, BorshSerialize, PartialEq, Eq, Debug, ProtocolSchema)]
pub struct StateStoredReceiptMetadata {
pub congestion_gas: Gas,
pub congestion_size: u64,
}
const STATE_STORED_RECEIPT_TAG: u8 = u8::MAX;
#[derive(PartialEq, Eq, Debug, ProtocolSchema)]
pub enum ReceiptOrStateStoredReceipt<'a> {
Receipt(Cow<'a, Receipt>),
StateStoredReceipt(StateStoredReceipt<'a>),
}
impl ReceiptOrStateStoredReceipt<'_> {
pub fn into_receipt(self) -> Receipt {
match self {
ReceiptOrStateStoredReceipt::Receipt(receipt) => receipt.into_owned(),
ReceiptOrStateStoredReceipt::StateStoredReceipt(receipt) => receipt.into_receipt(),
}
}
pub fn get_receipt(&self) -> &Receipt {
match self {
ReceiptOrStateStoredReceipt::Receipt(receipt) => receipt,
ReceiptOrStateStoredReceipt::StateStoredReceipt(receipt) => receipt.get_receipt(),
}
}
pub fn should_update_outgoing_metadatas(&self) -> bool {
match self {
ReceiptOrStateStoredReceipt::Receipt(_) => false,
ReceiptOrStateStoredReceipt::StateStoredReceipt(state_stored_receipt) => {
state_stored_receipt.should_update_outgoing_metadatas()
}
}
}
}
impl<'a> StateStoredReceipt<'a> {
pub fn new_owned(receipt: Receipt, metadata: StateStoredReceiptMetadata) -> Self {
let receipt = Cow::Owned(receipt);
Self::V1(StateStoredReceiptV1 { receipt, metadata })
}
pub fn new_borrowed(receipt: &'a Receipt, metadata: StateStoredReceiptMetadata) -> Self {
let receipt = Cow::Borrowed(receipt);
Self::V1(StateStoredReceiptV1 { receipt, metadata })
}
pub fn into_receipt(self) -> Receipt {
match self {
StateStoredReceipt::V0(v0) => v0.receipt.into_owned(),
StateStoredReceipt::V1(v1) => v1.receipt.into_owned(),
}
}
pub fn get_receipt(&self) -> &Receipt {
match self {
StateStoredReceipt::V0(v0) => &v0.receipt,
StateStoredReceipt::V1(v1) => &v1.receipt,
}
}
pub fn metadata(&self) -> &StateStoredReceiptMetadata {
match self {
StateStoredReceipt::V0(v0) => &v0.metadata,
StateStoredReceipt::V1(v1) => &v1.metadata,
}
}
pub fn should_update_outgoing_metadatas(&self) -> bool {
match self {
StateStoredReceipt::V0(_) => false,
StateStoredReceipt::V1(_) => true,
}
}
}
impl BorshSerialize for Receipt {
fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
match self {
Receipt::V0(receipt) => BorshSerialize::serialize(&receipt, writer),
}
}
}
impl BorshDeserialize for Receipt {
fn deserialize_reader<R: Read>(reader: &mut R) -> std::io::Result<Self> {
Ok(Receipt::V0(ReceiptV0::deserialize_reader(reader)?))
}
}
impl BorshSerialize for StateStoredReceipt<'_> {
fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
BorshSerialize::serialize(&STATE_STORED_RECEIPT_TAG, writer)?;
BorshSerialize::serialize(&STATE_STORED_RECEIPT_TAG, writer)?;
match self {
StateStoredReceipt::V0(v0) => {
BorshSerialize::serialize(&0_u8, writer)?;
BorshSerialize::serialize(&v0, writer)?;
}
StateStoredReceipt::V1(v1) => {
BorshSerialize::serialize(&1_u8, writer)?;
BorshSerialize::serialize(&v1, writer)?;
}
}
Ok(())
}
}
impl BorshDeserialize for StateStoredReceipt<'_> {
fn deserialize_reader<R: Read>(reader: &mut R) -> io::Result<Self> {
let u1 = u8::deserialize_reader(reader)?;
let u2 = u8::deserialize_reader(reader)?;
let u3 = u8::deserialize_reader(reader)?;
if u1 != STATE_STORED_RECEIPT_TAG || u2 != STATE_STORED_RECEIPT_TAG {
let error = format!(
"Invalid tag found when deserializing StateStoredReceipt. Found: {}, {}. Expected: {}, {}",
u1, u2, STATE_STORED_RECEIPT_TAG, STATE_STORED_RECEIPT_TAG
);
let error = Error::new(ErrorKind::Other, error);
return Err(io::Error::new(ErrorKind::InvalidData, error));
}
match u3 {
0 => {
let v0 = StateStoredReceiptV0::deserialize_reader(reader)?;
Ok(StateStoredReceipt::V0(v0))
}
1 => {
let v1 = StateStoredReceiptV1::deserialize_reader(reader)?;
Ok(StateStoredReceipt::V1(v1))
}
v => {
let error = format!(
"Invalid version found when deserializing StateStoredReceipt. Found: {}. Expected: 0",
v
);
let error = Error::new(ErrorKind::Other, error);
Err(io::Error::new(ErrorKind::InvalidData, error))
}
}
}
}
impl BorshSerialize for ReceiptOrStateStoredReceipt<'_> {
fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
match self {
ReceiptOrStateStoredReceipt::Receipt(receipt) => {
BorshSerialize::serialize(receipt, writer)
}
ReceiptOrStateStoredReceipt::StateStoredReceipt(receipt) => {
BorshSerialize::serialize(receipt, writer)
}
}
}
}
impl BorshDeserialize for ReceiptOrStateStoredReceipt<'_> {
fn deserialize_reader<R: Read>(reader: &mut R) -> io::Result<Self> {
let u1 = u8::deserialize_reader(reader)?;
let u2 = u8::deserialize_reader(reader)?;
let prefix = [u1, u2];
let mut reader = prefix.chain(reader);
if u1 == STATE_STORED_RECEIPT_TAG && u2 == STATE_STORED_RECEIPT_TAG {
let receipt = StateStoredReceipt::deserialize_reader(&mut reader)?;
Ok(ReceiptOrStateStoredReceipt::StateStoredReceipt(receipt))
} else {
let receipt = Receipt::deserialize_reader(&mut reader)?;
let receipt = Cow::Owned(receipt);
Ok(ReceiptOrStateStoredReceipt::Receipt(receipt))
}
}
}
impl Receipt {
pub fn from_tx(
receipt_id: CryptoHash,
tx_signer_id: AccountId,
tx_receiver_id: AccountId,
signer_public_key: PublicKey,
gas_price: Balance,
actions: Vec<Action>,
) -> Self {
Receipt::V0(ReceiptV0 {
predecessor_id: tx_signer_id.clone(),
receiver_id: tx_receiver_id,
receipt_id,
receipt: ReceiptEnum::Action(ActionReceipt {
signer_id: tx_signer_id,
signer_public_key,
gas_price,
output_data_receivers: vec![],
input_data_ids: vec![],
actions,
}),
})
}
pub fn receiver_id(&self) -> &AccountId {
let Receipt::V0(receipt) = self;
&receipt.receiver_id
}
pub fn set_receiver_id(&mut self, receiver_id: AccountId) {
let Receipt::V0(receipt) = self;
receipt.receiver_id = receiver_id;
}
pub fn predecessor_id(&self) -> &AccountId {
let Receipt::V0(receipt) = self;
&receipt.predecessor_id
}
pub fn set_predecessor_id(&mut self, predecessor_id: AccountId) {
let Receipt::V0(receipt) = self;
receipt.predecessor_id = predecessor_id;
}
pub fn receipt(&self) -> &ReceiptEnum {
let Receipt::V0(receipt) = self;
&receipt.receipt
}
pub fn versioned_receipt(&self) -> VersionedReceiptEnum {
let Receipt::V0(receipt) = self;
VersionedReceiptEnum::from(&receipt.receipt)
}
pub fn receipt_mut(&mut self) -> &mut ReceiptEnum {
let Receipt::V0(receipt) = self;
&mut receipt.receipt
}
pub fn take_versioned_receipt<'a>(self) -> VersionedReceiptEnum<'a> {
let Receipt::V0(receipt) = self;
VersionedReceiptEnum::from(receipt.receipt)
}
pub fn receipt_id(&self) -> &CryptoHash {
let Receipt::V0(receipt) = self;
&receipt.receipt_id
}
pub fn set_receipt_id(&mut self, receipt_id: CryptoHash) {
let Receipt::V0(receipt) = self;
receipt.receipt_id = receipt_id;
}
pub fn refund_to(&self) -> &Option<AccountId> {
match self.receipt() {
ReceiptEnum::Action(_)
| ReceiptEnum::Data(_)
| ReceiptEnum::PromiseYield(_)
| ReceiptEnum::PromiseResume(_)
| ReceiptEnum::GlobalContractDistribution(_) => &None,
ReceiptEnum::ActionV2(action_receipt_v2)
| ReceiptEnum::PromiseYieldV2(action_receipt_v2) => &action_receipt_v2.refund_to,
}
}
pub fn balance_refund_receiver(&self) -> &AccountId {
self.refund_to().as_ref().unwrap_or_else(|| self.predecessor_id())
}
pub fn get_hash(&self) -> CryptoHash {
*self.receipt_id()
}
pub fn receiver_shard_id(&self, shard_layout: &ShardLayout) -> Result<ShardId, EpochError> {
let shard_id = match self.receipt() {
ReceiptEnum::Action(_)
| ReceiptEnum::ActionV2(_)
| ReceiptEnum::Data(_)
| ReceiptEnum::PromiseYield(_)
| ReceiptEnum::PromiseYieldV2(_)
| ReceiptEnum::PromiseResume(_) => {
shard_layout.account_id_to_shard_id(self.receiver_id())
}
ReceiptEnum::GlobalContractDistribution(receipt) => {
let target_shard = receipt.target_shard();
if shard_layout.shard_ids().contains(&target_shard) {
target_shard
} else {
let Some(children_shards) = shard_layout.get_children_shards_ids(target_shard)
else {
return Err(EpochError::ShardingError(format!(
"Shard {target_shard} does not exist in the parent shard layout",
)));
};
children_shards[0]
}
}
};
Ok(shard_id)
}
pub fn is_instant_receipt(&self, protocol_version: ProtocolVersion) -> bool {
match self.versioned_receipt() {
VersionedReceiptEnum::PromiseYield(_) => {
ProtocolFeature::InstantPromiseYield.enabled(protocol_version)
}
VersionedReceiptEnum::Action(action_receipt) => {
ProtocolFeature::InstantDeleteAccount.enabled(protocol_version)
&& matches!(action_receipt.actions(), [Action::DeleteAccount(_)])
&& action_receipt.input_data_ids().is_empty()
}
VersionedReceiptEnum::Data(_)
| VersionedReceiptEnum::PromiseResume(_)
| VersionedReceiptEnum::GlobalContractDistribution(_) => false,
}
}
pub fn new_balance_refund(receiver_id: &AccountId, refund: Balance) -> Self {
Receipt::V0(ReceiptV0 {
predecessor_id: "system".parse().unwrap(),
receiver_id: receiver_id.clone(),
receipt_id: CryptoHash::default(),
receipt: ReceiptEnum::Action(ActionReceipt {
signer_id: "system".parse().unwrap(),
signer_public_key: PublicKey::empty(KeyType::ED25519),
gas_price: Balance::ZERO,
output_data_receivers: vec![],
input_data_ids: vec![],
actions: vec![Action::Transfer(TransferAction { deposit: refund })],
}),
})
}
pub fn new_gas_refund(
receiver_id: &AccountId,
refund: Balance,
signer_public_key: PublicKey,
) -> Self {
Receipt::V0(ReceiptV0 {
predecessor_id: "system".parse().unwrap(),
receiver_id: receiver_id.clone(),
receipt_id: CryptoHash::default(),
receipt: ReceiptEnum::Action(ActionReceipt {
signer_id: receiver_id.clone(),
signer_public_key,
gas_price: Balance::ZERO,
output_data_receivers: vec![],
input_data_ids: vec![],
actions: vec![Action::Transfer(TransferAction { deposit: refund })],
}),
})
}
pub fn new_global_contract_distribution(
predecessor_id: AccountId,
receipt: GlobalContractDistributionReceipt,
) -> Self {
Self::V0(ReceiptV0 {
predecessor_id,
receiver_id: "system".parse().unwrap(),
receipt_id: CryptoHash::default(),
receipt: ReceiptEnum::GlobalContractDistribution(receipt),
})
}
}
#[derive(
BorshSerialize,
BorshDeserialize,
Clone,
Debug,
PartialEq,
Eq,
serde::Serialize,
serde::Deserialize,
ProtocolSchema,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[borsh(use_discriminant = true)]
#[repr(u8)]
pub enum ReceiptEnum {
Action(ActionReceipt) = 0,
Data(DataReceipt) = 1,
PromiseYield(ActionReceipt) = 2,
PromiseResume(DataReceipt) = 3,
GlobalContractDistribution(GlobalContractDistributionReceipt) = 4,
ActionV2(ActionReceiptV2) = 5,
PromiseYieldV2(ActionReceiptV2) = 6,
}
#[derive(
BorshSerialize,
BorshDeserialize,
Debug,
PartialEq,
Eq,
Clone,
serde::Serialize,
serde::Deserialize,
ProtocolSchema,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct ActionReceipt {
pub signer_id: AccountId,
pub signer_public_key: PublicKey,
pub gas_price: Balance,
pub output_data_receivers: Vec<DataReceiver>,
pub input_data_ids: Vec<CryptoHash>,
pub actions: Vec<Action>,
}
#[derive(
BorshSerialize,
BorshDeserialize,
Debug,
PartialEq,
Eq,
Clone,
serde::Serialize,
serde::Deserialize,
ProtocolSchema,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct ActionReceiptV2 {
pub signer_id: AccountId,
pub refund_to: Option<AccountId>,
pub signer_public_key: PublicKey,
pub gas_price: Balance,
pub output_data_receivers: Vec<DataReceiver>,
pub input_data_ids: Vec<CryptoHash>,
pub actions: Vec<Action>,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum VersionedActionReceipt<'a> {
V1(Cow<'a, ActionReceipt>),
V2(Cow<'a, ActionReceiptV2>),
}
impl VersionedActionReceipt<'_> {
pub fn signer_id(&self) -> &AccountId {
match self {
VersionedActionReceipt::V1(action_receipt) => &action_receipt.signer_id,
VersionedActionReceipt::V2(action_receipt) => &action_receipt.signer_id,
}
}
pub fn refund_to(&self) -> &Option<AccountId> {
match self {
VersionedActionReceipt::V1(_) => &None,
VersionedActionReceipt::V2(action_receipt) => &action_receipt.refund_to,
}
}
pub fn signer_public_key(&self) -> &PublicKey {
match self {
VersionedActionReceipt::V1(action_receipt) => &action_receipt.signer_public_key,
VersionedActionReceipt::V2(action_receipt) => &action_receipt.signer_public_key,
}
}
pub fn gas_price(&self) -> Balance {
match self {
VersionedActionReceipt::V1(action_receipt) => action_receipt.gas_price,
VersionedActionReceipt::V2(action_receipt) => action_receipt.gas_price,
}
}
pub fn output_data_receivers(&self) -> &[DataReceiver] {
match self {
VersionedActionReceipt::V1(action_receipt) => &action_receipt.output_data_receivers,
VersionedActionReceipt::V2(action_receipt) => &action_receipt.output_data_receivers,
}
}
pub fn input_data_ids(&self) -> &[CryptoHash] {
match self {
VersionedActionReceipt::V1(action_receipt) => &action_receipt.input_data_ids,
VersionedActionReceipt::V2(action_receipt) => &action_receipt.input_data_ids,
}
}
pub fn actions(&self) -> &[Action] {
match self {
VersionedActionReceipt::V1(action_receipt) => &action_receipt.actions,
VersionedActionReceipt::V2(action_receipt) => &action_receipt.actions,
}
}
}
impl<'a> From<&'a ActionReceipt> for VersionedActionReceipt<'a> {
fn from(other: &'a ActionReceipt) -> Self {
VersionedActionReceipt::V1(Cow::Borrowed(other))
}
}
impl From<ActionReceipt> for VersionedActionReceipt<'_> {
fn from(other: ActionReceipt) -> Self {
VersionedActionReceipt::V1(Cow::Owned(other))
}
}
impl<'a> From<&'a ActionReceiptV2> for VersionedActionReceipt<'a> {
fn from(other: &'a ActionReceiptV2) -> Self {
VersionedActionReceipt::V2(Cow::Borrowed(other))
}
}
impl From<ActionReceiptV2> for VersionedActionReceipt<'_> {
fn from(other: ActionReceiptV2) -> Self {
VersionedActionReceipt::V2(Cow::Owned(other))
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum VersionedReceiptEnum<'a> {
Action(VersionedActionReceipt<'a>),
Data(Cow<'a, DataReceipt>),
PromiseYield(VersionedActionReceipt<'a>),
PromiseResume(Cow<'a, DataReceipt>),
GlobalContractDistribution(Cow<'a, GlobalContractDistributionReceipt>),
}
impl<'a> From<&'a ReceiptEnum> for VersionedReceiptEnum<'a> {
fn from(other: &'a ReceiptEnum) -> Self {
match other {
ReceiptEnum::Action(action_receipt) => {
VersionedReceiptEnum::Action(action_receipt.into())
}
ReceiptEnum::Data(data_receipt) => {
VersionedReceiptEnum::Data(Cow::Borrowed(data_receipt))
}
ReceiptEnum::PromiseYield(action_receipt) => {
VersionedReceiptEnum::PromiseYield(action_receipt.into())
}
ReceiptEnum::PromiseResume(data_receipt) => {
VersionedReceiptEnum::PromiseResume(Cow::Borrowed(data_receipt))
}
ReceiptEnum::GlobalContractDistribution(global_contract_distribution_receipt) => {
VersionedReceiptEnum::GlobalContractDistribution(Cow::Borrowed(
global_contract_distribution_receipt,
))
}
ReceiptEnum::ActionV2(action_receipt) => {
VersionedReceiptEnum::Action(action_receipt.into())
}
ReceiptEnum::PromiseYieldV2(action_receipt) => {
VersionedReceiptEnum::PromiseYield(action_receipt.into())
}
}
}
}
impl<'a> From<ReceiptEnum> for VersionedReceiptEnum<'a> {
fn from(other: ReceiptEnum) -> Self {
match other {
ReceiptEnum::Action(action_receipt) => {
VersionedReceiptEnum::Action(action_receipt.into())
}
ReceiptEnum::Data(data_receipt) => VersionedReceiptEnum::Data(Cow::Owned(data_receipt)),
ReceiptEnum::PromiseYield(action_receipt) => {
VersionedReceiptEnum::PromiseYield(action_receipt.into())
}
ReceiptEnum::PromiseResume(data_receipt) => {
VersionedReceiptEnum::PromiseResume(Cow::Owned(data_receipt))
}
ReceiptEnum::GlobalContractDistribution(global_contract_distribution_receipt) => {
VersionedReceiptEnum::GlobalContractDistribution(Cow::Owned(
global_contract_distribution_receipt,
))
}
ReceiptEnum::ActionV2(action_receipt) => {
VersionedReceiptEnum::Action(action_receipt.into())
}
ReceiptEnum::PromiseYieldV2(action_receipt) => {
VersionedReceiptEnum::PromiseYield(action_receipt.into())
}
}
}
}
#[serde_as]
#[derive(
BorshSerialize,
BorshDeserialize,
Hash,
PartialEq,
Eq,
Clone,
serde::Serialize,
serde::Deserialize,
ProtocolSchema,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct DataReceipt {
pub data_id: CryptoHash,
#[serde_as(as = "Option<Base64>")]
#[cfg_attr(feature = "schemars", schemars(with = "Option<String>"))]
pub data: Option<Vec<u8>>,
}
impl fmt::Debug for DataReceipt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DataReceipt")
.field("data_id", &self.data_id)
.field("data", &format_args!("{}", AbbrBytes(self.data.as_deref())))
.finish()
}
}
#[derive(BorshSerialize, BorshDeserialize, Hash, PartialEq, Eq, Clone, ProtocolSchema)]
pub struct ReceivedData {
pub data: Option<Vec<u8>>,
}
impl fmt::Debug for ReceivedData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ReceivedData")
.field("data", &format_args!("{}", AbbrBytes(self.data.as_deref())))
.finish()
}
}
#[derive(
BorshSerialize,
BorshDeserialize,
Hash,
PartialEq,
Eq,
Clone,
Debug,
serde::Deserialize,
serde::Serialize,
ProtocolSchema,
)]
#[borsh(use_discriminant = true)]
#[repr(u8)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum GlobalContractDistributionReceipt {
V1(GlobalContractDistributionReceiptV1) = 0,
V2(GlobalContractDistributionReceiptV2) = 1,
}
impl GlobalContractDistributionReceipt {
pub fn new(
id: GlobalContractIdentifier,
target_shard: ShardId,
already_delivered_shards: Vec<ShardId>,
code: Arc<[u8]>,
nonce: u64,
protocol_version: ProtocolVersion,
) -> Self {
if ProtocolFeature::GlobalContractDistributionNonce.enabled(protocol_version) {
Self::new_v2(id, target_shard, already_delivered_shards, code, nonce)
} else {
Self::new_v1(id, target_shard, already_delivered_shards, code)
}
}
pub fn new_v1(
id: GlobalContractIdentifier,
target_shard: ShardId,
already_delivered_shards: Vec<ShardId>,
code: Arc<[u8]>,
) -> Self {
Self::V1(GlobalContractDistributionReceiptV1 {
id,
target_shard,
already_delivered_shards,
code,
})
}
pub fn new_v2(
id: GlobalContractIdentifier,
target_shard: ShardId,
already_delivered_shards: Vec<ShardId>,
code: Arc<[u8]>,
nonce: u64,
) -> Self {
Self::V2(GlobalContractDistributionReceiptV2 {
id,
target_shard,
already_delivered_shards,
code,
nonce,
})
}
pub fn id(&self) -> &GlobalContractIdentifier {
match &self {
Self::V1(v1) => &v1.id,
Self::V2(v2) => &v2.id,
}
}
pub fn target_shard(&self) -> ShardId {
match &self {
Self::V1(v1) => v1.target_shard,
Self::V2(v2) => v2.target_shard,
}
}
pub fn already_delivered_shards(&self) -> &[ShardId] {
match &self {
Self::V1(v1) => &v1.already_delivered_shards,
Self::V2(v2) => &v2.already_delivered_shards,
}
}
pub fn code(&self) -> &Arc<[u8]> {
match &self {
Self::V1(v1) => &v1.code,
Self::V2(v2) => &v2.code,
}
}
pub fn nonce(&self) -> u64 {
match &self {
Self::V1(_) => 0,
Self::V2(v2) => v2.nonce,
}
}
pub fn maybe_nonce(&self) -> Option<u64> {
match &self {
Self::V1(_) => None,
Self::V2(v2) => Some(v2.nonce),
}
}
pub fn forward(&self, target_shard: ShardId, already_delivered_shards: Vec<ShardId>) -> Self {
match self {
Self::V1(v1) => Self::V1(GlobalContractDistributionReceiptV1 {
target_shard,
already_delivered_shards,
..v1.clone()
}),
Self::V2(v2) => Self::V2(GlobalContractDistributionReceiptV2 {
target_shard,
already_delivered_shards,
..v2.clone()
}),
}
}
}
#[serde_as]
#[derive(
BorshSerialize,
BorshDeserialize,
Hash,
PartialEq,
Eq,
Clone,
serde::Deserialize,
serde::Serialize,
ProtocolSchema,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct GlobalContractDistributionReceiptV1 {
id: GlobalContractIdentifier,
target_shard: ShardId,
already_delivered_shards: Vec<ShardId>,
#[serde_as(as = "Base64")]
#[cfg_attr(feature = "schemars", schemars(with = "String"))]
code: Arc<[u8]>,
}
impl fmt::Debug for GlobalContractDistributionReceiptV1 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("GlobalContractDistributionReceipt")
.field("id", &self.id)
.field("target_shard", &self.target_shard)
.field("already_delivered_shards", &self.already_delivered_shards)
.field("code", &..)
.finish()
}
}
#[serde_as]
#[derive(
BorshSerialize,
BorshDeserialize,
Hash,
PartialEq,
Eq,
Clone,
serde::Deserialize,
serde::Serialize,
ProtocolSchema,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct GlobalContractDistributionReceiptV2 {
id: GlobalContractIdentifier,
target_shard: ShardId,
already_delivered_shards: Vec<ShardId>,
#[serde_as(as = "Base64")]
#[cfg_attr(feature = "schemars", schemars(with = "String"))]
code: Arc<[u8]>,
nonce: u64,
}
impl fmt::Debug for GlobalContractDistributionReceiptV2 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("GlobalContractDistributionReceiptV2")
.field("id", &self.id)
.field("target_shard", &self.target_shard)
.field("already_delivered_shards", &self.already_delivered_shards)
.field("code", &..)
.field("nonce", &self.nonce)
.finish()
}
}
#[derive(Default, BorshSerialize, BorshDeserialize, Clone, PartialEq, Debug, ProtocolSchema)]
pub struct DelayedReceiptIndices {
pub first_index: u64,
pub next_available_index: u64,
}
impl DelayedReceiptIndices {
pub fn len(&self) -> u64 {
self.next_available_index - self.first_index
}
}
#[derive(Default, BorshSerialize, BorshDeserialize, Clone, PartialEq, Debug, ProtocolSchema)]
pub struct PromiseYieldIndices {
pub first_index: u64,
pub next_available_index: u64,
}
impl PromiseYieldIndices {
pub fn len(&self) -> u64 {
self.next_available_index - self.first_index
}
}
#[derive(BorshSerialize, BorshDeserialize, Clone, PartialEq, Debug, ProtocolSchema)]
pub struct PromiseYieldTimeout {
pub account_id: AccountId,
pub data_id: CryptoHash,
pub expires_at: BlockHeight,
}
#[derive(Default, BorshSerialize, BorshDeserialize, Clone, PartialEq, Debug, ProtocolSchema)]
pub struct TrieQueueIndices {
pub first_index: u64,
pub next_available_index: u64,
}
impl TrieQueueIndices {
pub fn len(&self) -> u64 {
self.next_available_index - self.first_index
}
pub fn is_default(&self) -> bool {
self.next_available_index == 0
}
}
impl From<DelayedReceiptIndices> for TrieQueueIndices {
fn from(other: DelayedReceiptIndices) -> Self {
Self { first_index: other.first_index, next_available_index: other.next_available_index }
}
}
impl From<TrieQueueIndices> for DelayedReceiptIndices {
fn from(other: TrieQueueIndices) -> Self {
Self { first_index: other.first_index, next_available_index: other.next_available_index }
}
}
#[derive(Default, BorshSerialize, BorshDeserialize, Clone, PartialEq, Debug, ProtocolSchema)]
pub struct BufferedReceiptIndices {
pub shard_buffers: BTreeMap<ShardId, TrieQueueIndices>,
}
pub type ReceiptResult = HashMap<ShardId, Vec<Receipt>>;
#[cfg(test)]
mod tests {
use super::*;
fn get_receipt_v0() -> Receipt {
let receipt_v0 = Receipt::V0(ReceiptV0 {
predecessor_id: "predecessor_id".parse().unwrap(),
receiver_id: "receiver_id".parse().unwrap(),
receipt_id: CryptoHash::default(),
receipt: ReceiptEnum::Action(ActionReceipt {
signer_id: "signer_id".parse().unwrap(),
signer_public_key: PublicKey::empty(KeyType::ED25519),
gas_price: Balance::ZERO,
output_data_receivers: vec![],
input_data_ids: vec![],
actions: vec![Action::Transfer(TransferAction { deposit: Balance::ZERO })],
}),
});
receipt_v0
}
#[test]
fn test_receipt_v0_serialization() {
let receipt_v0 = get_receipt_v0();
let serialized_receipt = borsh::to_vec(&receipt_v0).unwrap();
let receipt2 = Receipt::try_from_slice(&serialized_receipt).unwrap();
assert_eq!(receipt_v0, receipt2);
}
#[test]
fn test_state_stored_receipt_serialization() {
let receipt = get_receipt_v0();
let metadata =
StateStoredReceiptMetadata { congestion_gas: Gas::from_gas(42), congestion_size: 43 };
let receipt = StateStoredReceipt::new_owned(receipt, metadata);
let serialized_receipt = borsh::to_vec(&receipt).unwrap();
let deserialized_receipt = StateStoredReceipt::try_from_slice(&serialized_receipt).unwrap();
assert_eq!(receipt, deserialized_receipt);
}
#[test]
fn test_global_contract_distribution_receipt_v1_nonce() {
let receipt = GlobalContractDistributionReceipt::new_v1(
GlobalContractIdentifier::AccountId("test.near".parse().unwrap()),
ShardId::new(0),
vec![],
vec![0u8; 10].into(),
);
assert_eq!(receipt.nonce(), 0);
}
#[test]
fn test_global_contract_distribution_receipt_v2_nonce() {
let receipt = GlobalContractDistributionReceipt::new_v2(
GlobalContractIdentifier::AccountId("test.near".parse().unwrap()),
ShardId::new(0),
vec![],
vec![0u8; 10].into(),
42,
);
assert_eq!(receipt.nonce(), 42);
}
#[test]
fn test_global_contract_distribution_receipt_v2_serialization() {
let receipt = GlobalContractDistributionReceipt::new_v2(
GlobalContractIdentifier::AccountId("test.near".parse().unwrap()),
ShardId::new(1),
vec![ShardId::new(0)],
vec![1u8, 2, 3].into(),
100,
);
let serialized = borsh::to_vec(&receipt).unwrap();
let deserialized = GlobalContractDistributionReceipt::try_from_slice(&serialized).unwrap();
assert_eq!(receipt, deserialized);
assert_eq!(deserialized.nonce(), 100);
assert_eq!(deserialized.target_shard(), ShardId::new(1));
}
#[test]
fn test_receipt_or_state_stored_receipt_serialization() {
{
let receipt = get_receipt_v0();
let receipt = Cow::Owned(receipt);
let serialized_receipt = borsh::to_vec(&receipt).unwrap();
let deserialized_receipt =
ReceiptOrStateStoredReceipt::try_from_slice(&serialized_receipt).unwrap();
assert_eq!(ReceiptOrStateStoredReceipt::Receipt(receipt), deserialized_receipt);
}
{
let receipt = get_receipt_v0();
let metadata = StateStoredReceiptMetadata {
congestion_gas: Gas::from_gas(42),
congestion_size: 43,
};
let state_stored_receipt = StateStoredReceipt::new_owned(receipt, metadata);
let serialized_receipt = borsh::to_vec(&state_stored_receipt).unwrap();
let deserialized_receipt =
ReceiptOrStateStoredReceipt::try_from_slice(&serialized_receipt).unwrap();
assert_eq!(
ReceiptOrStateStoredReceipt::StateStoredReceipt(state_stored_receipt),
deserialized_receipt
);
}
{
let receipt = get_receipt_v0();
let receipt = Cow::Owned(receipt);
let receipt_or_state_stored_receipt = ReceiptOrStateStoredReceipt::Receipt(receipt);
let serialized_receipt = borsh::to_vec(&receipt_or_state_stored_receipt).unwrap();
let deserialized_receipt =
ReceiptOrStateStoredReceipt::try_from_slice(&serialized_receipt).unwrap();
assert_eq!(receipt_or_state_stored_receipt, deserialized_receipt);
}
{
let receipt = get_receipt_v0();
let metadata = StateStoredReceiptMetadata {
congestion_gas: Gas::from_gas(42),
congestion_size: 43,
};
let state_stored_receipt = StateStoredReceipt::new_owned(receipt, metadata);
let receipt_or_state_stored_receipt =
ReceiptOrStateStoredReceipt::StateStoredReceipt(state_stored_receipt);
let serialized_receipt = borsh::to_vec(&receipt_or_state_stored_receipt).unwrap();
let deserialized_receipt =
ReceiptOrStateStoredReceipt::try_from_slice(&serialized_receipt).unwrap();
assert_eq!(receipt_or_state_stored_receipt, deserialized_receipt);
}
}
}
#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, PartialEq, Eq, ProtocolSchema)]
pub enum ReceiptSource {
Local,
Delayed,
Instant,
}
#[derive(Debug)]
pub struct ProcessedReceipt {
pub receipt: Receipt,
pub source: ReceiptSource,
}
#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, PartialEq, Eq, ProtocolSchema)]
pub enum ProcessedReceiptMetadata {
V0(ProcessedReceiptMetadataV0),
}
impl ProcessedReceiptMetadata {
pub fn new(receipt_id: CryptoHash, source: ReceiptSource) -> Self {
Self::V0(ProcessedReceiptMetadataV0 { receipt_id, source })
}
pub fn receipt_id(&self) -> &CryptoHash {
match self {
Self::V0(v0) => &v0.receipt_id,
}
}
pub fn source(&self) -> &ReceiptSource {
match self {
Self::V0(v0) => &v0.source,
}
}
}
#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, PartialEq, Eq, ProtocolSchema)]
pub struct ProcessedReceiptMetadataV0 {
pub receipt_id: CryptoHash,
pub source: ReceiptSource,
}