add transition function for program deployment

This commit is contained in:
Sergio Chouhy 2025-10-15 17:25:26 -03:00
parent 54c54199d7
commit da28f3317b
9 changed files with 87 additions and 41 deletions

View File

@ -14,6 +14,7 @@ secp256k1 = "0.31.1"
rand = "0.8"
borsh = "1.5.7"
hex = "0.4.3"
risc0-binfmt = "3.0.2"
[dev-dependencies]
test-program-methods = { path = "test_program_methods" }

View File

@ -48,4 +48,10 @@ pub enum NssaError {
#[error("Circuit proving error")]
CircuitProvingError(String),
#[error("Invalid program bytecode")]
InvalidProgramBytecode,
#[error("Program already exists")]
ProgramAlreadyExists,
}

View File

@ -3,8 +3,8 @@ pub mod error;
mod merkle_tree;
pub mod privacy_preserving_transaction;
pub mod program;
pub mod public_transaction;
pub mod program_deployment_transaction;
pub mod public_transaction;
mod signature;
mod state;

View File

@ -29,7 +29,7 @@ impl PrivacyPreservingTransaction {
pub(crate) fn validate_and_produce_public_state_diff(
&self,
state: &mut V01State,
state: &V01State,
) -> Result<HashMap<Address, Account>, NssaError> {
let message = &self.message;
let witness_set = &self.witness_set;

View File

@ -2,10 +2,7 @@ use nssa_core::{
account::{Account, AccountWithMetadata},
program::{InstructionData, ProgramId, ProgramOutput},
};
use program_methods::{
AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, PINATA_ELF, PINATA_ID, TOKEN_ELF,
TOKEN_ID,
};
use program_methods::{AUTHENTICATED_TRANSFER_ELF, PINATA_ELF, TOKEN_ELF};
use risc0_zkvm::{ExecutorEnv, ExecutorEnvBuilder, default_executor, serde::to_vec};
use serde::Serialize;
@ -14,16 +11,26 @@ use crate::error::NssaError;
#[derive(Debug, PartialEq, Eq)]
pub struct Program {
id: ProgramId,
elf: &'static [u8],
elf: Vec<u8>,
}
impl Program {
pub(crate) fn new(bytecode: Vec<u8>) -> Result<Self, NssaError> {
let binary = risc0_binfmt::ProgramBinary::decode(&bytecode)
.map_err(|_| NssaError::InvalidProgramBytecode)?;
let id = binary
.compute_image_id()
.map_err(|_| NssaError::InvalidProgramBytecode)?
.into();
Ok(Self { elf: bytecode, id })
}
pub fn id(&self) -> ProgramId {
self.id
}
pub(crate) fn elf(&self) -> &'static [u8] {
self.elf
pub(crate) fn elf(&self) -> &[u8] {
&self.elf
}
pub fn serialize_instruction<T: Serialize>(
@ -45,7 +52,7 @@ impl Program {
// Execute the program (without proving)
let executor = default_executor();
let session_info = executor
.execute(env, self.elf)
.execute(env, self.elf())
.map_err(|e| NssaError::ProgramExecutionFailed(e.to_string()))?;
// Get outputs
@ -71,33 +78,34 @@ impl Program {
}
pub fn authenticated_transfer_program() -> Self {
Self {
id: AUTHENTICATED_TRANSFER_ID,
elf: AUTHENTICATED_TRANSFER_ELF,
}
// This unwrap won't panic since the `AUTHENTICATED_TRANSFER_ELF` comes from risc0 build of
// `program_methods`
Self::new(AUTHENTICATED_TRANSFER_ELF.to_vec()).unwrap()
}
pub fn token() -> Self {
Self {
id: TOKEN_ID,
elf: TOKEN_ELF,
}
// This unwrap won't panic since the `TOKEN_ELF` comes from risc0 build of
// `program_methods`
Self::new(TOKEN_ELF.to_vec()).unwrap()
}
}
// TODO: Testnet only. Refactor to prevent compilation on mainnet.
impl Program {
pub fn pinata() -> Self {
Self {
id: PINATA_ID,
elf: PINATA_ELF,
}
// This unwrap won't panic since the `PINATA_ELF` comes from risc0 build of
// `program_methods`
Self::new(PINATA_ELF.to_vec()).unwrap()
}
}
#[cfg(test)]
mod tests {
use nssa_core::account::{Account, AccountId, AccountWithMetadata};
use program_methods::{
AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, PINATA_ELF, PINATA_ID, TOKEN_ELF,
TOKEN_ID,
};
use crate::program::Program;
@ -108,7 +116,7 @@ mod tests {
Program {
id: NONCE_CHANGER_ID,
elf: NONCE_CHANGER_ELF,
elf: NONCE_CHANGER_ELF.to_vec(),
}
}
@ -118,7 +126,7 @@ mod tests {
Program {
id: EXTRA_OUTPUT_ID,
elf: EXTRA_OUTPUT_ELF,
elf: EXTRA_OUTPUT_ELF.to_vec(),
}
}
@ -128,7 +136,7 @@ mod tests {
Program {
id: MISSING_OUTPUT_ID,
elf: MISSING_OUTPUT_ELF,
elf: MISSING_OUTPUT_ELF.to_vec(),
}
}
@ -138,7 +146,7 @@ mod tests {
Program {
id: PROGRAM_OWNER_CHANGER_ID,
elf: PROGRAM_OWNER_CHANGER_ELF,
elf: PROGRAM_OWNER_CHANGER_ELF.to_vec(),
}
}
@ -148,7 +156,7 @@ mod tests {
Program {
id: SIMPLE_BALANCE_TRANSFER_ID,
elf: SIMPLE_BALANCE_TRANSFER_ELF,
elf: SIMPLE_BALANCE_TRANSFER_ELF.to_vec(),
}
}
@ -158,7 +166,7 @@ mod tests {
Program {
id: DATA_CHANGER_ID,
elf: DATA_CHANGER_ELF,
elf: DATA_CHANGER_ELF.to_vec(),
}
}
@ -168,7 +176,7 @@ mod tests {
Program {
id: MINTER_ID,
elf: MINTER_ELF,
elf: MINTER_ELF.to_vec(),
}
}
@ -178,7 +186,7 @@ mod tests {
Program {
id: BURNER_ID,
elf: BURNER_ELF,
elf: BURNER_ELF.to_vec(),
}
}
}
@ -216,4 +224,18 @@ mod tests {
assert_eq!(sender_post, expected_sender_post);
assert_eq!(recipient_post, expected_recipient_post);
}
#[test]
fn test_builtin_programs() {
let auth_transfer_program = Program::authenticated_transfer_program();
let token_program = Program::token();
let pinata_program = Program::pinata();
assert_eq!(auth_transfer_program.id, AUTHENTICATED_TRANSFER_ID);
assert_eq!(auth_transfer_program.elf, AUTHENTICATED_TRANSFER_ELF);
assert_eq!(token_program.id, TOKEN_ID);
assert_eq!(token_program.elf, TOKEN_ELF);
assert_eq!(pinata_program.id, PINATA_ID);
assert_eq!(pinata_program.elf, PINATA_ELF);
}
}

View File

@ -1,2 +1,4 @@
mod message;
mod transaction;
pub use transaction::ProgramDeploymentTransaction;

View File

@ -1,8 +1,6 @@
use std::collections::HashMap;
use nssa_core::{account::Account, address::Address};
use crate::{V01State, error::NssaError, program_deployment_transaction::message::Message};
use crate::{
V01State, error::NssaError, program::Program, program_deployment_transaction::message::Message,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProgramDeploymentTransaction {
@ -13,10 +11,17 @@ impl ProgramDeploymentTransaction {
pub fn new(message: Message) -> Self {
Self { message }
}
pub(crate) fn validate_and_produce_public_state_diff(
&self,
state: &mut V01State,
) -> Result<HashMap<Address, Account>, NssaError> {
todo!()
state: &V01State,
) -> Result<Program, NssaError> {
// TODO: remove clone
let program = Program::new(self.message.bytecode.clone())?;
if state.programs().contains_key(&program.id()) {
Err(NssaError::ProgramAlreadyExists)
} else {
Ok(program)
}
}
}

View File

@ -102,7 +102,7 @@ impl PublicTransaction {
// Check the `program_id` corresponds to a built-in program
// Only allowed program so far is the authenticated transfer program
let Some(program) = state.builtin_programs().get(&message.program_id) else {
let Some(program) = state.programs().get(&message.program_id) else {
return Err(NssaError::InvalidInput("Unknown program".into()));
};

View File

@ -1,6 +1,7 @@
use crate::{
error::NssaError, merkle_tree::MerkleTree,
privacy_preserving_transaction::PrivacyPreservingTransaction, program::Program,
program_deployment_transaction::ProgramDeploymentTransaction,
public_transaction::PublicTransaction,
};
use nssa_core::{
@ -157,6 +158,15 @@ impl V01State {
Ok(())
}
pub fn transition_from_program_deployment_transaction(
&mut self,
tx: &ProgramDeploymentTransaction,
) -> Result<(), NssaError> {
let program = tx.validate_and_produce_public_state_diff(self)?;
self.insert_program(program);
Ok(())
}
fn get_account_by_address_mut(&mut self, address: Address) -> &mut Account {
self.public_state.entry(address).or_default()
}
@ -172,7 +182,7 @@ impl V01State {
self.private_state.0.get_proof_for(commitment)
}
pub(crate) fn builtin_programs(&self) -> &HashMap<ProgramId, Program> {
pub(crate) fn programs(&self) -> &HashMap<ProgramId, Program> {
&self.programs
}
@ -355,7 +365,7 @@ pub mod tests {
fn test_builtin_programs_getter() {
let state = V01State::new_with_genesis_accounts(&[], &[]);
let builtin_programs = state.builtin_programs();
let builtin_programs = state.programs();
assert_eq!(builtin_programs, &state.programs);
}