Skip to main content

mina_tree/scan_state/transaction_logic/
signed_command.rs

1use mina_p2p_messages::v2::MinaBaseSignedCommandStableV2;
2use mina_signer::{CompressedPubKey, Signature};
3
4use crate::{
5    decompress_pk,
6    scan_state::{
7        currency::{Amount, Fee, Nonce, Signed, Slot},
8        fee_excess::FeeExcess,
9    },
10    AccountId, TokenId,
11};
12
13use super::{zkapp_command::AccessedOrNot, Memo, TransactionStatus};
14
15/// Common fields shared by all signed command payloads.
16///
17/// OCaml: <https://github.com/MinaProtocol/mina/blob/5da42ccd72e791f164d4d200cf1ce300262873b3/src/lib/mina_base/signed_command_payload.ml#L34-L48>
18#[derive(Debug, Clone, PartialEq)]
19pub struct Common {
20    /// Fee paid to the block producer
21    pub fee: Fee,
22    /// Public key paying the fee
23    pub fee_payer_pk: CompressedPubKey,
24    /// Account nonce for replay protection
25    pub nonce: Nonce,
26    /// Slot after which the transaction expires
27    pub valid_until: Slot,
28    /// Optional memo field (34 bytes)
29    pub memo: Memo,
30}
31
32/// Payment payload for transferring MINA tokens.
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct PaymentPayload {
35    /// Recipient's public key
36    pub receiver_pk: CompressedPubKey,
37    /// Amount to transfer
38    pub amount: Amount,
39}
40
41/// Stake delegation payload for delegating stake to another account.
42///
43/// OCaml: <https://github.com/MinaProtocol/mina/blob/5da42ccd72e791f164d4d200cf1ce300262873b3/src/lib/mina_base/stake_delegation.ml#L11-L13>
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub enum StakeDelegationPayload {
46    /// Delegate stake to a new delegate
47    SetDelegate {
48        /// Public key of the new delegate
49        new_delegate: CompressedPubKey,
50    },
51}
52
53impl StakeDelegationPayload {
54    /// OCaml: <https://github.com/MinaProtocol/mina/blob/5da42ccd72e791f164d4d200cf1ce300262873b3/src/lib/mina_base/stake_delegation.ml#L35-L37>
55    pub fn receiver(&self) -> AccountId {
56        let Self::SetDelegate { new_delegate } = self;
57        AccountId::new(new_delegate.clone(), TokenId::default())
58    }
59
60    /// OCaml: <https://github.com/MinaProtocol/mina/blob/5da42ccd72e791f164d4d200cf1ce300262873b3/src/lib/mina_base/stake_delegation.ml#L33-L33>
61    pub fn receiver_pk(&self) -> &CompressedPubKey {
62        let Self::SetDelegate { new_delegate } = self;
63        new_delegate
64    }
65}
66
67/// The body of a signed command, which can be either a payment or stake
68/// delegation.
69///
70/// OCaml: <https://github.com/MinaProtocol/mina/blob/5da42ccd72e791f164d4d200cf1ce300262873b3/src/lib/mina_base/signed_command_payload.ml#L179-L181>
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub enum Body {
73    /// Transfer MINA tokens from fee payer to receiver
74    Payment(PaymentPayload),
75    /// Delegate fee payer's stake to another account
76    StakeDelegation(StakeDelegationPayload),
77}
78
79/// Signed command payload containing common fields and the transaction body.
80///
81/// OCaml: <https://github.com/MinaProtocol/mina/blob/5da42ccd72e791f164d4d200cf1ce300262873b3/src/lib/mina_base/signed_command_payload.ml#L239-L243>
82#[derive(Debug, Clone, PartialEq)]
83pub struct SignedCommandPayload {
84    /// Common fields (fee, fee payer, nonce, valid_until, memo)
85    pub common: Common,
86    /// Transaction body (payment or stake delegation)
87    pub body: Body,
88}
89
90impl SignedCommandPayload {
91    pub fn create(
92        fee: Fee,
93        fee_payer_pk: CompressedPubKey,
94        nonce: Nonce,
95        valid_until: Option<Slot>,
96        memo: Memo,
97        body: Body,
98    ) -> Self {
99        Self {
100            common: Common {
101                fee,
102                fee_payer_pk,
103                nonce,
104                valid_until: valid_until.unwrap_or_else(Slot::max),
105                memo,
106            },
107            body,
108        }
109    }
110}
111
112/// OCaml: <https://github.com/MinaProtocol/mina/blob/5da42ccd72e791f164d4d200cf1ce300262873b3/src/lib/mina_base/signed_command_payload.ml#L352-L362>
113mod weight {
114    use super::*;
115
116    fn payment(_: &PaymentPayload) -> u64 {
117        1
118    }
119    fn stake_delegation(_: &StakeDelegationPayload) -> u64 {
120        1
121    }
122    pub fn of_body(body: &Body) -> u64 {
123        match body {
124            Body::Payment(p) => payment(p),
125            Body::StakeDelegation(s) => stake_delegation(s),
126        }
127    }
128}
129
130/// A signed command is a transaction that transfers value or delegates stake.
131///
132/// Signed commands are authorized by a cryptographic signature and consist of a
133/// payload (containing the transaction details) and the signature proving
134/// authorization.
135#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
136#[serde(into = "MinaBaseSignedCommandStableV2")]
137#[serde(try_from = "MinaBaseSignedCommandStableV2")]
138pub struct SignedCommand {
139    /// The transaction payload (common fields and body)
140    pub payload: SignedCommandPayload,
141    /// The public key that signed the transaction
142    pub signer: CompressedPubKey, // TODO: This should be a `mina_signer::PubKey`
143    /// The cryptographic signature
144    pub signature: Signature,
145}
146
147impl SignedCommand {
148    pub fn valid_until(&self) -> Slot {
149        self.payload.common.valid_until
150    }
151
152    /// OCaml: <https://github.com/MinaProtocol/mina/blob/5da42ccd72e791f164d4d200cf1ce300262873b3/src/lib/mina_base/signed_command_payload.ml#L292-L292>
153    pub fn fee_payer(&self) -> AccountId {
154        let public_key = self.payload.common.fee_payer_pk.clone();
155        AccountId::new(public_key, TokenId::default())
156    }
157
158    /// OCaml: <https://github.com/MinaProtocol/mina/blob/5da42ccd72e791f164d4d200cf1ce300262873b3/src/lib/mina_base/signed_command_payload.ml#L290-L290>
159    pub fn fee_payer_pk(&self) -> &CompressedPubKey {
160        &self.payload.common.fee_payer_pk
161    }
162
163    pub fn weight(&self) -> u64 {
164        let Self {
165            payload: SignedCommandPayload { common: _, body },
166            signer: _,
167            signature: _,
168        } = self;
169        weight::of_body(body)
170    }
171
172    /// OCaml: <https://github.com/MinaProtocol/mina/blob/5da42ccd72e791f164d4d200cf1ce300262873b3/src/lib/mina_base/signed_command_payload.ml#L288-L288>
173    pub fn fee_token(&self) -> TokenId {
174        TokenId::default()
175    }
176
177    pub fn fee(&self) -> Fee {
178        self.payload.common.fee
179    }
180
181    /// OCaml: <https://github.com/MinaProtocol/mina/blob/5da42ccd72e791f164d4d200cf1ce300262873b3/src/lib/mina_base/signed_command_payload.ml#L304-L304>
182    pub fn receiver(&self) -> AccountId {
183        match &self.payload.body {
184            Body::Payment(payload) => {
185                AccountId::new(payload.receiver_pk.clone(), TokenId::default())
186            }
187            Body::StakeDelegation(payload) => payload.receiver(),
188        }
189    }
190
191    /// OCaml: <https://github.com/MinaProtocol/mina/blob/5da42ccd72e791f164d4d200cf1ce300262873b3/src/lib/mina_base/signed_command_payload.ml#L302-L302>
192    pub fn receiver_pk(&self) -> &CompressedPubKey {
193        match &self.payload.body {
194            Body::Payment(payload) => &payload.receiver_pk,
195            Body::StakeDelegation(payload) => payload.receiver_pk(),
196        }
197    }
198
199    pub fn amount(&self) -> Option<Amount> {
200        match &self.payload.body {
201            Body::Payment(payload) => Some(payload.amount),
202            Body::StakeDelegation(_) => None,
203        }
204    }
205
206    pub fn nonce(&self) -> Nonce {
207        self.payload.common.nonce
208    }
209
210    pub fn fee_excess(&self) -> FeeExcess {
211        FeeExcess::of_single((self.fee_token(), Signed::<Fee>::of_unsigned(self.fee())))
212    }
213
214    /// OCaml: <https://github.com/MinaProtocol/mina/blob/5da42ccd72e791f164d4d200cf1ce300262873b3/src/lib/mina_base/signed_command_payload.ml#L320-L338>
215    pub fn account_access_statuses(
216        &self,
217        status: &TransactionStatus,
218    ) -> Vec<(AccountId, AccessedOrNot)> {
219        use AccessedOrNot::*;
220        use TransactionStatus::*;
221
222        match status {
223            Applied => vec![(self.fee_payer(), Accessed), (self.receiver(), Accessed)],
224            // Note: The fee payer is always accessed, even if the transaction fails
225            // OCaml: <https://github.com/MinaProtocol/mina/blob/5da42ccd72e791f164d4d200cf1ce300262873b3/src/lib/mina_base/signed_command_payload.mli#L205-L209>
226            Failed(_) => vec![(self.fee_payer(), Accessed), (self.receiver(), NotAccessed)],
227        }
228    }
229
230    pub fn accounts_referenced(&self) -> Vec<AccountId> {
231        self.account_access_statuses(&TransactionStatus::Applied)
232            .into_iter()
233            .map(|(id, _status)| id)
234            .collect()
235    }
236
237    /// OCaml: <https://github.com/MinaProtocol/mina/blob/5da42ccd72e791f164d4d200cf1ce300262873b3/src/lib/mina_base/signed_command.ml#L417-L420>
238    pub fn public_keys(&self) -> [&CompressedPubKey; 2] {
239        [self.fee_payer_pk(), self.receiver_pk()]
240    }
241
242    /// OCaml: <https://github.com/MinaProtocol/mina/blob/5da42ccd72e791f164d4d200cf1ce300262873b3/src/lib/mina_base/signed_command.ml#L422-L424>
243    pub fn check_valid_keys(&self) -> bool {
244        self.public_keys()
245            .into_iter()
246            .all(|pk| decompress_pk(pk).is_some())
247    }
248}