penumbra_sdk_shielded_pool/component/action_handler/
spend.rs1use anyhow::{Context, Result};
2use async_trait::async_trait;
3use cnidarium::StateWrite;
4use cnidarium_component::ActionHandler;
5use penumbra_sdk_proof_params::SPEND_PROOF_VERIFICATION_KEY;
6use penumbra_sdk_proto::{DomainType, StateWriteProto as _};
7use penumbra_sdk_sct::component::{
8 source::SourceContext,
9 tree::{SctManager, VerificationExt},
10};
11use penumbra_sdk_txhash::TransactionContext;
12
13use crate::{event, Spend, SpendProofPublic};
14
15#[async_trait]
16impl ActionHandler for Spend {
17 type CheckStatelessContext = TransactionContext;
18 async fn check_stateless(&self, context: TransactionContext) -> Result<()> {
19 let spend = self;
20 spend
22 .body
23 .rk
24 .verify(context.effect_hash.as_ref(), &spend.auth_sig)
25 .context("spend auth signature failed to verify")?;
26
27 let public = SpendProofPublic {
29 anchor: context.anchor,
30 balance_commitment: spend.body.balance_commitment,
31 nullifier: spend.body.nullifier,
32 rk: spend.body.rk,
33 };
34 spend
35 .proof
36 .verify(&SPEND_PROOF_VERIFICATION_KEY, public)
37 .context("a spend proof did not verify")?;
38
39 Ok(())
40 }
41
42 async fn check_and_execute<S: StateWrite>(&self, mut state: S) -> Result<()> {
43 let spent_nullifier = self.body.nullifier;
45 state.check_nullifier_unspent(spent_nullifier).await?;
46
47 let source = state.get_current_source().expect("source should be set");
48
49 state.nullify(self.body.nullifier, source).await;
50
51 state.record_proto(
53 event::EventSpend {
54 nullifier: self.body.nullifier,
55 }
56 .to_proto(),
57 );
58
59 Ok(())
60 }
61}