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}