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_gas_key_key, parse_account_id_from_received_data_key,
9 parse_data_id_from_received_data_key, parse_data_key_from_contract_data_key,
10 parse_index_from_delayed_receipt_key, parse_nonce_index_from_gas_key_key,
11 parse_public_key_from_access_key_key, parse_public_key_from_gas_key_key,
12};
13use crate::trie_key::{TrieKey, col};
14use crate::types::{AccountId, StoreKey, StoreValue};
15use borsh::BorshDeserialize;
16use near_crypto::PublicKey;
17use near_primitives_core::account::GasKey;
18use near_primitives_core::types::{Nonce, NonceIndex, ShardId};
19use serde_with::base64::Base64;
20use serde_with::serde_as;
21use std::fmt::{Display, Formatter};
22
23#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Eq, PartialEq)]
24#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
25pub struct DelayedReceipt {
26 #[serde(skip)]
27 pub index: Option<u64>,
28
29 #[serde(flatten)]
30 pub receipt: Box<Receipt>,
31}
32
33#[serde_as]
35#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Eq, PartialEq)]
36#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
37pub enum StateRecord {
38 Account { account_id: AccountId, account: Account },
40 Data { account_id: AccountId, data_key: StoreKey, value: StoreValue },
42 Contract {
44 account_id: AccountId,
45 #[serde_as(as = "Base64")]
46 #[cfg_attr(feature = "schemars", schemars(with = "String"))]
47 code: Vec<u8>,
48 },
49 AccessKey { account_id: AccountId, public_key: PublicKey, access_key: AccessKey },
51 PostponedReceipt(Box<Receipt>),
53 ReceivedData {
55 account_id: AccountId,
56 data_id: CryptoHash,
57 #[serde_as(as = "Option<Base64>")]
58 #[cfg_attr(feature = "schemars", schemars(with = "Option<String>"))]
59 data: Option<Vec<u8>>,
60 },
61 DelayedReceipt(DelayedReceipt),
64 GasKey { account_id: AccountId, public_key: PublicKey, gas_key: GasKey },
66 GasKeyNonce { account_id: AccountId, public_key: PublicKey, index: NonceIndex, nonce: Nonce },
68}
69
70impl StateRecord {
71 pub fn from_raw_key_value(key: &[u8], value: Vec<u8>) -> Option<StateRecord> {
76 Self::from_raw_key_value_impl(key, value).unwrap_or(None)
77 }
78
79 pub fn from_raw_key_value_impl(
80 key: &[u8],
81 value: Vec<u8>,
82 ) -> Result<Option<StateRecord>, std::io::Error> {
83 Ok(match key[0] {
84 col::ACCOUNT => Some(StateRecord::Account {
85 account_id: parse_account_id_from_account_key(key)?,
86 account: Account::try_from_slice(&value)?,
87 }),
88 col::CONTRACT_DATA => {
89 let account_id = parse_account_id_from_contract_data_key(key)?;
90 let data_key = parse_data_key_from_contract_data_key(key, &account_id)?;
91 Some(StateRecord::Data {
92 account_id,
93 data_key: data_key.to_vec().into(),
94 value: value.into(),
95 })
96 }
97 col::CONTRACT_CODE => Some(StateRecord::Contract {
98 account_id: parse_account_id_from_contract_code_key(key)?,
99 code: value,
100 }),
101 col::ACCESS_KEY => {
102 let access_key = AccessKey::try_from_slice(&value)?;
103 let account_id = parse_account_id_from_access_key_key(key)?;
104 let public_key = parse_public_key_from_access_key_key(key, &account_id)?;
105 Some(StateRecord::AccessKey { account_id, public_key, access_key })
106 }
107 col::GAS_KEY => {
108 let account_id = parse_account_id_from_gas_key_key(key)?;
109 let public_key = parse_public_key_from_gas_key_key(key, &account_id)?;
110 let index = parse_nonce_index_from_gas_key_key(key, &account_id, &public_key)?;
111 if let Some(index) = index {
112 let nonce = u64::try_from_slice(&value)?;
113 Some(StateRecord::GasKeyNonce { account_id, public_key, index, nonce })
114 } else {
115 let gas_key = GasKey::try_from_slice(&value)?;
116 Some(StateRecord::GasKey { account_id, public_key, gas_key })
117 }
118 }
119 col::RECEIVED_DATA => {
120 let data = ReceivedData::try_from_slice(&value)?.data;
121 let account_id = parse_account_id_from_received_data_key(key)?;
122 let data_id = parse_data_id_from_received_data_key(key, &account_id)?;
123 Some(StateRecord::ReceivedData { account_id, data_id, data })
124 }
125 col::POSTPONED_RECEIPT_ID => None,
126 col::PENDING_DATA_COUNT => None,
127 col::POSTPONED_RECEIPT => {
128 let receipt = Receipt::try_from_slice(&value)?;
129 Some(StateRecord::PostponedReceipt(Box::new(receipt)))
130 }
131 col::DELAYED_RECEIPT_OR_INDICES
132 if key.len() == TrieKey::DelayedReceiptIndices.len() =>
133 {
134 None
135 }
136 col::DELAYED_RECEIPT_OR_INDICES => {
137 let receipt = ReceiptOrStateStoredReceipt::try_from_slice(&value)?.into_receipt();
138 let index = Some(parse_index_from_delayed_receipt_key(key)?);
139 Some(StateRecord::DelayedReceipt(DelayedReceipt {
140 index,
141 receipt: Box::new(receipt),
142 }))
143 }
144 _ => {
145 println!("key[0]: {} is unreachable", key[0]);
146 None
147 }
148 })
149 }
150
151 pub fn get_type_string(&self) -> String {
152 match self {
153 StateRecord::Account { .. } => "Account",
154 StateRecord::Data { .. } => "Data",
155 StateRecord::Contract { .. } => "Contract",
156 StateRecord::AccessKey { .. } => "AccessKey",
157 StateRecord::GasKey { .. } => "GasKey",
158 StateRecord::GasKeyNonce { .. } => "GasKeyNonce",
159 StateRecord::PostponedReceipt { .. } => "PostponedReceipt",
160 StateRecord::ReceivedData { .. } => "ReceivedData",
161 StateRecord::DelayedReceipt { .. } => "DelayedReceipt",
162 }
163 .to_string()
164 }
165}
166
167impl Display for StateRecord {
168 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
169 match self {
170 StateRecord::Account { account_id, account } => {
171 write!(f, "Account {:?}: {:?}", account_id, account)
172 }
173 StateRecord::Data { account_id, data_key, value } => write!(
174 f,
175 "Storage {:?},{:?}: {:?}",
176 account_id,
177 to_printable(data_key.as_ref()),
178 to_printable(value.as_ref())
179 ),
180 StateRecord::Contract { account_id, code: _ } => {
181 write!(f, "Code for {:?}: ...", account_id)
182 }
183 StateRecord::AccessKey { account_id, public_key, access_key } => {
184 write!(f, "Access key {:?},{:?}: {:?}", account_id, public_key, access_key)
185 }
186 StateRecord::ReceivedData { account_id, data_id, data } => write!(
187 f,
188 "Received data {:?},{:?}: {:?}",
189 account_id,
190 data_id,
191 data.as_ref().map(|v| to_printable(v))
192 ),
193 StateRecord::PostponedReceipt(receipt) => write!(f, "Postponed receipt {:?}", receipt),
194 StateRecord::DelayedReceipt(receipt) => write!(f, "Delayed receipt {:?}", receipt),
195 StateRecord::GasKey { account_id, public_key, gas_key } => {
196 write!(f, "Gas key {:?},{:?}: {:?}", account_id, public_key, gas_key)
197 }
198 StateRecord::GasKeyNonce { account_id, public_key, index, nonce } => {
199 write!(f, "Gas key nonce {:?},{:?}[{}]: {}", account_id, public_key, index, nonce)
200 }
201 }
202 }
203}
204
205fn to_printable(blob: &[u8]) -> String {
206 if blob.len() > 60 {
207 format!("{} bytes, hash: {}", blob.len(), hash(blob))
208 } else {
209 let ugly = blob.iter().any(|&x| x < b' ');
210 if ugly {
211 return format!("0x{}", hex::encode(blob));
212 }
213 match String::from_utf8(blob.to_vec()) {
214 Ok(v) => v,
215 Err(_e) => format!("0x{}", hex::encode(blob)),
216 }
217 }
218}
219
220pub fn state_record_to_shard_id(state_record: &StateRecord, shard_layout: &ShardLayout) -> ShardId {
221 match state_record {
222 StateRecord::Account { account_id, .. }
223 | StateRecord::AccessKey { account_id, .. }
224 | StateRecord::GasKey { account_id, .. }
225 | StateRecord::GasKeyNonce { account_id, .. }
226 | StateRecord::Contract { account_id, .. }
227 | StateRecord::ReceivedData { account_id, .. }
228 | StateRecord::Data { account_id, .. } => shard_layout.account_id_to_shard_id(account_id),
229 StateRecord::PostponedReceipt(receipt) => receipt.receiver_shard_id(shard_layout).unwrap(),
230 StateRecord::DelayedReceipt(receipt) => {
231 receipt.receipt.receiver_shard_id(shard_layout).unwrap()
232 }
233 }
234}
235
236pub fn state_record_to_account_id(state_record: &StateRecord) -> &AccountId {
237 match state_record {
238 StateRecord::Account { account_id, .. }
239 | StateRecord::AccessKey { account_id, .. }
240 | StateRecord::GasKey { account_id, .. }
241 | StateRecord::GasKeyNonce { account_id, .. }
242 | StateRecord::Contract { account_id, .. }
243 | StateRecord::ReceivedData { account_id, .. }
244 | StateRecord::Data { account_id, .. } => account_id,
245 StateRecord::PostponedReceipt(receipt) => receipt.receiver_id(),
246 StateRecord::DelayedReceipt(receipt) => receipt.receipt.receiver_id(),
247 }
248}
249
250pub fn is_contract_code_key(key: &[u8]) -> bool {
251 debug_assert!(!key.is_empty());
252 key[0] == col::CONTRACT_CODE
253}