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