near_primitives/
transaction.rs

1pub use crate::action::{
2    Action, AddKeyAction, CreateAccountAction, DeleteAccountAction, DeleteKeyAction,
3    DeployContractAction, FunctionCallAction, StakeAction, TransferAction,
4};
5use crate::errors::{InvalidTxError, TxExecutionError};
6use crate::hash::{CryptoHash, hash};
7use crate::merkle::MerklePath;
8use crate::profile_data_v3::ProfileDataV3;
9use crate::types::{AccountId, Balance, Gas, Nonce};
10use borsh::{BorshDeserialize, BorshSerialize};
11use near_crypto::{PublicKey, Signature};
12use near_fmt::{AbbrBytes, Slice};
13use near_parameters::RuntimeConfig;
14use near_primitives_core::serialize::{from_base64, to_base64};
15use near_primitives_core::types::Compute;
16use near_schema_checker_lib::ProtocolSchema;
17#[cfg(feature = "schemars")]
18use schemars::json_schema;
19use serde::de::Error as DecodeError;
20use serde::ser::Error as EncodeError;
21use std::borrow::Borrow;
22use std::fmt;
23use std::hash::{Hash, Hasher};
24use std::io::{Error, ErrorKind, Read, Write};
25
26pub type LogEntry = String;
27
28#[derive(
29    BorshSerialize, BorshDeserialize, serde::Serialize, PartialEq, Eq, Debug, Clone, ProtocolSchema,
30)]
31pub struct TransactionV0 {
32    /// An account on which behalf transaction is signed
33    pub signer_id: AccountId,
34    /// A public key of the access key which was used to sign an account.
35    /// Access key holds permissions for calling certain kinds of actions.
36    pub public_key: PublicKey,
37    /// Nonce is used to determine order of transaction in the pool.
38    /// It increments for a combination of `signer_id` and `public_key`
39    pub nonce: Nonce,
40    /// Receiver account for this transaction
41    pub receiver_id: AccountId,
42    /// The hash of the block in the blockchain on top of which the given transaction is valid
43    pub block_hash: CryptoHash,
44    /// A list of actions to be applied
45    pub actions: Vec<Action>,
46}
47
48#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone, ProtocolSchema)]
49pub struct TransactionV1 {
50    /// An account on which behalf transaction is signed
51    pub signer_id: AccountId,
52    /// A public key of the access key which was used to sign an account.
53    /// Access key holds permissions for calling certain kinds of actions.
54    pub public_key: PublicKey,
55    /// Nonce is used to determine order of transaction in the pool.
56    /// It increments for a combination of `signer_id` and `public_key`
57    pub nonce: Nonce,
58    /// Receiver account for this transaction
59    pub receiver_id: AccountId,
60    /// The hash of the block in the blockchain on top of which the given transaction is valid
61    pub block_hash: CryptoHash,
62    /// A list of actions to be applied
63    pub actions: Vec<Action>,
64    /// Priority fee. Unit is 10^12 yoctoNEAR
65    pub priority_fee: u64,
66}
67
68impl Transaction {
69    /// Computes a hash of the transaction for signing and size of serialized transaction
70    pub fn get_hash_and_size(&self) -> (CryptoHash, u64) {
71        let bytes = borsh::to_vec(&self).expect("Failed to deserialize");
72        (hash(&bytes), bytes.len() as u64)
73    }
74}
75
76#[derive(Eq, PartialEq, Debug, Clone)]
77pub enum Transaction {
78    V0(TransactionV0),
79    V1(TransactionV1),
80}
81
82impl Transaction {
83    pub fn signer_id(&self) -> &AccountId {
84        match self {
85            Transaction::V0(tx) => &tx.signer_id,
86            Transaction::V1(tx) => &tx.signer_id,
87        }
88    }
89
90    pub fn receiver_id(&self) -> &AccountId {
91        match self {
92            Transaction::V0(tx) => &tx.receiver_id,
93            Transaction::V1(tx) => &tx.receiver_id,
94        }
95    }
96
97    pub fn public_key(&self) -> &PublicKey {
98        match self {
99            Transaction::V0(tx) => &tx.public_key,
100            Transaction::V1(tx) => &tx.public_key,
101        }
102    }
103
104    pub fn nonce(&self) -> Nonce {
105        match self {
106            Transaction::V0(tx) => tx.nonce,
107            Transaction::V1(tx) => tx.nonce,
108        }
109    }
110
111    pub fn actions(&self) -> &[Action] {
112        match self {
113            Transaction::V0(tx) => &tx.actions,
114            Transaction::V1(tx) => &tx.actions,
115        }
116    }
117
118    pub fn take_actions(self) -> Vec<Action> {
119        match self {
120            Transaction::V0(tx) => tx.actions,
121            Transaction::V1(tx) => tx.actions,
122        }
123    }
124
125    pub fn block_hash(&self) -> &CryptoHash {
126        match self {
127            Transaction::V0(tx) => &tx.block_hash,
128            Transaction::V1(tx) => &tx.block_hash,
129        }
130    }
131
132    pub fn priority_fee(&self) -> Option<u64> {
133        match self {
134            Transaction::V0(_) => None,
135            Transaction::V1(tx) => Some(tx.priority_fee),
136        }
137    }
138}
139
140impl BorshSerialize for Transaction {
141    fn serialize<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
142        match self {
143            Transaction::V0(tx) => tx.serialize(writer)?,
144            Transaction::V1(tx) => {
145                BorshSerialize::serialize(&1_u8, writer)?;
146                tx.serialize(writer)?;
147            }
148        }
149        Ok(())
150    }
151}
152
153impl BorshDeserialize for Transaction {
154    /// Deserialize based on the first and second bytes of the stream. For V0, we do backward compatible deserialization by deserializing
155    /// the entire stream into V0. For V1, we consume the first byte and then deserialize the rest.
156    fn deserialize_reader<R: Read>(reader: &mut R) -> std::io::Result<Self> {
157        let u1 = u8::deserialize_reader(reader)?;
158        let u2 = u8::deserialize_reader(reader)?;
159        let u3 = u8::deserialize_reader(reader)?;
160        let u4 = u8::deserialize_reader(reader)?;
161        // This is a ridiculous hackery: because the first field in `TransactionV0` is an `AccountId`
162        // and an account id is at most 64 bytes, for all valid `TransactionV0` the second byte must be 0
163        // because of the little endian encoding of the length of the account id.
164        // On the other hand, for `TransactionV1`, since the first byte is 1 and an account id must have nonzero
165        // length, so the second byte must not be zero. Therefore, we can distinguish between the two versions
166        // by looking at the second byte.
167
168        let read_signer_id = |buf: [u8; 4], reader: &mut R| -> std::io::Result<AccountId> {
169            let str_len = u32::from_le_bytes(buf);
170            let mut str_vec = Vec::with_capacity(str_len as usize);
171            for _ in 0..str_len {
172                str_vec.push(u8::deserialize_reader(reader)?);
173            }
174            AccountId::try_from(String::from_utf8(str_vec).map_err(|_| {
175                Error::new(ErrorKind::InvalidData, "Failed to parse AccountId from bytes")
176            })?)
177            .map_err(|e| Error::new(ErrorKind::InvalidData, e.to_string()))
178        };
179
180        if u2 == 0 {
181            let signer_id = read_signer_id([u1, u2, u3, u4], reader)?;
182            let public_key = PublicKey::deserialize_reader(reader)?;
183            let nonce = Nonce::deserialize_reader(reader)?;
184            let receiver_id = AccountId::deserialize_reader(reader)?;
185            let block_hash = CryptoHash::deserialize_reader(reader)?;
186            let actions = Vec::<Action>::deserialize_reader(reader)?;
187            Ok(Transaction::V0(TransactionV0 {
188                signer_id,
189                public_key,
190                nonce,
191                receiver_id,
192                block_hash,
193                actions,
194            }))
195        } else {
196            let u5 = u8::deserialize_reader(reader)?;
197            let signer_id = read_signer_id([u2, u3, u4, u5], reader)?;
198            let public_key = PublicKey::deserialize_reader(reader)?;
199            let nonce = Nonce::deserialize_reader(reader)?;
200            let receiver_id = AccountId::deserialize_reader(reader)?;
201            let block_hash = CryptoHash::deserialize_reader(reader)?;
202            let actions = Vec::<Action>::deserialize_reader(reader)?;
203            let priority_fee = u64::deserialize_reader(reader)?;
204            Ok(Transaction::V1(TransactionV1 {
205                signer_id,
206                public_key,
207                nonce,
208                receiver_id,
209                block_hash,
210                actions,
211                priority_fee,
212            }))
213        }
214    }
215}
216
217/// Using the new type pattern to construct a type of signed transaction that is
218/// guaranteed to have various checks performed on it.  In particular, ensure
219/// that the signature is verified and the max transaction size checks have been
220/// conducted.
221#[derive(Clone, Debug, PartialEq, Eq)]
222pub struct ValidatedTransaction(SignedTransaction);
223
224impl ValidatedTransaction {
225    #[allow(clippy::result_large_err)]
226    pub fn new(
227        config: &RuntimeConfig,
228        signed_tx: SignedTransaction,
229    ) -> Result<Self, (InvalidTxError, SignedTransaction)> {
230        // Don't allow V1 currently. This will be changed when the new protocol version is introduced.
231        if matches!(signed_tx.transaction, Transaction::V1(_)) {
232            return Err((InvalidTxError::InvalidTransactionVersion, signed_tx));
233        }
234        let tx_size = signed_tx.get_size();
235        let max_tx_size = config.wasm_config.limit_config.max_transaction_size;
236        if tx_size > max_tx_size {
237            return Err((
238                InvalidTxError::TransactionSizeExceeded { size: tx_size, limit: max_tx_size },
239                signed_tx,
240            ));
241        }
242
243        if signed_tx
244            .signature
245            .verify(signed_tx.get_hash().as_ref(), signed_tx.transaction.public_key())
246        {
247            Ok(Self(signed_tx))
248        } else {
249            Err((InvalidTxError::InvalidSignature, signed_tx))
250        }
251    }
252
253    /// This method should only be used for test purposes.
254    pub fn new_for_test(signed_tx: SignedTransaction) -> Self {
255        Self(signed_tx)
256    }
257
258    /// Builds a list of ValidatedTransactions from an iterator of
259    /// SignedTransactions.
260    ///
261    /// Note that the if a subset of SignedTransactions pass validation and then
262    /// one fails, then this function will drop the validated txs.  Currently,
263    /// this is not problematic for any of the callers of this function. It is
264    /// possible to improve this function to never drop any txs if callers
265    /// require such functionality in the future.
266    #[allow(clippy::result_large_err)]
267    pub fn new_list(
268        config: &RuntimeConfig,
269        signed_txs: impl IntoIterator<Item = SignedTransaction>,
270    ) -> Result<Vec<ValidatedTransaction>, (InvalidTxError, SignedTransaction)> {
271        let mut validated_txs = vec![];
272        for signed_tx in signed_txs {
273            validated_txs.push(ValidatedTransaction::new(&config, signed_tx)?);
274        }
275        Ok(validated_txs)
276    }
277
278    pub fn to_signed_tx(&self) -> &SignedTransaction {
279        &self.0
280    }
281
282    pub fn into_signed_tx(self) -> SignedTransaction {
283        self.0
284    }
285
286    pub fn to_tx(&self) -> &Transaction {
287        &self.0.transaction
288    }
289
290    /// This function should probably be deprecated in favour of `to_hash()`
291    /// below as that offers stronger type safety.
292    pub fn get_hash(&self) -> CryptoHash {
293        self.0.get_hash()
294    }
295
296    /// See additional documentation around `ValidatedTransactionHash`.
297    pub fn to_hash(&self) -> ValidatedTransactionHash {
298        ValidatedTransactionHash(self.get_hash())
299    }
300
301    pub fn get_size(&self) -> u64 {
302        self.0.get_size()
303    }
304
305    pub fn signer_id(&self) -> &AccountId {
306        self.to_tx().signer_id()
307    }
308
309    pub fn receiver_id(&self) -> &AccountId {
310        self.to_tx().receiver_id()
311    }
312
313    pub fn nonce(&self) -> Nonce {
314        self.to_tx().nonce()
315    }
316
317    pub fn public_key(&self) -> &PublicKey {
318        self.to_tx().public_key()
319    }
320
321    pub fn actions(&self) -> &[Action] {
322        self.to_tx().actions()
323    }
324}
325
326/// Using the new type pattern, wraps a `CryptoHash` to indicate that it could
327/// have only come from a `ValidatedTransaction`.  The only way to construct
328/// this type should be by calling `ValidatedTransaction::to_transaction_hash()`.
329pub struct ValidatedTransactionHash(CryptoHash);
330
331impl ValidatedTransactionHash {
332    pub fn get_hash(&self) -> CryptoHash {
333        self.0
334    }
335}
336
337#[derive(BorshSerialize, BorshDeserialize, Eq, Debug, Clone, ProtocolSchema)]
338#[borsh(init=init)]
339pub struct SignedTransaction {
340    pub transaction: Transaction,
341    pub signature: Signature,
342    #[borsh(skip)]
343    hash: CryptoHash,
344    #[borsh(skip)]
345    size: u64,
346}
347
348impl SignedTransaction {
349    pub fn new(signature: Signature, transaction: Transaction) -> Self {
350        let mut signed_tx =
351            Self { signature, transaction, hash: CryptoHash::default(), size: u64::default() };
352        signed_tx.init();
353        signed_tx
354    }
355
356    pub fn init(&mut self) {
357        let (hash, size) = self.transaction.get_hash_and_size();
358        self.hash = hash;
359        self.size = size;
360    }
361
362    pub fn get_hash(&self) -> CryptoHash {
363        self.hash
364    }
365
366    pub fn get_size(&self) -> u64 {
367        self.size
368    }
369}
370
371impl Hash for SignedTransaction {
372    fn hash<H: Hasher>(&self, state: &mut H) {
373        self.hash.hash(state)
374    }
375}
376
377impl PartialEq for SignedTransaction {
378    fn eq(&self, other: &SignedTransaction) -> bool {
379        self.hash == other.hash && self.signature == other.signature
380    }
381}
382
383impl Borrow<CryptoHash> for SignedTransaction {
384    fn borrow(&self) -> &CryptoHash {
385        &self.hash
386    }
387}
388
389impl serde::Serialize for SignedTransaction {
390    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
391    where
392        S: serde::Serializer,
393    {
394        let signed_tx_borsh = borsh::to_vec(self).map_err(|err| {
395            S::Error::custom(&format!("the value could not be borsh encoded due to: {}", err))
396        })?;
397        let signed_tx_base64 = to_base64(&signed_tx_borsh);
398        serializer.serialize_str(&signed_tx_base64)
399    }
400}
401
402impl<'de> serde::Deserialize<'de> for SignedTransaction {
403    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
404    where
405        D: serde::Deserializer<'de>,
406    {
407        let signed_tx_base64 = <String as serde::Deserialize>::deserialize(deserializer)?;
408        let signed_tx_borsh = from_base64(&signed_tx_base64).map_err(|err| {
409            D::Error::custom(&format!("the value could not decoded from base64 due to: {}", err))
410        })?;
411        borsh::from_slice::<Self>(&signed_tx_borsh).map_err(|err| {
412            D::Error::custom(&format!("the value could not decoded from borsh due to: {}", err))
413        })
414    }
415}
416
417#[cfg(feature = "schemars")]
418impl schemars::JsonSchema for SignedTransaction {
419    fn schema_name() -> std::borrow::Cow<'static, str> {
420        "SignedTransaction".to_string().into()
421    }
422
423    fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
424        json_schema!({
425            "type": "string",
426            "format": "byte"
427        })
428    }
429}
430
431/// The status of execution for a transaction or a receipt.
432#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Clone, Default, ProtocolSchema)]
433pub enum ExecutionStatus {
434    /// The execution is pending or unknown.
435    #[default]
436    Unknown,
437    /// The execution has failed with the given execution error.
438    Failure(TxExecutionError),
439    /// The final action succeeded and returned some value or an empty vec.
440    SuccessValue(Vec<u8>),
441    /// The final action of the receipt returned a promise or the signed transaction was converted
442    /// to a receipt. Contains the receipt_id of the generated receipt.
443    SuccessReceiptId(CryptoHash),
444}
445
446impl fmt::Debug for ExecutionStatus {
447    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
448        match self {
449            ExecutionStatus::Unknown => f.write_str("Unknown"),
450            ExecutionStatus::Failure(e) => f.write_fmt(format_args!("Failure({})", e)),
451            ExecutionStatus::SuccessValue(v) => {
452                f.write_fmt(format_args!("SuccessValue({})", AbbrBytes(v)))
453            }
454            ExecutionStatus::SuccessReceiptId(receipt_id) => {
455                f.write_fmt(format_args!("SuccessReceiptId({})", receipt_id))
456            }
457        }
458    }
459}
460
461/// ExecutionOutcome for proof. Excludes logs and metadata
462#[derive(BorshSerialize, BorshDeserialize, PartialEq, Clone)]
463pub struct PartialExecutionOutcome {
464    pub receipt_ids: Vec<CryptoHash>,
465    pub gas_burnt: Gas,
466    pub tokens_burnt: Balance,
467    pub executor_id: AccountId,
468    pub status: PartialExecutionStatus,
469}
470
471impl From<&ExecutionOutcome> for PartialExecutionOutcome {
472    fn from(outcome: &ExecutionOutcome) -> Self {
473        Self {
474            receipt_ids: outcome.receipt_ids.clone(),
475            gas_burnt: outcome.gas_burnt,
476            tokens_burnt: outcome.tokens_burnt,
477            executor_id: outcome.executor_id.clone(),
478            status: outcome.status.clone().into(),
479        }
480    }
481}
482
483/// ExecutionStatus for proof. Excludes failure debug info.
484#[derive(BorshSerialize, BorshDeserialize, PartialEq, Clone)]
485pub enum PartialExecutionStatus {
486    Unknown,
487    Failure,
488    SuccessValue(Vec<u8>),
489    SuccessReceiptId(CryptoHash),
490}
491
492impl From<ExecutionStatus> for PartialExecutionStatus {
493    fn from(status: ExecutionStatus) -> PartialExecutionStatus {
494        match status {
495            ExecutionStatus::Unknown => PartialExecutionStatus::Unknown,
496            ExecutionStatus::Failure(_) => PartialExecutionStatus::Failure,
497            ExecutionStatus::SuccessValue(value) => PartialExecutionStatus::SuccessValue(value),
498            ExecutionStatus::SuccessReceiptId(id) => PartialExecutionStatus::SuccessReceiptId(id),
499        }
500    }
501}
502
503/// Execution outcome for one signed transaction or one receipt.
504#[derive(
505    BorshSerialize,
506    BorshDeserialize,
507    PartialEq,
508    Clone,
509    smart_default::SmartDefault,
510    Eq,
511    ProtocolSchema,
512)]
513pub struct ExecutionOutcome {
514    /// Logs from this transaction or receipt.
515    pub logs: Vec<LogEntry>,
516    /// Receipt IDs generated by this transaction or receipt.
517    pub receipt_ids: Vec<CryptoHash>,
518    /// The amount of the gas burnt by the given transaction or receipt.
519    pub gas_burnt: Gas,
520    /// The amount of compute time spent by the given transaction or receipt.
521    // TODO(#8859): Treat this field in the same way as `gas_burnt`.
522    // At the moment this field is only set at runtime and is not persisted in the database.
523    // This means that when execution outcomes are read from the database, this value will not be
524    // set and any code that attempts to use it will crash.
525    #[borsh(skip)]
526    pub compute_usage: Option<Compute>,
527    /// The amount of tokens burnt corresponding to the burnt gas amount.
528    /// This value doesn't always equal to the `gas_burnt` multiplied by the gas price, because
529    /// the prepaid gas price might be lower than the actual gas price and it creates a deficit.
530    pub tokens_burnt: Balance,
531    /// The id of the account on which the execution happens. For transaction this is signer_id,
532    /// for receipt this is receiver_id.
533    #[default("test".parse().unwrap())]
534    pub executor_id: AccountId,
535    /// Execution status. Contains the result in case of successful execution.
536    /// NOTE: Should be the latest field since it contains unparsable by light client
537    /// ExecutionStatus::Failure
538    pub status: ExecutionStatus,
539    /// Execution metadata, versioned
540    pub metadata: ExecutionMetadata,
541}
542
543#[derive(
544    BorshSerialize, BorshDeserialize, PartialEq, Clone, Eq, Debug, Default, ProtocolSchema,
545)]
546pub enum ExecutionMetadata {
547    /// V1: Empty Metadata
548    #[default]
549    V1,
550    /// V2: With ProfileData by legacy `Cost` enum
551    V2(crate::profile_data_v2::ProfileDataV2),
552    /// V3: With ProfileData by gas parameters
553    V3(Box<ProfileDataV3>),
554}
555
556impl fmt::Debug for ExecutionOutcome {
557    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
558        f.debug_struct("ExecutionOutcome")
559            .field("logs", &Slice(&self.logs))
560            .field("receipt_ids", &Slice(&self.receipt_ids))
561            .field("burnt_gas", &self.gas_burnt)
562            .field("compute_usage", &self.compute_usage.unwrap_or_default())
563            .field("tokens_burnt", &self.tokens_burnt)
564            .field("status", &self.status)
565            .field("metadata", &self.metadata)
566            .finish()
567    }
568}
569
570/// Execution outcome with the identifier.
571/// For a signed transaction, the ID is the hash of the transaction.
572/// For a receipt, the ID is the receipt ID.
573#[derive(
574    PartialEq, Clone, Default, Debug, BorshSerialize, BorshDeserialize, Eq, ProtocolSchema,
575)]
576pub struct ExecutionOutcomeWithId {
577    /// The transaction hash or the receipt ID.
578    pub id: CryptoHash,
579    /// Should be the latest field since contains unparsable by light client ExecutionStatus::Failure
580    pub outcome: ExecutionOutcome,
581}
582
583impl ExecutionOutcomeWithId {
584    pub fn to_hashes(&self) -> Vec<CryptoHash> {
585        let mut result = Vec::with_capacity(2 + self.outcome.logs.len());
586        result.push(self.id);
587        result.push(CryptoHash::hash_borsh(PartialExecutionOutcome::from(&self.outcome)));
588        result.extend(self.outcome.logs.iter().map(|log| hash(log.as_bytes())));
589        result
590    }
591}
592
593/// Execution outcome with path from it to the outcome root and ID.
594#[derive(
595    PartialEq, Clone, Default, Debug, BorshSerialize, BorshDeserialize, Eq, ProtocolSchema,
596)]
597pub struct ExecutionOutcomeWithIdAndProof {
598    pub proof: MerklePath,
599    pub block_hash: CryptoHash,
600    /// Should be the latest field since contains unparsable by light client ExecutionStatus::Failure
601    pub outcome_with_id: ExecutionOutcomeWithId,
602}
603
604impl ExecutionOutcomeWithIdAndProof {
605    pub fn id(&self) -> &CryptoHash {
606        &self.outcome_with_id.id
607    }
608}
609
610pub fn verify_transaction_signature(
611    transaction: &SignedTransaction,
612    public_keys: &[PublicKey],
613) -> bool {
614    let hash = transaction.get_hash();
615    let hash = hash.as_ref();
616    public_keys.iter().any(|key| transaction.signature.verify(hash, key))
617}
618
619/// A more compact struct, just for storage.
620#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, ProtocolSchema)]
621pub struct ExecutionOutcomeWithProof {
622    pub proof: MerklePath,
623    pub outcome: ExecutionOutcome,
624}
625#[cfg(test)]
626mod tests {
627    use super::*;
628    use crate::account::{AccessKey, AccessKeyPermission, FunctionCallPermission};
629    use borsh::BorshDeserialize;
630    use near_crypto::{InMemorySigner, KeyType, Signature, Signer};
631
632    #[test]
633    fn test_verify_transaction() {
634        let signer: Signer =
635            InMemorySigner::from_random("test".parse().unwrap(), KeyType::ED25519).into();
636        let transaction = Transaction::V0(TransactionV0 {
637            signer_id: "test".parse().unwrap(),
638            public_key: signer.public_key(),
639            nonce: 0,
640            receiver_id: "test".parse().unwrap(),
641            block_hash: Default::default(),
642            actions: vec![],
643        })
644        .sign(&signer);
645        let wrong_public_key = PublicKey::from_seed(KeyType::ED25519, "wrong");
646        let valid_keys = vec![signer.public_key(), wrong_public_key.clone()];
647        assert!(verify_transaction_signature(&transaction, &valid_keys));
648
649        let invalid_keys = vec![wrong_public_key];
650        assert!(!verify_transaction_signature(&transaction, &invalid_keys));
651
652        let bytes = borsh::to_vec(&transaction).unwrap();
653        let decoded_tx = SignedTransaction::try_from_slice(&bytes).unwrap();
654        assert!(verify_transaction_signature(&decoded_tx, &valid_keys));
655    }
656
657    fn create_transaction_v0() -> TransactionV0 {
658        let public_key: PublicKey = "22skMptHjFWNyuEWY22ftn2AbLPSYpmYwGJRGwpNHbTV".parse().unwrap();
659        TransactionV0 {
660            signer_id: "test.near".parse().unwrap(),
661            public_key: public_key.clone(),
662            nonce: 1,
663            receiver_id: "123".parse().unwrap(),
664            block_hash: Default::default(),
665            actions: vec![
666                Action::CreateAccount(CreateAccountAction {}),
667                Action::DeployContract(DeployContractAction { code: vec![1, 2, 3] }),
668                Action::FunctionCall(Box::new(FunctionCallAction {
669                    method_name: "qqq".to_string(),
670                    args: vec![1, 2, 3],
671                    gas: 1_000,
672                    deposit: 1_000_000,
673                })),
674                Action::Transfer(TransferAction { deposit: 123 }),
675                Action::Stake(Box::new(StakeAction {
676                    public_key: public_key.clone(),
677                    stake: 1_000_000,
678                })),
679                Action::AddKey(Box::new(AddKeyAction {
680                    public_key: public_key.clone(),
681                    access_key: AccessKey {
682                        nonce: 0,
683                        permission: AccessKeyPermission::FunctionCall(FunctionCallPermission {
684                            allowance: None,
685                            receiver_id: "zzz".parse().unwrap(),
686                            method_names: vec!["www".to_string()],
687                        }),
688                    },
689                })),
690                Action::DeleteKey(Box::new(DeleteKeyAction { public_key })),
691                Action::DeleteAccount(DeleteAccountAction {
692                    beneficiary_id: "123".parse().unwrap(),
693                }),
694            ],
695        }
696    }
697
698    fn create_transaction_v1() -> TransactionV1 {
699        let public_key: PublicKey = "22skMptHjFWNyuEWY22ftn2AbLPSYpmYwGJRGwpNHbTV".parse().unwrap();
700        TransactionV1 {
701            signer_id: "test.near".parse().unwrap(),
702            public_key: public_key.clone(),
703            nonce: 1,
704            receiver_id: "123".parse().unwrap(),
705            block_hash: Default::default(),
706            actions: vec![
707                Action::CreateAccount(CreateAccountAction {}),
708                Action::DeployContract(DeployContractAction { code: vec![1, 2, 3] }),
709                Action::FunctionCall(Box::new(FunctionCallAction {
710                    method_name: "qqq".to_string(),
711                    args: vec![1, 2, 3],
712                    gas: 1_000,
713                    deposit: 1_000_000,
714                })),
715                Action::Transfer(TransferAction { deposit: 123 }),
716                Action::Stake(Box::new(StakeAction {
717                    public_key: public_key.clone(),
718                    stake: 1_000_000,
719                })),
720                Action::AddKey(Box::new(AddKeyAction {
721                    public_key: public_key.clone(),
722                    access_key: AccessKey {
723                        nonce: 0,
724                        permission: AccessKeyPermission::FunctionCall(FunctionCallPermission {
725                            allowance: None,
726                            receiver_id: "zzz".parse().unwrap(),
727                            method_names: vec!["www".to_string()],
728                        }),
729                    },
730                })),
731                Action::DeleteKey(Box::new(DeleteKeyAction { public_key })),
732                Action::DeleteAccount(DeleteAccountAction {
733                    beneficiary_id: "123".parse().unwrap(),
734                }),
735            ],
736            priority_fee: 1,
737        }
738    }
739
740    /// This test is change checker for a reason - we don't expect transaction format to change.
741    /// If it does - you MUST update all of the dependencies: like nearlib and other clients.
742    #[test]
743    fn test_serialize_transaction() {
744        let transaction = Transaction::V0(create_transaction_v0());
745        let signed_tx = SignedTransaction::new(Signature::empty(KeyType::ED25519), transaction);
746        let new_signed_tx =
747            SignedTransaction::try_from_slice(&borsh::to_vec(&signed_tx).unwrap()).unwrap();
748
749        assert_eq!(
750            new_signed_tx.get_hash().to_string(),
751            "4GXvjMFN6wSxnU9jEVT8HbXP5Yk6yELX9faRSKp6n9fX"
752        );
753    }
754
755    #[test]
756    fn test_serialize_transaction_versions() {
757        let transaction_v0 = Transaction::V0(create_transaction_v0());
758        let serialized_tx_v0 = borsh::to_vec(&transaction_v0).unwrap();
759        let deserialized_tx_v0 = Transaction::try_from_slice(&serialized_tx_v0).unwrap();
760        assert_eq!(transaction_v0, deserialized_tx_v0);
761
762        let transaction_v1 = Transaction::V1(create_transaction_v1());
763        let serialized_tx_v1 = borsh::to_vec(&transaction_v1).unwrap();
764        let deserialized_tx_v1 = Transaction::try_from_slice(&serialized_tx_v1).unwrap();
765        assert_eq!(transaction_v1, deserialized_tx_v1);
766    }
767
768    #[test]
769    fn test_outcome_to_hashes() {
770        let outcome = ExecutionOutcome {
771            status: ExecutionStatus::SuccessValue(vec![123]),
772            logs: vec!["123".to_string(), "321".to_string()],
773            receipt_ids: vec![],
774            gas_burnt: 123,
775            compute_usage: Some(456),
776            tokens_burnt: 1234000,
777            executor_id: "alice".parse().unwrap(),
778            metadata: ExecutionMetadata::V1,
779        };
780        let id = CryptoHash([42u8; 32]);
781        let outcome = ExecutionOutcomeWithId { id, outcome };
782        assert_eq!(
783            vec![
784                id,
785                "5JQs5ekQqKudMmYejuccbtEu1bzhQPXa92Zm4HdV64dQ".parse().unwrap(),
786                hash("123".as_bytes()),
787                hash("321".as_bytes()),
788            ],
789            outcome.to_hashes()
790        );
791    }
792}