consume goal notes in the executor tx

This commit is contained in:
David Rusu 2025-03-12 17:08:08 +04:00
parent 2947329764
commit bfb8594112
14 changed files with 145 additions and 62 deletions

View File

@ -69,14 +69,14 @@ impl SwapArgs {
}
}
pub fn swap_goal_note(nonce: Nonce) -> OutputWitness {
OutputWitness {
pub fn swap_goal_note(nonce: Nonce) -> InputWitness {
InputWitness {
state: [0u8; 32],
value: 1,
unit: swap_goal_unit().unit(),
unit_witness: swap_goal_unit(),
nonce,
zone_id: ZONE_ID,
nf_pk: NullifierSecret::zero().commit(),
nf_sk: NullifierSecret::zero(),
}
}

View File

@ -1,5 +1,5 @@
use app::ZoneData;
use cl::crust::{NullifierSecret, UnitWitness};
use cl::crust::UnitWitness;
fn nmo() -> UnitWitness {
UnitWitness::nop(b"NMO")
@ -10,8 +10,6 @@ fn mem() -> UnitWitness {
#[test]
fn pair_price() {
let mut rng = rand::thread_rng();
let mut swapvm_state = ZoneData::new();
// initially there is no NMO/MEM pair

View File

@ -1,16 +1,15 @@
use std::collections::BTreeMap;
use app::{swap_goal_unit, SwapArgs, ZoneData};
use cl::crust::{
BundleWitness, InputWitness, Nonce, NoteCommitment, Nullifier, NullifierSecret, Tx, TxWitness,
Unit,
};
use cl::ds::mmr::{MMRProof, MMR};
use cl::crust::{BundleWitness, InputWitness, NoteCommitment, Nullifier, Tx, TxWitness, Unit};
use cl::ds::mmr::{MMRFolds, MMRProof, MMR};
use cl::mantle::ledger::Ledger;
use cl::mantle::ledger::LedgerState;
use cl::mantle::ZoneState;
use ledger::stf::{risc0_stf, StfProof};
use ledger_proof_statements::ledger::SyncLog;
use methods::{STF_ELF, STF_ID};
use risc0_zkvm::{ExecutorEnv, Prover, Receipt, Result};
use risc0_zkvm::{ExecutorEnv, Prover, Result};
#[derive(Debug)]
struct FundNote {
@ -33,31 +32,84 @@ pub struct ExecutorState {
pub ledger: LedgerState,
pub swapvm: ZoneData,
fund_notes: BTreeMap<Unit, FundNote>,
goal_notes: Vec<(InputWitness, MMR, MMRProof)>,
}
impl ExecutorState {
pub fn observe_cms(&mut self, cms: impl IntoIterator<Item = NoteCommitment>) {
for cm in cms {
self.ledger.add_commitment(&cm);
pub fn zone_state(&self) -> ZoneState {
ZoneState {
stf: risc0_stf(STF_ID),
zone_data: self.swapvm.commit(),
ledger: self.ledger.to_witness().commit(),
}
}
pub fn observe_cm(&mut self, cm: &NoteCommitment) -> ((MMR, MMRProof), MMRFolds) {
let folds = self.ledger.commitments.folds(&cm.0);
for (_, fund_note) in self.fund_notes.iter_mut() {
assert_eq!(fund_note.mmr, self.ledger.commitments);
fund_note
.path
.update(&fund_note.note.note_commitment().0, &folds);
}
let proof = self.ledger.add_commitment(cm);
for (_, fund_note) in self.fund_notes.iter_mut() {
fund_note.mmr = self.ledger.commitments.clone();
}
(proof, folds)
}
pub fn observe_nfs(&mut self, nfs: Vec<Nullifier>) {
self.ledger.add_nullifiers(nfs);
}
pub fn process_tx(&mut self, tx: &Tx) {
let Some(swapvm_update) = tx.updates.get(&self.swapvm.zone_id) else {
// this tx is not related to the swapvm zone
return;
};
let mut output_mmr_proofs = BTreeMap::<NoteCommitment, MMRProof>::new();
for (cm, _) in &swapvm_update.outputs {
let (proof, folds) = self.observe_cm(cm);
for (other_cm, other_cm_proof) in output_mmr_proofs.iter_mut() {
other_cm_proof.update(&other_cm.0, &folds);
}
output_mmr_proofs.insert(*cm, proof.1);
for (other_cm, other_cm_proof) in &output_mmr_proofs {
assert!(self
.ledger
.commitments
.verify_proof(&other_cm.0, &other_cm_proof))
}
}
for nf in &swapvm_update.inputs {
self.ledger.add_nullifiers(vec![*nf]);
}
if tx.balance.unit_balance(swap_goal_unit().unit()).is_neg() {
// this is a SWAP
let (swap_cm, swap_args_bytes) =
&tx.updates.get(&self.swapvm.zone_id).unwrap().outputs[0];
let (swap_goal_cm, swap_args_bytes) = &swapvm_update.outputs[0];
let swap_args: SwapArgs = cl::deserialize(&swap_args_bytes);
// verify the user proved the correct swap goal note
assert_eq!(
swap_cm,
&app::swap_goal_note(swap_args.nonce).note_commitment()
);
let swap_goal_witness = app::swap_goal_note(swap_args.nonce);
assert_eq!(swap_goal_cm, &swap_goal_witness.note_commitment());
self.goal_notes.push((
swap_goal_witness,
self.ledger.commitments.clone(),
output_mmr_proofs[swap_goal_cm].clone(),
));
// assume there are only the goal unit and tokenIn units at play
assert_eq!(tx.balance.balances.len(), 2);
@ -76,14 +128,6 @@ impl ExecutorState {
let amount_in = balance_in.pos;
self.swapvm.swap(token_in, amount_in, swap_args);
}
for (cm, _) in tx
.updates
.get(&self.swapvm.zone_id)
.map(|u| u.outputs.iter())
.unwrap_or_default()
{
self.ledger.add_commitment(cm);
}
}
pub fn update_and_get_executor_tx(&mut self) -> (TxWitness, Vec<InputWitness>) {
@ -103,14 +147,7 @@ impl ExecutorState {
let note = if let Some(note) = fund_notes.get(&unit) {
note.evolve(value)
} else {
InputWitness {
state: [0; 32],
value,
unit_witness: todo!(),
nonce: Nonce::from_bytes([0; 32]),
zone_id: self.swapvm.zone_id,
nf_sk: NullifierSecret([0; 16]),
}
panic!("dynamically created fund notes are not supported");
};
new_fund_notes.push(note);
let output = note.to_output();
@ -124,6 +161,10 @@ impl ExecutorState {
tx = tx.add_input(note, (mmr, path));
}
for (goal_note, mmr, path) in std::mem::take(&mut self.goal_notes) {
tx = tx.add_input(goal_note, (mmr, path));
}
(tx, new_fund_notes)
}
@ -143,7 +184,7 @@ pub struct StfPrivate {
}
impl StfPrivate {
pub fn prove(&self, prover: &dyn Prover) -> Result<Receipt> {
pub fn prove(&self, prover: &dyn Prover) -> Result<StfProof> {
let env = ExecutorEnv::builder()
.write(&self.zone_data)?
.write(&self.old_ledger)?
@ -156,6 +197,7 @@ impl StfPrivate {
let prove_info = prover.prove(env, STF_ELF)?;
debug_assert!(prove_info.receipt.verify(STF_ID).is_ok());
Ok(prove_info.receipt)
Ok(StfProof::from_risc0(risc0_stf(STF_ID), prove_info.receipt))
}
}

View File

@ -1,7 +1,10 @@
use app::ZONE_ID;
use cl::crust::{BundleWitness, InputWitness, Nonce, NullifierSecret, TxWitness, UnitWitness};
use cl::mantle::ledger::LedgerState;
use cl::mantle::update::{BatchUpdate, Update};
use host::{ExecutorState, StfPrivate};
use ledger::ledger::ProvedLedgerTransition;
use ledger::update::ProvedBatchUpdate;
use ledger::{bundle::ProvedBundle, tx::ProvedTx};
use rand::RngCore;
@ -25,9 +28,7 @@ fn setup_executor(mut rng: impl RngCore, ledger: LedgerState) -> ExecutorState {
nf_sk: NullifierSecret::zero(),
};
let (mmr, mmr_proof) = exec_state
.ledger
.add_commitment(&nmo_fund.note_commitment());
let ((mmr, mmr_proof), _) = exec_state.observe_cm(&nmo_fund.note_commitment());
exec_state.set_fund_note(nmo_fund, mmr, mmr_proof);
let mem_fund = InputWitness {
@ -38,9 +39,7 @@ fn setup_executor(mut rng: impl RngCore, ledger: LedgerState) -> ExecutorState {
zone_id: ZONE_ID,
nf_sk: NullifierSecret::zero(),
};
let (mmr, mmr_proof) = exec_state
.ledger
.add_commitment(&mem_fund.note_commitment());
let ((mmr, mmr_proof), _) = exec_state.observe_cm(&mem_fund.note_commitment());
exec_state.set_fund_note(mem_fund, mmr, mmr_proof);
// HACK: we don't currently support liquidity notes, we directly hard code the corresponding liquidity
@ -77,11 +76,15 @@ fn simple_swap() {
// Alice now has a valid 10 NMO note, she wants to swap it for 90 MEM
// ---- begin swap ----
let old_zone_state = exec_state.zone_state();
let mut temp_ledger_state = exec_state.ledger.clone();
let swap_goal_nonce = Nonce::random(&mut rng);
let swap_tx = TxWitness::default()
.add_input(alice_in, alice_in_proof)
.add_output(
app::swap_goal_note(swap_goal_nonce),
app::swap_goal_note(swap_goal_nonce).to_output(),
app::SwapArgs {
output: app::SwapOutput::basic(mem().unit(), ZONE_ID, alice_sk.commit(), &mut rng),
limit: 90,
@ -101,16 +104,44 @@ fn simple_swap() {
let (exec_tx, fund_notes) = exec_state.update_and_get_executor_tx();
let proved_exec_tx = ProvedTx::prove(exec_tx, vec![], vec![]).unwrap();
let swap_bundle = BundleWitness {
txs: vec![swap_tx_proof.public(), proved_exec_tx.public()],
};
let _ = swap_bundle.clone().commit();
let swap_bundle_proof = ProvedBundle::prove(vec![swap_tx_proof, proved_exec_tx]);
// prove stf
StfPrivate {
let stf_proof = StfPrivate {
zone_data: exec_state.swapvm.clone(),
old_ledger: ledger.to_witness().commit(),
new_ledger: exec_state.ledger.clone().to_witness().commit(),
sync_logs: Vec::new(),
fund_notes,
bundle: BundleWitness {
txs: vec![swap_tx_proof.public(), proved_exec_tx.public()],
},
bundle: swap_bundle,
}
.prove(risc0_zkvm::default_prover().as_ref());
.prove(risc0_zkvm::default_prover().as_ref())
.unwrap();
let ledger_proof =
ProvedLedgerTransition::prove(&mut temp_ledger_state, ZONE_ID, vec![swap_bundle_proof]);
let new_zone_state = exec_state.zone_state();
assert_eq!(ledger_proof.public().old_ledger, old_zone_state.ledger);
assert_eq!(ledger_proof.public().ledger, new_zone_state.ledger);
let zone_update = ProvedBatchUpdate {
batch: BatchUpdate {
updates: vec![Update {
old: old_zone_state,
new: new_zone_state,
}],
},
ledger_proofs: vec![ledger_proof],
stf_proofs: vec![stf_proof],
};
assert!(zone_update.verify())
}

View File

@ -108,8 +108,20 @@ impl LedgerUpdate {
impl TxWitness {
pub fn add_input(mut self, input: InputWitness, input_cm_proof: (MMR, MMRProof)) -> Self {
assert!(input_cm_proof
.0
.verify_proof(&input.note_commitment().0, &input_cm_proof.1));
for (i, other_input) in self.inputs.iter().enumerate() {
if other_input.zone_id == input.zone_id {
// ensure a single MMR per zone per tx
assert_eq!(self.frontier_paths[i].0, input_cm_proof.0);
}
}
self.inputs.push(input);
self.frontier_paths.push(input_cm_proof);
self
}

View File

@ -153,13 +153,13 @@ impl MMRProof {
merkle::path_root(leaf, &self.path)
}
pub fn update(&mut self, elem: &[u8], folds: MMRFolds) {
for (l, r) in folds.folds {
pub fn update(&mut self, elem: &[u8], folds: &MMRFolds) {
for (l, r) in &folds.folds {
let root = self.root(elem);
if root == l {
self.path.push(merkle::PathNode::Right(r))
} else if root == r {
self.path.push(merkle::PathNode::Left(l))
if &root == l {
self.path.push(merkle::PathNode::Right(*r))
} else if &root == r {
self.path.push(merkle::PathNode::Left(*l))
}
}
}

View File

@ -1 +1 @@
48099ec33aa35c95b7bbe614fb6ca00accd456371a63b9c7720cd3a614301aa8
beb565f9da33f558659da6d9f9fd68ebe3d7d14a3e7e8bba8aaadd32e9836f93

View File

@ -1 +1 @@
a1d3accfcff20421924c30f8195e956b522bda0847b3b3fab726c49e3205423b
555127aec2abe879def82c31b50c2f2b19bf4e5456e7de31d9126fb77a9650d1

View File

@ -1 +1 @@
29a96bc98531b44e806051daff1be35fe84e00f4cd7f45de0213c1387184c12e
557a0804bbf1220e66f4a695c24d57de02cb9fd52d646cb99dd43572561a6807

Binary file not shown.

View File

@ -1 +1 @@
327c489a2c52ea70bcf161ed4b90d1d681afb2bc82be7f4e5cf12ebbb5595f71
b332bcd5db82793c52664324cec76b67332d449bc4d7a2425a1e84c6d2282857