near_primitives/
state_record.rs

1use crate::account::{AccessKey, Account};
2use crate::hash::{CryptoHash, hash};
3use crate::receipt::{Receipt, ReceiptOrStateStoredReceipt, ReceivedData};
4use crate::shard_layout::ShardLayout;
5use crate::trie_key::trie_key_parsers::{
6    parse_account_id_from_access_key_key, parse_account_id_from_account_key,
7    parse_account_id_from_contract_code_key, parse_account_id_from_contract_data_key,
8    parse_account_id_from_received_data_key, parse_data_id_from_received_data_key,
9    parse_data_key_from_contract_data_key, parse_index_from_delayed_receipt_key,
10    parse_public_key_from_access_key_key,
11};
12use crate::trie_key::{TrieKey, col};
13use crate::types::{AccountId, StoreKey, StoreValue};
14use borsh::BorshDeserialize;
15use near_crypto::PublicKey;
16use near_primitives_core::types::ShardId;
17use serde_with::base64::Base64;
18use serde_with::serde_as;
19use std::fmt::{Display, Formatter};
20
21#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Eq, PartialEq)]
22#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
23pub struct DelayedReceipt {
24    #[serde(skip)]
25    pub index: Option<u64>,
26
27    #[serde(flatten)]
28    pub receipt: Box<Receipt>,
29}
30
31/// Record in the state storage.
32#[serde_as]
33#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Eq, PartialEq)]
34#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
35pub enum StateRecord {
36    /// Account information.
37    Account { account_id: AccountId, account: Account },
38    /// Data records inside the contract, encoded in base64.
39    Data { account_id: AccountId, data_key: StoreKey, value: StoreValue },
40    /// Contract code encoded in base64.
41    Contract {
42        account_id: AccountId,
43        #[serde_as(as = "Base64")]
44        #[cfg_attr(feature = "schemars", schemars(with = "String"))]
45        code: Vec<u8>,
46    },
47    /// Access key associated with some account.
48    AccessKey { account_id: AccountId, public_key: PublicKey, access_key: AccessKey },
49    /// Postponed Action Receipt.
50    PostponedReceipt(Box<Receipt>),
51    /// Received data from DataReceipt encoded in base64 for the given account_id and data_id.
52    ReceivedData {
53        account_id: AccountId,
54        data_id: CryptoHash,
55        #[serde_as(as = "Option<Base64>")]
56        #[cfg_attr(feature = "schemars", schemars(with = "Option<String>"))]
57        data: Option<Vec<u8>>,
58    },
59    /// Delayed Receipt.
60    /// The receipt was delayed because the shard was overwhelmed.
61    DelayedReceipt(DelayedReceipt),
62}
63
64impl StateRecord {
65    /// NOTE: This function is not safe to be running during block production. It contains a lot
66    /// of `unwrap` and should only be used during `state_dump`.
67    /// Most `unwrap()` here are because the implementation of columns and data are internal and
68    /// can't be influenced by external calls.
69    pub fn from_raw_key_value(key: &[u8], value: Vec<u8>) -> Option<StateRecord> {
70        Self::from_raw_key_value_impl(key, value).unwrap_or(None)
71    }
72
73    pub fn from_raw_key_value_impl(
74        key: &[u8],
75        value: Vec<u8>,
76    ) -> Result<Option<StateRecord>, std::io::Error> {
77        Ok(match key[0] {
78            col::ACCOUNT => Some(StateRecord::Account {
79                account_id: parse_account_id_from_account_key(key)?,
80                account: Account::try_from_slice(&value)?,
81            }),
82            col::CONTRACT_DATA => {
83                let account_id = parse_account_id_from_contract_data_key(key)?;
84                let data_key = parse_data_key_from_contract_data_key(key, &account_id)?;
85                Some(StateRecord::Data {
86                    account_id,
87                    data_key: data_key.to_vec().into(),
88                    value: value.into(),
89                })
90            }
91            col::CONTRACT_CODE => Some(StateRecord::Contract {
92                account_id: parse_account_id_from_contract_code_key(key)?,
93                code: value,
94            }),
95            col::ACCESS_KEY => {
96                let access_key = AccessKey::try_from_slice(&value)?;
97                let account_id = parse_account_id_from_access_key_key(key)?;
98                let public_key = parse_public_key_from_access_key_key(key, &account_id)?;
99                Some(StateRecord::AccessKey { account_id, public_key, access_key })
100            }
101            col::RECEIVED_DATA => {
102                let data = ReceivedData::try_from_slice(&value)?.data;
103                let account_id = parse_account_id_from_received_data_key(key)?;
104                let data_id = parse_data_id_from_received_data_key(key, &account_id)?;
105                Some(StateRecord::ReceivedData { account_id, data_id, data })
106            }
107            col::POSTPONED_RECEIPT_ID => None,
108            col::PENDING_DATA_COUNT => None,
109            col::POSTPONED_RECEIPT => {
110                let receipt = Receipt::try_from_slice(&value)?;
111                Some(StateRecord::PostponedReceipt(Box::new(receipt)))
112            }
113            col::DELAYED_RECEIPT_OR_INDICES
114                if key.len() == TrieKey::DelayedReceiptIndices.len() =>
115            {
116                None
117            }
118            col::DELAYED_RECEIPT_OR_INDICES => {
119                let receipt = ReceiptOrStateStoredReceipt::try_from_slice(&value)?.into_receipt();
120                let index = Some(parse_index_from_delayed_receipt_key(key)?);
121                Some(StateRecord::DelayedReceipt(DelayedReceipt {
122                    index,
123                    receipt: Box::new(receipt),
124                }))
125            }
126            _ => {
127                println!("key[0]: {} is unreachable", key[0]);
128                None
129            }
130        })
131    }
132
133    pub fn get_type_string(&self) -> String {
134        match self {
135            StateRecord::Account { .. } => "Account",
136            StateRecord::Data { .. } => "Data",
137            StateRecord::Contract { .. } => "Contract",
138            StateRecord::AccessKey { .. } => "AccessKey",
139            StateRecord::PostponedReceipt { .. } => "PostponedReceipt",
140            StateRecord::ReceivedData { .. } => "ReceivedData",
141            StateRecord::DelayedReceipt { .. } => "DelayedReceipt",
142        }
143        .to_string()
144    }
145}
146
147impl Display for StateRecord {
148    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
149        match self {
150            StateRecord::Account { account_id, account } => {
151                write!(f, "Account {:?}: {:?}", account_id, account)
152            }
153            StateRecord::Data { account_id, data_key, value } => write!(
154                f,
155                "Storage {:?},{:?}: {:?}",
156                account_id,
157                to_printable(data_key.as_ref()),
158                to_printable(value.as_ref())
159            ),
160            StateRecord::Contract { account_id, code: _ } => {
161                write!(f, "Code for {:?}: ...", account_id)
162            }
163            StateRecord::AccessKey { account_id, public_key, access_key } => {
164                write!(f, "Access key {:?},{:?}: {:?}", account_id, public_key, access_key)
165            }
166            StateRecord::ReceivedData { account_id, data_id, data } => write!(
167                f,
168                "Received data {:?},{:?}: {:?}",
169                account_id,
170                data_id,
171                data.as_ref().map(|v| to_printable(v))
172            ),
173            StateRecord::PostponedReceipt(receipt) => write!(f, "Postponed receipt {:?}", receipt),
174            StateRecord::DelayedReceipt(receipt) => write!(f, "Delayed receipt {:?}", receipt),
175        }
176    }
177}
178
179fn to_printable(blob: &[u8]) -> String {
180    if blob.len() > 60 {
181        format!("{} bytes, hash: {}", blob.len(), hash(blob))
182    } else {
183        let ugly = blob.iter().any(|&x| x < b' ');
184        if ugly {
185            return format!("0x{}", hex::encode(blob));
186        }
187        match String::from_utf8(blob.to_vec()) {
188            Ok(v) => v,
189            Err(_e) => format!("0x{}", hex::encode(blob)),
190        }
191    }
192}
193
194pub fn state_record_to_shard_id(state_record: &StateRecord, shard_layout: &ShardLayout) -> ShardId {
195    match state_record {
196        StateRecord::Account { account_id, .. }
197        | StateRecord::AccessKey { account_id, .. }
198        | StateRecord::Contract { account_id, .. }
199        | StateRecord::ReceivedData { account_id, .. }
200        | StateRecord::Data { account_id, .. } => shard_layout.account_id_to_shard_id(account_id),
201        StateRecord::PostponedReceipt(receipt) => receipt.receiver_shard_id(shard_layout).unwrap(),
202        StateRecord::DelayedReceipt(receipt) => {
203            receipt.receipt.receiver_shard_id(shard_layout).unwrap()
204        }
205    }
206}
207
208pub fn state_record_to_account_id(state_record: &StateRecord) -> &AccountId {
209    match state_record {
210        StateRecord::Account { account_id, .. }
211        | StateRecord::AccessKey { account_id, .. }
212        | StateRecord::Contract { account_id, .. }
213        | StateRecord::ReceivedData { account_id, .. }
214        | StateRecord::Data { account_id, .. } => account_id,
215        StateRecord::PostponedReceipt(receipt) => receipt.receiver_id(),
216        StateRecord::DelayedReceipt(receipt) => receipt.receipt.receiver_id(),
217    }
218}
219
220pub fn is_contract_code_key(key: &[u8]) -> bool {
221    debug_assert!(!key.is_empty());
222    key[0] == col::CONTRACT_CODE
223}