1use crate::errors::TxExecutionError;
2use crate::hash::{hash, CryptoHash};
3use crate::merkle::MerklePath;
4use crate::types::{AccountId, Balance, Gas, Nonce};
5use borsh::{BorshDeserialize, BorshSerialize};
6use serde::de::Error as DecodeError;
7use serde::ser::Error as EncodeError;
8use std::borrow::Borrow;
9use std::fmt;
10use std::hash::{Hash, Hasher};
11use unc_crypto::{PublicKey, Signature};
12use unc_fmt::{AbbrBytes, Slice};
13use unc_primitives_core::serialize::{from_base64, to_base64};
14use unc_primitives_core::types::Compute;
15use unc_vm_runner::{ProfileDataV2, ProfileDataV3};
16
17pub use crate::action::{
18 Action, AddKeyAction, CreateAccountAction, CreateRsa2048ChallengeAction, DeleteAccountAction,
19 DeleteKeyAction, DeployContractAction, FunctionCallAction, PledgeAction,
20 RegisterRsa2048KeysAction, TransferAction,
21};
22
23pub type LogEntry = String;
24
25#[derive(BorshSerialize, BorshDeserialize, serde::Serialize, PartialEq, Eq, Debug, Clone)]
26pub struct Transaction {
27 pub signer_id: AccountId,
29 pub public_key: PublicKey,
32 pub nonce: Nonce,
35 pub receiver_id: AccountId,
37 pub block_hash: CryptoHash,
39 pub actions: Vec<Action>,
41}
42
43impl Transaction {
44 pub fn get_hash_and_size(&self) -> (CryptoHash, u64) {
46 let bytes = borsh::to_vec(&self).expect("Failed to deserialize");
47 (hash(&bytes), bytes.len() as u64)
48 }
49}
50
51#[derive(BorshSerialize, BorshDeserialize, Eq, Debug, Clone)]
52#[borsh(init=init)]
53pub struct SignedTransaction {
54 pub transaction: Transaction,
55 pub signature: Signature,
56 #[borsh(skip)]
57 hash: CryptoHash,
58 #[borsh(skip)]
59 size: u64,
60}
61
62impl SignedTransaction {
63 pub fn new(signature: Signature, transaction: Transaction) -> Self {
64 let mut signed_tx =
65 Self { signature, transaction, hash: CryptoHash::default(), size: u64::default() };
66 signed_tx.init();
67 signed_tx
68 }
69
70 pub fn init(&mut self) {
71 let (hash, size) = self.transaction.get_hash_and_size();
72 self.hash = hash;
73 self.size = size;
74 }
75
76 pub fn get_hash(&self) -> CryptoHash {
77 self.hash
78 }
79
80 pub fn get_size(&self) -> u64 {
81 self.size
82 }
83}
84
85impl Hash for SignedTransaction {
86 fn hash<H: Hasher>(&self, state: &mut H) {
87 self.hash.hash(state)
88 }
89}
90
91impl PartialEq for SignedTransaction {
92 fn eq(&self, other: &SignedTransaction) -> bool {
93 self.hash == other.hash && self.signature == other.signature
94 }
95}
96
97impl Borrow<CryptoHash> for SignedTransaction {
98 fn borrow(&self) -> &CryptoHash {
99 &self.hash
100 }
101}
102
103impl serde::Serialize for SignedTransaction {
104 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
105 where
106 S: serde::Serializer,
107 {
108 let signed_tx_borsh = borsh::to_vec(self).map_err(|err| {
109 S::Error::custom(&format!("the value could not be borsh encoded due to: {}", err))
110 })?;
111 let signed_tx_base64 = to_base64(&signed_tx_borsh);
112 serializer.serialize_str(&signed_tx_base64)
113 }
114}
115
116impl<'de> serde::Deserialize<'de> for SignedTransaction {
117 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
118 where
119 D: serde::Deserializer<'de>,
120 {
121 let signed_tx_base64 = <String as serde::Deserialize>::deserialize(deserializer)?;
122 let signed_tx_borsh = from_base64(&signed_tx_base64).map_err(|err| {
123 D::Error::custom(&format!("the value could not decoded from base64 due to: {}", err))
124 })?;
125 borsh::from_slice::<Self>(&signed_tx_borsh).map_err(|err| {
126 D::Error::custom(&format!("the value could not decoded from borsh due to: {}", err))
127 })
128 }
129}
130
131#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Clone, Default)]
133pub enum ExecutionStatus {
134 #[default]
136 Unknown,
137 Failure(TxExecutionError),
139 SuccessValue(Vec<u8>),
141 SuccessReceiptId(CryptoHash),
144}
145
146impl fmt::Debug for ExecutionStatus {
147 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148 match self {
149 ExecutionStatus::Unknown => f.write_str("Unknown"),
150 ExecutionStatus::Failure(e) => f.write_fmt(format_args!("Failure({})", e)),
151 ExecutionStatus::SuccessValue(v) => {
152 f.write_fmt(format_args!("SuccessValue({})", AbbrBytes(v)))
153 }
154 ExecutionStatus::SuccessReceiptId(receipt_id) => {
155 f.write_fmt(format_args!("SuccessReceiptId({})", receipt_id))
156 }
157 }
158 }
159}
160
161#[derive(BorshSerialize, BorshDeserialize, PartialEq, Clone)]
163pub struct PartialExecutionOutcome {
164 pub receipt_ids: Vec<CryptoHash>,
165 pub gas_burnt: Gas,
166 pub tokens_burnt: Balance,
167 pub executor_id: AccountId,
168 pub status: PartialExecutionStatus,
169}
170
171impl From<&ExecutionOutcome> for PartialExecutionOutcome {
172 fn from(outcome: &ExecutionOutcome) -> Self {
173 Self {
174 receipt_ids: outcome.receipt_ids.clone(),
175 gas_burnt: outcome.gas_burnt,
176 tokens_burnt: outcome.tokens_burnt,
177 executor_id: outcome.executor_id.clone(),
178 status: outcome.status.clone().into(),
179 }
180 }
181}
182
183#[derive(BorshSerialize, BorshDeserialize, PartialEq, Clone)]
185pub enum PartialExecutionStatus {
186 Unknown,
187 Failure,
188 SuccessValue(Vec<u8>),
189 SuccessReceiptId(CryptoHash),
190}
191
192impl From<ExecutionStatus> for PartialExecutionStatus {
193 fn from(status: ExecutionStatus) -> PartialExecutionStatus {
194 match status {
195 ExecutionStatus::Unknown => PartialExecutionStatus::Unknown,
196 ExecutionStatus::Failure(_) => PartialExecutionStatus::Failure,
197 ExecutionStatus::SuccessValue(value) => PartialExecutionStatus::SuccessValue(value),
198 ExecutionStatus::SuccessReceiptId(id) => PartialExecutionStatus::SuccessReceiptId(id),
199 }
200 }
201}
202
203#[derive(BorshSerialize, BorshDeserialize, PartialEq, Clone, smart_default::SmartDefault, Eq)]
205pub struct ExecutionOutcome {
206 pub logs: Vec<LogEntry>,
208 pub receipt_ids: Vec<CryptoHash>,
210 pub gas_burnt: Gas,
212 #[borsh(skip)]
218 pub compute_usage: Option<Compute>,
219 pub tokens_burnt: Balance,
223 #[default("test".parse().unwrap())]
226 pub executor_id: AccountId,
227 pub status: ExecutionStatus,
231 pub metadata: ExecutionMetadata,
233}
234
235#[derive(BorshSerialize, BorshDeserialize, PartialEq, Clone, Eq, Debug, Default)]
236pub enum ExecutionMetadata {
237 #[default]
239 V1,
240 V2(ProfileDataV2),
242 V3(Box<ProfileDataV3>),
244}
245
246impl fmt::Debug for ExecutionOutcome {
247 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248 f.debug_struct("ExecutionOutcome")
249 .field("logs", &Slice(&self.logs))
250 .field("receipt_ids", &Slice(&self.receipt_ids))
251 .field("burnt_gas", &self.gas_burnt)
252 .field("compute_usage", &self.compute_usage.unwrap_or_default())
253 .field("tokens_burnt", &self.tokens_burnt)
254 .field("status", &self.status)
255 .field("metadata", &self.metadata)
256 .finish()
257 }
258}
259
260#[derive(PartialEq, Clone, Default, Debug, BorshSerialize, BorshDeserialize, Eq)]
264pub struct ExecutionOutcomeWithId {
265 pub id: CryptoHash,
267 pub outcome: ExecutionOutcome,
269}
270
271impl ExecutionOutcomeWithId {
272 pub fn to_hashes(&self) -> Vec<CryptoHash> {
273 let mut result = Vec::with_capacity(2 + self.outcome.logs.len());
274 result.push(self.id);
275 result.push(CryptoHash::hash_borsh(PartialExecutionOutcome::from(&self.outcome)));
276 result.extend(self.outcome.logs.iter().map(|log| hash(log.as_bytes())));
277 result
278 }
279}
280
281#[derive(PartialEq, Clone, Default, Debug, BorshSerialize, BorshDeserialize, Eq)]
283pub struct ExecutionOutcomeWithIdAndProof {
284 pub proof: MerklePath,
285 pub block_hash: CryptoHash,
286 pub outcome_with_id: ExecutionOutcomeWithId,
288}
289
290impl ExecutionOutcomeWithIdAndProof {
291 pub fn id(&self) -> &CryptoHash {
292 &self.outcome_with_id.id
293 }
294}
295
296pub fn verify_transaction_signature(
297 transaction: &SignedTransaction,
298 public_keys: &[PublicKey],
299) -> bool {
300 let hash = transaction.get_hash();
301 let hash = hash.as_ref();
302 public_keys.iter().any(|key| transaction.signature.verify(hash, key))
303}
304
305#[derive(Clone, BorshSerialize, BorshDeserialize, Debug)]
307pub struct ExecutionOutcomeWithProof {
308 pub proof: MerklePath,
309 pub outcome: ExecutionOutcome,
310}
311#[cfg(test)]
312mod tests {
313 use super::*;
314 use crate::account::{AccessKey, AccessKeyPermission, FunctionCallPermission};
315 use borsh::BorshDeserialize;
316 use unc_crypto::{InMemorySigner, KeyType, Signature, Signer};
317
318 #[test]
319 fn test_verify_transaction() {
320 let signer = InMemorySigner::from_random("test".parse().unwrap(), KeyType::ED25519);
321 let transaction = Transaction {
322 signer_id: "test".parse().unwrap(),
323 public_key: signer.public_key(),
324 nonce: 0,
325 receiver_id: "test".parse().unwrap(),
326 block_hash: Default::default(),
327 actions: vec![],
328 }
329 .sign(&signer);
330 let wrong_public_key = PublicKey::from_seed(KeyType::ED25519, "wrong");
331 let valid_keys = vec![signer.public_key(), wrong_public_key.clone()];
332 assert!(verify_transaction_signature(&transaction, &valid_keys));
333
334 let invalid_keys = vec![wrong_public_key];
335 assert!(!verify_transaction_signature(&transaction, &invalid_keys));
336
337 let bytes = borsh::to_vec(&transaction).unwrap();
338 let decoded_tx = SignedTransaction::try_from_slice(&bytes).unwrap();
339 assert!(verify_transaction_signature(&decoded_tx, &valid_keys));
340 }
341
342 #[test]
345 fn test_serialize_transaction() {
346 let public_key: PublicKey = "22skMptHjFWNyuEWY22ftn2AbLPSYpmYwGJRGwpNHbTV".parse().unwrap();
347 let transaction = Transaction {
348 signer_id: "test.unc".parse().unwrap(),
349 public_key: public_key.clone(),
350 nonce: 1,
351 receiver_id: "123".parse().unwrap(),
352 block_hash: Default::default(),
353 actions: vec![
354 Action::CreateAccount(CreateAccountAction {}),
355 Action::DeployContract(DeployContractAction { code: vec![1, 2, 3] }),
356 Action::FunctionCall(Box::new(FunctionCallAction {
357 method_name: "qqq".to_string(),
358 args: vec![1, 2, 3],
359 gas: 1_000,
360 deposit: 1_000_000,
361 })),
362 Action::Transfer(TransferAction { deposit: 123 }),
363 Action::Pledge(Box::new(PledgeAction {
364 public_key: public_key.clone(),
365 pledge: 1_000_000,
366 })),
367 Action::AddKey(Box::new(AddKeyAction {
368 public_key: public_key.clone(),
369 access_key: AccessKey {
370 nonce: 0,
371 permission: AccessKeyPermission::FunctionCall(FunctionCallPermission {
372 allowance: None,
373 receiver_id: "zzz".parse().unwrap(),
374 method_names: vec!["www".to_string()],
375 }),
376 },
377 })),
378 Action::DeleteKey(Box::new(DeleteKeyAction { public_key })),
379 Action::DeleteAccount(DeleteAccountAction {
380 beneficiary_id: "123".parse().unwrap(),
381 }),
382 ],
383 };
384 let signed_tx = SignedTransaction::new(Signature::empty(KeyType::ED25519), transaction);
385 let new_signed_tx =
386 SignedTransaction::try_from_slice(&borsh::to_vec(&signed_tx).unwrap()).unwrap();
387
388 assert_eq!(
389 new_signed_tx.get_hash().to_string(),
390 "4GXvjMFN6wSxnU9jEVT8HbXP5Yk6yELX9faRSKp6n9fX"
391 );
392 }
393
394 #[test]
395 fn test_outcome_to_hashes() {
396 let outcome = ExecutionOutcome {
397 status: ExecutionStatus::SuccessValue(vec![123]),
398 logs: vec!["123".to_string(), "321".to_string()],
399 receipt_ids: vec![],
400 gas_burnt: 123,
401 compute_usage: Some(456),
402 tokens_burnt: 1234000,
403 executor_id: "alice".parse().unwrap(),
404 metadata: ExecutionMetadata::V1,
405 };
406 let id = CryptoHash([42u8; 32]);
407 let outcome = ExecutionOutcomeWithId { id, outcome };
408 assert_eq!(
409 vec![
410 id,
411 "5JQs5ekQqKudMmYejuccbtEu1bzhQPXa92Zm4HdV64dQ".parse().unwrap(),
412 hash("123".as_bytes()),
413 hash("321".as_bytes()),
414 ],
415 outcome.to_hashes()
416 );
417 }
418}