refactor programs. Add builtin programs. Implement account claim logic

This commit is contained in:
Sergio Chouhy 2025-08-08 16:19:50 -03:00
parent 3dfcb47534
commit 002ad048b0
12 changed files with 114 additions and 387 deletions

View File

@ -4,7 +4,7 @@ use mempoolitem::MemPoolItem;
pub mod mempoolitem;
pub struct MemPool<Item: MemPoolItem> {
pub struct MemPool<Item> {
items: VecDeque<Item>,
}

View File

@ -1,3 +0,0 @@
pub(crate) struct Commitment {
value: [u8; 32],
}

View File

@ -1,8 +1,3 @@
mod commitment;
mod nullifier;
pub(crate) use commitment::Commitment;
pub(crate) use nullifier::Nullifier;
use serde::{Deserialize, Serialize};
use crate::program::ProgramId;
@ -27,6 +22,8 @@ pub struct AccountWithMetadata {
#[cfg(test)]
mod tests {
use crate::program::DEFAULT_PROGRAM_ID;
use super::*;
#[test]
@ -54,6 +51,6 @@ mod tests {
fn test_default_program_owner_account_data_creation() {
let new_acc = Account::default();
assert_eq!(new_acc.program_owner, [0; 8]);
assert_eq!(new_acc.program_owner, DEFAULT_PROGRAM_ID);
}
}

View File

@ -1,3 +0,0 @@
pub(crate) struct Nullifier {
value: [u8; 32]
}

View File

@ -3,12 +3,11 @@ use serde::{Deserialize, Serialize};
use crate::account::{Account, AccountWithMetadata};
pub type ProgramId = [u32; 8];
pub const DEFAULT_PROGRAM_ID: ProgramId = [0; 8];
/// A trait to be implemented by inner programs.
pub trait Program {
const PROGRAM_ID: ProgramId;
const PROGRAM_ELF: &[u8];
type InstructionData: Serialize + for<'de> Deserialize<'de>;
pub struct Program {
pub id: ProgramId,
pub elf: &'static [u8],
}
/// Validates well-behaved program execution

View File

@ -1,6 +1,6 @@
use nssa_core::{
account::{Account, AccountWithMetadata},
program::ProgramId,
program::{DEFAULT_PROGRAM_ID, ProgramId},
};
use program_methods::{AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID};
use risc0_zkvm::{ExecutorEnv, ExecutorEnvBuilder, default_executor};
@ -16,17 +16,15 @@ pub use public_transaction::PublicTransaction;
pub use signature::PrivateKey;
pub use state::V01State;
pub struct AuthenticatedTransferProgram;
impl Program for AuthenticatedTransferProgram {
const PROGRAM_ID: ProgramId = AUTHENTICATED_TRANSFER_ID;
const PROGRAM_ELF: &[u8] = AUTHENTICATED_TRANSFER_ELF;
type InstructionData = u128;
}
pub const AUTHENTICATED_TRANSFER_PROGRAM: Program = Program {
id: AUTHENTICATED_TRANSFER_ID,
elf: AUTHENTICATED_TRANSFER_ELF,
};
/// Writes inputs to `env_builder` in the order expected by the programs
fn write_inputs<P: Program>(
fn write_inputs(
pre_states: &[AccountWithMetadata],
instruction_data: P::InstructionData,
instruction_data: u128,
env_builder: &mut ExecutorEnvBuilder,
) -> Result<(), ()> {
let pre_states = pre_states.to_vec();
@ -35,19 +33,29 @@ fn write_inputs<P: Program>(
Ok(())
}
fn execute_public<P: Program>(
fn execute_public(
pre_states: &[AccountWithMetadata],
instruction_data: P::InstructionData,
instruction_data: u128,
program: &Program,
) -> Result<Vec<Account>, ()> {
// Write inputs to the program
let mut env_builder = ExecutorEnv::builder();
write_inputs::<P>(pre_states, instruction_data, &mut env_builder)?;
write_inputs(pre_states, instruction_data, &mut env_builder)?;
let env = env_builder.build().unwrap();
// Execute the program (without proving)
let executor = default_executor();
let session_info = executor.execute(env, P::PROGRAM_ELF).map_err(|_| ())?;
let session_info = executor.execute(env, program.elf).map_err(|_| ())?;
// Get (inputs and) outputs
session_info.journal.decode().map_err(|_| ())
// Get outputs
let mut post_states: Vec<Account> = session_info.journal.decode().map_err(|_| ())?;
// Claim any output account with default program owner field
for account in post_states.iter_mut() {
if account.program_owner == DEFAULT_PROGRAM_ID {
account.program_owner = program.id;
}
}
Ok(post_states)
}

View File

@ -1,32 +1,41 @@
use crate::{
AuthenticatedTransferProgram, address::Address, execute_public,
AUTHENTICATED_TRANSFER_PROGRAM, address::Address, execute_public,
public_transaction::PublicTransaction,
};
use nssa_core::{
account::{Account, AccountWithMetadata},
program::{Program, validate_constraints},
program::{Program, ProgramId, validate_constraints},
};
use std::collections::{HashMap, HashSet};
pub struct V01State {
public_state: HashMap<Address, Account>,
builtin_programs: HashMap<ProgramId, Program>,
}
impl V01State {
pub fn new_with_genesis_accounts(initial_data: &[([u8; 32], u128)]) -> Self {
// TODO:: remove this assert?
let public_state = initial_data
.to_owned()
.into_iter()
.map(|(address_value, balance)| {
let mut account = Account::default();
account.balance = balance;
account.program_owner = AuthenticatedTransferProgram::PROGRAM_ID;
account.program_owner = AUTHENTICATED_TRANSFER_PROGRAM.id;
let address = Address::new(address_value);
(address, account)
})
.collect();
Self { public_state }
let builtin_programs = HashMap::from([(
AUTHENTICATED_TRANSFER_PROGRAM.id,
AUTHENTICATED_TRANSFER_PROGRAM,
)]);
Self {
public_state,
builtin_programs,
}
}
pub fn transition_from_public_transaction(&mut self, tx: &PublicTransaction) -> Result<(), ()> {
@ -108,20 +117,19 @@ impl V01State {
// Check the `program_id` corresponds to a built-in program
// Only allowed program so far is the authenticated transfer program
if message.program_id != AuthenticatedTransferProgram::PROGRAM_ID {
let Some(program) = self.builtin_programs.get(&message.program_id) else {
return Err(());
}
};
// // Execute program
let post_states =
execute_public::<AuthenticatedTransferProgram>(&pre_states, message.instruction_data)
.map_err(|_| ())?;
execute_public(&pre_states, message.instruction_data, program).map_err(|_| ())?;
// Verify execution corresponds to a well-behaved program.
// See the # Programs section for the definition of the `validate_constraints` method.
validate_constraints(&pre_states, &post_states, message.program_id).map_err(|_| ())?;
if (post_states.len() != message.addresses.len()) {
if post_states.len() != message.addresses.len() {
return Err(());
}
@ -148,7 +156,7 @@ mod tests {
) -> PublicTransaction {
let addresses = vec![from, to];
let nonces = vec![nonce];
let program_id = AuthenticatedTransferProgram::PROGRAM_ID;
let program_id = AUTHENTICATED_TRANSFER_PROGRAM.id;
let message = public_transaction::Message::new(program_id, addresses, nonces, balance);
let witness_set = public_transaction::WitnessSet::for_message(&message, &[from_key]);
PublicTransaction::new(message, witness_set)
@ -157,44 +165,77 @@ mod tests {
#[test]
fn test_1() {
let initial_data = [([1; 32], 100)];
let mut genesis_state = V01State::new_with_genesis_accounts(&initial_data);
let mut state = V01State::new_with_genesis_accounts(&initial_data);
let from = Address::new(initial_data[0].0.clone());
let from_key = PrivateKey(1);
let to = Address::new([2; 32]);
let balance_to_move = 5;
let tx =
transfer_transaction_for_tests(from.clone(), from_key, 0, to.clone(), balance_to_move);
let _ = genesis_state.transition_from_public_transaction(&tx);
assert_eq!(
genesis_state.get_account_by_address(&to).balance,
balance_to_move
);
assert_eq!(
genesis_state.get_account_by_address(&from).balance,
initial_data[0].1 - balance_to_move
);
assert_eq!(genesis_state.get_account_by_address(&from).nonce, 1);
assert_eq!(genesis_state.get_account_by_address(&to).nonce, 0);
state.transition_from_public_transaction(&tx).unwrap();
assert_eq!(state.get_account_by_address(&to).balance, 5);
assert_eq!(state.get_account_by_address(&from).balance, 95);
assert_eq!(state.get_account_by_address(&from).nonce, 1);
assert_eq!(state.get_account_by_address(&to).nonce, 0);
}
#[test]
fn test_2() {
let initial_data = [([1; 32], 100), ([99; 32], 200)];
let mut genesis_state = V01State::new_with_genesis_accounts(&initial_data);
let mut state = V01State::new_with_genesis_accounts(&initial_data);
let from = Address::new(initial_data[1].0.clone());
let from_key = PrivateKey(99);
let to = Address::new(initial_data[0].0.clone());
let balance_to_move = 8;
let to_previous_balance = genesis_state.get_account_by_address(&to).balance;
let to_previous_balance = state.get_account_by_address(&to).balance;
let tx =
transfer_transaction_for_tests(from.clone(), from_key, 0, to.clone(), balance_to_move);
let _ = genesis_state.transition_from_public_transaction(&tx);
assert_eq!(genesis_state.get_account_by_address(&to).balance, 108);
assert_eq!(
genesis_state.get_account_by_address(&from).balance,
initial_data[1].1 - balance_to_move
state.transition_from_public_transaction(&tx).unwrap();
assert_eq!(state.get_account_by_address(&to).balance, 108);
assert_eq!(state.get_account_by_address(&from).balance, 192);
assert_eq!(state.get_account_by_address(&from).nonce, 1);
assert_eq!(state.get_account_by_address(&to).nonce, 0);
}
#[test]
fn test_3() {
let initial_data = [([1; 32], 100)];
let mut state = V01State::new_with_genesis_accounts(&initial_data);
let address_1 = Address::new(initial_data[0].0.clone());
let key_1 = PrivateKey(1);
let address_2 = Address::new([2; 32]);
let key_2 = PrivateKey(2);
let address_3 = Address::new([3; 32]);
let balance_to_move = 5;
let tx = transfer_transaction_for_tests(
address_1.clone(),
key_1,
0,
address_2.clone(),
balance_to_move,
);
assert_eq!(genesis_state.get_account_by_address(&from).nonce, 1);
assert_eq!(genesis_state.get_account_by_address(&to).nonce, 0);
state.transition_from_public_transaction(&tx).unwrap();
let balance_to_move = 3;
let tx = transfer_transaction_for_tests(
address_2.clone(),
key_2,
0,
address_3.clone(),
balance_to_move,
);
state.transition_from_public_transaction(&tx).unwrap();
assert_eq!(state.get_account_by_address(&address_1).balance, 95);
assert_eq!(state.get_account_by_address(&address_2).balance, 2);
assert_eq!(state.get_account_by_address(&address_3).balance, 3);
assert_eq!(state.get_account_by_address(&address_1).nonce, 1);
assert_eq!(state.get_account_by_address(&address_2).nonce, 1);
assert_eq!(state.get_account_by_address(&address_3).nonce, 0);
}
}

View File

@ -202,7 +202,7 @@ mod tests {
}
fn create_dummy_transaction() -> nssa::PublicTransaction {
let program_id = nssa::AuthenticatedTransferProgram::PROGRAM_ID;
let program_id = nssa::AUTHENTICATED_TRANSFER_PROGRAM.id;
let addresses = vec![];
let nonces = vec![];
let instruction_data = 0;
@ -223,7 +223,7 @@ mod tests {
) -> nssa::PublicTransaction {
let addresses = vec![nssa::Address::new(from), nssa::Address::new(to)];
let nonces = vec![nonce];
let program_id = nssa::AuthenticatedTransferProgram::PROGRAM_ID;
let program_id = nssa::AUTHENTICATED_TRANSFER_PROGRAM.id;
let message =
nssa::public_transaction::Message::new(program_id, addresses, nonces, balance_to_move);
let witness_set =

View File

@ -1,4 +1,4 @@
use common::{merkle_tree_public::TreeHashType, transaction::AuthenticatedTransaction};
use common::merkle_tree_public::TreeHashType;
use mempool::mempoolitem::MemPoolItem;
pub struct MempoolTransaction {

View File

@ -1,314 +0,0 @@
use accounts::account_core::address::AccountAddress;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct AccountPublicData {
pub balance: u64,
pub address: AccountAddress,
nonce: u64,
}
impl AccountPublicData {
pub fn new(address: AccountAddress) -> Self {
Self {
balance: 0,
nonce: 0,
address,
}
}
fn new_with_balance(address: AccountAddress, balance: u64) -> Self {
Self {
balance,
address,
nonce: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct SequencerAccountsStore {
accounts: HashMap<AccountAddress, AccountPublicData>,
}
impl SequencerAccountsStore {
pub fn new(initial_accounts: &[(AccountAddress, u64)]) -> Self {
let mut accounts = HashMap::new();
for (account_addr, balance) in initial_accounts {
accounts.insert(
*account_addr,
AccountPublicData::new_with_balance(*account_addr, *balance),
);
}
Self { accounts }
}
///Register new account in accounts store
///
///Starts with zero public balance
pub fn register_account(&mut self, account_addr: AccountAddress) {
self.accounts
.insert(account_addr, AccountPublicData::new(account_addr));
}
///Check, if `account_addr` present in account store
pub fn contains_account(&self, account_addr: &AccountAddress) -> bool {
self.accounts.contains_key(account_addr)
}
///Check `account_addr` balance,
///
///returns 0, if account address not found
pub fn get_account_balance(&self, account_addr: &AccountAddress) -> u64 {
self.accounts
.get(account_addr)
.map(|acc| acc.balance)
.unwrap_or(0)
}
pub fn get_account_nonce(&self, account_addr: &AccountAddress) -> u64 {
self.accounts
.get(account_addr)
.map(|acc| acc.nonce)
.unwrap_or(0)
}
///Update `account_addr` balance,
///
/// returns 0, if account address not found, otherwise returns previous balance
///
/// Also, if account was not previously found, sets it with zero balance
pub fn set_account_balance(&mut self, account_addr: &AccountAddress, new_balance: u64) -> u64 {
let acc_data = self.accounts.get_mut(account_addr);
if let Some(acc_data) = acc_data {
let old_balance = acc_data.balance;
acc_data.balance = new_balance;
old_balance
} else {
self.register_account(*account_addr);
let acc = self.accounts.get_mut(account_addr).unwrap();
acc.balance = new_balance;
0
}
}
///Update `account_addr` nonce,
///
/// Returns previous nonce
pub fn increase_nonce(&mut self, account_addr: &AccountAddress) -> u64 {
if let Some(acc_data) = self.accounts.get_mut(account_addr) {
let old_nonce = acc_data.nonce;
acc_data.nonce += 1;
old_nonce
} else {
self.register_account(*account_addr);
self.increase_nonce(account_addr)
}
}
///Remove account from storage
///
/// Fails, if `balance` is != 0
///
/// Returns `Option<AccountAddress>` which is `None` if `account_addr` vere not present in store
pub fn unregister_account(
&mut self,
account_addr: AccountAddress,
) -> Result<Option<AccountAddress>> {
if self.get_account_balance(&account_addr) == 0 {
Ok(self.accounts.remove(&account_addr).map(|data| data.address))
} else {
anyhow::bail!("Chain consistency violation: It is forbidden to remove account with nonzero balance");
}
}
///Number of accounts present in store
pub fn len(&self) -> usize {
self.accounts.len()
}
///Is accounts store empty
pub fn is_empty(&self) -> bool {
self.accounts.is_empty()
}
}
impl Default for SequencerAccountsStore {
fn default() -> Self {
Self::new(&[])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_zero_balance_account_data_creation() {
let new_acc = AccountPublicData::new([1; 32]);
assert_eq!(new_acc.balance, 0);
assert_eq!(new_acc.address, [1; 32]);
}
#[test]
fn test_zero_nonce_account_data_creation() {
let new_acc = AccountPublicData::new([1; 32]);
assert_eq!(new_acc.nonce, 0);
}
#[test]
fn test_non_zero_balance_account_data_creation() {
let new_acc = AccountPublicData::new_with_balance([1; 32], 10);
assert_eq!(new_acc.balance, 10);
assert_eq!(new_acc.address, [1; 32]);
}
#[test]
fn test_zero_nonce_account_data_creation_with_balance() {
let new_acc = AccountPublicData::new_with_balance([1; 32], 10);
assert_eq!(new_acc.nonce, 0);
}
#[test]
fn default_account_sequencer_store() {
let seq_acc_store = SequencerAccountsStore::default();
assert!(seq_acc_store.accounts.is_empty());
}
#[test]
fn account_sequencer_store_register_acc() {
let mut seq_acc_store = SequencerAccountsStore::default();
seq_acc_store.register_account([1; 32]);
assert!(seq_acc_store.contains_account(&[1; 32]));
let acc_balance = seq_acc_store.get_account_balance(&[1; 32]);
assert_eq!(acc_balance, 0);
}
#[test]
fn account_sequencer_store_unregister_acc_not_present() {
let mut seq_acc_store = SequencerAccountsStore::default();
seq_acc_store.register_account([1; 32]);
let rem_res = seq_acc_store.unregister_account([2; 32]).unwrap();
assert!(rem_res.is_none());
}
#[test]
fn account_sequencer_store_unregister_acc_not_zero_balance() {
let mut seq_acc_store = SequencerAccountsStore::new(&[([1; 32], 12), ([2; 32], 100)]);
let rem_res = seq_acc_store.unregister_account([1; 32]);
assert!(rem_res.is_err());
}
#[test]
fn account_sequencer_store_unregister_acc() {
let mut seq_acc_store = SequencerAccountsStore::default();
seq_acc_store.register_account([1; 32]);
assert!(seq_acc_store.contains_account(&[1; 32]));
seq_acc_store.unregister_account([1; 32]).unwrap().unwrap();
assert!(!seq_acc_store.contains_account(&[1; 32]));
}
#[test]
fn account_sequencer_store_with_preset_accounts_1() {
let seq_acc_store = SequencerAccountsStore::new(&[([1; 32], 12), ([2; 32], 100)]);
assert!(seq_acc_store.contains_account(&[1; 32]));
assert!(seq_acc_store.contains_account(&[2; 32]));
let acc_balance = seq_acc_store.get_account_balance(&[1; 32]);
assert_eq!(acc_balance, 12);
let acc_balance = seq_acc_store.get_account_balance(&[2; 32]);
assert_eq!(acc_balance, 100);
}
#[test]
fn account_sequencer_store_with_preset_accounts_2() {
let seq_acc_store =
SequencerAccountsStore::new(&[([6; 32], 120), ([7; 32], 15), ([8; 32], 10)]);
assert!(seq_acc_store.contains_account(&[6; 32]));
assert!(seq_acc_store.contains_account(&[7; 32]));
assert!(seq_acc_store.contains_account(&[8; 32]));
let acc_balance = seq_acc_store.get_account_balance(&[6; 32]);
assert_eq!(acc_balance, 120);
let acc_balance = seq_acc_store.get_account_balance(&[7; 32]);
assert_eq!(acc_balance, 15);
let acc_balance = seq_acc_store.get_account_balance(&[8; 32]);
assert_eq!(acc_balance, 10);
}
#[test]
fn account_sequencer_store_fetch_unknown_account() {
let seq_acc_store =
SequencerAccountsStore::new(&[([6; 32], 120), ([7; 32], 15), ([8; 32], 10)]);
let acc_balance = seq_acc_store.get_account_balance(&[9; 32]);
assert_eq!(acc_balance, 0);
}
#[test]
fn account_sequencer_store_is_empty_test() {
let seq_acc_store = SequencerAccountsStore::default();
assert!(seq_acc_store.is_empty());
}
#[test]
fn account_sequencer_store_set_balance_to_unknown_account() {
let mut seq_acc_store = SequencerAccountsStore::default();
let ret = seq_acc_store.set_account_balance(&[1; 32], 100);
assert_eq!(ret, 0);
assert!(seq_acc_store.contains_account(&[1; 32]));
assert_eq!(seq_acc_store.get_account_balance(&[1; 32]), 100);
}
#[test]
fn test_increase_nonce() {
let mut account_store = SequencerAccountsStore::default();
let address = [1; 32];
let first_nonce = account_store.increase_nonce(&address);
assert_eq!(first_nonce, 0);
let second_nonce = account_store.increase_nonce(&address);
assert_eq!(second_nonce, 1);
}
}

View File

@ -81,13 +81,15 @@ mod tests {
use tempfile::tempdir;
fn create_dummy_block_with_transaction(block_id: u64) -> (Block, nssa::PublicTransaction) {
let program_id = nssa::AuthenticatedTransferProgram::PROGRAM_ID;
let program_id = nssa::AUTHENTICATED_TRANSFER_PROGRAM.id;
let addresses = vec![];
let nonces = vec![];
let instruction_data = 0;
let message = nssa::public_transaction::Message::new(program_id, addresses, nonces, instruction_data);
let message =
nssa::public_transaction::Message::new(program_id, addresses, nonces, instruction_data);
let private_key = nssa::PrivateKey::new(1);
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[private_key]);
let witness_set =
nssa::public_transaction::WitnessSet::for_message(&message, &[private_key]);
let tx = nssa::PublicTransaction::new(message, witness_set);
(
Block {

View File

@ -267,7 +267,7 @@ mod tests {
let addresses = vec![from, to];
let nonces = vec![0];
let program_id = nssa::AuthenticatedTransferProgram::PROGRAM_ID;
let program_id = nssa::AUTHENTICATED_TRANSFER_PROGRAM.id;
let message =
nssa::public_transaction::Message::new(program_id, addresses, nonces, balance_to_move);
let witness_set =
@ -497,7 +497,7 @@ mod tests {
],
"instruction_data": 10,
"nonces": [0],
"program_id": nssa::AuthenticatedTransferProgram::PROGRAM_ID,
"program_id": nssa::AUTHENTICATED_TRANSFER_PROGRAM.id,
},
"witness_set": {
"signatures_and_public_keys": [